/// Manipulate history of interactive commands executed by the user. int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); history_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } // Use the default history if we have none (which happens if invoked non-interactively, e.g. // from webconfig.py. history_t *history = reader_get_history(); if (!history) history = &history_t::history_with_name(L"fish"); // If a history command hasn't already been specified via a flag check the first word. // Note that this can be simplified after we eliminate allowing subcommands as flags. // See the TODO above regarding the `long_options` array. if (optind < argc) { hist_cmd_t subcmd = str_to_enum(argv[optind], hist_enum_map, hist_enum_map_len); if (subcmd != HIST_UNDEF) { if (!set_hist_cmd(cmd, &opts.hist_cmd, subcmd, streams)) { return STATUS_INVALID_ARGS; } optind++; } } // Every argument that we haven't consumed already is an argument for a subcommand (e.g., a // search term). const wcstring_list_t args(argv + optind, argv + argc); // Establish appropriate defaults. if (opts.hist_cmd == HIST_UNDEF) opts.hist_cmd = HIST_SEARCH; if (!opts.history_search_type_defined) { if (opts.hist_cmd == HIST_SEARCH) opts.search_type = HISTORY_SEARCH_TYPE_CONTAINS; if (opts.hist_cmd == HIST_DELETE) opts.search_type = HISTORY_SEARCH_TYPE_EXACT; } int status = STATUS_CMD_OK; switch (opts.hist_cmd) { case HIST_SEARCH: { if (!history->search(opts.search_type, args, opts.show_time_format, opts.max_items, opts.case_sensitive, opts.null_terminate, streams)) { status = STATUS_CMD_ERROR; } break; } case HIST_DELETE: { // TODO: Move this code to the history module and support the other search types // including case-insensitive matches. At this time we expect the non-exact deletions to // be handled only by the history function's interactive delete feature. if (opts.search_type != HISTORY_SEARCH_TYPE_EXACT) { streams.err.append_format(_(L"builtin history delete only supports --exact\n")); status = STATUS_INVALID_ARGS; break; } if (!opts.case_sensitive) { streams.err.append_format( _(L"builtin history delete only supports --case-sensitive\n")); status = STATUS_INVALID_ARGS; break; } for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter) { wcstring delete_string = *iter; if (delete_string[0] == '"' && delete_string[delete_string.length() - 1] == '"') { delete_string = delete_string.substr(1, delete_string.length() - 2); } history->remove(delete_string); } break; } case HIST_CLEAR: { CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd) history->clear(); history->save(); break; } case HIST_MERGE: { CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd) history->incorporate_external_changes(); break; } case HIST_SAVE: { CHECK_FOR_UNEXPECTED_HIST_ARGS(opts.hist_cmd) history->save(); break; } case HIST_UNDEF: { DIE("Unexpected HIST_UNDEF seen"); break; } } return status; }
/// The status builtin. Gives various status information on fish. int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); status_cmd_opts_t opts; int optind; int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } // If a status command hasn't already been specified via a flag check the first word. // Note that this can be simplified after we eliminate allowing subcommands as flags. if (optind < argc) { status_cmd_t subcmd = str_to_enum(argv[optind], status_enum_map, status_enum_map_len); if (subcmd != STATUS_UNDEF) { if (!set_status_cmd(cmd, opts, subcmd, streams)) { return STATUS_CMD_ERROR; } optind++; } else { streams.err.append_format(BUILTIN_ERR_INVALID_SUBCMD, cmd, argv[1]); return STATUS_INVALID_ARGS; } } // Every argument that we haven't consumed already is an argument for a subcommand. const wcstring_list_t args(argv + optind, argv + argc); switch (opts.status_cmd) { case STATUS_UNDEF: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) if (is_login) { streams.out.append_format(_(L"This is a login shell\n")); } else { streams.out.append_format(_(L"This is not a login shell\n")); } streams.out.append_format( _(L"Job control: %ls\n"), job_control_mode == JOB_CONTROL_INTERACTIVE ? _(L"Only on interactive jobs") : (job_control_mode == JOB_CONTROL_NONE ? _(L"Never") : _(L"Always"))); streams.out.append(parser.stack_trace()); break; } case STATUS_SET_JOB_CONTROL: { if (opts.new_job_control_mode != -1) { // Flag form was used. CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) } else { if (args.size() != 1) { const wchar_t *subcmd_str = enum_to_str(opts.status_cmd, status_enum_map); streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 1, args.size()); return STATUS_INVALID_ARGS; } opts.new_job_control_mode = job_control_str_to_mode(args[0].c_str(), cmd, streams); if (opts.new_job_control_mode == -1) { return STATUS_CMD_ERROR; } } job_control_mode = opts.new_job_control_mode; break; } case STATUS_FEATURES: { print_features(streams); break; } case STATUS_TEST_FEATURE: { if (args.size() != 1) { const wchar_t *subcmd_str = enum_to_str(opts.status_cmd, status_enum_map); streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, subcmd_str, 1, args.size()); return STATUS_INVALID_ARGS; } const auto *metadata = features_t::metadata_for(args.front().c_str()); if (!metadata) { retval = TEST_FEATURE_NOT_RECOGNIZED; } else { retval = feature_test(metadata->flag) ? TEST_FEATURE_ON : TEST_FEATURE_OFF; } break; } case STATUS_FILENAME: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) const wchar_t *fn = parser.current_filename(); if (!fn) fn = _(L"Standard input"); streams.out.append_format(L"%ls\n", fn); break; } case STATUS_FUNCTION: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) const wchar_t *fn = parser.get_function_name(opts.level); if (!fn) fn = _(L"Not a function"); streams.out.append_format(L"%ls\n", fn); break; } case STATUS_LINE_NUMBER: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) // TBD is how to interpret the level argument when fetching the line number. // See issue #4161. // streams.out.append_format(L"%d\n", parser.get_lineno(opts.level)); streams.out.append_format(L"%d\n", parser.get_lineno()); break; } case STATUS_IS_INTERACTIVE: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_interactive_session; break; } case STATUS_IS_COMMAND_SUB: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_subshell; break; } case STATUS_IS_BLOCK: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_block; break; } case STATUS_IS_BREAKPOINT: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_breakpoint; break; } case STATUS_IS_LOGIN: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = !is_login; break; } case STATUS_IS_FULL_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_ALL; break; } case STATUS_IS_INTERACTIVE_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_INTERACTIVE; break; } case STATUS_IS_NO_JOB_CTRL: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) retval = job_control_mode != JOB_CONTROL_NONE; break; } case STATUS_STACK_TRACE: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) streams.out.append(parser.stack_trace()); break; } case STATUS_CURRENT_CMD: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) // HACK: Go via the deprecated variable to get the command. const auto var = env_get(L"_"); if (!var.missing_or_empty()) { streams.out.append(var->as_string()); streams.out.push_back(L'\n'); } else { streams.out.append(program_name); streams.out.push_back(L'\n'); } break; } case STATUS_FISH_PATH: { CHECK_FOR_UNEXPECTED_STATUS_ARGS(opts.status_cmd) streams.out.append(str2wcstring(get_executable_path("fish"))); streams.out.push_back(L'\n'); break; } } return retval; }