allow customizing history --show-time format

It would make fish more friendly if we allowed the user to specify the
format of the history entry timestamps.

Fixes #3361
This commit is contained in:
Kurtis Rader 2016-09-18 20:21:27 -07:00
parent 98470ab608
commit 204e79105a
6 changed files with 52 additions and 49 deletions

View file

@ -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.

View file

@ -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

View file

@ -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;

View file

@ -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, &timestamp)) return false;
wchar_t timestamp_string[24];
if (std::wcsftime(timestamp_string, 23, L"# %Y-%m-%d %H:%M:%S\n", &timestamp) == 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, &timestamp) == 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;
}
}

View file

@ -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();

View file

@ -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 {