diff --git a/doc_src/history.txt b/doc_src/history.txt index 8d230f129..704c13cf7 100644 --- a/doc_src/history.txt +++ b/doc_src/history.txt @@ -38,7 +38,7 @@ These flags can appear before or immediately after on of the sub-commands listed - `-p` or `--prefix` searches or deletes items in the history that begin with the specified text string. This is not currently supported by the `--delete` flag. -- `-t` or `--show-time` outputs the date and time ("%Y-%m-%d %H:%M:%S") history items were recorded at on a line starting with "#" before each history entry. Note that `--with-time` is also allowed but is deprecated and will be removed at a future date. +- `-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. - `-h` or `--help` display help for this command. diff --git a/share/functions/history.fish b/share/functions/history.fish index 97bf006a2..6bf228e14 100644 --- a/share/functions/history.fish +++ b/share/functions/history.fish @@ -22,8 +22,7 @@ function __fish_unexpected_hist_args --no-scope-shadowing return 0 end if set -q argv[1] - printf (_ "%ls: %ls command expected %d args, got %d\n") \ - $cmd $hist_cmd 0 (count $argv) >&2 + printf (_ "%ls: %ls command expected %d args, got %d\n") $cmd $hist_cmd 0 (count $argv) >&2 return 0 end return 1 @@ -71,8 +70,8 @@ function history --description "display or manipulate interactive command histor case -h --help builtin history --help return - case -t --show-time --with-time - set show_time -t + case -t --show-time '--show-time=*' --with-time '--with-time=*' + set show_time $argv[1] case -p --prefix set search_mode --prefix case -c --contains diff --git a/src/builtin.cpp b/src/builtin.cpp index 10f5a3728..e0eb05643 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -2826,19 +2826,18 @@ static bool set_hist_cmd(wchar_t *const cmd, hist_cmd_t *hist_cmd, hist_cmd_t su return true; } -#define CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) \ - if (history_search_type_defined || show_time) { \ - streams.err.append_format( \ - _(L"%ls: you cannot use any options with the %ls command\n"), \ - cmd, hist_cmd_to_string(hist_cmd).c_str()); \ - status = STATUS_BUILTIN_ERROR; \ - break; \ - } \ - if (args.size() != 0) { \ - streams.err.append_format(BUILTIN_ERR_ARG_COUNT, cmd, \ - hist_cmd_to_string(hist_cmd).c_str(), 0, args.size()); \ - status = STATUS_BUILTIN_ERROR; \ - break; \ +#define CHECK_FOR_UNEXPECTED_HIST_ARGS(hist_cmd) \ + if (history_search_type_defined || show_time_format) { \ + streams.err.append_format(_(L"%ls: you cannot use any options with the %ls command\n"), \ + cmd, hist_cmd_to_string(hist_cmd).c_str()); \ + status = STATUS_BUILTIN_ERROR; \ + break; \ + } \ + if (args.size() != 0) { \ + streams.err.append_format(BUILTIN_ERR_ARG_COUNT, cmd, \ + hist_cmd_to_string(hist_cmd).c_str(), 0, args.size()); \ + status = STATUS_BUILTIN_ERROR; \ + break; \ } /// Manipulate history of interactive commands executed by the user. @@ -2848,7 +2847,7 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar hist_cmd_t hist_cmd = HIST_NOOP; history_search_type_t search_type = (history_search_type_t)-1; bool history_search_type_defined = false; - bool show_time = 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). @@ -2856,8 +2855,8 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar 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", no_argument, NULL, 't'}, - {L"with-time", no_argument, NULL, 't'}, + {L"show-time", optional_argument, NULL, 't'}, + {L"with-time", optional_argument, NULL, 't'}, {L"exact", no_argument, NULL, 'e'}, {L"delete", no_argument, NULL, 1}, {L"search", no_argument, NULL, 2}, @@ -2921,7 +2920,7 @@ static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **ar break; } case 't': { - show_time = true; + show_time_format = w.woptarg ? w.woptarg : L"# %c%n"; break; } case 'h': { @@ -2964,7 +2963,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, streams)) { + if (!history->search(search_type, args, show_time_format, streams)) { status = STATUS_BUILTIN_ERROR; } break; diff --git a/src/history.cpp b/src/history.cpp index 12e0ef1ef..035309027 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1396,14 +1396,17 @@ void history_t::save(void) { // Formats a single history record, including a trailing newline. Returns true // if bytes were written to the output stream and false otherwise. -static bool format_history_record(const history_item_t &item, const bool with_time, +static bool format_history_record(const history_item_t &item, const wchar_t *show_time_format, io_streams_t &streams) { - if (with_time) { + if (show_time_format) { const time_t seconds = item.timestamp(); struct tm timestamp; if (!localtime_r(&seconds, ×tamp)) return false; - wchar_t timestamp_string[24]; - if (std::wcsftime(timestamp_string, 23, L"# %Y-%m-%d %H:%M:%S\n", ×tamp) == 0) return false; + const int max_tstamp_length = 100; + wchar_t timestamp_string[max_tstamp_length + 1]; + if (std::wcsftime(timestamp_string, max_tstamp_length, show_time_format, ×tamp) == 0) { + return false; + } streams.out.append(timestamp_string); } streams.out.append(item.str()); @@ -1412,12 +1415,14 @@ static bool format_history_record(const history_item_t &item, const bool with_ti } bool history_t::search(history_search_type_t search_type, wcstring_list_t search_args, - bool with_time, io_streams_t &streams) { + const wchar_t *show_time_format, 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) { - if (!format_history_record(this->item_at_index(i), with_time, streams)) return false; + if (!format_history_record(this->item_at_index(i), show_time_format, streams)) { + return false; + } } return true; } @@ -1431,7 +1436,7 @@ bool history_t::search(history_search_type_t search_type, wcstring_list_t search } history_search_t searcher = history_search_t(*this, search_string, search_type); while (searcher.go_backwards()) { - if (!format_history_record(searcher.current_item(), with_time, streams)) { + if (!format_history_record(searcher.current_item(), show_time_format, streams)) { return false; } } diff --git a/src/history.h b/src/history.h index 4b54bd2be..c624d4658 100644 --- a/src/history.h +++ b/src/history.h @@ -226,8 +226,8 @@ class history_t { void save(); // Searches history. - bool search(history_search_type_t search_type, wcstring_list_t search_args, bool with_time, - io_streams_t &streams); + bool search(history_search_type_t search_type, wcstring_list_t search_args, + const wchar_t *show_time_format, io_streams_t &streams); // Enable / disable automatic saving. Main thread only! void disable_automatic_saving(); diff --git a/tests/history.expect b/tests/history.expect index 467bd2d70..d8e4ebd1a 100644 --- a/tests/history.expect +++ b/tests/history.expect @@ -18,7 +18,7 @@ expect_prompt # ========== # Start by ensuring we're not affected by earlier tests. Clear the history. -send "builtin history --clear\r" +send "builtin history clear\r" expect_prompt # ========== @@ -50,7 +50,7 @@ expect_prompt -re {start2\r\necho start1; builtin history; echo end1\r\nend2\r\n # ========== # Verify explicit searching for the first two commands in the previous tests # returns the expected results. -send "history --search echo start\r" +send "history search echo start\r" expect_prompt -re {\r\necho start1.*\r\necho start2} { puts "history function explicit search succeeded" } unmatched { @@ -59,17 +59,17 @@ expect_prompt -re {\r\necho start1.*\r\necho start2} { # ========== # Verify searching is the implicit action. -send "history echo start\r" -expect_prompt -re {\r\necho start1.*\r\necho start2} { +send "history -p 'echo start'\r" +expect_prompt -re {\r\necho start2.*\r\necho start1} { puts "history function implicit search succeeded" } unmatched { puts stderr "history function implicit search failed" } # ========== -# Verify implicit searching with a request for timestamps includes the timestamps. -send "history -t echo start\r" -expect_prompt -re {\r\n# \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\r\necho start1; builtin history;.*\r\n# \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\r\necho start2; builtin history} { +# Verify searching with a request for timestamps includes the timestamps. +send "history search --show-time='# %F %T%n' --prefix 'echo start'\r" +expect_prompt -re {\r\n# \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\r\necho start2; .*\r\n# \d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\r\necho start1; } { puts "history function implicit search with timestamps succeeded" } unmatched { puts stderr "history function implicit search with timestamps failed" @@ -87,14 +87,14 @@ expect_prompt send "echo hello AGAIN\r" expect_prompt -send "history --search --exact 'echo goodbye'\r" +send "history search --exact 'echo goodbye'\r" expect_prompt -re {\r\necho goodbye\r\n} { puts "history function explicit exact search 'echo goodbye' succeeded" } unmatched { puts stderr "history function explicit exact search 'echo goodbye' failed" } -send "history --search --exact 'echo hello'\r" +send "history search --exact 'echo hello'\r" expect_prompt -re {\r\necho hello\r\n} { puts "history function explicit exact search 'echo hello' succeeded" } unmatched { @@ -103,8 +103,8 @@ expect_prompt -re {\r\necho hello\r\n} { # This is slightly subtle in that it shouldn't actually match anything between # the command we sent and the next prompt. -send "history --search --exact 'echo hell'\r" -expect_prompt -re {history --search --exact 'echo hell'\r\n} { +send "history search --exact 'echo hell'\r" +expect_prompt -re {history search --exact 'echo hell'\r\n} { puts "history function explicit exact search 'echo hell' succeeded" } unmatched { puts stderr "history function explicit exact search 'echo hell' failed" @@ -112,8 +112,8 @@ expect_prompt -re {history --search --exact 'echo hell'\r\n} { # ========== # Delete a single command we recently ran. -send "history --delete 'echo hello'\r" -expect_prompt -re {history --delete 'echo hello'\r\n} { +send "history delete 'echo hello'\r" +expect_prompt -re {history delete 'echo hello'\r\n} { puts "history function explicit exact delete 'echo hello' succeeded" } unmatched { puts stderr "history function explicit exact delete 'echo hello' failed" @@ -123,8 +123,8 @@ expect_prompt -re {history --delete 'echo hello'\r\n} { # Interactively delete one of multiple matched commands. This verifies that we # delete the first entry matched by the prefix search (the most recent command # sent above that matches). -send "history --delete -p 'echo hello'\r" -expect -re {history --delete -p 'echo hello'\r\n} +send "history delete -p 'echo hello'\r" +expect -re {history delete -p 'echo hello'\r\n} expect -re {\[1\] echo hello AGAIN\r\n} expect -re {\[2\] echo hello again\r\n\r\n} expect -re {Enter nothing to cancel.*\r\nEnter "all" to delete all the matching entries\.\r\n} @@ -138,14 +138,14 @@ expect_prompt -re {Deleting history entry 1: "echo hello AGAIN"\r\n} { # Verify that the deleted history entry is gone and the other one that matched # the prefix search above is still there. -send "history --search --exact 'echo hello again'\r" +send "history search --exact 'echo hello again'\r" expect_prompt -re {\r\necho hello again\r\n} { puts "history function explicit exact search 'echo hello again' succeeded" } unmatched { puts stderr "history function explicit exact search 'echo hello again' failed" } -send "history --search --exact 'echo hello AGAIN'\r" +send "history search --exact 'echo hello AGAIN'\r" expect_prompt -re {\r\necho hello AGAIN\r\n} { puts stderr "history function explicit exact search 'echo hello AGAIN' found the entry" } unmatched {