Make the history session configurable

Using the FISH_HISTFILE variable will let people customise the session
to use for the history file. The resulting history file is:

    `$XDG_DATA_HOME/fish/name_history`

Where `name` is the name of the session. The default value is `fish`
which results in the current history file.

If it's set to an empty string, the history will not be stored to a
file.

Fixes #102
This commit is contained in:
tomassedovic 2017-06-30 17:13:02 -07:00 committed by Kurtis Rader
parent 6f6a4a842c
commit aec0973196
12 changed files with 213 additions and 16 deletions

View File

@ -62,7 +62,7 @@ Examples:
- Fish allows the user to set various syntax highlighting colors. This is needed because fish does not know what colors the terminal uses by default, which might make some things unreadable. The proper solution would be for text color preferences to be defined centrally by the user for all programs, and for the terminal emulator to send these color properties to fish.
- Fish does not allow you to set the history filename, the number of history entries, different language substyles or any number of other common shell configuration options.
- Fish does not allow you to set the number of history entries, different language substyles or any number of other common shell configuration options.
A special note on the evils of configurability is the long list of very useful features found in some shells, that are not turned on by default. Both zsh and bash support command-specific completions, but no such completions are shipped with bash by default, and they are turned off by default in zsh. Other features that zsh supports that are disabled by default include tab-completion of strings containing wildcards, a sane completion pager and a history file.

View File

@ -60,6 +60,16 @@ history delete --prefix "foo"
# You can select more than one entry by entering their IDs seperated by a space.
\endfish
\subsection customizing-the-histfile Customizing the name of the history file
By default interactive commands are logged to `$XDG_DATA_HOME/fish/fish_history` (typically `~/.local/share/fish/fish_history`).
You can set the `FISH_HISTFILE` variable to another name for the current shell session. The default value (when the variable is unset) is `fish` which corresponds to `$XDG_DATA_HOME/fish/fish_history`. If you set it to e.g. `fun`, the history would be written to `$XDG_DATA_HOME/fish/fun_history`. An empty string means history will not be stored at all. This is similar to the private session features in web browsers.
You can change `FISH_HISTFILE` at any time (by using `set -x FISH_HISTFILE "session_name"`) and it will take effect right away. If you set it to `"default"`, it will use the default session name (which is `"fish"`).
Other shells such as bash and zsh use a variable named `HISTFILE` for a similar purpose. Fish uses a different name to avoid conflicts and signal that the behavior is different (session name instead of a file path).
\subsection history-notes Notes
If you specify both `--prefix` and `--contains` the last flag seen is used.

View File

@ -846,6 +846,8 @@ The user can change the settings of `fish` by changing the values of certain var
- `history`, an array containing the last commands that were entered.
- `FISH_HISTFILE`: Name of the history session. It defaults to `fish` (which typically means the history will be saved in `~/.local/share/fish/fish_history`). This variable can be changed by the user. It does not have to be an environment variable. You can change it at will within an interactive fish session to change where subsequent commands are logged.
- `HOME`, the user's home directory. This variable can be changed by the user.
- `IFS`, the internal field separator that is used for word splitting with the <a href="commands.html#read">read builtin</a>. Setting this to the empty string will also disable line splitting in <a href="#expand-command-substitution">command substitution</a>. This variable can be changed by the user.
@ -1129,7 +1131,7 @@ History searches can be aborted by pressing the escape key.
Prefixing the commandline with a space will prevent the entire line from being stored in the history.
The history is stored in the file `~/.local/share/fish/fish_history` (or `$XDG_DATA_HOME/fish/fish_history` if that variable is set).
The command history is stored in the file `~/.local/share/fish/fish_history` (or `$XDG_DATA_HOME/fish/fish_history` if that variable is set) by default. However, you can set the `FISH_HISTFILE` environment variable to change the name of the history session (resulting in a `<session>_history` file); both before starting the shell and while the shell is running.
Examples:

View File

@ -212,7 +212,7 @@ int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// 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 (!history) history = &history_t::history_with_name(history_session_id());
// 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.

View File

