From e9b55051698fa26ec19b93d4e98fe2b03fd84c27 Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Tue, 20 Sep 2016 22:32:38 -0700 Subject: [PATCH] add a flag to limit history search results This adds a flag to the `history search` command to limit the number of matching entries to the first "n". The default is unlimited. This is mostly useful in conjunction with aliases (i.e., functions) that are intended to report the "n" most recent matching history entries without piping the result through the user's pager. Fixes #3244 --- doc_src/history.txt | 6 ++++-- share/completions/history.fish | 2 ++ share/functions/history.fish | 21 ++++++++++++++++++--- src/builtin.cpp | 34 ++++++++++++++++++++++++++++++---- src/history.cpp | 5 +++-- src/history.h | 2 +- 6 files changed, 58 insertions(+), 12 deletions(-) diff --git a/doc_src/history.txt b/doc_src/history.txt index 704c13cf7..a7456d837 100644 --- a/doc_src/history.txt +++ b/doc_src/history.txt @@ -2,7 +2,7 @@ \subsection history-synopsis Synopsis \fish{synopsis} -history search [ --show-time ] [ --exact | --prefix | --contains ] [ "search string"... ] +history search [ --show-time ] [ --exact | --prefix | --contains ] [ --max=n ] [ "search string"... ] history delete [ --show-time ] [ --exact | --prefix | --contains ] "search string"... history merge history save @@ -30,7 +30,7 @@ The following operations (sub-commands) are available: The following options are available: -These flags can appear before or immediately after on of the sub-commands listed above. +These flags can appear before or immediately after one of the sub-commands listed above. - `-c` or `--contains` searches or deletes items in the history that contain the specified text string. This is the default for the `--search` flag. This is not currently supported by the `--delete` flag. @@ -40,6 +40,8 @@ These flags can appear before or immediately after on of the sub-commands listed - `-t` or `--show-time` prepends each history entry with the date and time the entry was recorded . By default it uses the strftime format `# %c%n`. You can specify another format; e.g., `--show-time='%Y-%m-%d %H:%M:%S '` or `--show-time='%a%I%p'`. The short option, `-t` doesn't accept a stftime format string; it only uses the default format. Any strftime format is allowed, including `%s` to get the raw UNIX seconds since the epoch. Note that `--with-time` is also allowed but is deprecated and will be removed at a future date. +- `-` `-n ` or `--max=` limits the matched history items to the first "n" matching entries. This is only valid for `history search`. + - `-h` or `--help` display help for this command. \subsection history-examples Example diff --git a/share/completions/history.fish b/share/completions/history.fish index 6899da03f..f4d9b9114 100644 --- a/share/completions/history.fish +++ b/share/completions/history.fish @@ -10,6 +10,8 @@ complete -c history -n '__fish_seen_subcommand_from search delete' \ -s e -l exact -d "Match items identical to the string" complete -c history -n '__fish_seen_subcommand_from search delete' \ -s t -l show-time -d "Output with timestamps" +complete -c history -n '__fish_seen_subcommand_from search' \ + -s n -l max -d "Limit output to the first 'n' matches" # We don't include a completion for the "save" subcommand because it should not be used # interactively. diff --git a/share/functions/history.fish b/share/functions/history.fish index 6bf228e14..c3dca2b22 100644 --- a/share/functions/history.fish +++ b/share/functions/history.fish @@ -34,6 +34,7 @@ function history --description "display or manipulate interactive command histor set -l hist_cmd set -l search_mode set -l show_time + set -l max_count # Check for a recognized subcommand as the first argument. if set -q argv[1] @@ -78,11 +79,24 @@ function history --description "display or manipulate interactive command histor set search_mode --contains case -e --exact set search_mode --exact + case -n --max + if string match -- '-n?*' $argv[1] + or string match -- '--max=*' $argv[1] + set max_count $argv[1] + else + set max_count $argv[1] $argv[2] + set -e argv[1] + end case -- set -e argv[1] break case '*' - break + if string match -r -- '-\d+' $argv[1] + set max_count $argv[1] + set -e argv[1] + else + break + end end set -e argv[1] end @@ -107,13 +121,14 @@ function history --description "display or manipulate interactive command histor test -z "$search_mode" and set search_mode "--contains" + echo "builtin history search $search_mode $show_time $max_count -- $argv" >>/tmp/x if isatty stdout set -l pager less set -q PAGER and set pager $PAGER - builtin history search $search_mode $show_time -- $argv | eval $pager + builtin history search $search_mode $show_time $max_count -- $argv | eval $pager else - builtin history search $search_mode $show_time -- $argv + builtin history search $search_mode $show_time $max_count -- $argv end case delete # interactively delete history diff --git a/src/builtin.cpp b/src/builtin.cpp index e0eb05643..9ba131622 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -2846,18 +2846,20 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar int argc = builtin_count_args(argv); hist_cmd_t hist_cmd = HIST_NOOP; history_search_type_t search_type = (history_search_type_t)-1; + long max_items = LONG_MAX; bool history_search_type_defined = false; const wchar_t *show_time_format = NULL; // TODO: Remove the long options that correspond to subcommands (e.g., '--delete') on or after // 2017-10 (which will be a full year after these flags have been deprecated). - const wchar_t *short_options = L":mepcht"; + const wchar_t *short_options = L":mn:epcht"; const struct woption long_options[] = {{L"prefix", no_argument, NULL, 'p'}, {L"contains", no_argument, NULL, 'c'}, {L"help", no_argument, NULL, 'h'}, {L"show-time", optional_argument, NULL, 't'}, {L"with-time", optional_argument, NULL, 't'}, {L"exact", no_argument, NULL, 'e'}, + {L"max", required_argument, NULL, 'n'}, {L"delete", no_argument, NULL, 1}, {L"search", no_argument, NULL, 2}, {L"save", no_argument, NULL, 3}, @@ -2923,6 +2925,17 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar show_time_format = w.woptarg ? w.woptarg : L"# %c%n"; break; } + case 'n': { + wchar_t *end = 0; + max_items = wcstol(w.woptarg, &end, 10); + if (!(*w.woptarg != L'\0' && *end == L'\0')) { + streams.err.append_format( + _(L"%ls: max value '%ls' is not a valid number\n"), argv[0], + w.woptarg); + return STATUS_BUILTIN_ERROR; + } + break; + } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_BUILTIN_OK; @@ -2932,13 +2945,26 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar return STATUS_BUILTIN_ERROR; } case '?': { - streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]); - return STATUS_BUILTIN_ERROR; + // Try to parse it as a number; e.g., "-123". + wchar_t *end = 0; + max_items = wcstol(argv[w.woptind - 1] + 1, &end, 10); + if (!(argv[w.woptind - 1][1] != L'\0' && *end == L'\0')) { + streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]); + return STATUS_BUILTIN_ERROR; + } + w.nextchar = NULL; + break; } default: { DIE("unexpected retval from wgetopt_long"); } } } + if (max_items <= 0) { + streams.err.append_format(_(L"%ls: max value '%ls' is not a valid number\n"), argv[0], + w.woptarg); + return STATUS_BUILTIN_ERROR; + } + // 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. @@ -2963,7 +2989,7 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar int status = STATUS_BUILTIN_OK; switch (hist_cmd) { case HIST_SEARCH: { - if (!history->search(search_type, args, show_time_format, streams)) { + if (!history->search(search_type, args, show_time_format, max_items, streams)) { status = STATUS_BUILTIN_ERROR; } break; diff --git a/src/history.cpp b/src/history.cpp index 035309027..16c8fab26 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1415,11 +1415,11 @@ static bool format_history_record(const history_item_t &item, const wchar_t *sho } bool history_t::search(history_search_type_t search_type, wcstring_list_t search_args, - const wchar_t *show_time_format, io_streams_t &streams) { + const wchar_t *show_time_format, long max_items, io_streams_t &streams) { // scoped_lock locker(lock); if (search_args.empty()) { // Start at one because zero is the current command. - for (int i = 1; !this->item_at_index(i).empty(); ++i) { + for (int i = 1; !this->item_at_index(i).empty() && max_items; ++i, --max_items) { if (!format_history_record(this->item_at_index(i), show_time_format, streams)) { return false; } @@ -1439,6 +1439,7 @@ bool history_t::search(history_search_type_t search_type, wcstring_list_t search if (!format_history_record(searcher.current_item(), show_time_format, streams)) { return false; } + if (--max_items == 0) return true; } } diff --git a/src/history.h b/src/history.h index c624d4658..db1e4615d 100644 --- a/src/history.h +++ b/src/history.h @@ -227,7 +227,7 @@ class history_t { // Searches history. bool search(history_search_type_t search_type, wcstring_list_t search_args, - const wchar_t *show_time_format, io_streams_t &streams); + const wchar_t *show_time_format, long max_items, io_streams_t &streams); // Enable / disable automatic saving. Main thread only! void disable_automatic_saving();