diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 5440f9caa2..4bd86ddbf9 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -16,7 +16,7 @@ use crate::{ use crossterm::cursor::SetCursorStyle; use log::{error, trace, warn}; use miette::{ErrReport, IntoDiagnostic, Result}; -use nu_cmd_base::{hook::eval_hook, util::get_editor}; +use nu_cmd_base::util::get_editor; use nu_color_config::StyleComputer; #[allow(deprecated)] use nu_engine::{convert_env_values, current_dir_str, env_to_strings}; @@ -313,20 +313,26 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("reset signals", start_time, use_color); start_time = std::time::Instant::now(); - // Right before we start our prompt and take input from the user, - // fire the "pre_prompt" hook - if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() { - if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") { - report_shell_error(engine_state, &err); - } + // Right before we start our prompt and take input from the user, fire the "pre_prompt" hook + if let Err(err) = hook::eval_hooks( + engine_state, + &mut stack, + vec![], + &engine_state.get_config().hooks.pre_prompt.clone(), + "pre_prompt", + ) { + report_shell_error(engine_state, &err); } perf!("pre-prompt hook", start_time, use_color); start_time = std::time::Instant::now(); // Next, check all the environment variables they ask for // fire the "env_change" hook - let env_change = engine_state.get_config().hooks.env_change.clone(); - if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) { + if let Err(error) = hook::eval_env_change_hook( + &engine_state.get_config().hooks.env_change.clone(), + engine_state, + &mut stack, + ) { report_shell_error(engine_state, &error) } perf!("env-change hook", start_time, use_color); @@ -511,18 +517,17 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { // Right before we start running the code the user gave us, fire the `pre_execution` // hook - if let Some(hook) = config.hooks.pre_execution.clone() { + { // Set the REPL buffer to the current command for the "pre_execution" hook let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); repl.buffer = repl_cmd_line_text.to_string(); drop(repl); - if let Err(err) = eval_hook( + if let Err(err) = hook::eval_hooks( engine_state, &mut stack, - None, vec![], - &hook, + &engine_state.get_config().hooks.pre_execution.clone(), "pre_execution", ) { report_shell_error(engine_state, &err); diff --git a/crates/nu-cmd-base/src/hook.rs b/crates/nu-cmd-base/src/hook.rs index 4983dbdc8f..9082a16063 100644 --- a/crates/nu-cmd-base/src/hook.rs +++ b/crates/nu-cmd-base/src/hook.rs @@ -7,49 +7,56 @@ use nu_protocol::{ engine::{Closure, EngineState, Stack, StateWorkingSet}, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId, }; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; pub fn eval_env_change_hook( - env_change_hook: Option, + env_change_hook: &HashMap, engine_state: &mut EngineState, stack: &mut Stack, ) -> Result<(), ShellError> { - if let Some(hook) = env_change_hook { - match hook { - Value::Record { val, .. } => { - for (env_name, hook_value) in &*val { - let before = engine_state.previous_env_vars.get(env_name); - let after = stack.get_env_var(engine_state, env_name); - if before != after { - let before = before.cloned().unwrap_or_default(); - let after = after.cloned().unwrap_or_default(); + for (env, hook) in env_change_hook { + let before = engine_state.previous_env_vars.get(env); + let after = stack.get_env_var(engine_state, env); + if before != after { + let before = before.cloned().unwrap_or_default(); + let after = after.cloned().unwrap_or_default(); - eval_hook( - engine_state, - stack, - None, - vec![("$before".into(), before), ("$after".into(), after.clone())], - hook_value, - "env_change", - )?; + eval_hook( + engine_state, + stack, + None, + vec![("$before".into(), before), ("$after".into(), after.clone())], + hook, + "env_change", + )?; - Arc::make_mut(&mut engine_state.previous_env_vars) - .insert(env_name.clone(), after); - } - } - } - x => { - return Err(ShellError::TypeMismatch { - err_message: "record for the 'env_change' hook".to_string(), - span: x.span(), - }); - } + Arc::make_mut(&mut engine_state.previous_env_vars).insert(env.clone(), after); } } Ok(()) } +pub fn eval_hooks( + engine_state: &mut EngineState, + stack: &mut Stack, + arguments: Vec<(String, Value)>, + hooks: &[Value], + hook_name: &str, +) -> Result<(), ShellError> { + for hook in hooks { + eval_hook( + engine_state, + stack, + None, + arguments.clone(), + hook, + &format!("{hook_name} list, recursive"), + )?; + } + Ok(()) +} + pub fn eval_hook( engine_state: &mut EngineState, stack: &mut Stack, @@ -127,16 +134,7 @@ pub fn eval_hook( } } Value::List { vals, .. } => { - for val in vals { - eval_hook( - engine_state, - stack, - None, - arguments.clone(), - val, - &format!("{hook_name} list, recursive"), - )?; - } + eval_hooks(engine_state, stack, arguments, vals, hook_name)?; } Value::Record { val, .. } => { // Hooks can optionally be a record in this form: diff --git a/crates/nu-protocol/src/config/hooks.rs b/crates/nu-protocol/src/config/hooks.rs index 374b3818d6..b872bca26a 100644 --- a/crates/nu-protocol/src/config/hooks.rs +++ b/crates/nu-protocol/src/config/hooks.rs @@ -1,13 +1,13 @@ use super::prelude::*; use crate as nu_protocol; -use crate::Record; +use std::collections::HashMap; /// Definition of a parsed hook from the config object #[derive(Clone, Debug, IntoValue, PartialEq, Serialize, Deserialize)] pub struct Hooks { - pub pre_prompt: Option, - pub pre_execution: Option, - pub env_change: Option, + pub pre_prompt: Vec, + pub pre_execution: Vec, + pub env_change: HashMap, pub display_output: Option, pub command_not_found: Option, } @@ -15,14 +15,14 @@ pub struct Hooks { impl Hooks { pub fn new() -> Self { Self { - pre_prompt: Some(Value::list(vec![], Span::unknown())), - pre_execution: Some(Value::list(vec![], Span::unknown())), - env_change: Some(Value::record(Record::default(), Span::unknown())), + pre_prompt: Vec::new(), + pre_execution: Vec::new(), + env_change: HashMap::new(), display_output: Some(Value::string( "if (term size).columns >= 100 { table -e } else { table }", Span::unknown(), )), - command_not_found: Some(Value::list(vec![], Span::unknown())), + command_not_found: None, } } } @@ -40,14 +40,6 @@ impl UpdateFromValue for Hooks { path: &mut ConfigPath<'a>, errors: &mut ConfigErrors, ) { - fn update_option(field: &mut Option, value: &Value) { - if value.is_nothing() { - *field = None; - } else { - *field = Some(value.clone()); - } - } - let Value::Record { val: record, .. } = value else { errors.type_mismatch(path, Type::record(), value); return; @@ -56,11 +48,35 @@ impl UpdateFromValue for Hooks { for (col, val) in record.iter() { let path = &mut path.push(col); match col.as_str() { - "pre_prompt" => update_option(&mut self.pre_prompt, val), - "pre_execution" => update_option(&mut self.pre_execution, val), - "env_change" => update_option(&mut self.env_change, val), - "display_output" => update_option(&mut self.display_output, val), - "command_not_found" => update_option(&mut self.command_not_found, val), + "pre_prompt" => { + if let Ok(hooks) = val.as_list() { + self.pre_prompt = hooks.into() + } else { + errors.type_mismatch(path, Type::list(Type::Any), val); + } + } + "pre_execution" => { + if let Ok(hooks) = val.as_list() { + self.pre_execution = hooks.into() + } else { + errors.type_mismatch(path, Type::list(Type::Any), val); + } + } + "env_change" => self.env_change.update(val, path, errors), + "display_output" => { + self.display_output = if val.is_nothing() { + None + } else { + Some(val.clone()) + } + } + "command_not_found" => { + self.command_not_found = if val.is_nothing() { + None + } else { + Some(val.clone()) + } + } _ => errors.unknown_option(path, val), } } diff --git a/src/test_bins.rs b/src/test_bins.rs index 7e684d5794..d31fd07c57 100644 --- a/src/test_bins.rs +++ b/src/test_bins.rs @@ -1,4 +1,4 @@ -use nu_cmd_base::hook::{eval_env_change_hook, eval_hook}; +use nu_cmd_base::hook::{eval_env_change_hook, eval_hooks}; use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ @@ -250,24 +250,14 @@ pub fn nu_repl() { } // Check for pre_prompt hook - let config = engine_state.get_config(); - if let Some(hook) = config.hooks.pre_prompt.clone() { - if let Err(err) = eval_hook( - &mut engine_state, - &mut stack, - None, - vec![], - &hook, - "pre_prompt", - ) { - outcome_err(&engine_state, &err); - } + let hook = engine_state.get_config().hooks.pre_prompt.clone(); + if let Err(err) = eval_hooks(&mut engine_state, &mut stack, vec![], &hook, "pre_prompt") { + outcome_err(&engine_state, &err); } // Check for env change hook - let config = engine_state.get_config(); if let Err(err) = eval_env_change_hook( - config.hooks.env_change.clone(), + &engine_state.get_config().hooks.env_change.clone(), &mut engine_state, &mut stack, ) { @@ -275,7 +265,6 @@ pub fn nu_repl() { } // Check for pre_execution hook - let config = engine_state.get_config(); engine_state .repl_state @@ -283,17 +272,15 @@ pub fn nu_repl() { .expect("repl state mutex") .buffer = line.to_string(); - if let Some(hook) = config.hooks.pre_execution.clone() { - if let Err(err) = eval_hook( - &mut engine_state, - &mut stack, - None, - vec![], - &hook, - "pre_execution", - ) { - outcome_err(&engine_state, &err); - } + let hook = engine_state.get_config().hooks.pre_execution.clone(); + if let Err(err) = eval_hooks( + &mut engine_state, + &mut stack, + vec![], + &hook, + "pre_execution", + ) { + outcome_err(&engine_state, &err); } // Eval the REPL line