@ -614,6 +614,9 @@ static void react_to_variable_change(const wcstring &key) {
invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars
} else if (key == L"FISH_READ_BYTE_LIMIT") {
env_set_read_limit();
} else if (key == L"FISH_HISTFILE") {
history_destroy();
reader_push(history_session_id().c_str());
}
}
@ -1178,7 +1181,7 @@ env_var_t env_get_string(const wcstring &key, env_mode_flags_t mode) {
history_t *history = reader_get_history();
if (!history) {
history = &history_t::history_with_name(L"fish");
history = &history_t::history_with_name(history_session_id());
}
if (history) history->get_string_representation(&result, ARRAY_SEP_STR);
return result;

View File

@ -1139,16 +1139,20 @@ static void unescape_yaml(std::string *str) {
}
}
static wcstring history_filename(const wcstring &name, const wcstring &suffix) {
wcstring path;
if (!path_get_data(path)) return L"";
static wcstring history_filename(const wcstring &session_id, const wcstring &suffix) {
if (session_id.empty()) {
return L"";
} else {
wcstring path;
if (!path_get_data(path)) return L"";
wcstring result = path;
result.append(L"/");
result.append(name);
result.append(L"_history");
result.append(suffix);
return result;
wcstring result = path;
result.append(L"/");
result.append(session_id);
result.append(L"_history");
result.append(suffix);
return result;
}
}
void history_t::clear_file_state() {
@ -1401,6 +1405,9 @@ bool history_t::save_internal_via_appending() {
// Get the path to the real history file.
wcstring history_path = history_filename(name, wcstring());
if (history_path.empty()) {
return true;
}
signal_block();
@ -1508,6 +1515,13 @@ void history_t::save_internal(bool vacuum) {
// Nothing to do if there's no new items.
if (first_unwritten_new_item_index >= new_items.size() && deleted_items.empty()) return;
if (history_filename(name, L"").empty()) {
// We're in the "incognito" mode. Pretend we've saved the history.
this->first_unwritten_new_item_index = new_items.size();
this->deleted_items.clear();
this->clear_file_state();
}
// Compact our new items so we don't have duplicates.
this->compact_new_items();
@ -1627,6 +1641,10 @@ bool history_t::is_empty(void) {
// If we have not loaded old items, don't actually load them (which may be expensive); just
// stat the file and see if it exists and is nonempty.
const wcstring where = history_filename(name, L"");
if (where.empty()) {
return true;
}
struct stat buf = {};
if (wstat(where, &buf) != 0) {
// Access failed, assume missing.
@ -1643,6 +1661,11 @@ bool history_t::is_empty(void) {
/// clearing ourselves, and copying the contents of the old history file to the new history file.
/// The new contents will automatically be re-mapped later.
void history_t::populate_from_config_path() {
wcstring new_file = history_filename(name, wcstring());
if (new_file.empty()) {
return;
}
wcstring old_file;
if (path_get_config(old_file)) {
old_file.append(L"/");
@ -1650,8 +1673,6 @@ void history_t::populate_from_config_path() {
old_file.append(L"_history");
int src_fd = wopen_cloexec(old_file, O_RDONLY, 0);
if (src_fd != -1) {
wcstring new_file = history_filename(name, wcstring());
// Clear must come after we've retrieved the new_file name, and before we open
// destination file descriptor, since it destroys the name and the file.
this->clear();
@ -1778,6 +1799,28 @@ void history_sanity_check() {
// No sanity checking implemented yet...
}
wcstring history_session_id() {
wcstring result = L"fish";
const env_var_t session_id = env_get_string(L"FISH_HISTFILE");
if (!session_id.missing()) {
if (session_id.empty()) {
result = L"";
} else if (session_id == L"default") {
; // using the default value
} else if (valid_var_name(session_id)) {
result = session_id;
} else {
debug(0,
_(L"History session ID '%ls' is not a valid variable name. "
L"Falling back to `%ls`."),
session_id.c_str(), result.c_str());
}
}
return result;
}
path_list_t valid_paths(const path_list_t &paths, const wcstring &working_directory) {
ASSERT_IS_BACKGROUND_THREAD();
wcstring_list_t result;

View File

@ -350,6 +350,8 @@ void history_destroy();
// Perform sanity checks.
void history_sanity_check();
wcstring history_session_id();
// Given a list of paths and a working directory, return the paths that are valid
// This does disk I/O and may only be called in a background thread
path_list_t valid_paths(const path_list_t &paths, const wcstring &working_directory);

View File

@ -2240,7 +2240,7 @@ static bool selection_is_at_top() {
/// Read interactively. Read input from stdin while providing editing facilities.
static int read_i(void) {
reader_push(L"fish");
reader_push(history_session_id().c_str());
reader_set_complete_function(&complete);
reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test);

129
tests/histfile.expect Normal file
View File

@ -0,0 +1,129 @@
# vim: set filetype=expect:
# We're going to use three history files, including the default, to verify
# that the FISH_HISTFILE variable works as expected.
set default_histfile "../test/data/fish/fish_history"
set my_histfile "../test/data/fish/my_history"
set env_histfile "../test/data/fish/env_history"
# =============
# Verify that if we spawn fish with no FISH_HISTFILE env var it uses the
# default file.
# =============
set fish_pid [spawn $fish]
expect_prompt
# Verify that a command is recorded in the default history file.
set cmd1 "echo $fish_pid default histfile"
set hist_line "- cmd: $cmd1"
send "$cmd1\r"
expect_prompt
# TODO: Figure out why this `history --save` is only needed in one of the
# three Travis CI build environments and neither of my OS X or Ubuntu servers.
send "history --save\r"
expect_prompt
send "grep '^$hist_line' $default_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts "cmd1 found in default histfile"
} unmatched {
puts stderr "cmd1 not found in default histfile"
}
# Switch to a new history file and verify it is written to and the default
# history file is not written to.
set cmd2 "echo $fish_pid my histfile"
set hist_line "- cmd: $cmd2"
send "set FISH_HISTFILE my\r"
expect_prompt
send "$cmd2\r"
expect_prompt
# TODO: Figure out why this `history --save` is only needed in one of the
# three Travis CI build environments and neither of my OS X or Ubuntu servers.
send "history --save\r"
expect_prompt
send "grep '^$hist_line' $my_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts "cmd2 found in my histfile"
} unmatched {
puts stderr "cmd2 not found in my histfile"
}
# We expect this grep to fail to find the pattern and thus the expect_prompt
# block is inverted.
send "grep '^$hist_line' $default_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts stderr "cmd2 found in default histfile"
} unmatched {
puts "cmd2 not found in default histfile"
}
# Switch back to the default history file.
set cmd3 "echo $fish_pid default histfile again"
set hist_line "- cmd: $cmd3"
send "set FISH_HISTFILE default\r"
expect_prompt
send "$cmd3\r"
expect_prompt
# TODO: Figure out why this `history --save` is only needed in one of the
# three Travis CI build environments and neither of my OS X or Ubuntu servers.
send "history --save\r"
expect_prompt
send "grep '^$hist_line' $default_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts "cmd3 found in default histfile"
} unmatched {
puts stderr "cmd3 not found in default histfile"
}
# We expect this grep to fail to find the pattern and thus the expect_prompt
# block is inverted.
send "grep '^$hist_line' $my_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts stderr "cmd3 found in my histfile"
} unmatched {
puts "cmd3 not found in my histfile"
}
# =============
# Verify that if we spawn fish with a HISTFILE env var it uses that file.
# =============
# Start by shutting down the previous shell.
send "exit\r"
close $spawn_id
# Set the FISH_HISTFILE env var.
set ::env(FISH_HISTFILE) env
# Spawn a new shell.
set prompt_counter 1
set fish_pid [spawn $fish]
expect_prompt
# Verify that the new fish shell is using the FISH_HISTFILE value for history.
set cmd4 "echo $fish_pid env histfile"
set hist_line "- cmd: $cmd4"
send "$cmd4\r"
expect_prompt
# TODO: Figure out why this `history --save` is only needed in one of the
# three Travis CI build environments and neither of my OS X or Ubuntu servers.
send "history --save\r"
expect_prompt
send "grep '^$hist_line' $env_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts "cmd4 found in env histfile"
} unmatched {
puts stderr "cmd4 not found in env histfile"
}
# We expect this grep to fail to find the pattern and thus the expect_prompt
# block is inverted.
send "grep '^$hist_line' $default_histfile\r"
expect_prompt -re "\r\n$hist_line\r\n" {
puts stderr "cmd4 found in default histfile"
} unmatched {
puts "cmd4 not found in default histfile"
}

View File

View File

@ -0,0 +1,7 @@
cmd1 found in default histfile
cmd2 found in my histfile
cmd2 not found in default histfile
cmd3 found in default histfile
cmd3 not found in my histfile
cmd4 found in env histfile
cmd4 not found in default histfile

View File

@ -0,0 +1 @@
0