diff --git a/crates/nu-cli/src/commands/history/history_.rs b/crates/nu-cli/src/commands/history/history_.rs index 7d248dd945..b8e7c3ac41 100644 --- a/crates/nu-cli/src/commands/history/history_.rs +++ b/crates/nu-cli/src/commands/history/history_.rs @@ -44,6 +44,10 @@ impl Command for History { ) -> Result { let head = call.head; + let Some(history) = engine_state.history_config() else { + return Ok(PipelineData::empty()); + }; + // todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history` if let Some(config_path) = nu_path::config_dir() { let clear = call.has_flag(engine_state, stack, "clear")?; @@ -52,7 +56,7 @@ impl Command for History { let mut history_path = config_path; history_path.push("nushell"); - match engine_state.config.history_file_format { + match history.file_format { HistoryFileFormat::Sqlite => { history_path.push("history.sqlite3"); } @@ -66,29 +70,27 @@ impl Command for History { // TODO: FIXME also clear the auxiliary files when using sqlite Ok(PipelineData::empty()) } else { - let history_reader: Option> = - match engine_state.config.history_file_format { - HistoryFileFormat::Sqlite => { - SqliteBackedHistory::with_file(history_path, None, None) - .map(|inner| { - let boxed: Box = Box::new(inner); - boxed - }) - .ok() - } + let history_reader: Option> = match history.file_format { + HistoryFileFormat::Sqlite => { + SqliteBackedHistory::with_file(history_path, None, None) + .map(|inner| { + let boxed: Box = Box::new(inner); + boxed + }) + .ok() + } - HistoryFileFormat::PlainText => FileBackedHistory::with_file( - engine_state.config.max_history_size as usize, - history_path, - ) - .map(|inner| { - let boxed: Box = Box::new(inner); - boxed - }) - .ok(), - }; + HistoryFileFormat::PlainText => { + FileBackedHistory::with_file(history.max_size as usize, history_path) + .map(|inner| { + let boxed: Box = Box::new(inner); + boxed + }) + .ok() + } + }; - match engine_state.config.history_file_format { + match history.file_format { HistoryFileFormat::PlainText => Ok(history_reader .and_then(|h| { h.search(SearchQuery::everything(SearchDirection::Forward, None)) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index ea0ebef7e5..b93eb406bc 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -17,8 +17,8 @@ use nu_protocol::{ config::NuCursorShape, engine::{EngineState, Stack, StateWorkingSet}, eval_const::create_nu_constant, - report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, - Value, NU_VARIABLE_ID, + report_error, report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, + Span, Spanned, Value, NU_VARIABLE_ID, }; use nu_utils::utils::perf; use reedline::{ @@ -28,7 +28,7 @@ use reedline::{ use std::{ env::temp_dir, io::{self, IsTerminal, Write}, - path::Path, + path::PathBuf, sync::atomic::Ordering, time::Instant, }; @@ -109,31 +109,36 @@ pub fn evaluate_repl( use_color, ); - // Setup history_isolation aka "history per session" - let history_isolation = engine_state.get_config().history_isolation; - let history_session_id = if history_isolation { - Reedline::create_history_session_id() - } else { - None - }; + if let Some(history) = engine_state.history_config() { + start_time = std::time::Instant::now(); - start_time = std::time::Instant::now(); - let history_path = crate::config_files::get_history_path( - nushell_path, - engine_state.config.history_file_format, - ); - if let Some(history_path) = history_path.as_deref() { - line_editor = - update_line_editor_history(engine_state, history_path, line_editor, history_session_id)? - }; - perf( - "setup history", - start_time, - file!(), - line!(), - column!(), - use_color, - ); + // Setup history_isolation aka "history per session" + let history_session_id = if history.isolation { + Reedline::create_history_session_id() + } else { + None + }; + + if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) + { + line_editor = update_line_editor_history( + engine_state, + path, + history, + line_editor, + history_session_id, + )? + }; + + perf( + "setup history", + start_time, + file!(), + line!(), + column!(), + use_color, + ); + } if let Some(s) = prerun_command { eval_source( @@ -313,20 +318,22 @@ pub fn evaluate_repl( use_color, ); - start_time = std::time::Instant::now(); - if config.sync_history_on_enter { - if let Err(e) = line_editor.sync_history() { - warn!("Failed to sync history: {}", e); + if let Some(history) = engine_state.history_config() { + start_time = std::time::Instant::now(); + if history.sync_on_enter { + if let Err(e) = line_editor.sync_history() { + warn!("Failed to sync history: {}", e); + } } + perf( + "sync_history", + start_time, + file!(), + line!(), + column!(), + use_color, + ); } - perf( - "sync_history", - start_time, - file!(), - line!(), - column!(), - use_color, - ); start_time = std::time::Instant::now(); // Changing the line editor based on the found keybindings @@ -418,8 +425,10 @@ pub fn evaluate_repl( match input { Ok(Signal::Success(s)) => { let hostname = System::host_name(); - let history_supports_meta = - matches!(config.history_file_format, HistoryFileFormat::Sqlite); + let history_supports_meta = matches!( + engine_state.history_config().map(|h| h.file_format), + Some(HistoryFileFormat::Sqlite) + ); if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context() { line_editor @@ -715,18 +724,15 @@ fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reed fn update_line_editor_history( engine_state: &mut EngineState, - history_path: &Path, + history_path: PathBuf, + history: HistoryConfig, line_editor: Reedline, history_session_id: Option, ) -> Result { - let config = engine_state.get_config(); - let history: Box = match engine_state.config.history_file_format { + let history: Box = match history.file_format { HistoryFileFormat::PlainText => Box::new( - FileBackedHistory::with_file( - config.max_history_size as usize, - history_path.to_path_buf(), - ) - .into_diagnostic()?, + FileBackedHistory::with_file(history.max_size as usize, history_path) + .into_diagnostic()?, ), HistoryFileFormat::Sqlite => Box::new( SqliteBackedHistory::with_file( @@ -834,14 +840,18 @@ fn trailing_slash_looks_like_path() { #[test] fn are_session_ids_in_sync() { let engine_state = &mut EngineState::new(); - let history_path_o = - crate::config_files::get_history_path("nushell", engine_state.config.history_file_format); - assert!(history_path_o.is_some()); - let history_path = history_path_o.as_deref().unwrap(); + let history = engine_state.history_config().unwrap(); + let history_path = + crate::config_files::get_history_path("nushell", history.file_format).unwrap(); let line_editor = reedline::Reedline::create(); let history_session_id = reedline::Reedline::create_history_session_id(); - let line_editor = - update_line_editor_history(engine_state, history_path, line_editor, history_session_id); + let line_editor = update_line_editor_history( + engine_state, + history_path, + history, + line_editor, + history_session_id, + ); assert_eq!( i64::from(line_editor.unwrap().get_history_session_id().unwrap()), engine_state.history_session_id diff --git a/crates/nu-cli/tests/completions.rs b/crates/nu-cli/tests/completions.rs index e48c1e9ee4..6a0af5b116 100644 --- a/crates/nu-cli/tests/completions.rs +++ b/crates/nu-cli/tests/completions.rs @@ -66,7 +66,7 @@ fn custom_completer() -> NuCompleter { // Add record value as example let record = r#" - let external_completer = {|spans| + let external_completer = {|spans| $spans } @@ -684,13 +684,14 @@ fn variables_completions() { // Test completions for $nu let suggestions = completer.complete("$nu.", 4); - assert_eq!(14, suggestions.len()); + assert_eq!(15, suggestions.len()); let expected: Vec = vec![ "config-path".into(), "current-exe".into(), "default-config-dir".into(), "env-path".into(), + "history-enabled".into(), "history-path".into(), "home-path".into(), "is-interactive".into(), @@ -709,9 +710,13 @@ fn variables_completions() { // Test completions for $nu.h (filter) let suggestions = completer.complete("$nu.h", 5); - assert_eq!(2, suggestions.len()); + assert_eq!(3, suggestions.len()); - let expected: Vec = vec!["history-path".into(), "home-path".into()]; + let expected: Vec = vec![ + "history-enabled".into(), + "history-path".into(), + "home-path".into(), + ]; // Match results match_suggestions(expected, suggestions); diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index 4f578fb827..312938d6c6 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -25,6 +25,25 @@ mod output; mod reedline; mod table; +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct HistoryConfig { + pub max_size: i64, + pub sync_on_enter: bool, + pub file_format: HistoryFileFormat, + pub isolation: bool, +} + +impl Default for HistoryConfig { + fn default() -> Self { + Self { + max_size: 100_000, + sync_on_enter: true, + file_format: HistoryFileFormat::PlainText, + isolation: false, + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { pub external_completer: Option, @@ -46,10 +65,7 @@ pub struct Config { pub partial_completions: bool, pub completion_algorithm: CompletionAlgorithm, pub edit_mode: EditBindings, - pub max_history_size: i64, - pub sync_history_on_enter: bool, - pub history_file_format: HistoryFileFormat, - pub history_isolation: bool, + pub history: HistoryConfig, pub keybindings: Vec, pub menus: Vec, pub hooks: Hooks, @@ -104,10 +120,7 @@ impl Default for Config { explore: HashMap::new(), - max_history_size: 100_000, - sync_history_on_enter: true, - history_file_format: HistoryFileFormat::PlainText, - history_isolation: false, + history: HistoryConfig::default(), case_sensitive_completions: false, quick_completions: true, @@ -172,7 +185,7 @@ impl Value { // the `2`. if let Value::Record { val, .. } = self { - val.retain_mut( |key, value| { + val.retain_mut(|key, value| { let span = value.span(); match key { // Grouped options @@ -232,22 +245,23 @@ impl Value { } } "history" => { + let history = &mut config.history; if let Value::Record { val, .. } = value { val.retain_mut(|key2, value| { let span = value.span(); match key2 { "isolation" => { - process_bool_config(value, &mut errors, &mut config.history_isolation); + process_bool_config(value, &mut errors, &mut history.isolation); } "sync_on_enter" => { - process_bool_config(value, &mut errors, &mut config.sync_history_on_enter); + process_bool_config(value, &mut errors, &mut history.sync_on_enter); } "max_size" => { - process_int_config(value, &mut errors, &mut config.max_history_size); + process_int_config(value, &mut errors, &mut history.max_size); } "file_format" => { process_string_enum( - &mut config.history_file_format, + &mut history.file_format, &[key, key2], value, &mut errors); @@ -264,10 +278,10 @@ impl Value { // Reconstruct *value = Value::record( record! { - "sync_on_enter" => Value::bool(config.sync_history_on_enter, span), - "max_size" => Value::int(config.max_history_size, span), - "file_format" => config.history_file_format.reconstruct_value(span), - "isolation" => Value::bool(config.history_isolation, span), + "sync_on_enter" => Value::bool(history.sync_on_enter, span), + "max_size" => Value::int(history.max_size, span), + "file_format" => history.file_format.reconstruct_value(span), + "isolation" => Value::bool(history.isolation, span), }, span, ); @@ -364,7 +378,7 @@ impl Value { let config_point = match key2 { "vi_insert" => &mut config.cursor_shape_vi_insert, "vi_normal" => &mut config.cursor_shape_vi_normal, - "emacs" => &mut config.cursor_shape_emacs, + "emacs" => &mut config.cursor_shape_emacs, _ => { report_invalid_key(&[key, key2], span, &mut errors); return false; @@ -720,12 +734,12 @@ impl Value { } // Catch all _ => { - report_invalid_key(&[key], span, &mut errors); + report_invalid_key(&[key], span, &mut errors); return false; } - }; - true - }); + }; + true + }); } else { return ( config, diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index b9c6d48edd..8647743438 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -5,8 +5,8 @@ use super::{usage::build_usage, usage::Usage, StateDelta}; use super::{Command, EnvVars, OverlayFrame, ScopeFrame, Stack, Visibility, DEFAULT_OVERLAY_NAME}; use crate::ast::Block; use crate::{ - BlockId, Config, DeclId, Example, FileId, Module, ModuleId, OverlayId, ShellError, Signature, - Span, Type, VarId, Variable, VirtualPathId, + BlockId, Config, DeclId, Example, FileId, HistoryConfig, Module, ModuleId, OverlayId, + ShellError, Signature, Span, Type, VarId, Variable, VirtualPathId, }; use crate::{Category, Value}; use std::borrow::Borrow; @@ -96,6 +96,7 @@ pub struct EngineState { #[cfg(feature = "plugin")] pub plugin_signatures: Option, config_path: HashMap, + pub history_enabled: bool, pub history_session_id: i64, // If Nushell was started, e.g., with `nu spam.nu`, the file's parent is stored here pub(super) currently_parsed_cwd: Option, @@ -151,6 +152,7 @@ impl EngineState { #[cfg(feature = "plugin")] plugin_signatures: None, config_path: HashMap::new(), + history_enabled: true, history_session_id: 0, currently_parsed_cwd: None, regex_cache: Arc::new(Mutex::new(LruCache::new( @@ -720,6 +722,15 @@ impl EngineState { self.config.plugins.get(plugin) } + /// Returns the configuration settings for command history or `None` if history is disabled + pub fn history_config(&self) -> Option { + if self.history_enabled { + Some(self.config.history) + } else { + None + } + } + pub fn get_var(&self, var_id: VarId) -> &Variable { self.vars .get(var_id) diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 091bf5432d..edbca1c322 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -80,7 +80,7 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result { path.push("history.sqlite3"); } @@ -187,6 +187,11 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result>, pub(crate) no_config_file: Option>, + pub(crate) no_history: Option>, pub(crate) no_std_lib: Option>, pub(crate) config_file: Option>, pub(crate) env_file: Option>, @@ -281,6 +284,11 @@ impl Command for Nu { "start with no config file and no env file", Some('n'), ) + .switch( + "no-history", + "disable reading and writing to command history", + None, + ) .switch("no-std-lib", "start with no standard library", None) .named( "threads", diff --git a/src/main.rs b/src/main.rs index eca31ed5ac..84fab009bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -128,6 +128,8 @@ fn main() -> Result<()> { engine_state.is_login = parsed_nu_cli_args.login_shell.is_some(); + engine_state.history_enabled = parsed_nu_cli_args.no_history.is_none(); + let use_color = engine_state.get_config().use_ansi_coloring; if let Some(level) = parsed_nu_cli_args .log_level