Add config.history.path

This was originally brought up in #11962, but closed in favour of the
more general #10100. However this commit doesn't address the broader
theme of using alternate XDG vars for the default history path.

Here is the updated `sample_config.nu` documentation for the field:

> # When this config doesn't exist or is set to null, then a default path is used
> # based on the OS and ENV.
> #
> # You will likely want to match the file extension to the `file_format` setting,
> # therefore ".txt" or ".sqlite".
> #
> # Nushell will create the file if it doesn't exist. However it won't create the
> # directory path, and will error if it doesn't exist.
> $env.config.history.path = null
This commit is contained in:
Thomas Buckley-Houston 2024-11-25 16:18:52 +01:00
parent 4d3283e235
commit 85772e0407
No known key found for this signature in database
GPG key ID: 6A8FA0E4BA72A791
7 changed files with 82 additions and 13 deletions

View file

@ -45,8 +45,10 @@ impl Command for History {
return Ok(PipelineData::empty()); return Ok(PipelineData::empty());
}; };
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history` // todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
let Some(history_path) = history.file_path() else { let result = history.file_path(Some(call.head));
return Err(ShellError::ConfigDirNotFound { span: Some(head) }); let history_path = match result {
Ok(path) => path,
Err(err) => return Err(err),
}; };
if call.has_flag(engine_state, stack, "clear")? { if call.has_flag(engine_state, stack, "clear")? {

View file

@ -73,10 +73,10 @@ Note that history item IDs are ignored when importing from file."#
let Some(history) = engine_state.history_config() else { let Some(history) = engine_state.history_config() else {
return ok; return ok;
}; };
let Some(current_history_path) = history.file_path() else { let result = history.file_path(Some(call.head));
return Err(ShellError::ConfigDirNotFound { let current_history_path = match result {
span: Some(call.head), Ok(path) => path,
}); Err(err) => return Err(err),
}; };
if let Some(bak_path) = backup(&current_history_path)? { if let Some(bak_path) = backup(&current_history_path)? {
println!("Backed history to {}", bak_path.display()); println!("Backed history to {}", bak_path.display());

View file

@ -1126,7 +1126,7 @@ fn setup_history(
None None
}; };
if let Some(path) = history.file_path() { if let Ok(path) = history.file_path(None) {
return update_line_editor_history( return update_line_editor_history(
engine_state, engine_state,
path, path,
@ -1401,7 +1401,7 @@ fn trailing_slash_looks_like_path() {
fn are_session_ids_in_sync() { fn are_session_ids_in_sync() {
let engine_state = &mut EngineState::new(); let engine_state = &mut EngineState::new();
let history = engine_state.history_config().unwrap(); let history = engine_state.history_config().unwrap();
let history_path = history.file_path().unwrap(); let history_path = history.file_path(None).unwrap();
let line_editor = reedline::Reedline::create(); let line_editor = reedline::Reedline::create();
let history_session_id = reedline::Reedline::create_history_session_id(); let history_session_id = reedline::Reedline::create_history_session_id();
let line_editor = update_line_editor_history( let line_editor = update_line_editor_history(

View file

@ -37,20 +37,54 @@ impl UpdateFromValue for HistoryFileFormat {
} }
} }
#[derive(Clone, Copy, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, IntoValue, PartialEq, Eq, Serialize, Deserialize)]
pub struct HistoryConfig { pub struct HistoryConfig {
pub max_size: i64, pub max_size: i64,
pub sync_on_enter: bool, pub sync_on_enter: bool,
pub file_format: HistoryFileFormat, pub file_format: HistoryFileFormat,
pub isolation: bool, pub isolation: bool,
pub path: Option<String>,
} }
impl HistoryConfig { impl HistoryConfig {
pub fn file_path(&self) -> Option<std::path::PathBuf> { pub fn file_path(&self, call_head: Option<Span>) -> Result<std::path::PathBuf, ShellError> {
nu_path::nu_config_dir().map(|mut history_path| { match self.path.clone() {
None => self.system_defined_file_path(call_head),
Some(path) => self.user_defined_file_path(path, call_head),
}
}
fn system_defined_file_path(
&self,
call_head: Option<Span>,
) -> Result<std::path::PathBuf, ShellError> {
let system_path = nu_path::nu_config_dir().map(|mut history_path| {
history_path.push(self.file_format.default_file_name()); history_path.push(self.file_format.default_file_name());
history_path.into() history_path.into()
}) });
match system_path {
Some(path) => Ok(path),
None => Err(ShellError::ConfigDirNotFound { span: call_head }),
}
}
fn user_defined_file_path(
&self,
path_from_config: String,
call_head: Option<Span>,
) -> Result<std::path::PathBuf, ShellError> {
let error = Err(ShellError::HistoryDirNotFound { span: call_head });
let user_path = std::path::Path::new(&path_from_config);
let dir_path = match user_path.parent() {
Some(path) => path,
None => return error,
};
match dir_path.exists() {
true => Ok(user_path.into()),
false => error,
}
} }
} }
@ -61,6 +95,7 @@ impl Default for HistoryConfig {
sync_on_enter: true, sync_on_enter: true,
file_format: HistoryFileFormat::Plaintext, file_format: HistoryFileFormat::Plaintext,
isolation: false, isolation: false,
path: None,
} }
} }
} }
@ -84,6 +119,11 @@ impl UpdateFromValue for HistoryConfig {
"sync_on_enter" => self.sync_on_enter.update(val, path, errors), "sync_on_enter" => self.sync_on_enter.update(val, path, errors),
"max_size" => self.max_size.update(val, path, errors), "max_size" => self.max_size.update(val, path, errors),
"file_format" => self.file_format.update(val, path, errors), "file_format" => self.file_format.update(val, path, errors),
"path" => match val {
Value::Nothing { .. } => self.path = None,
Value::String { val, .. } => self.path = Some(val.clone()),
_ => errors.type_mismatch(path, Type::custom("path or nothing"), val),
},
_ => errors.unknown_option(path, val), _ => errors.unknown_option(path, val),
} }
} }

View file

@ -762,7 +762,7 @@ impl EngineState {
/// Returns the configuration settings for command history or `None` if history is disabled /// Returns the configuration settings for command history or `None` if history is disabled
pub fn history_config(&self) -> Option<HistoryConfig> { pub fn history_config(&self) -> Option<HistoryConfig> {
self.history_enabled.then(|| self.config.history) self.history_enabled.then(|| self.config.history.clone())
} }
pub fn get_var(&self, var_id: VarId) -> &Variable { pub fn get_var(&self, var_id: VarId) -> &Variable {

View file

@ -1434,6 +1434,20 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
span: Option<Span>, span: Option<Span>,
}, },
/// The config directory could not be found
#[error("The base directory for the history file could not be found")]
#[diagnostic(
code(nu::shell::history_dir_not_found),
help(
r#"A user-defined path for the history file was given in the config, but the base directory for the path does not exist.
Please create it."#
)
)]
HistoryDirNotFound {
#[label = "Could not find history directory"]
span: Option<Span>,
},
/// XDG_CONFIG_HOME was set to an invalid path /// XDG_CONFIG_HOME was set to an invalid path
#[error("$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}")] #[error("$env.XDG_CONFIG_HOME ({xdg}) is invalid, using default config directory instead: {default}")]
#[diagnostic( #[diagnostic(

View file

@ -63,6 +63,19 @@ $env.config.history.sync_on_enter = true
# This setting only applies to SQLite-backed history # This setting only applies to SQLite-backed history
$env.config.history.isolation = true $env.config.history.isolation = true
# path (string):
# The absolute path to the history file.
#
# When this config doesn't exist or is set to null, then a default path is used
# based on the OS and ENV.
#
# You will likely want to match the file extension to the `file_format` setting,
# therefore ".txt" or ".sqlite".
#
# Nushell will create the file if it doesn't exist. However it won't create the
# directory path, and will error if it doesn't exist.
$env.config.history.path = null
# ---------------------- # ----------------------
# Miscellaneous Settings # Miscellaneous Settings
# ---------------------- # ----------------------