diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 18390345e4..4ff1776d0f 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -1357,7 +1357,7 @@ fn variables_completions() { // Test completions for $nu let suggestions = completer.complete("$nu.", 4); - assert_eq!(18, suggestions.len()); + assert_eq!(19, suggestions.len()); let expected: Vec = vec![ "cache-dir".into(), @@ -1377,6 +1377,7 @@ fn variables_completions() { "plugin-path".into(), "startup-time".into(), "temp-path".into(), + "user-autoload-dirs".into(), "vendor-autoload-dirs".into(), ]; diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 695c044a79..42bf6b4de6 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -194,6 +194,17 @@ pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Valu ), ); + record.push( + "user-autoload-dirs", + Value::list( + get_user_autoload_dirs(engine_state) + .iter() + .map(|path| Value::string(path.to_string_lossy(), span)) + .collect(), + span, + ), + ); + record.push("temp-path", { let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); Value::string(canon_temp_path.to_string_lossy(), span) @@ -306,10 +317,9 @@ pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec { .map(into_autoload_path_fn) .for_each(&mut append_fn); - option_env!("NU_VENDOR_AUTOLOAD_DIR") - .into_iter() - .map(PathBuf::from) - .for_each(&mut append_fn); + if let Some(path) = option_env!("NU_VENDOR_AUTOLOAD_DIR") { + append_fn(PathBuf::from(path)); + } #[cfg(target_os = "macos")] std::env::var("XDG_DATA_HOME") @@ -326,15 +336,37 @@ pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec { .into_iter() .for_each(&mut append_fn); - dirs::data_dir() - .into_iter() - .map(into_autoload_path_fn) - .for_each(&mut append_fn); + if let Some(data_dir) = dirs::data_dir() { + append_fn(into_autoload_path_fn(data_dir)); + } - std::env::var_os("NU_VENDOR_AUTOLOAD_DIR") - .into_iter() - .map(PathBuf::from) - .for_each(&mut append_fn); + if let Some(path) = std::env::var_os("NU_VENDOR_AUTOLOAD_DIR") { + append_fn(PathBuf::from(path)); + } + + dirs +} + +pub fn get_user_autoload_dirs(_engine_state: &EngineState) -> Vec { + // User autoload directories - Currently just `autoload` in the default + // configuration directory + let into_autoload_path_fn = |mut path: PathBuf| { + path.push("nushell"); + path.push("autoload"); + path + }; + + let mut dirs = Vec::new(); + + let mut append_fn = |path: PathBuf| { + if !dirs.contains(&path) { + dirs.push(path) + } + }; + + if let Some(config_dir) = dirs::config_dir() { + append_fn(into_autoload_path_fn(config_dir)); + } dirs } diff --git a/src/config_files.rs b/src/config_files.rs index 56769cf330..3493fe0be8 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -6,6 +6,7 @@ use nu_engine::convert_env_values; use nu_path::canonicalize_with; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, + eval_const::{get_user_autoload_dirs, get_vendor_autoload_dirs}, report_parse_error, report_shell_error, Config, ParseError, PipelineData, Spanned, }; use nu_utils::{get_default_config, get_default_env, get_scaffold_config, get_scaffold_env, perf}; @@ -197,24 +198,29 @@ pub(crate) fn read_vendor_autoload_files(engine_state: &mut EngineState, stack: // The evaluation order is first determined by the semantics of `get_vendor_autoload_dirs` // to determine the order of directories to evaluate - for autoload_dir in nu_protocol::eval_const::get_vendor_autoload_dirs(engine_state) { - warn!("read_vendor_autoload_files: {}", autoload_dir.display()); + get_vendor_autoload_dirs(engine_state) + .iter() + // User autoload directories are evaluated after vendor, which means that + // the user can override vendor autoload files + .chain(get_user_autoload_dirs(engine_state).iter()) + .for_each(|autoload_dir| { + warn!("read_vendor_autoload_files: {}", autoload_dir.display()); - if autoload_dir.exists() { - // on a second levels files are lexicographically sorted by the string of the filename - let entries = read_and_sort_directory(&autoload_dir); - if let Ok(entries) = entries { - for entry in entries { - if !entry.ends_with(".nu") { - continue; + if autoload_dir.exists() { + // on a second levels files are lexicographically sorted by the string of the filename + let entries = read_and_sort_directory(autoload_dir); + if let Ok(entries) = entries { + for entry in entries { + if !entry.ends_with(".nu") { + continue; + } + let path = autoload_dir.join(entry); + warn!("AutoLoading: {:?}", path); + eval_config_contents(path, engine_state, stack); } - let path = autoload_dir.join(entry); - warn!("AutoLoading: {:?}", path); - eval_config_contents(path, engine_state, stack); } } - } - } + }); } fn eval_default_config(