diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index a3a42f1650..5e1764692e 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -42,7 +42,9 @@ pub fn evaluate_commands( // Merge the delta in case env vars changed in the config match nu_engine::env::current_dir(engine_state, stack) { Ok(cwd) => { - if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { + if let Err(e) = + engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd) + { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); std::process::exit(1); diff --git a/crates/nu-cli/src/completions/command_completions.rs b/crates/nu-cli/src/completions/command_completions.rs index 76113d8aaa..7554261f0a 100644 --- a/crates/nu-cli/src/completions/command_completions.rs +++ b/crates/nu-cli/src/completions/command_completions.rs @@ -39,7 +39,7 @@ impl CommandCompletion { ) -> Vec { let mut executables = vec![]; - let paths = self.engine_state.env_vars.get("PATH"); + let paths = self.engine_state.get_env_var("PATH"); if let Some(paths) = paths { if let Ok(paths) = paths.as_list() { @@ -214,7 +214,7 @@ impl Completer for CommandCompletion { vec![] }; - let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { + let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") { match d.as_string() { Ok(s) => s, Err(_) => "".to_string(), diff --git a/crates/nu-cli/src/completions/directory_completions.rs b/crates/nu-cli/src/completions/directory_completions.rs index c04e910ce5..263d8df9be 100644 --- a/crates/nu-cli/src/completions/directory_completions.rs +++ b/crates/nu-cli/src/completions/directory_completions.rs @@ -33,7 +33,7 @@ impl Completer for DirectoryCompletion { _: usize, options: &CompletionOptions, ) -> Vec { - let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { + let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") { match d.as_string() { Ok(s) => s, Err(_) => "".to_string(), diff --git a/crates/nu-cli/src/completions/dotnu_completions.rs b/crates/nu-cli/src/completions/dotnu_completions.rs index 7907b2252a..1ab38ddef5 100644 --- a/crates/nu-cli/src/completions/dotnu_completions.rs +++ b/crates/nu-cli/src/completions/dotnu_completions.rs @@ -37,7 +37,7 @@ impl Completer for DotNuCompletion { // Fetch the lib dirs let lib_dirs: Vec = - if let Some(lib_dirs) = self.engine_state.env_vars.get("NU_LIB_DIRS") { + if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") { lib_dirs .as_list() .into_iter() @@ -70,7 +70,7 @@ impl Completer for DotNuCompletion { partial = base_dir_partial; } else { // Fetch the current folder - let current_folder = if let Some(d) = self.engine_state.env_vars.get("PWD") { + let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") { match d.as_string() { Ok(s) => s, Err(_) => "".to_string(), diff --git a/crates/nu-cli/src/completions/file_completions.rs b/crates/nu-cli/src/completions/file_completions.rs index a24139a894..e65c768f4b 100644 --- a/crates/nu-cli/src/completions/file_completions.rs +++ b/crates/nu-cli/src/completions/file_completions.rs @@ -30,7 +30,7 @@ impl Completer for FileCompletion { _: usize, options: &CompletionOptions, ) -> Vec { - let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { + let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") { match d.as_string() { Ok(s) => s, Err(_) => "".to_string(), diff --git a/crates/nu-cli/src/completions/variable_completions.rs b/crates/nu-cli/src/completions/variable_completions.rs index 5372b39af5..1bfd622edf 100644 --- a/crates/nu-cli/src/completions/variable_completions.rs +++ b/crates/nu-cli/src/completions/variable_completions.rs @@ -11,7 +11,7 @@ use std::sync::Arc; #[derive(Clone)] pub struct VariableCompletion { - engine_state: Arc, + engine_state: Arc, // TODO: Is engine state necessary? It's already a part of working set in fetch() stack: Stack, var_context: (Vec, Vec>), // tuple with $var and the sublevels (.b.c.d) } @@ -143,24 +143,39 @@ impl Completer for VariableCompletion { } } + // TODO: The following can be refactored (see find_commands_by_predicate() used in + // command_completions). + let mut removed_overlays = vec![]; // Working set scope vars - for scope in &working_set.delta.scope { - for v in &scope.vars { - if options.match_algorithm.matches_u8(v.0, &prefix) { - output.push(Suggestion { - value: String::from_utf8_lossy(v.0).to_string(), - description: None, - extra: None, - span: current_span, - append_whitespace: false, - }); + for scope_frame in working_set.delta.scope.iter().rev() { + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + for v in &overlay_frame.vars { + if options.match_algorithm.matches_u8(v.0, &prefix) { + output.push(Suggestion { + value: String::from_utf8_lossy(v.0).to_string(), + description: None, + extra: None, + span: current_span, + append_whitespace: false, + }); + } } } } // Permanent state vars - for scope in &self.engine_state.scope { - for v in &scope.vars { + // for scope in &self.engine_state.scope { + for overlay_frame in self + .engine_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + for v in &overlay_frame.vars { if options.match_algorithm.matches_u8(v.0, &prefix) { output.push(Suggestion { value: String::from_utf8_lossy(v.0).to_string(), @@ -173,7 +188,7 @@ impl Completer for VariableCompletion { } } - output.dedup(); + output.dedup(); // TODO: Removes only consecutive duplicates, is it intended? output } diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index 676beff889..30cf1e4767 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -69,7 +69,9 @@ pub fn eval_config_contents( // Merge the delta in case env vars changed in the config match nu_engine::env::current_dir(engine_state, stack) { Ok(cwd) => { - if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) { + if let Err(e) = + engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd) + { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &e); } diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index d7e4b084c7..d229b78451 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -71,7 +71,7 @@ pub fn print_table_or_error( _ => None, }; - match engine_state.find_decl("table".as_bytes()) { + match engine_state.find_decl("table".as_bytes(), &[]) { Some(decl_id) => { let table = engine_state.get_decl(decl_id).run( engine_state, diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index a1ada1efd5..c69cc8c54e 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -24,7 +24,7 @@ const DEFAULT_COMPLETION_MENU: &str = r#" type: { layout: columnar columns: 4 - col_width: 20 + col_width: 20 col_padding: 2 } style: { @@ -58,7 +58,7 @@ const DEFAULT_HELP_MENU: &str = r#" type: { layout: description columns: 4 - col_width: 20 + col_width: 20 col_padding: 2 selection_rows: 4 description_rows: 10 diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 72f6e96081..f798cedd91 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -301,7 +301,7 @@ pub fn evaluate_repl( if let Some(cwd) = stack.get_env_var(engine_state, "PWD") { let path = cwd.as_string()?; let _ = std::env::set_current_dir(path); - engine_state.env_vars.insert("PWD".into(), cwd); + engine_state.add_env_var("PWD".into(), cwd); } if use_shell_integration { diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 5e9f95def2..7a478c67cf 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -179,7 +179,7 @@ fn gather_env_vars(vars: impl Iterator, engine_state: & }; // stack.add_env_var(name, value); - engine_state.env_vars.insert(name, value); + engine_state.add_env_var(name, value); } } } @@ -319,12 +319,18 @@ mod test { &mut engine_state, ); - let env = engine_state.env_vars; + let env = engine_state.render_env_vars(); - assert!(matches!(env.get("FOO"), Some(Value::String { val, .. }) if val == "foo")); - assert!(matches!(env.get("SYMBOLS"), Some(Value::String { val, .. }) if val == symbols)); - assert!(matches!(env.get(symbols), Some(Value::String { val, .. }) if val == "symbols")); - assert!(env.get("PWD").is_some()); + assert!( + matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo") + ); + assert!( + matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols) + ); + assert!( + matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols") + ); + assert!(env.get(&"PWD".to_string()).is_some()); assert_eq!(env.len(), 4); } } diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 553e2a841b..e16ce786ba 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -63,23 +63,23 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are- return Err(ShellError::NonUtf8(import_pattern.head.span)); }; - if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) { + if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) { // The first word is a module - let overlay = engine_state.get_overlay(overlay_id); + let module = engine_state.get_module(module_id); let env_vars_to_hide = if import_pattern.members.is_empty() { - overlay.env_vars_with_head(&import_pattern.head.name) + module.env_vars_with_head(&import_pattern.head.name) } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => overlay.env_vars(), + ImportPatternMember::Glob { .. } => module.env_vars(), ImportPatternMember::Name { name, span } => { let mut output = vec![]; if let Some((name, id)) = - overlay.env_var_with_head(name, &import_pattern.head.name) + module.env_var_with_head(name, &import_pattern.head.name) { output.push((name, id)); - } else if !(overlay.has_alias(name) || overlay.has_decl(name)) { + } else if !(module.has_alias(name) || module.has_decl(name)) { return Err(ShellError::EnvVarNotFoundAtRuntime( String::from_utf8_lossy(name).into(), *span, @@ -93,10 +93,10 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are- for (name, span) in names { if let Some((name, id)) = - overlay.env_var_with_head(name, &import_pattern.head.name) + module.env_var_with_head(name, &import_pattern.head.name) { output.push((name, id)); - } else if !(overlay.has_alias(name) || overlay.has_decl(name)) { + } else if !(module.has_alias(name) || module.has_decl(name)) { return Err(ShellError::EnvVarNotFoundAtRuntime( String::from_utf8_lossy(name).into(), *span, diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index a8d135826f..39d4f4e143 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -22,6 +22,7 @@ mod ignore; mod let_; mod metadata; mod module; +pub(crate) mod overlay; mod source; mod tutor; mod use_; @@ -51,6 +52,7 @@ pub use ignore::Ignore; pub use let_::Let; pub use metadata::Metadata; pub use module::Module; +pub use overlay::*; pub use source::Source; pub use tutor::Tutor; pub use use_::Use; diff --git a/crates/nu-command/src/core_commands/overlay/add.rs b/crates/nu-command/src/core_commands/overlay/add.rs new file mode 100644 index 0000000000..d63d9d794c --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/add.rs @@ -0,0 +1,139 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape}; + +use std::path::Path; + +#[derive(Clone)] +pub struct OverlayAdd; + +impl Command for OverlayAdd { + fn name(&self) -> &str { + "overlay add" + } + + fn usage(&self) -> &str { + "Add definitions from a module as an overlay" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("overlay add") + .required( + "name", + SyntaxShape::String, + "Module name to create overlay for", + ) + // TODO: + // .switch( + // "prefix", + // "Prepend module name to the imported symbols", + // Some('p'), + // ) + .category(Category::Core) + } + + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check +https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"# + } + + fn is_parser_keyword(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let name_arg: Spanned = call.req(engine_state, stack, 0)?; + + // TODO: This logic is duplicated in the parser. + if stack.has_env_overlay(&name_arg.item, engine_state) { + stack.add_overlay(name_arg.item); + } else { + let (overlay_name, module) = + if let Some(module_id) = engine_state.find_module(name_arg.item.as_bytes(), &[]) { + (name_arg.item, engine_state.get_module(module_id)) + } else if let Some(os_str) = Path::new(&name_arg.item).file_stem() { + let name = if let Some(s) = os_str.to_str() { + s.to_string() + } else { + return Err(ShellError::NonUtf8(name_arg.span)); + }; + + if let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) { + (name, engine_state.get_module(module_id)) + } else { + return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime( + name_arg.item, + name_arg.span, + )); + } + } else { + return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime( + name_arg.item, + name_arg.span, + )); + }; + + stack.add_overlay(overlay_name); + + for (name, block_id) in module.env_vars() { + let name = if let Ok(s) = String::from_utf8(name.clone()) { + s + } else { + return Err(ShellError::NonUtf8(name_arg.span)); + }; + + let block = engine_state.get_block(block_id); + + let val = eval_block( + engine_state, + stack, + block, + PipelineData::new(call.head), + false, + true, + )? + .into_value(call.head); + + stack.add_env_var(name, val); + } + } + + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Create an overlay from a module", + example: r#"module spam { export def foo [] { "foo" } } + overlay add spam"#, + result: None, + }, + Example { + description: "Create an overlay from a file", + example: r#"echo 'export env FOO { "foo" }' | save spam.nu + overlay add spam.nu"#, + result: None, + }, + ] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(OverlayAdd {}) + } +} diff --git a/crates/nu-command/src/core_commands/overlay/command.rs b/crates/nu-command/src/core_commands/overlay/command.rs new file mode 100644 index 0000000000..80d099449d --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/command.rs @@ -0,0 +1,58 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, Signature, Value, +}; + +#[derive(Clone)] +pub struct Overlay; + +impl Command for Overlay { + fn name(&self) -> &str { + "overlay" + } + + fn signature(&self) -> Signature { + Signature::build("overlay").category(Category::Core) + } + + fn usage(&self) -> &str { + "Commands for manipulating overlays." + } + + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check +https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"# + } + + fn is_parser_keyword(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Overlay.signature(), &[], engine_state, stack), + span: call.head, + } + .into_pipeline_data()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Overlay {}) + } +} diff --git a/crates/nu-command/src/core_commands/overlay/list.rs b/crates/nu-command/src/core_commands/overlay/list.rs new file mode 100644 index 0000000000..40ee947a1d --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/list.rs @@ -0,0 +1,85 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value, +}; + +use log::trace; + +#[derive(Clone)] +pub struct OverlayList; + +impl Command for OverlayList { + fn name(&self) -> &str { + "overlay list" + } + + fn usage(&self) -> &str { + "List all active overlays" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("overlay list").category(Category::Core) + } + + fn extra_usage(&self) -> &str { + "The overlays are listed in the order they were activated." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let active_overlays_parser: Vec = engine_state + .active_overlay_names(&[]) + .iter() + .map(|s| Value::string(String::from_utf8_lossy(s), call.head)) + .collect(); + + let active_overlays_engine: Vec = stack + .active_overlays + .iter() + .map(|s| Value::string(s, call.head)) + .collect(); + + // Check if the overlays in the engine match the overlays in the parser + if (active_overlays_parser.len() != active_overlays_engine.len()) + || active_overlays_parser + .iter() + .zip(active_overlays_engine.iter()) + .any(|(op, oe)| op != oe) + { + trace!("parser overlays: {:?}", active_overlays_parser); + trace!("engine overlays: {:?}", active_overlays_engine); + + return Err(ShellError::NushellFailedSpannedHelp( + "Overlay mismatch".into(), + "Active overlays do not match between the engine and the parser.".into(), + call.head, + "Run Nushell with --log-level=trace to see what went wrong.".into(), + )); + } + + Ok(Value::List { + vals: active_overlays_engine, + span: call.head, + } + .into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the last activated overlay", + example: r#"module spam { export def foo [] { "foo" } } + overlay add spam + overlay list | last"#, + result: Some(Value::String { + val: "spam".to_string(), + span: Span::test_data(), + }), + }] + } +} diff --git a/crates/nu-command/src/core_commands/overlay/mod.rs b/crates/nu-command/src/core_commands/overlay/mod.rs new file mode 100644 index 0000000000..9fbaab377b --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/mod.rs @@ -0,0 +1,9 @@ +mod add; +mod command; +mod list; +mod remove; + +pub use add::OverlayAdd; +pub use command::Overlay; +pub use list::OverlayList; +pub use remove::OverlayRemove; diff --git a/crates/nu-command/src/core_commands/overlay/remove.rs b/crates/nu-command/src/core_commands/overlay/remove.rs new file mode 100644 index 0000000000..2cd216e18c --- /dev/null +++ b/crates/nu-command/src/core_commands/overlay/remove.rs @@ -0,0 +1,82 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, Signature, Spanned, SyntaxShape}; + +#[derive(Clone)] +pub struct OverlayRemove; + +impl Command for OverlayRemove { + fn name(&self) -> &str { + "overlay remove" + } + + fn usage(&self) -> &str { + "Remove an active overlay" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("overlay remove") + .optional("name", SyntaxShape::String, "Overlay to remove") + .category(Category::Core) + } + + fn extra_usage(&self) -> &str { + r#"This command is a parser keyword. For details, check +https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"# + } + + fn is_parser_keyword(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + // let module_name: Spanned = call.req(engine_state, stack, 0)?; + + let module_name: Spanned = if let Some(name) = call.opt(engine_state, stack, 0)? { + name + } else { + Spanned { + item: stack.last_overlay_name()?, + span: call.head, + } + }; + + // TODO: Add env merging + stack.remove_overlay(&module_name.item, &module_name.span)?; + + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Remove an overlay created from a module", + example: r#"module spam { export def foo [] { "foo" } } + overlay add spam + overlay remove spam"#, + result: None, + }, + Example { + description: "Remove an overlay created from a file", + example: r#"echo 'export alias f = "foo"' | save spam.nu + overlay add spam.nu + overlay remove spam"#, + result: None, + }, + Example { + description: "Remove the last activated overlay", + example: r#"module spam { export env FOO { "foo" } } + overlay add spam + overlay remove"#, + result: None, + }, + ] + } +} diff --git a/crates/nu-command/src/core_commands/tutor.rs b/crates/nu-command/src/core_commands/tutor.rs index 00afc1ba5c..c3954f9b9b 100644 --- a/crates/nu-command/src/core_commands/tutor.rs +++ b/crates/nu-command/src/core_commands/tutor.rs @@ -424,7 +424,7 @@ fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span code_mode = false; //TODO: support no-color mode - if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); if let Ok(output) = decl.run( diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 9a4b966ead..0818ec9550 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -55,20 +55,20 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are- )); }; - if let Some(overlay_id) = import_pattern.head.id { - let overlay = engine_state.get_overlay(overlay_id); + if let Some(module_id) = import_pattern.head.id { + let module = engine_state.get_module(module_id); let env_vars_to_use = if import_pattern.members.is_empty() { - overlay.env_vars_with_head(&import_pattern.head.name) + module.env_vars_with_head(&import_pattern.head.name) } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => overlay.env_vars(), + ImportPatternMember::Glob { .. } => module.env_vars(), ImportPatternMember::Name { name, span } => { let mut output = vec![]; - if let Some(id) = overlay.get_env_var_id(name) { + if let Some(id) = module.get_env_var_id(name) { output.push((name.clone(), id)); - } else if !overlay.has_decl(name) && !overlay.has_alias(name) { + } else if !module.has_decl(name) && !module.has_alias(name) { return Err(ShellError::EnvVarNotFoundAtRuntime( String::from_utf8_lossy(name).into(), *span, @@ -81,9 +81,9 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are- let mut output = vec![]; for (name, span) in names { - if let Some(id) = overlay.get_env_var_id(name) { + if let Some(id) = module.get_env_var_id(name) { output.push((name.clone(), id)); - } else if !overlay.has_decl(name) && !overlay.has_alias(name) { + } else if !module.has_decl(name) && !module.has_alias(name) { return Err(ShellError::EnvVarNotFoundAtRuntime( String::from_utf8_lossy(name).into(), *span, @@ -105,8 +105,6 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are- let block = engine_state.get_block(block_id); - // TODO: Add string conversions (e.g. int to string) - // TODO: Later expand env to take all Values let val = eval_block( engine_state, stack, diff --git a/crates/nu-command/src/dataframe/eager/list.rs b/crates/nu-command/src/dataframe/eager/list.rs index 8cb1605d2e..fce19cc854 100644 --- a/crates/nu-command/src/dataframe/eager/list.rs +++ b/crates/nu-command/src/dataframe/eager/list.rs @@ -38,25 +38,19 @@ impl Command for ListDF { call: &Call, _input: PipelineData, ) -> Result { - let vals = engine_state - .scope - .iter() - .flat_map(|frame| { - frame - .vars - .iter() - .filter_map(|var| { - let value = stack.get_var(*var.1, call.head); - match value { - Ok(value) => { - let name = String::from_utf8_lossy(var.0).to_string(); - Some((name, value)) - } - Err(_) => None, - } - }) - .collect::>() - }) + let mut vals: Vec<(String, Value)> = vec![]; + + for overlay_frame in engine_state.active_overlays(&[]) { + for var in &overlay_frame.vars { + if let Ok(value) = stack.get_var(*var.1, call.head) { + let name = String::from_utf8_lossy(var.0).to_string(); + vals.push((name, value)); + } + } + } + + let vals = vals + .into_iter() .filter_map(|(name, value)| match NuDataFrame::try_from_value(value) { Ok(df) => Some((name, df)), Err(_) => None, diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c28bd98f20..3ee0f1edf1 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -52,6 +52,10 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { History, If, Ignore, + Overlay, + OverlayAdd, + OverlayList, + OverlayRemove, Let, Metadata, Module, diff --git a/crates/nu-command/src/experimental/view_source.rs b/crates/nu-command/src/experimental/view_source.rs index c1e76b61df..ad261c4574 100644 --- a/crates/nu-command/src/experimental/view_source.rs +++ b/crates/nu-command/src/experimental/view_source.rs @@ -47,7 +47,7 @@ impl Command for ViewSource { } } Value::String { val, .. } => { - if let Some(decl_id) = engine_state.find_decl(val.as_bytes()) { + if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) { // arg is a command let decl = engine_state.get_decl(decl_id); let sig = decl.signature(); @@ -115,11 +115,11 @@ impl Command for ViewSource { Vec::new(), )) } - } else if let Some(overlay_id) = engine_state.find_overlay(val.as_bytes()) { + } else if let Some(module_id) = engine_state.find_module(val.as_bytes(), &[]) { // arg is a module - let overlay = engine_state.get_overlay(overlay_id); - if let Some(overlay_span) = overlay.span { - let contents = engine_state.get_span_contents(&overlay_span); + let module = engine_state.get_module(module_id); + if let Some(module_span) = module.span { + let contents = engine_state.get_span_contents(&module_span); Ok(Value::string(String::from_utf8_lossy(contents), call.head) .into_pipeline_data()) } else { diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index fc4fcc7d1a..7e523f6d99 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -151,7 +151,7 @@ impl Command for Open { }; if let Some(ext) = ext { - match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) { Some(converter_id) => { let decl = engine_state.get_decl(converter_id); if let Some(block_id) = decl.get_block_id() { diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 8f71c095de..cd5df6316f 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -82,7 +82,7 @@ impl Command for Save { }; if let Some(ext) = ext { - let output = match engine_state.find_decl(format!("to {}", ext).as_bytes()) { + let output = match engine_state.find_decl(format!("to {}", ext).as_bytes(), &[]) { Some(converter_id) => { let output = engine_state.get_decl(converter_id).run( engine_state, diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 582028d4c8..3a4b0e582e 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -294,7 +294,7 @@ fn helper( } if let Some(ext) = ext { - match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) { Some(converter_id) => engine_state.get_decl(converter_id).run( engine_state, stack, diff --git a/crates/nu-command/src/network/post.rs b/crates/nu-command/src/network/post.rs index 4d8c50116d..5c92b8f833 100644 --- a/crates/nu-command/src/network/post.rs +++ b/crates/nu-command/src/network/post.rs @@ -351,7 +351,7 @@ fn helper( return Ok(output); } if let Some(ext) = ext { - match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) { Some(converter_id) => engine_state.get_decl(converter_id).run( engine_state, stack, diff --git a/crates/nu-command/src/system/which_.rs b/crates/nu-command/src/system/which_.rs index a05a600a45..224f9d7977 100644 --- a/crates/nu-command/src/system/which_.rs +++ b/crates/nu-command/src/system/which_.rs @@ -69,7 +69,7 @@ fn entry(arg: impl Into, path: impl Into, builtin: bool, span: S } fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Option { - if let Some(alias_id) = engine_state.find_alias(name.as_bytes()) { + if let Some(alias_id) = engine_state.find_alias(name.as_bytes(), &[]) { let alias = engine_state.get_alias(alias_id); let alias_str = alias .iter() @@ -90,7 +90,7 @@ fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> O } fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option { - if let Some(decl_id) = engine_state.find_decl(name.as_bytes()) { + if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) { let (msg, is_builtin) = if engine_state.get_decl(decl_id).get_block_id().is_some() { ("Nushell custom command", false) } else { diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 42b3ac522b..aaf9a54663 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -115,7 +115,7 @@ fn get_documentation( if config.no_color { long_desc.push_str(&format!("\n > {}\n", example.example)); - } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); match decl.run( diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index c1728b7f48..62e89abbd0 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -34,7 +34,9 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti let mut new_scope = HashMap::new(); - for (name, val) in &engine_state.env_vars { + let env_vars = engine_state.render_env_vars(); + + for (name, val) in env_vars { match get_converted_value(engine_state, stack, name, val, "from_string") { ConversionResult::Ok(v) => { let _ = new_scope.insert(name.to_string(), v); @@ -64,8 +66,26 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti } } - for (k, v) in new_scope { - engine_state.env_vars.insert(k, v); + if let Ok(last_overlay_name) = &stack.last_overlay_name() { + if let Some(env_vars) = engine_state.env_vars.get_mut(last_overlay_name) { + for (k, v) in new_scope { + env_vars.insert(k, v); + } + } else { + error = error.or_else(|| { + Some(ShellError::NushellFailedHelp( + "Last active overlay not found in permanent state.".into(), + "This error happened during the conversion of environment variables from strings to Nushell values.".into(), + )) + }); + } + } else { + error = error.or_else(|| { + Some(ShellError::NushellFailedHelp( + "Last active overlay not found in stack.".into(), + "This error happened during the conversion of environment variables from strings to Nushell values.".into(), + )) + }); } error diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index a123418a3b..cf8851be08 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -169,10 +169,8 @@ pub fn eval_call( } // add new env vars from callee to caller - for env_vars in callee_stack.env_vars { - for (var, value) in env_vars { - caller_stack.add_env_var(var, value); - } + for (var, value) in callee_stack.get_stack_env_vars() { + caller_stack.add_env_var(var, value); } } @@ -195,7 +193,7 @@ fn eval_external( redirect_stderr: bool, ) -> Result { let decl_id = engine_state - .find_decl("run-external".as_bytes()) + .find_decl("run-external".as_bytes(), &[]) .ok_or(ShellError::ExternalNotSupported(head.span))?; let command = engine_state.get_decl(decl_id); @@ -660,7 +658,7 @@ pub fn eval_block( // Drain the input to the screen via tabular output let config = engine_state.get_config(); - match engine_state.find_decl("table".as_bytes()) { + match engine_state.find_decl("table".as_bytes(), &[]) { Some(decl_id) => { let table = engine_state.get_decl(decl_id).run( engine_state, @@ -716,7 +714,7 @@ pub fn eval_block( // Drain the input to the screen via tabular output let config = engine_state.get_config(); - match engine_state.find_decl("table".as_bytes()) { + match engine_state.find_decl("table".as_bytes(), &[]) { Some(decl_id) => { let table = engine_state.get_decl(decl_id).run( engine_state, @@ -806,21 +804,21 @@ pub fn create_scope( let mut vars = vec![]; let mut commands = vec![]; let mut aliases = vec![]; - let mut overlays = vec![]; + let mut modules = vec![]; let mut vars_map = HashMap::new(); let mut commands_map = HashMap::new(); let mut aliases_map = HashMap::new(); - let mut overlays_map = HashMap::new(); + let mut modules_map = HashMap::new(); let mut visibility = Visibility::new(); - for frame in &engine_state.scope { - vars_map.extend(&frame.vars); - commands_map.extend(&frame.decls); - aliases_map.extend(&frame.aliases); - overlays_map.extend(&frame.overlays); + for overlay_frame in engine_state.active_overlays(&[]) { + vars_map.extend(&overlay_frame.vars); + commands_map.extend(&overlay_frame.decls); + aliases_map.extend(&overlay_frame.aliases); + modules_map.extend(&overlay_frame.modules); - visibility.merge_with(frame.visibility.clone()); + visibility.merge_with(overlay_frame.visibility.clone()); } for var in vars_map { @@ -846,14 +844,14 @@ pub fn create_scope( let mut cols = vec![]; let mut vals = vec![]; - let mut overlay_commands = vec![]; - for overlay in &overlays_map { - let overlay_name = String::from_utf8_lossy(*overlay.0).to_string(); - let overlay_id = engine_state.find_overlay(*overlay.0); - if let Some(overlay_id) = overlay_id { - let overlay = engine_state.get_overlay(overlay_id); - if overlay.has_decl(command_name) { - overlay_commands.push(overlay_name); + let mut module_commands = vec![]; + for module in &modules_map { + let module_name = String::from_utf8_lossy(module.0).to_string(); + let module_id = engine_state.find_module(module.0, &[]); + if let Some(module_id) = module_id { + let module = engine_state.get_module(module_id); + if module.has_decl(command_name) { + module_commands.push(module_name); } } } @@ -865,7 +863,7 @@ pub fn create_scope( }); cols.push("module_name".into()); - vals.push(Value::string(overlay_commands.join(", "), span)); + vals.push(Value::string(module_commands.join(", "), span)); let decl = engine_state.get_decl(*decl_id); let signature = decl.signature(); @@ -1132,9 +1130,9 @@ pub fn create_scope( } } - for overlay in overlays_map { - overlays.push(Value::String { - val: String::from_utf8_lossy(overlay.0).to_string(), + for module in modules_map { + modules.push(Value::String { + val: String::from_utf8_lossy(module.0).to_string(), span, }); } @@ -1179,10 +1177,10 @@ pub fn create_scope( span, }); - overlays.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); - output_cols.push("overlays".to_string()); + modules.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); + output_cols.push("modules".to_string()); output_vals.push(Value::List { - vals: overlays, + vals: modules, span, }); diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 05a743ff8c..aad64cdcdc 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -102,6 +102,34 @@ pub enum ParseError { )] ModuleNotFound(#[label = "module not found"] Span), + #[error("Active overlay not found.")] + #[diagnostic(code(nu::parser::active_overlay_not_found), url(docsrs))] + ActiveOverlayNotFound(#[label = "not an active overlay"] Span), + + #[error("Module or overlay not found.")] + #[diagnostic( + code(nu::parser::module_or_overlay_not_found), + url(docsrs), + help("Requires either an existing overlay, a module, or an import pattern defining a module.") + )] + ModuleOrOverlayNotFound(#[label = "not a module or an overlay"] Span), + + #[error("Cannot remove the last overlay.")] + #[diagnostic( + code(nu::parser::cant_remove_last_overlay), + url(docsrs), + help("At least one overlay must always be active.") + )] + CantRemoveLastOverlay(#[label = "this is the last overlay, can't remove it"] Span), + + #[error("Cannot remove default overlay.")] + #[diagnostic( + code(nu::parser::cant_remove_default_overlay), + url(docsrs), + help("'{0}' is a default overlay. Default overlays cannot be removed.") + )] + CantRemoveDefaultOverlay(String, #[label = "can't remove overlay"] Span), + #[error("Not found.")] #[diagnostic(code(nu::parser::not_found), url(docsrs))] NotFound(#[label = "did not find anything under this name"] Span), @@ -245,6 +273,15 @@ pub enum ParseError { #[diagnostic(code(nu::parser::file_not_found), url(docsrs))] FileNotFound(String, #[label("File not found: {0}")] Span), + /// Error while trying to read a file + /// + /// ## Resolution + /// + /// The error will show the result from a file operation + #[error("Error trying to read file")] + #[diagnostic(code(nu::shell::error_reading_file), url(docsrs))] + ReadingFile(String, #[label("{0}")] Span), + #[error("{0}")] #[diagnostic()] LabeledError(String, String, #[label("{1}")] Span), @@ -268,6 +305,10 @@ impl ParseError { ParseError::VariableNotFound(s) => *s, ParseError::VariableNotValid(s) => *s, ParseError::ModuleNotFound(s) => *s, + ParseError::ModuleOrOverlayNotFound(s) => *s, + ParseError::ActiveOverlayNotFound(s) => *s, + ParseError::CantRemoveLastOverlay(s) => *s, + ParseError::CantRemoveDefaultOverlay(_, s) => *s, ParseError::NotFound(s) => *s, ParseError::DuplicateCommandDef(s) => *s, ParseError::UnknownCommand(s) => *s, @@ -297,6 +338,7 @@ impl ParseError { ParseError::SourcedFileNotFound(_, s) => *s, ParseError::RegisteredFileNotFound(_, s) => *s, ParseError::FileNotFound(_, s) => *s, + ParseError::ReadingFile(_, s) => *s, ParseError::LabeledError(_, _, s) => *s, } } diff --git a/crates/nu-parser/src/known_external.rs b/crates/nu-parser/src/known_external.rs index 2b1672397b..70ac93f82a 100644 --- a/crates/nu-parser/src/known_external.rs +++ b/crates/nu-parser/src/known_external.rs @@ -40,7 +40,7 @@ impl Command for KnownExternal { let call_span = call.span(); let head_span = call.head; let decl_id = engine_state - .find_decl("run-external".as_bytes()) + .find_decl("run-external".as_bytes(), &[]) .ok_or(ShellError::ExternalNotSupported(head_span))?; let command = engine_state.get_decl(decl_id); diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 0667cd182c..8f17a0475b 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -4,8 +4,8 @@ use nu_protocol::{ Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead, ImportPatternMember, Pipeline, }, - engine::StateWorkingSet, - span, Exportable, Overlay, PositionalArg, Span, SyntaxShape, Type, + engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME}, + span, Exportable, Module, PositionalArg, Span, SyntaxShape, Type, }; use std::collections::HashSet; use std::path::{Path, PathBuf}; @@ -1013,7 +1013,7 @@ pub fn parse_module_block( working_set: &mut StateWorkingSet, span: Span, expand_aliases_denylist: &[usize], -) -> (Block, Overlay, Option) { +) -> (Block, Module, Option) { let mut error = None; working_set.enter_scope(); @@ -1037,7 +1037,7 @@ pub fn parse_module_block( } } - let mut overlay = Overlay::from_span(span); + let mut module = Module::from_span(span); let block: Block = output .block @@ -1093,13 +1093,13 @@ pub fn parse_module_block( match exportable { Some(Exportable::Decl(decl_id)) => { - overlay.add_decl(name, decl_id); + module.add_decl(name, decl_id); } Some(Exportable::EnvVar(block_id)) => { - overlay.add_env_var(name, block_id); + module.add_env_var(name, block_id); } Some(Exportable::Alias(alias_id)) => { - overlay.add_alias(name, alias_id); + module.add_alias(name, alias_id); } None => {} // None should always come with error from parse_export() } @@ -1130,7 +1130,7 @@ pub fn parse_module_block( working_set.exit_scope(); - (block, overlay, error) + (block, module, error) } pub fn parse_module( @@ -1175,12 +1175,12 @@ pub fn parse_module( let block_span = Span { start, end }; - let (block, overlay, err) = + let (block, module, err) = parse_module_block(working_set, block_span, expand_aliases_denylist); error = error.or(err); let block_id = working_set.add_block(block); - let _ = working_set.add_overlay(&module_name, overlay); + let _ = working_set.add_module(&module_name, module); let block_expr = Expression { expr: Expr::Block(block_id), @@ -1286,7 +1286,7 @@ pub fn parse_use( garbage_pipeline(spans), Some(ParseError::UnknownState( "internal error: Import pattern positional is not import pattern".into(), - call_span, + expr.span, )), ); } @@ -1306,11 +1306,11 @@ pub fn parse_use( // TODO: Add checking for importing too long import patterns, e.g.: // > use spam foo non existent names here do not throw error - let (import_pattern, overlay) = - if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { - (import_pattern, working_set.get_overlay(overlay_id).clone()) + let (import_pattern, module) = + if let Some(module_id) = working_set.find_module(&import_pattern.head.name) { + (import_pattern, working_set.get_module(module_id).clone()) } else { - // TODO: Do not close over when loading module from file + // TODO: Do not close over when loading module from file? // It could be a file let (module_filename, err) = @@ -1338,7 +1338,7 @@ pub fn parse_use( working_set.add_file(module_filename, &contents); let span_end = working_set.next_span_start(); - let (block, overlay, err) = parse_module_block( + let (block, module, err) = parse_module_block( working_set, Span::new(span_start, span_end), expand_aliases_denylist, @@ -1346,19 +1346,19 @@ pub fn parse_use( error = error.or(err); let _ = working_set.add_block(block); - let overlay_id = working_set.add_overlay(&module_name, overlay.clone()); + let module_id = working_set.add_module(&module_name, module.clone()); ( ImportPattern { head: ImportPatternHead { name: module_name.into(), - id: Some(overlay_id), + id: Some(module_id), span: spans[1], }, members: import_pattern.members, hidden: HashSet::new(), }, - overlay, + module, ) } else { return ( @@ -1377,7 +1377,7 @@ pub fn parse_use( let mut import_pattern = ImportPattern::new(); import_pattern.head.span = spans[1]; - (import_pattern, Overlay::new()) + (import_pattern, Module::new()) } } else { return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1]))); @@ -1386,21 +1386,21 @@ pub fn parse_use( let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() { ( - overlay.decls_with_head(&import_pattern.head.name), - overlay.aliases_with_head(&import_pattern.head.name), + module.decls_with_head(&import_pattern.head.name), + module.aliases_with_head(&import_pattern.head.name), ) } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => (overlay.decls(), overlay.aliases()), + ImportPatternMember::Glob { .. } => (module.decls(), module.aliases()), ImportPatternMember::Name { name, span } => { let mut decl_output = vec![]; let mut alias_output = vec![]; - if let Some(id) = overlay.get_decl_id(name) { + if let Some(id) = module.get_decl_id(name) { decl_output.push((name.clone(), id)); - } else if let Some(id) = overlay.get_alias_id(name) { + } else if let Some(id) = module.get_alias_id(name) { alias_output.push((name.clone(), id)); - } else if !overlay.has_env_var(name) { + } else if !module.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))) } @@ -1411,11 +1411,11 @@ pub fn parse_use( let mut alias_output = vec![]; for (name, span) in names { - if let Some(id) = overlay.get_decl_id(name) { + if let Some(id) = module.get_decl_id(name) { decl_output.push((name.clone(), id)); - } else if let Some(id) = overlay.get_alias_id(name) { + } else if let Some(id) = module.get_alias_id(name) { alias_output.push((name.clone(), id)); - } else if !overlay.has_env_var(name) { + } else if !module.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))); break; } @@ -1426,7 +1426,7 @@ pub fn parse_use( } }; - // Extend the current scope with the module's overlay + // Extend the current scope with the module's exportables working_set.use_decls(decls_to_use); working_set.use_aliases(aliases_to_use); @@ -1542,26 +1542,26 @@ pub fn parse_hide( error = error.or(err); } - let (is_module, overlay) = - if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { - (true, working_set.get_overlay(overlay_id).clone()) + let (is_module, module) = + if let Some(module_id) = working_set.find_module(&import_pattern.head.name) { + (true, working_set.get_module(module_id).clone()) } else if import_pattern.members.is_empty() { // The pattern head can be: if let Some(id) = working_set.find_alias(&import_pattern.head.name) { // an alias, - let mut overlay = Overlay::new(); - overlay.add_alias(&import_pattern.head.name, id); + let mut module = Module::new(); + module.add_alias(&import_pattern.head.name, id); - (false, overlay) + (false, module) } else if let Some(id) = working_set.find_decl(&import_pattern.head.name) { // a custom command, - let mut overlay = Overlay::new(); - overlay.add_decl(&import_pattern.head.name, id); + let mut module = Module::new(); + module.add_decl(&import_pattern.head.name, id); - (false, overlay) + (false, module) } else { // , or it could be an env var (handled by the engine) - (false, Overlay::new()) + (false, Module::new()) } } else { return ( @@ -1574,28 +1574,27 @@ pub fn parse_hide( let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() { if is_module { ( - overlay.alias_names_with_head(&import_pattern.head.name), - overlay.decl_names_with_head(&import_pattern.head.name), + module.alias_names_with_head(&import_pattern.head.name), + module.decl_names_with_head(&import_pattern.head.name), ) } else { - (overlay.alias_names(), overlay.decl_names()) + (module.alias_names(), module.decl_names()) } } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => (overlay.alias_names(), overlay.decl_names()), + ImportPatternMember::Glob { .. } => (module.alias_names(), module.decl_names()), ImportPatternMember::Name { name, span } => { let mut aliases = vec![]; let mut decls = vec![]; - if let Some(item) = - overlay.alias_name_with_head(name, &import_pattern.head.name) + if let Some(item) = module.alias_name_with_head(name, &import_pattern.head.name) { aliases.push(item); } else if let Some(item) = - overlay.decl_name_with_head(name, &import_pattern.head.name) + module.decl_name_with_head(name, &import_pattern.head.name) { decls.push(item); - } else if !overlay.has_env_var(name) { + } else if !module.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))); } @@ -1607,14 +1606,14 @@ pub fn parse_hide( for (name, span) in names { if let Some(item) = - overlay.alias_name_with_head(name, &import_pattern.head.name) + module.alias_name_with_head(name, &import_pattern.head.name) { aliases.push(item); } else if let Some(item) = - overlay.decl_name_with_head(name, &import_pattern.head.name) + module.decl_name_with_head(name, &import_pattern.head.name) { decls.push(item); - } else if !overlay.has_env_var(name) { + } else if !module.has_env_var(name) { error = error.or(Some(ParseError::ExportNotFound(*span))); break; } @@ -1673,6 +1672,445 @@ pub fn parse_hide( } } +pub fn parse_overlay( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + if working_set.get_span_contents(spans[0]) != b"overlay" { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'overlay' command".into(), + span(spans), + )), + ); + } + + if spans.len() > 1 { + let subcommand = working_set.get_span_contents(spans[1]); + + match subcommand { + b"add" => { + return parse_overlay_add(working_set, spans, expand_aliases_denylist); + } + b"list" => { + // TODO: Abstract this code blob, it's repeated all over the place: + let call = match working_set.find_decl(b"overlay list") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call( + working_set, + span(&spans[..2]), + if spans.len() > 2 { &spans[2..] } else { &[] }, + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Any, + custom_completion: None, + }]), + err, + ); + } + + call + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'overlay' declaration not found".into(), + span(spans), + )), + ) + } + }; + + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]), + None, + ); + } + b"remove" => { + return parse_overlay_remove(working_set, spans, expand_aliases_denylist); + } + _ => { /* continue parsing overlay */ } + } + } + + let call = match working_set.find_decl(b"overlay") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call( + working_set, + spans[0], + &spans[1..], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Any, + custom_completion: None, + }]), + err, + ); + } + + call + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'overlay' declaration not found".into(), + span(spans), + )), + ) + } + }; + + ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]), + None, + ) +} + +pub fn parse_overlay_add( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + if spans.len() > 1 && working_set.get_span_contents(span(&spans[0..2])) != b"overlay add" { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'overlay add' command".into(), + span(spans), + )), + ); + } + + // TODO: Allow full import pattern as argument (requires custom naming of module/overlay) + let (call, call_span) = match working_set.find_decl(b"overlay add") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call( + working_set, + span(&spans[0..2]), + &spans[2..], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Any, + custom_completion: None, + }]), + err, + ); + } + + (call, call_span) + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'overlay add' declaration not found".into(), + span(spans), + )), + ) + } + }; + + let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) { + if let Some(s) = expr.as_string() { + (s, expr.span) + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Module name not a string".into(), + expr.span, + )), + ); + } + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Missing required positional after call parsing".into(), + call_span, + )), + ); + }; + + let pipeline = Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]); + + // TODO: Add support for it -- needs to play well with overlay remove + let has_prefix = false; //call.has_flag("prefix"); + + let cwd = working_set.get_cwd(); + + let mut error = None; + + let result = if let Some(module_id) = working_set.find_overlay_origin(overlay_name.as_bytes()) { + // Activate existing overlay + if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) { + if module_id == new_module_id { + Some((overlay_name, Module::new(), module_id)) + } else { + // The origin module of an overlay changed => update it + Some(( + overlay_name, + working_set.get_module(new_module_id).clone(), + new_module_id, + )) + } + } else { + Some((overlay_name, Module::new(), module_id)) + } + } else { + // Create a new overlay from a module + if let Some(module_id) = + // the name is a module + working_set.find_module(overlay_name.as_bytes()) + { + Some(( + overlay_name, + working_set.get_module(module_id).clone(), + module_id, + )) + } else { + // try if the name is a file + if let Ok(module_filename) = + String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec()) + { + if let Some(module_path) = + find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV) + { + let overlay_name = if let Some(stem) = module_path.file_stem() { + stem.to_string_lossy().to_string() + } else { + return ( + pipeline, + Some(ParseError::ModuleOrOverlayNotFound(spans[1])), + ); + }; + + if let Ok(contents) = std::fs::read(module_path) { + let span_start = working_set.next_span_start(); + working_set.add_file(module_filename, &contents); + let span_end = working_set.next_span_start(); + + let (block, module, err) = parse_module_block( + working_set, + Span::new(span_start, span_end), + expand_aliases_denylist, + ); + error = error.or(err); + + let _ = working_set.add_block(block); + let module_id = working_set.add_module(&overlay_name, module.clone()); + + Some((overlay_name, module, module_id)) + } else { + return ( + pipeline, + Some(ParseError::ModuleOrOverlayNotFound(spans[1])), + ); + } + } else { + error = error.or(Some(ParseError::ModuleOrOverlayNotFound(overlay_name_span))); + None + } + } else { + return (garbage_pipeline(spans), Some(ParseError::NonUtf8(spans[1]))); + } + } + }; + + if let Some((name, module, module_id)) = result { + let (decls_to_lay, aliases_to_lay) = if has_prefix { + ( + module.decls_with_head(name.as_bytes()), + module.aliases_with_head(name.as_bytes()), + ) + } else { + (module.decls(), module.aliases()) + }; + + working_set.add_overlay( + name.as_bytes().to_vec(), + module_id, + decls_to_lay, + aliases_to_lay, + ); + } + + (pipeline, error) +} + +pub fn parse_overlay_remove( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + if spans.len() > 1 && working_set.get_span_contents(span(&spans[0..2])) != b"overlay remove" { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'overlay remove' command".into(), + span(spans), + )), + ); + } + + let call = match working_set.find_decl(b"overlay remove") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call( + working_set, + span(&spans[0..2]), + &spans[2..], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Any, + custom_completion: None, + }]), + err, + ); + } + + call + } + None => { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: 'overlay remove' declaration not found".into(), + span(spans), + )), + ) + } + }; + + let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) { + if let Some(s) = expr.as_string() { + (s, expr.span) + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::UnknownState( + "internal error: Module name not a string".into(), + expr.span, + )), + ); + } + } else { + ( + String::from_utf8_lossy(working_set.last_overlay_name()).to_string(), + call.head, + ) + }; + + let pipeline = Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }]); + + if overlay_name == DEFAULT_OVERLAY_NAME { + return ( + pipeline, + Some(ParseError::CantRemoveDefaultOverlay( + overlay_name, + overlay_name_span, + )), + ); + } + + if !working_set + .unique_overlay_names() + .contains(&overlay_name.as_bytes().to_vec()) + { + return ( + pipeline, + Some(ParseError::ActiveOverlayNotFound(overlay_name_span)), + ); + } + + if working_set.num_overlays() < 2 { + return ( + pipeline, + Some(ParseError::CantRemoveLastOverlay(overlay_name_span)), + ); + } + + // let original_module = if call.has_flag("discard") { + // None + // } else if let Some(module_id) = working_set.find_module(overlay_name.as_bytes()) { + // // TODO: Remove clone + // Some(working_set.get_module(module_id).clone()) + // } else { + // Some(Module::new()) + // }; + + working_set.remove_overlay(overlay_name.as_bytes()); + + (pipeline, None) +} + pub fn parse_let( working_set: &mut StateWorkingSet, spans: &[Span], @@ -2108,7 +2546,7 @@ pub fn parse_register( ) } -fn find_in_dirs( +pub fn find_in_dirs( filename: &str, working_set: &StateWorkingSet, cwd: &str, @@ -2120,7 +2558,7 @@ fn find_in_dirs( let path = Path::new(filename); if path.is_relative() { - if let Some(lib_dirs) = working_set.get_env(dirs_env) { + if let Some(lib_dirs) = working_set.get_env_var(dirs_env) { if let Ok(dirs) = lib_dirs.as_list() { for lib_dir in dirs { if let Ok(dir) = lib_dir.as_path() { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9e2ed63473..44f20ec9b3 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -18,7 +18,8 @@ use nu_protocol::{ }; use crate::parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, + parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_overlay, + parse_use, }; use log::trace; @@ -2700,7 +2701,7 @@ pub fn parse_import_pattern( ); }; - let maybe_overlay_id = working_set.find_overlay(&head); + let maybe_module_id = working_set.find_module(&head); let (import_pattern, err) = if let Some(tail_span) = spans.get(1) { // FIXME: expand this to handle deeper imports once we support module imports @@ -2710,7 +2711,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: head, - id: maybe_overlay_id, + id: maybe_module_id, span: *head_span, }, members: vec![ImportPatternMember::Glob { span: *tail_span }], @@ -2743,7 +2744,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: head, - id: maybe_overlay_id, + id: maybe_module_id, span: *head_span, }, members: vec![ImportPatternMember::List { names: output }], @@ -2756,7 +2757,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: head, - id: maybe_overlay_id, + id: maybe_module_id, span: *head_span, }, members: vec![], @@ -2771,7 +2772,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: head, - id: maybe_overlay_id, + id: maybe_module_id, span: *head_span, }, members: vec![ImportPatternMember::Name { @@ -2788,7 +2789,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: head, - id: maybe_overlay_id, + id: maybe_module_id, span: *head_span, }, members: vec![], @@ -4404,6 +4405,31 @@ pub fn parse_expression( .0, Some(ParseError::BuiltinCommandInPipeline("use".into(), spans[0])), ), + b"overlay" => { + if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"list" { + // whitelist 'overlay list' + parse_call( + working_set, + &spans[pos..], + spans[0], + expand_aliases_denylist, + ) + } else { + ( + parse_call( + working_set, + &spans[pos..], + spans[0], + expand_aliases_denylist, + ) + .0, + Some(ParseError::BuiltinCommandInPipeline( + "overlay".into(), + spans[0], + )), + ) + } + } b"source" => ( parse_call( working_set, @@ -4558,6 +4584,7 @@ pub fn parse_builtin_commands( b"alias" => parse_alias(working_set, &lite_command.parts, expand_aliases_denylist), b"module" => parse_module(working_set, &lite_command.parts, expand_aliases_denylist), b"use" => parse_use(working_set, &lite_command.parts, expand_aliases_denylist), + b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist), b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist), b"export" => { if let Some(decl_id) = working_set.find_decl(b"alias") { diff --git a/crates/nu-protocol/src/ast/import_pattern.rs b/crates/nu-protocol/src/ast/import_pattern.rs index 5b0568aa11..5abea47905 100644 --- a/crates/nu-protocol/src/ast/import_pattern.rs +++ b/crates/nu-protocol/src/ast/import_pattern.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{span, OverlayId, Span}; +use crate::{span, ModuleId, Span}; use std::collections::HashSet; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -13,7 +13,7 @@ pub enum ImportPatternMember { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ImportPatternHead { pub name: Vec, - pub id: Option, + pub id: Option, pub span: Span, } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 6aba446161..494b5a671c 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,6 +1,6 @@ -use super::{Command, Stack}; +use super::{Command, EnvVars, Stack}; use crate::{ - ast::Block, AliasId, BlockId, Config, DeclId, Example, Overlay, OverlayId, ShellError, + ast::Block, AliasId, BlockId, Config, DeclId, Example, Module, ModuleId, OverlayId, ShellError, Signature, Span, Type, VarId, Variable, }; use core::panic; @@ -12,14 +12,16 @@ use std::{ use crate::Value; use std::borrow::Borrow; +use std::collections::HashSet; use std::path::Path; #[cfg(feature = "plugin")] use std::path::PathBuf; static PWD_ENV: &str = "PWD"; +pub static DEFAULT_OVERLAY_NAME: &str = "zero"; -// Tells whether a decl etc. is visible or not +/// Tells whether a decl or alias is visible or not #[derive(Debug, Clone)] pub struct Visibility { decl_ids: HashMap, @@ -62,7 +64,6 @@ impl Visibility { // overwrite own values with the other self.decl_ids.extend(other.decl_ids); self.alias_ids.extend(other.alias_ids); - // self.env_var_ids.extend(other.env_var_ids); } fn append(&mut self, other: &Visibility) { @@ -81,45 +82,202 @@ impl Visibility { } } -impl Default for Visibility { - fn default() -> Self { - Self::new() - } -} - #[derive(Debug, Clone)] pub struct ScopeFrame { - pub vars: HashMap, VarId>, - predecls: HashMap, DeclId>, // temporary storage for predeclarations - pub decls: HashMap, DeclId>, - pub aliases: HashMap, AliasId>, - pub env_vars: HashMap, BlockId>, - pub overlays: HashMap, OverlayId>, - pub visibility: Visibility, + /// List of both active and incactive overlays in this ScopeFrame. + /// + /// The order does not have any menaning. Indexed locally (within this ScopeFrame) by + /// OverlayIds in active_overlays. + overlays: Vec<(Vec, OverlayFrame)>, + + /// List of currently active overlays. + /// + /// Order is significant: The last item points at the last activated overlay. + pub active_overlays: Vec, + + /// Deactivated overlays from permanent state. + /// ! Stores OverlayIds from the permanent state, not from this frame. ! + // removed_overlays: Vec, + + /// Removed overlays from previous scope frames / permanent state + removed_overlays: Vec>, + + /// temporary storage for predeclarations + predecls: HashMap, DeclId>, } impl ScopeFrame { pub fn new() -> Self { Self { - vars: HashMap::new(), + overlays: vec![], + active_overlays: vec![], + removed_overlays: vec![], + predecls: HashMap::new(), + } + } + + pub fn with_empty_overlay(name: Vec, origin: ModuleId) -> Self { + Self { + overlays: vec![(name, OverlayFrame::from(origin))], + active_overlays: vec![0], + removed_overlays: vec![], predecls: HashMap::new(), - decls: HashMap::new(), - aliases: HashMap::new(), - env_vars: HashMap::new(), - overlays: HashMap::new(), - visibility: Visibility::new(), } } pub fn get_var(&self, var_name: &[u8]) -> Option<&VarId> { - self.vars.get(var_name) + for overlay_id in self.active_overlays.iter().rev() { + if let Some(var_id) = self + .overlays + .get(*overlay_id) + .expect("internal error: missing overlay") + .1 + .vars + .get(var_name) + { + return Some(var_id); + } + } + + None + } + + pub fn active_overlay_ids(&self, removed_overlays: &mut Vec>) -> Vec { + for name in &self.removed_overlays { + if !removed_overlays.contains(name) { + removed_overlays.push(name.clone()); + } + } + + self.active_overlays + .iter() + .filter(|id| !removed_overlays.contains(self.get_overlay_name(**id))) + .copied() + .collect() + } + + pub fn active_overlays(&self, removed_overlays: &mut Vec>) -> Vec<&OverlayFrame> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay(*id)) + .collect() + } + + pub fn active_overlay_names(&self, removed_overlays: &mut Vec>) -> Vec<&Vec> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay_name(*id)) + .collect() + } + + pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &Vec { + &self + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .0 + } + + pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame { + &self + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .1 + } + + pub fn get_overlay_mut(&mut self, overlay_id: OverlayId) -> &mut OverlayFrame { + &mut self + .overlays + .get_mut(overlay_id) + .expect("internal error: missing overlay") + .1 + } + + pub fn find_overlay(&self, name: &[u8]) -> Option { + self.overlays.iter().position(|(n, _)| n == name) + } + + pub fn find_active_overlay(&self, name: &[u8]) -> Option { + self.overlays + .iter() + .position(|(n, _)| n == name) + .and_then(|id| { + if self.active_overlays.contains(&id) { + Some(id) + } else { + None + } + }) } } -impl Default for ScopeFrame { - fn default() -> Self { - Self::new() +// type OverlayDiff = (Vec<(Vec, DeclId)>, Vec<(Vec, AliasId)>); + +#[derive(Debug, Clone)] +pub struct OverlayFrame { + pub vars: HashMap, VarId>, + predecls: HashMap, DeclId>, // temporary storage for predeclarations + pub decls: HashMap, DeclId>, + pub aliases: HashMap, AliasId>, + pub modules: HashMap, ModuleId>, + pub visibility: Visibility, + pub origin: ModuleId, // The original module the overlay was created from +} + +impl OverlayFrame { + pub fn from(origin: ModuleId) -> Self { + Self { + vars: HashMap::new(), + predecls: HashMap::new(), + decls: HashMap::new(), + aliases: HashMap::new(), + modules: HashMap::new(), + visibility: Visibility::new(), + origin, + } } + + // Find out which definitions are custom compared to the origin module + // pub fn diff(&self, engine_state: &EngineState) -> OverlayDiff { + // let module = engine_state.get_module(self.origin); + + // let decls = self + // .decls + // .iter() + // .filter(|(name, decl_id)| { + // if self.visibility.is_decl_id_visible(decl_id) { + // if let Some(original_id) = module.get_decl_id(name) { + // &original_id != *decl_id + // } else { + // true + // } + // } else { + // false + // } + // }) + // .map(|(name, decl_id)| (name.to_owned(), *decl_id)) + // .collect(); + + // let aliases = self + // .aliases + // .iter() + // .filter(|(name, alias_id)| { + // if self.visibility.is_alias_id_visible(alias_id) { + // if let Some(original_id) = module.get_alias_id(name) { + // &original_id != *alias_id + // } else { + // true + // } + // } else { + // false + // } + // }) + // .map(|(name, alias_id)| (name.to_owned(), *alias_id)) + // .collect(); + + // (decls, aliases) + // } } /// The core global engine state. This includes all global definitions as well as any global state that @@ -172,10 +330,10 @@ pub struct EngineState { decls: Vec>, aliases: Vec>, blocks: Vec, - overlays: Vec, - pub scope: Vec, + modules: Vec, + pub scope: ScopeFrame, pub ctrlc: Option>, - pub env_vars: HashMap, + pub env_vars: EnvVars, pub config: Config, #[cfg(feature = "plugin")] pub plugin_signatures: Option, @@ -201,10 +359,11 @@ impl EngineState { decls: vec![], aliases: vec![], blocks: vec![], - overlays: vec![], - scope: vec![ScopeFrame::new()], + modules: vec![Module::new()], + // make sure we have some default overlay: + scope: ScopeFrame::with_empty_overlay(DEFAULT_OVERLAY_NAME.as_bytes().to_vec(), 0), ctrlc: None, - env_vars: HashMap::new(), + env_vars: EnvVars::from([(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())]), config: Config::default(), #[cfg(feature = "plugin")] plugin_signatures: None, @@ -231,44 +390,88 @@ impl EngineState { self.aliases.extend(delta.aliases); self.vars.extend(delta.vars); self.blocks.extend(delta.blocks); - self.overlays.extend(delta.overlays); + self.modules.extend(delta.modules); - if let Some(last) = self.scope.last_mut() { - let first = delta.scope.remove(0); - for item in first.decls.into_iter() { - last.decls.insert(item.0, item.1); - } - for item in first.vars.into_iter() { - last.vars.insert(item.0, item.1); - } - for item in first.aliases.into_iter() { - last.aliases.insert(item.0, item.1); - } - for item in first.overlays.into_iter() { - last.overlays.insert(item.0, item.1); - } - last.visibility.merge_with(first.visibility); + let first = delta.scope.remove(0); - #[cfg(feature = "plugin")] - if delta.plugins_changed { - let result = self.update_plugin_file(); - - if result.is_ok() { - delta.plugins_changed = false; + for (delta_name, delta_overlay) in first.clone().overlays { + if let Some((_, existing_overlay)) = self + .scope + .overlays + .iter_mut() + .find(|(name, _)| name == &delta_name) + { + // Upating existing overlay + for item in delta_overlay.decls.into_iter() { + existing_overlay.decls.insert(item.0, item.1); + } + for item in delta_overlay.vars.into_iter() { + existing_overlay.vars.insert(item.0, item.1); + } + for item in delta_overlay.aliases.into_iter() { + existing_overlay.aliases.insert(item.0, item.1); + } + for item in delta_overlay.modules.into_iter() { + existing_overlay.modules.insert(item.0, item.1); } - return result; + existing_overlay + .visibility + .merge_with(delta_overlay.visibility); + } else { + // New overlay was added to the delta + self.scope.overlays.push((delta_name, delta_overlay)); } } - if let Some(stack) = stack { - for mut env_scope in stack.env_vars.drain(..) { - for (k, v) in env_scope.drain() { - if k == "config" { - self.config = v.clone().into_config().unwrap_or_default(); - } + let mut activated_ids = self.translate_overlay_ids(&first); - self.env_vars.insert(k, v); + let mut removed_ids = vec![]; + + for name in &first.removed_overlays { + if let Some(overlay_id) = self.find_overlay(name) { + removed_ids.push(overlay_id); + } + } + + // Remove overlays removed in delta + self.scope + .active_overlays + .retain(|id| !removed_ids.contains(id)); + + // Move overlays activated in the delta to be first + self.scope + .active_overlays + .retain(|id| !activated_ids.contains(id)); + self.scope.active_overlays.append(&mut activated_ids); + + #[cfg(feature = "plugin")] + if delta.plugins_changed { + let result = self.update_plugin_file(); + + if result.is_ok() { + delta.plugins_changed = false; + } + + return result; + } + + if let Some(stack) = stack { + for mut scope in stack.env_vars.drain(..) { + for (overlay_name, mut env) in scope.drain() { + if let Some(env_vars) = self.env_vars.get_mut(&overlay_name) { + // Updating existing overlay + for (k, v) in env.drain() { + if k == "config" { + self.config = v.clone().into_config().unwrap_or_default(); + } + + env_vars.insert(k, v); + } + } else { + // Pushing a new overlay + self.env_vars.insert(overlay_name, env); + } } } } @@ -280,6 +483,122 @@ impl EngineState { Ok(()) } + pub fn has_overlay(&self, name: &[u8]) -> bool { + self.scope + .overlays + .iter() + .any(|(overlay_name, _)| name == overlay_name) + } + + pub fn active_overlay_ids(&self, removed_overlays: &[Vec]) -> Vec { + self.scope + .active_overlays + .iter() + .filter(|id| !removed_overlays.contains(self.get_overlay_name(**id))) + .copied() + .collect() + } + + pub fn active_overlays(&self, removed_overlays: &[Vec]) -> Vec<&OverlayFrame> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay(*id)) + .collect() + } + + pub fn active_overlay_names(&self, removed_overlays: &[Vec]) -> Vec<&Vec> { + self.active_overlay_ids(removed_overlays) + .iter() + .map(|id| self.get_overlay_name(*id)) + .collect() + } + + /// Translate overlay IDs from other to IDs in self + pub fn translate_overlay_ids(&self, other: &ScopeFrame) -> Vec { + let other_names = other.active_overlays.iter().map(|other_id| { + &other + .overlays + .get(*other_id) + .expect("internal error: missing overlay") + .0 + }); + + other_names + .map(|other_name| { + self.find_overlay(other_name) + .expect("internal error: missing overlay") + }) + .collect() + } + + pub fn last_overlay_name(&self, removed_overlays: &[Vec]) -> &Vec { + self.active_overlay_names(removed_overlays) + .last() + .expect("internal error: no active overlays") + } + + pub fn last_overlay(&self, removed_overlays: &[Vec]) -> &OverlayFrame { + self.active_overlay_ids(removed_overlays) + .last() + .map(|id| self.get_overlay(*id)) + .expect("internal error: no active overlays") + } + + pub fn get_overlay_name(&self, overlay_id: OverlayId) -> &Vec { + &self + .scope + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .0 + } + + pub fn get_overlay(&self, overlay_id: OverlayId) -> &OverlayFrame { + &self + .scope + .overlays + .get(overlay_id) + .expect("internal error: missing overlay") + .1 + } + + pub fn render_env_vars(&self) -> HashMap<&String, &Value> { + let mut result = HashMap::new(); + + for overlay_name in self.active_overlay_names(&[]) { + let name = String::from_utf8_lossy(overlay_name); + if let Some(env_vars) = self.env_vars.get(name.as_ref()) { + result.extend(env_vars); + } + } + + result + } + + pub fn add_env_var(&mut self, name: String, val: Value) { + let overlay_name = String::from_utf8_lossy(self.last_overlay_name(&[])).to_string(); + + if let Some(env_vars) = self.env_vars.get_mut(&overlay_name) { + env_vars.insert(name, val); + } else { + self.env_vars + .insert(overlay_name, HashMap::from([(name, val)])); + } + } + + pub fn get_env_var(&self, name: &str) -> Option<&Value> { + for overlay_id in self.scope.active_overlays.iter().rev() { + let overlay_name = String::from_utf8_lossy(self.get_overlay_name(*overlay_id)); + if let Some(env_vars) = self.env_vars.get(overlay_name.as_ref()) { + if let Some(val) = env_vars.get(name) { + return Some(val); + } + } + } + + None + } + #[cfg(feature = "plugin")] pub fn update_plugin_file(&self) -> Result<(), ShellError> { use std::io::Write; @@ -356,8 +675,8 @@ impl EngineState { self.blocks.len() } - pub fn num_overlays(&self) -> usize { - self.overlays.len() + pub fn num_modules(&self) -> usize { + self.modules.len() } pub fn print_vars(&self) { @@ -385,13 +704,13 @@ impl EngineState { } } - pub fn find_decl(&self, name: &[u8]) -> Option { + pub fn find_decl(&self, name: &[u8], removed_overlays: &[Vec]) -> Option { let mut visibility: Visibility = Visibility::new(); - for scope in self.scope.iter().rev() { - visibility.append(&scope.visibility); + for overlay_frame in self.active_overlays(removed_overlays).iter().rev() { + visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = scope.decls.get(name) { + if let Some(decl_id) = overlay_frame.decls.get(name) { if visibility.is_decl_id_visible(decl_id) { return Some(*decl_id); } @@ -401,13 +720,13 @@ impl EngineState { None } - pub fn find_alias(&self, name: &[u8]) -> Option { + pub fn find_alias(&self, name: &[u8], removed_overlays: &[Vec]) -> Option { let mut visibility: Visibility = Visibility::new(); - for scope in self.scope.iter().rev() { - visibility.append(&scope.visibility); + for overlay_frame in self.active_overlays(removed_overlays).iter().rev() { + visibility.append(&overlay_frame.visibility); - if let Some(alias_id) = scope.aliases.get(name) { + if let Some(alias_id) = overlay_frame.aliases.get(name) { if visibility.is_alias_id_visible(alias_id) { return Some(*alias_id); } @@ -434,25 +753,33 @@ impl EngineState { plugin_decls.into_iter().map(|(_, decl)| decl) } - pub fn find_overlay(&self, name: &[u8]) -> Option { - for scope in self.scope.iter().rev() { - if let Some(overlay_id) = scope.overlays.get(name) { - return Some(*overlay_id); + pub fn find_module(&self, name: &[u8], removed_overlays: &[Vec]) -> Option { + for overlay_frame in self.active_overlays(removed_overlays).iter().rev() { + if let Some(module_id) = overlay_frame.modules.get(name) { + return Some(*module_id); } } None } + pub fn find_overlay(&self, name: &[u8]) -> Option { + self.scope.find_overlay(name) + } + + pub fn find_active_overlay(&self, name: &[u8]) -> Option { + self.scope.find_active_overlay(name) + } + pub fn find_commands_by_predicate( &self, predicate: impl Fn(&[u8]) -> bool, ) -> Vec<(Vec, Option)> { let mut output = vec![]; - for scope in self.scope.iter().rev() { - for decl in &scope.decls { - if predicate(decl.0) { + for overlay_frame in self.active_overlays(&[]).iter().rev() { + for decl in &overlay_frame.decls { + if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { let command = self.get_decl(*decl.1); output.push((decl.0.clone(), Some(command.usage().to_string()))); } @@ -463,13 +790,17 @@ impl EngineState { } pub fn find_aliases_by_predicate(&self, predicate: impl Fn(&[u8]) -> bool) -> Vec> { - self.scope - .iter() - .rev() - .flat_map(|scope| &scope.aliases) - .filter(|decl| predicate(decl.0)) - .map(|decl| decl.0.clone()) - .collect() + let mut output = vec![]; + + for overlay_frame in self.active_overlays(&[]).iter().rev() { + for alias in &overlay_frame.aliases { + if overlay_frame.visibility.is_alias_id_visible(alias.1) && predicate(alias.0) { + output.push(alias.0.clone()); + } + } + } + + output } pub fn get_span_contents(&self, span: &Span) -> &[u8] { @@ -510,19 +841,19 @@ impl EngineState { pub fn get_decl_ids_sorted(&self, include_hidden: bool) -> impl Iterator { let mut decls_map = HashMap::new(); - for frame in &self.scope { - let frame_decls = if include_hidden { - frame.decls.clone() + for overlay_frame in self.active_overlays(&[]) { + let new_decls = if include_hidden { + overlay_frame.decls.clone() } else { - frame + overlay_frame .decls .clone() .into_iter() - .filter(|(_, id)| frame.visibility.is_decl_id_visible(id)) + .filter(|(_, id)| overlay_frame.visibility.is_decl_id_visible(id)) .collect() }; - decls_map.extend(frame_decls); + decls_map.extend(new_decls); } let mut decls: Vec<(Vec, DeclId)> = decls_map.into_iter().collect(); @@ -573,10 +904,10 @@ impl EngineState { .expect("internal error: missing block") } - pub fn get_overlay(&self, overlay_id: OverlayId) -> &Overlay { - self.overlays - .get(overlay_id) - .expect("internal error: missing overlay") + pub fn get_module(&self, module_id: ModuleId) -> &Module { + self.modules + .get(module_id) + .expect("internal error: missing module") } pub fn next_span_start(&self) -> usize { @@ -630,12 +961,6 @@ impl EngineState { } } -impl Default for EngineState { - fn default() -> Self { - Self::new() - } -} - /// A temporary extension to the global state. This handles bridging between the global state and the /// additional declarations and scope changes that are not yet part of the global scope. /// @@ -657,20 +982,19 @@ pub struct StateDelta { decls: Vec>, // indexed by DeclId aliases: Vec>, // indexed by AliasId pub blocks: Vec, // indexed by BlockId - overlays: Vec, // indexed by OverlayId + modules: Vec, // indexed by ModuleId pub scope: Vec, #[cfg(feature = "plugin")] plugins_changed: bool, // marks whether plugin file should be updated } -impl Default for StateDelta { - fn default() -> Self { - Self::new() - } -} - impl StateDelta { - pub fn new() -> Self { + pub fn new(engine_state: &EngineState) -> Self { + let scope_frame = ScopeFrame::with_empty_overlay( + engine_state.last_overlay_name(&[]).to_owned(), + engine_state.last_overlay(&[]).origin, + ); + StateDelta { files: vec![], file_contents: vec![], @@ -678,8 +1002,8 @@ impl StateDelta { decls: vec![], aliases: vec![], blocks: vec![], - overlays: vec![], - scope: vec![ScopeFrame::new()], + modules: vec![], + scope: vec![scope_frame], #[cfg(feature = "plugin")] plugins_changed: false, } @@ -701,8 +1025,58 @@ impl StateDelta { self.blocks.len() } - pub fn num_overlays(&self) -> usize { - self.overlays.len() + pub fn num_modules(&self) -> usize { + self.modules.len() + } + + pub fn last_scope_frame_mut(&mut self) -> &mut ScopeFrame { + self.scope + .last_mut() + .expect("internal error: missing required scope frame") + } + + pub fn last_scope_frame(&self) -> &ScopeFrame { + self.scope + .last() + .expect("internal error: missing required scope frame") + } + + pub fn last_overlay_mut(&mut self) -> Option<&mut OverlayFrame> { + let last_scope = self + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + if let Some(last_overlay_id) = last_scope.active_overlays.last() { + Some( + &mut last_scope + .overlays + .get_mut(*last_overlay_id) + .expect("internal error: missing required overlay") + .1, + ) + } else { + None + } + } + + pub fn last_overlay(&self) -> Option<&OverlayFrame> { + let last_scope = self + .scope + .last() + .expect("internal error: missing required scope frame"); + + if let Some(last_overlay_id) = last_scope.active_overlays.last() { + Some( + &last_scope + .overlays + .get(*last_overlay_id) + .expect("internal error: missing required overlay") + .1, + ) + } else { + None + } } pub fn enter_scope(&mut self) { @@ -717,7 +1091,7 @@ impl StateDelta { impl<'a> StateWorkingSet<'a> { pub fn new(permanent_state: &'a EngineState) -> Self { Self { - delta: StateDelta::new(), + delta: StateDelta::new(permanent_state), permanent_state, external_commands: vec![], } @@ -739,8 +1113,34 @@ impl<'a> StateWorkingSet<'a> { self.delta.num_blocks() + self.permanent_state.num_blocks() } + pub fn num_modules(&self) -> usize { + self.delta.num_modules() + self.permanent_state.num_modules() + } + + pub fn unique_overlay_names(&self) -> HashSet<&Vec> { + let mut names: HashSet<&Vec> = self + .permanent_state + .active_overlay_names(&[]) + .into_iter() + .collect(); + + for scope_frame in self.delta.scope.iter().rev() { + for overlay_id in scope_frame.active_overlays.iter().rev() { + let (overlay_name, _) = scope_frame + .overlays + .get(*overlay_id) + .expect("internal error: missing overlay"); + + names.insert(overlay_name); + names.retain(|n| !scope_frame.removed_overlays.contains(n)); + } + } + + names + } + pub fn num_overlays(&self) -> usize { - self.delta.num_overlays() + self.permanent_state.num_overlays() + self.unique_overlay_names().len() } pub fn add_decl(&mut self, decl: Box) -> DeclId { @@ -749,40 +1149,26 @@ impl<'a> StateWorkingSet<'a> { self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); - - scope_frame.decls.insert(name, decl_id); + self.last_overlay_mut().decls.insert(name, decl_id); decl_id } pub fn use_decls(&mut self, decls: Vec<(Vec, DeclId)>) { - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + let overlay_frame = self.last_overlay_mut(); for (name, decl_id) in decls { - scope_frame.decls.insert(name, decl_id); - scope_frame.visibility.use_decl_id(&decl_id); + overlay_frame.decls.insert(name, decl_id); + overlay_frame.visibility.use_decl_id(&decl_id); } } pub fn use_aliases(&mut self, aliases: Vec<(Vec, AliasId)>) { - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + let overlay_frame = self.last_overlay_mut(); for (name, alias_id) in aliases { - scope_frame.aliases.insert(name, alias_id); - scope_frame.visibility.use_alias_id(&alias_id); + overlay_frame.aliases.insert(name, alias_id); + overlay_frame.visibility.use_alias_id(&alias_id); } } @@ -792,13 +1178,10 @@ impl<'a> StateWorkingSet<'a> { self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); - - scope_frame.predecls.insert(name, decl_id) + self.delta + .last_scope_frame_mut() + .predecls + .insert(name, decl_id) } #[cfg(feature = "plugin")] @@ -807,14 +1190,12 @@ impl<'a> StateWorkingSet<'a> { } pub fn merge_predecl(&mut self, name: &[u8]) -> Option { - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + self.move_predecls_to_overlay(); - if let Some(decl_id) = scope_frame.predecls.remove(name) { - scope_frame.decls.insert(name.into(), decl_id); + let overlay_frame = self.last_overlay_mut(); + + if let Some(decl_id) = overlay_frame.predecls.remove(name) { + overlay_frame.decls.insert(name.into(), decl_id); return Some(decl_id); } @@ -822,36 +1203,52 @@ impl<'a> StateWorkingSet<'a> { None } + pub fn move_predecls_to_overlay(&mut self) { + let predecls: HashMap, DeclId> = + self.delta.last_scope_frame_mut().predecls.drain().collect(); + + self.last_overlay_mut().predecls.extend(predecls); + } + pub fn hide_decl(&mut self, name: &[u8]) -> Option { + let mut removed_overlays = vec![]; let mut visibility: Visibility = Visibility::new(); // Since we can mutate scope frames in delta, remove the id directly - for scope in self.delta.scope.iter_mut().rev() { - visibility.append(&scope.visibility); + for scope_frame in self.delta.scope.iter_mut().rev() { + for overlay_id in scope_frame + .active_overlay_ids(&mut removed_overlays) + .iter_mut() + .rev() + { + let overlay_frame = scope_frame.get_overlay_mut(*overlay_id); - if let Some(decl_id) = scope.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { - // Hide decl only if it's not already hidden - scope.visibility.hide_decl_id(decl_id); - return Some(*decl_id); + visibility.append(&overlay_frame.visibility); + + if let Some(decl_id) = overlay_frame.decls.get(name) { + if visibility.is_decl_id_visible(decl_id) { + // Hide decl only if it's not already hidden + overlay_frame.visibility.hide_decl_id(decl_id); + return Some(*decl_id); + } } } } - // We cannot mutate the permanent state => store the information in the current scope frame - let last_scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + // We cannot mutate the permanent state => store the information in the current overlay frame + // for scope in self.permanent_state.scope.iter().rev() { + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); - for scope in self.permanent_state.scope.iter().rev() { - visibility.append(&scope.visibility); - - if let Some(decl_id) = scope.decls.get(name) { + if let Some(decl_id) = overlay_frame.decls.get(name) { if visibility.is_decl_id_visible(decl_id) { // Hide decl only if it's not already hidden - last_scope_frame.visibility.hide_decl_id(decl_id); + self.last_overlay_mut().visibility.hide_decl_id(decl_id); return Some(*decl_id); } } @@ -861,33 +1258,42 @@ impl<'a> StateWorkingSet<'a> { } pub fn use_alias(&mut self, alias_id: &AliasId) { + let mut removed_overlays = vec![]; let mut visibility: Visibility = Visibility::new(); // Since we can mutate scope frames in delta, remove the id directly - for scope in self.delta.scope.iter_mut().rev() { - visibility.append(&scope.visibility); + for scope_frame in self.delta.scope.iter_mut().rev() { + for overlay_id in scope_frame + .active_overlay_ids(&mut removed_overlays) + .iter() + .rev() + { + let overlay_frame = scope_frame.get_overlay_mut(*overlay_id); - if !visibility.is_alias_id_visible(alias_id) { - // Hide alias only if it's not already hidden - scope.visibility.use_alias_id(alias_id); + visibility.append(&overlay_frame.visibility); - return; + if !visibility.is_alias_id_visible(alias_id) { + // Use alias only if it's already hidden + overlay_frame.visibility.use_alias_id(alias_id); + + return; + } } } // We cannot mutate the permanent state => store the information in the current scope frame - let last_scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); - - for scope in self.permanent_state.scope.iter().rev() { - visibility.append(&scope.visibility); + // for scope in self.permanent_state.scope.iter().rev() { + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); if !visibility.is_alias_id_visible(alias_id) { // Hide alias only if it's not already hidden - last_scope_frame.visibility.use_alias_id(alias_id); + self.last_overlay_mut().visibility.use_alias_id(alias_id); return; } @@ -895,36 +1301,44 @@ impl<'a> StateWorkingSet<'a> { } pub fn hide_alias(&mut self, name: &[u8]) -> Option { + let mut removed_overlays = vec![]; let mut visibility: Visibility = Visibility::new(); // Since we can mutate scope frames in delta, remove the id directly - for scope in self.delta.scope.iter_mut().rev() { - visibility.append(&scope.visibility); + for scope_frame in self.delta.scope.iter_mut().rev() { + for overlay_id in scope_frame + .active_overlay_ids(&mut removed_overlays) + .iter() + .rev() + { + let overlay_frame = scope_frame.get_overlay_mut(*overlay_id); - if let Some(alias_id) = scope.aliases.get(name) { - if visibility.is_alias_id_visible(alias_id) { - // Hide alias only if it's not already hidden - scope.visibility.hide_alias_id(alias_id); + visibility.append(&overlay_frame.visibility); - return Some(*alias_id); + if let Some(alias_id) = overlay_frame.aliases.get(name) { + if visibility.is_alias_id_visible(alias_id) { + // Hide alias only if it's not already hidden + overlay_frame.visibility.hide_alias_id(alias_id); + return Some(*alias_id); + } } } } // We cannot mutate the permanent state => store the information in the current scope frame - let last_scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + // for scope in self.permanent_state.scope.iter().rev() { + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); - for scope in self.permanent_state.scope.iter().rev() { - visibility.append(&scope.visibility); - - if let Some(alias_id) = scope.aliases.get(name) { + if let Some(alias_id) = overlay_frame.aliases.get(name) { if visibility.is_alias_id_visible(alias_id) { // Hide alias only if it's not already hidden - last_scope_frame.visibility.hide_alias_id(alias_id); + self.last_overlay_mut().visibility.hide_alias_id(alias_id); return Some(*alias_id); } @@ -952,37 +1366,15 @@ impl<'a> StateWorkingSet<'a> { self.num_blocks() - 1 } - pub fn add_env_var(&mut self, name_span: Span, block: Block) -> BlockId { - self.delta.blocks.push(block); - let block_id = self.num_blocks() - 1; - let name = self.get_span_contents(name_span).to_vec(); - - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); - - scope_frame.env_vars.insert(name, block_id); - - block_id - } - - pub fn add_overlay(&mut self, name: &str, overlay: Overlay) -> OverlayId { + pub fn add_module(&mut self, name: &str, module: Module) -> ModuleId { let name = name.as_bytes().to_vec(); - self.delta.overlays.push(overlay); - let overlay_id = self.num_overlays() - 1; + self.delta.modules.push(module); + let module_id = self.num_modules() - 1; - let scope_frame = self - .delta - .scope - .last_mut() - .expect("internal error: missing required scope frame"); + self.last_overlay_mut().modules.insert(name, module_id); - scope_frame.overlays.insert(name, overlay_id); - - overlay_id + module_id } pub fn next_span_start(&self) -> usize { @@ -1068,38 +1460,71 @@ impl<'a> StateWorkingSet<'a> { } pub fn find_predecl(&self, name: &[u8]) -> Option { - for scope in self.delta.scope.iter().rev() { - if let Some(decl_id) = scope.predecls.get(name) { + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + if let Some(decl_id) = scope_frame.predecls.get(name) { return Some(*decl_id); } + + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + if let Some(decl_id) = overlay_frame.predecls.get(name) { + return Some(*decl_id); + } + } } None } pub fn find_decl(&self, name: &[u8]) -> Option { + let mut removed_overlays = vec![]; + let mut visibility: Visibility = Visibility::new(); - for scope in self.delta.scope.iter().rev() { - visibility.append(&scope.visibility); - - if let Some(decl_id) = scope.predecls.get(name) { + for scope_frame in self.delta.scope.iter().rev() { + if let Some(decl_id) = scope_frame.predecls.get(name) { if visibility.is_decl_id_visible(decl_id) { return Some(*decl_id); } } - if let Some(decl_id) = scope.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { - return Some(*decl_id); + // check overlay in delta + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); + + if let Some(decl_id) = overlay_frame.predecls.get(name) { + if visibility.is_decl_id_visible(decl_id) { + return Some(*decl_id); + } + } + + if let Some(decl_id) = overlay_frame.decls.get(name) { + if visibility.is_decl_id_visible(decl_id) { + return Some(*decl_id); + } } } } - for scope in self.permanent_state.scope.iter().rev() { - visibility.append(&scope.visibility); + // check overlay in perma + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = scope.decls.get(name) { + if let Some(decl_id) = overlay_frame.decls.get(name) { if visibility.is_decl_id_visible(decl_id) { return Some(*decl_id); } @@ -1110,22 +1535,34 @@ impl<'a> StateWorkingSet<'a> { } pub fn find_alias(&self, name: &[u8]) -> Option { + let mut removed_overlays = vec![]; let mut visibility: Visibility = Visibility::new(); - for scope in self.delta.scope.iter().rev() { - visibility.append(&scope.visibility); + for scope_frame in self.delta.scope.iter().rev() { + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); - if let Some(alias_id) = scope.aliases.get(name) { - if visibility.is_alias_id_visible(alias_id) { - return Some(*alias_id); + if let Some(alias_id) = overlay_frame.aliases.get(name) { + if visibility.is_alias_id_visible(alias_id) { + return Some(*alias_id); + } } } } - for scope in self.permanent_state.scope.iter().rev() { - visibility.append(&scope.visibility); + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + visibility.append(&overlay_frame.visibility); - if let Some(alias_id) = scope.aliases.get(name) { + if let Some(alias_id) = overlay_frame.aliases.get(name) { if visibility.is_alias_id_visible(alias_id) { return Some(*alias_id); } @@ -1135,38 +1572,59 @@ impl<'a> StateWorkingSet<'a> { None } - pub fn find_overlay(&self, name: &[u8]) -> Option { - for scope in self.delta.scope.iter().rev() { - if let Some(overlay_id) = scope.overlays.get(name) { - return Some(*overlay_id); + pub fn find_module(&self, name: &[u8]) -> Option { + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + if let Some(module_id) = overlay_frame.modules.get(name) { + return Some(*module_id); + } } } - for scope in self.permanent_state.scope.iter().rev() { - if let Some(overlay_id) = scope.overlays.get(name) { - return Some(*overlay_id); + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + if let Some(module_id) = overlay_frame.modules.get(name) { + return Some(*module_id); } } None } - // pub fn update_decl(&mut self, decl_id: usize, block: Option) { - // let decl = self.get_decl_mut(decl_id); - // decl.body = block; - // } - pub fn contains_decl_partial_match(&self, name: &[u8]) -> bool { - for scope in self.delta.scope.iter().rev() { - for decl in &scope.decls { - if decl.0.starts_with(name) { - return true; + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + for decl in &overlay_frame.decls { + if decl.0.starts_with(name) { + return true; + } } } } - for scope in self.permanent_state.scope.iter().rev() { - for decl in &scope.decls { + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + for decl in &overlay_frame.decls { if decl.0.starts_with(name) { return true; } @@ -1182,14 +1640,27 @@ impl<'a> StateWorkingSet<'a> { } pub fn find_variable(&self, name: &[u8]) -> Option { - for scope in self.delta.scope.iter().rev() { - if let Some(var_id) = scope.vars.get(name) { - return Some(*var_id); + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + for overlay_frame in scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + { + if let Some(var_id) = overlay_frame.vars.get(name) { + return Some(*var_id); + } } } - for scope in self.permanent_state.scope.iter().rev() { - if let Some(var_id) = scope.vars.get(name) { + for overlay_frame in self + .permanent_state + .active_overlays(&removed_overlays) + .iter() + .rev() + { + if let Some(var_id) = overlay_frame.vars.get(name) { return Some(*var_id); } } @@ -1205,13 +1676,7 @@ impl<'a> StateWorkingSet<'a> { name.insert(0, b'$'); } - let last = self - .delta - .scope - .last_mut() - .expect("internal error: missing stack frame"); - - last.vars.insert(name, next_id); + self.last_overlay_mut().vars.insert(name, next_id); self.delta.vars.push(Variable::new(span, ty)); @@ -1222,11 +1687,7 @@ impl<'a> StateWorkingSet<'a> { self.delta.aliases.push(replacement); let alias_id = self.num_aliases() - 1; - let last = self - .delta - .scope - .last_mut() - .expect("internal error: missing stack frame"); + let last = self.last_overlay_mut(); last.aliases.insert(name, alias_id); last.visibility.use_alias_id(&alias_id); @@ -1235,14 +1696,13 @@ impl<'a> StateWorkingSet<'a> { pub fn get_cwd(&self) -> String { let pwd = self .permanent_state - .env_vars - .get(PWD_ENV) + .get_env_var(PWD_ENV) .expect("internal error: can't find PWD"); pwd.as_string().expect("internal error: PWD not a string") } - pub fn get_env(&self, name: &str) -> Option<&Value> { - self.permanent_state.env_vars.get(name) + pub fn get_env_var(&self, name: &str) -> Option<&Value> { + self.permanent_state.get_env_var(name) } pub fn get_config(&self) -> &Config { @@ -1324,11 +1784,15 @@ impl<'a> StateWorkingSet<'a> { ) -> Vec<(Vec, Option)> { let mut output = vec![]; - for scope in self.delta.scope.iter().rev() { - for decl in &scope.decls { - if predicate(decl.0) { - let command = self.get_decl(*decl.1); - output.push((decl.0.clone(), Some(command.usage().to_string()))); + for scope_frame in self.delta.scope.iter().rev() { + for overlay_id in scope_frame.active_overlays.iter().rev() { + let overlay_frame = scope_frame.get_overlay(*overlay_id); + + for decl in &overlay_frame.decls { + if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { + let command = self.get_decl(*decl.1); + output.push((decl.0.clone(), Some(command.usage().to_string()))); + } } } } @@ -1344,15 +1808,25 @@ impl<'a> StateWorkingSet<'a> { &self, predicate: impl Fn(&[u8]) -> bool + Copy, ) -> Vec> { - self.delta - .scope - .iter() - .rev() - .flat_map(|scope| &scope.aliases) - .filter(|decl| predicate(decl.0)) - .map(|decl| decl.0.clone()) - .chain(self.permanent_state.find_aliases_by_predicate(predicate)) - .collect() + let mut output = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + for overlay_id in scope_frame.active_overlays.iter().rev() { + let overlay_frame = scope_frame.get_overlay(*overlay_id); + + for alias in &overlay_frame.aliases { + if overlay_frame.visibility.is_alias_id_visible(alias.1) && predicate(alias.0) { + output.push(alias.0.clone()); + } + } + } + } + + let mut permanent = self.permanent_state.find_aliases_by_predicate(predicate); + + output.append(&mut permanent); + + output } pub fn get_block(&self, block_id: BlockId) -> &Block { @@ -1367,15 +1841,15 @@ impl<'a> StateWorkingSet<'a> { } } - pub fn get_overlay(&self, overlay_id: OverlayId) -> &Overlay { - let num_permanent_overlays = self.permanent_state.num_overlays(); - if overlay_id < num_permanent_overlays { - self.permanent_state.get_overlay(overlay_id) + pub fn get_module(&self, module_id: ModuleId) -> &Module { + let num_permanent_modules = self.permanent_state.num_modules(); + if module_id < num_permanent_modules { + self.permanent_state.get_module(module_id) } else { self.delta - .overlays - .get(overlay_id - num_permanent_overlays) - .expect("internal error: missing overlay") + .modules + .get(module_id - num_permanent_modules) + .expect("internal error: missing module") } } @@ -1391,11 +1865,172 @@ impl<'a> StateWorkingSet<'a> { } } + pub fn has_overlay(&self, name: &[u8]) -> bool { + for scope_frame in self.delta.scope.iter().rev() { + if scope_frame + .overlays + .iter() + .any(|(overlay_name, _)| name == overlay_name) + { + return true; + } + } + + self.permanent_state.has_overlay(name) + } + + pub fn find_overlay_origin(&self, name: &[u8]) -> Option { + for scope_frame in self.delta.scope.iter().rev() { + if let Some(overlay_id) = scope_frame.find_overlay(name) { + return Some(scope_frame.get_overlay(overlay_id).origin); + } + } + + self.permanent_state + .find_overlay(name) + .map(|id| self.permanent_state.get_overlay(id).origin) + } + + pub fn last_overlay_name(&self) -> &Vec { + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + if let Some(last_name) = scope_frame + .active_overlay_names(&mut removed_overlays) + .iter() + .rev() + .last() + { + return last_name; + } + } + + self.permanent_state.last_overlay_name(&removed_overlays) + } + + pub fn last_overlay(&self) -> &OverlayFrame { + let mut removed_overlays = vec![]; + + for scope_frame in self.delta.scope.iter().rev() { + if let Some(last_overlay) = scope_frame + .active_overlays(&mut removed_overlays) + .iter() + .rev() + .last() + { + return last_overlay; + } + } + + self.permanent_state.last_overlay(&removed_overlays) + } + + pub fn last_overlay_mut(&mut self) -> &mut OverlayFrame { + if self.delta.last_overlay_mut().is_none() { + // If there is no overlay, automatically activate the last one + let name = self.last_overlay_name().to_vec(); + let origin = self.last_overlay().origin; + self.add_overlay(name, origin, vec![], vec![]); + } + + self.delta + .last_overlay_mut() + .expect("internal error: missing added overlay") + } + + pub fn add_overlay( + &mut self, + name: Vec, + origin: ModuleId, + decls: Vec<(Vec, DeclId)>, + aliases: Vec<(Vec, AliasId)>, + ) { + let last_scope_frame = self.delta.last_scope_frame_mut(); + + last_scope_frame + .removed_overlays + .retain(|removed_name| removed_name != &name); + + let overlay_id = if let Some(overlay_id) = last_scope_frame.find_overlay(&name) { + last_scope_frame.get_overlay_mut(overlay_id).origin = origin; + + overlay_id + } else { + last_scope_frame + .overlays + .push((name, OverlayFrame::from(origin))); + last_scope_frame.overlays.len() - 1 + }; + + last_scope_frame + .active_overlays + .retain(|id| id != &overlay_id); + last_scope_frame.active_overlays.push(overlay_id); + + self.move_predecls_to_overlay(); + + self.use_decls(decls); + self.use_aliases(aliases); + } + + pub fn remove_overlay(&mut self, name: &[u8]) { + let last_scope_frame = self.delta.last_scope_frame_mut(); + + let removed_overlay = if let Some(overlay_id) = last_scope_frame.find_overlay(name) { + last_scope_frame + .active_overlays + .retain(|id| id != &overlay_id); + + Some(last_scope_frame.get_overlay(overlay_id).clone()) + } else { + self.permanent_state + .find_overlay(name) + .map(|id| self.permanent_state.get_overlay(id).clone()) + }; + + if removed_overlay.is_some() { + last_scope_frame.removed_overlays.push(name.to_owned()); + } + + // if let Some(module) = original_module { + // let last_overlay_name = self.last_overlay_name().to_owned(); + + // if let Some(overlay) = removed_overlay { + // let (diff_decls, diff_aliases) = overlay.diff(&module); + // self.add_overlay(last_overlay_name, diff_decls, diff_aliases); + // } + // } + } + pub fn render(self) -> StateDelta { self.delta } } +impl Default for Visibility { + fn default() -> Self { + Self::new() + } +} + +impl Default for ScopeFrame { + fn default() -> Self { + Self::new() + } +} + +// impl Default for OverlayFrame { +// fn default() -> Self { +// Self::new() +// } +// } + +impl Default for EngineState { + fn default() -> Self { + Self::new() + } +} + impl<'a> miette::SourceCode for &StateWorkingSet<'a> { fn read_span<'b>( &'b self, diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index d6d630bb11..c1ff76c91f 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,8 +1,11 @@ use std::collections::{HashMap, HashSet}; -use crate::engine::EngineState; +use crate::engine::{EngineState, DEFAULT_OVERLAY_NAME}; use crate::{ShellError, Span, Value, VarId}; +/// Environment variables per overlay +pub type EnvVars = HashMap>; + /// A runtime value stack used during evaluation /// /// A note on implementation: @@ -25,16 +28,11 @@ pub struct Stack { /// Variables pub vars: HashMap, /// Environment variables arranged as a stack to be able to recover values from parent scopes - pub env_vars: Vec>, - /// Tells which environment variables from engine state are hidden. We don't need to track the - /// env vars in the stack since we can just delete them. - pub env_hidden: HashSet, -} - -impl Default for Stack { - fn default() -> Self { - Self::new() - } + pub env_vars: Vec, + /// Tells which environment variables from engine state are hidden, per overlay. + pub env_hidden: HashMap>, + /// List of active overlays + pub active_overlays: Vec, } impl Stack { @@ -42,18 +40,23 @@ impl Stack { Stack { vars: HashMap::new(), env_vars: vec![], - env_hidden: HashSet::new(), + env_hidden: HashMap::new(), + active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], } } - pub fn with_env(&mut self, env_vars: &[HashMap], env_hidden: &HashSet) { + pub fn with_env( + &mut self, + env_vars: &[EnvVars], + env_hidden: &HashMap>, + ) { // Do not clone the environment if it hasn't changed if self.env_vars.iter().any(|scope| !scope.is_empty()) { self.env_vars = env_vars.to_owned(); } if !self.env_hidden.is_empty() { - self.env_hidden = env_hidden.clone(); + self.env_hidden = env_hidden.to_owned(); } } @@ -78,30 +81,52 @@ impl Stack { } pub fn add_env_var(&mut self, var: String, value: Value) { - // if the env var was hidden, let's activate it again - self.env_hidden.remove(&var); + if let Some(last_overlay) = self.active_overlays.last() { + if let Some(env_hidden) = self.env_hidden.get_mut(last_overlay) { + // if the env var was hidden, let's activate it again + env_hidden.remove(&var); + } - if let Some(scope) = self.env_vars.last_mut() { - scope.insert(var, value); + if let Some(scope) = self.env_vars.last_mut() { + if let Some(env_vars) = scope.get_mut(last_overlay) { + env_vars.insert(var, value); + } else { + scope.insert(last_overlay.into(), HashMap::from([(var, value)])); + } + } else { + self.env_vars.push(HashMap::from([( + last_overlay.into(), + HashMap::from([(var, value)]), + )])); + } } else { - self.env_vars.push(HashMap::from([(var, value)])); + // TODO: Remove panic + panic!("internal error: no active overlay"); } } + pub fn last_overlay_name(&self) -> Result { + self.active_overlays + .last() + .cloned() + .ok_or_else(|| ShellError::NushellFailed("No active overlay".into())) + } + pub fn captures_to_stack(&self, captures: &HashMap) -> Stack { - let mut output = Stack::new(); - - output.vars = captures.clone(); - // FIXME: this is probably slow - output.env_vars = self.env_vars.clone(); - output.env_vars.push(HashMap::new()); + let mut env_vars = self.env_vars.clone(); + env_vars.push(HashMap::new()); - output + Stack { + vars: captures.clone(), + env_vars, + env_hidden: HashMap::new(), + active_overlays: self.active_overlays.clone(), + } } pub fn gather_captures(&self, captures: &[VarId]) -> Stack { - let mut output = Stack::new(); + let mut vars = HashMap::new(); let fake_span = Span::new(0, 0); @@ -109,30 +134,59 @@ impl Stack { // Note: this assumes we have calculated captures correctly and that commands // that take in a var decl will manually set this into scope when running the blocks if let Ok(value) = self.get_var(*capture, fake_span) { - output.vars.insert(*capture, value); + vars.insert(*capture, value); } } - // FIXME: this is probably slow - output.env_vars = self.env_vars.clone(); - output.env_vars.push(HashMap::new()); + let mut env_vars = self.env_vars.clone(); + env_vars.push(HashMap::new()); - output + Stack { + vars, + env_vars, + env_hidden: HashMap::new(), + active_overlays: self.active_overlays.clone(), + } } /// Flatten the env var scope frames into one frame pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap { - // TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these - // the same data structure. - let mut result: HashMap = engine_state - .env_vars - .iter() - .filter(|(k, _)| !self.env_hidden.contains(*k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); + let mut result = HashMap::new(); + + for active_overlay in self.active_overlays.iter() { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + result.extend( + env_vars + .iter() + .filter(|(k, _)| { + if let Some(env_hidden) = self.env_hidden.get(active_overlay) { + !env_hidden.contains(*k) + } else { + // nothing has been hidden in this overlay + true + } + }) + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>(), + ); + } + } + + result.extend(self.get_stack_env_vars()); + + result + } + + /// Get flattened environment variables only from the stack + pub fn get_stack_env_vars(&self) -> HashMap { + let mut result = HashMap::new(); for scope in &self.env_vars { - result.extend(scope.clone()); + for active_overlay in self.active_overlays.iter() { + if let Some(env_vars) = scope.get(active_overlay) { + result.extend(env_vars.clone()); + } + } } result @@ -140,16 +194,33 @@ impl Stack { /// Same as get_env_vars, but returns only the names as a HashSet pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet { - let mut result: HashSet = engine_state - .env_vars - .keys() - .filter(|k| !self.env_hidden.contains(*k)) - .cloned() - .collect(); + let mut result = HashSet::new(); + + for active_overlay in self.active_overlays.iter() { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + result.extend( + env_vars + .keys() + .filter(|k| { + if let Some(env_hidden) = self.env_hidden.get(active_overlay) { + !env_hidden.contains(*k) + } else { + // nothing has been hidden in this overlay + true + } + }) + .cloned() + .collect::>(), + ); + } + } for scope in &self.env_vars { - let scope_keys: HashSet = scope.keys().cloned().collect(); - result.extend(scope_keys); + for active_overlay in self.active_overlays.iter() { + if let Some(env_vars) = scope.get(active_overlay) { + result.extend(env_vars.keys().cloned().collect::>()); + } + } } result @@ -157,83 +228,123 @@ impl Stack { pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter().rev() { - if let Some(v) = scope.get(name) { - return Some(v.clone()); + for active_overlay in self.active_overlays.iter().rev() { + if let Some(env_vars) = scope.get(active_overlay) { + if let Some(v) = env_vars.get(name) { + return Some(v.clone()); + } + } } } - if self.env_hidden.contains(name) { - None - } else { - engine_state.env_vars.get(name).cloned() + for active_overlay in self.active_overlays.iter().rev() { + let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) { + env_hidden.contains(name) + } else { + false + }; + + if !is_hidden { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + if let Some(v) = env_vars.get(name) { + return Some(v.clone()); + } + } + } } + + None } pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool { + for scope in self.env_vars.iter().rev() { + for active_overlay in self.active_overlays.iter().rev() { + if let Some(env_vars) = scope.get(active_overlay) { + if env_vars.contains_key(name) { + return true; + } + } + } + } + + for active_overlay in self.active_overlays.iter().rev() { + let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) { + env_hidden.contains(name) + } else { + false + }; + + if !is_hidden { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + if env_vars.contains_key(name) { + return true; + } + } + } + } + + false + } + + pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option { + for scope in self.env_vars.iter_mut().rev() { + for active_overlay in self.active_overlays.iter().rev() { + if let Some(env_vars) = scope.get_mut(active_overlay) { + if let Some(v) = env_vars.remove(name) { + return Some(v); + } + } + } + } + + for active_overlay in self.active_overlays.iter().rev() { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + if let Some(val) = env_vars.get(name) { + if let Some(env_hidden) = self.env_hidden.get_mut(active_overlay) { + env_hidden.insert(name.into()); + } else { + self.env_hidden + .insert(active_overlay.into(), HashSet::from([name.into()])); + } + + return Some(val.clone()); + } + } + } + + None + } + + pub fn has_env_overlay(&self, name: &str, engine_state: &EngineState) -> bool { for scope in self.env_vars.iter().rev() { if scope.contains_key(name) { return true; } } - if self.env_hidden.contains(name) { - false - } else { - engine_state.env_vars.contains_key(name) - } + engine_state.env_vars.contains_key(name) } - pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option { - for scope in self.env_vars.iter_mut().rev() { - if let Some(v) = scope.remove(name) { - return Some(v); - } - } + pub fn add_overlay(&mut self, name: String) { + self.env_hidden.remove(&name); - if self.env_hidden.contains(name) { - // the environment variable is already hidden - None - } else if let Some(val) = engine_state.env_vars.get(name) { - // the environment variable was found in the engine state => mark it as hidden - self.env_hidden.insert(name.to_string()); - Some(val.clone()) - } else { - None - } + self.active_overlays.retain(|o| o != &name); + self.active_overlays.push(name); } - // pub fn get_config(&self) -> Result { - // let config = self.get_var(CONFIG_VARIABLE_ID, Span::new(0, 0)); - - // match config { - // Ok(config) => config.into_config(), - // Err(e) => Err(e), - // } - // } - - // pub fn update_config(&mut self, name: &str, value: Value) { - // if let Some(Value::Record { cols, vals, .. }) = self.vars.get_mut(&CONFIG_VARIABLE_ID) { - // for col_val in cols.iter().zip(vals.iter_mut()) { - // if col_val.0 == name { - // *col_val.1 = value; - // return; - // } - // } - // cols.push(name.to_string()); - // vals.push(value); - // } - // } - - pub fn print_stack(&self) { - println!("vars:"); - for (var, val) in &self.vars { - println!(" {}: {:?}", var, val); - } - for (i, scope) in self.env_vars.iter().rev().enumerate() { - println!("env vars, scope {} (from the last);", i); - for (var, val) in scope { - println!(" {}: {:?}", var, val.clone().debug_value()); - } + pub fn remove_overlay(&mut self, name: &String, span: &Span) -> Result<(), ShellError> { + if !self.active_overlays.contains(name) { + return Err(ShellError::OverlayNotFoundAtRuntime(name.into(), *span)); } + + self.active_overlays.retain(|o| o != name); + + Ok(()) + } +} + +impl Default for Stack { + fn default() -> Self { + Self::new() } } diff --git a/crates/nu-protocol/src/id.rs b/crates/nu-protocol/src/id.rs index a380bb8b67..472a7ee370 100644 --- a/crates/nu-protocol/src/id.rs +++ b/crates/nu-protocol/src/id.rs @@ -2,4 +2,5 @@ pub type VarId = usize; pub type DeclId = usize; pub type AliasId = usize; pub type BlockId = usize; +pub type ModuleId = usize; pub type OverlayId = usize; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 7712a70c02..25d3c63eac 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -5,7 +5,7 @@ pub mod engine; mod example; mod exportable; mod id; -mod overlay; +mod module; mod pipeline_data; mod shell_error; mod signature; @@ -21,7 +21,7 @@ pub use engine::{ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID}; pub use example::*; pub use exportable::*; pub use id::*; -pub use overlay::*; +pub use module::*; pub use pipeline_data::*; pub use shell_error::*; pub use signature::*; diff --git a/crates/nu-protocol/src/overlay.rs b/crates/nu-protocol/src/module.rs similarity index 97% rename from crates/nu-protocol/src/overlay.rs rename to crates/nu-protocol/src/module.rs index 34520a89db..0479e0bca3 100644 --- a/crates/nu-protocol/src/overlay.rs +++ b/crates/nu-protocol/src/module.rs @@ -7,16 +7,16 @@ use indexmap::IndexMap; /// Collection of definitions that can be exported from a module #[derive(Debug, Clone)] -pub struct Overlay { +pub struct Module { pub decls: IndexMap, DeclId>, pub aliases: IndexMap, AliasId>, pub env_vars: IndexMap, BlockId>, pub span: Option, } -impl Overlay { +impl Module { pub fn new() -> Self { - Overlay { + Module { decls: IndexMap::new(), aliases: IndexMap::new(), env_vars: IndexMap::new(), @@ -25,7 +25,7 @@ impl Overlay { } pub fn from_span(span: Span) -> Self { - Overlay { + Module { decls: IndexMap::new(), aliases: IndexMap::new(), env_vars: IndexMap::new(), @@ -45,7 +45,7 @@ impl Overlay { self.env_vars.insert(name.to_vec(), block_id) } - pub fn extend(&mut self, other: &Overlay) { + pub fn extend(&mut self, other: &Module) { self.decls.extend(other.decls.clone()); self.env_vars.extend(other.env_vars.clone()); } @@ -201,7 +201,7 @@ impl Overlay { } } -impl Default for Overlay { +impl Default for Module { fn default() -> Self { Self::new() } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 853b675355..d65bfcf2c3 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -448,7 +448,7 @@ impl PipelineData { return Ok(()); } - match engine_state.find_decl("table".as_bytes()) { + match engine_state.find_decl("table".as_bytes(), &[]) { Some(decl_id) => { let table = engine_state.get_decl(decl_id).run( engine_state, diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 08d61bb6f0..052b87dea1 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -168,7 +168,7 @@ pub enum ShellError { /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. #[error("Nushell failed: {0}.")] #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] - // Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable + // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable NushellFailed(String), /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error. @@ -177,10 +177,30 @@ pub enum ShellError { /// /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. #[error("Nushell failed: {0}.")] - #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] - // Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable + #[diagnostic(code(nu::shell::nushell_failed_spanned), url(docsrs))] + // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable NushellFailedSpanned(String, String, #[label = "{1}"] Span), + /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error. + /// + /// ## Resolution + /// + /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. + #[error("Nushell failed: {0}.")] + #[diagnostic(code(nu::shell::nushell_failed_help), url(docsrs))] + // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable + NushellFailedHelp(String, #[help] String), + + /// Catastrophic nushell failure. This reflects a completely unexpected or unrecoverable error. + /// + /// ## Resolution + /// + /// It is very likely that this is a bug. Please file an issue at https://github.com/nushell/nushell/issues with relevant information. + #[error("Nushell failed: {0}.")] + #[diagnostic(code(nu::shell::nushell_failed_spanned_help), url(docsrs))] + // Only use this one if Nushell completely falls over and hits a state that isn't possible or isn't recoverable + NushellFailedSpannedHelp(String, String, #[label = "{1}"] Span, #[help] String), + /// A referenced variable was not found at runtime. /// /// ## Resolution @@ -199,6 +219,33 @@ pub enum ShellError { #[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))] EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span), + /// A referenced module was not found at runtime. + /// + /// ## Resolution + /// + /// Check the module name. Did you typo it? Did you forget to declare it? Is the casing right? + #[error("Module '{0}' not found")] + #[diagnostic(code(nu::shell::module_not_found), url(docsrs))] + ModuleNotFoundAtRuntime(String, #[label = "module not found"] Span), + + /// A referenced module or overlay was not found at runtime. + /// + /// ## Resolution + /// + /// Check the module name. Did you typo it? Did you forget to declare it? Is the casing right? + #[error("Module or overlay'{0}' not found")] + #[diagnostic(code(nu::shell::module_not_found), url(docsrs))] + ModuleOrOverlayNotFoundAtRuntime(String, #[label = "not a module or overlay"] Span), + + /// A referenced overlay was not found at runtime. + /// + /// ## Resolution + /// + /// Check the overlay name. Did you typo it? Did you forget to declare it? Is the casing right? + #[error("Overlay '{0}' not found")] + #[diagnostic(code(nu::shell::overlay_not_found), url(docsrs))] + OverlayNotFoundAtRuntime(String, #[label = "overlay not found"] Span), + /// The given item was not found. This is a fairly generic error that depends on context. /// /// ## Resolution diff --git a/tests/main.rs b/tests/main.rs index d508e265b4..5931ef6478 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,5 +1,6 @@ extern crate nu_test_support; +mod overlays; mod parsing; mod path; mod plugins; diff --git a/tests/overlays/mod.rs b/tests/overlays/mod.rs new file mode 100644 index 0000000000..4e0ce6257c --- /dev/null +++ b/tests/overlays/mod.rs @@ -0,0 +1,310 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn add_overlay() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + foo + "# + )); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn add_overlay_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export env FOO { "foo" } }; + overlay add spam; + $env.FOO + "# + )); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn add_overlay_from_file_decl() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + foo + "# + )); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn add_overlay_from_file_alias() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + bar + "# + )); + + assert_eq!(actual.out, "bar"); +} + +#[test] +fn add_overlay_from_file_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + $env.BAZ + "# + )); + + assert_eq!(actual.out, "baz"); +} + +#[test] +fn add_overlay_scoped() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + do { overlay add spam }; + foo + "# + )); + + assert!(!actual.err.is_empty()) +} + +#[test] +fn update_overlay_from_module() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + module spam { export def foo [] { "bar" } }; + overlay add spam; + foo + "# + )); + + assert_eq!(actual.out, "bar"); +} + +#[test] +fn update_overlay_from_module_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export env FOO { "foo" } }; + overlay add spam; + module spam { export env FOO { "bar" } }; + overlay add spam; + $env.FOO + "# + )); + + assert_eq!(actual.out, "bar"); +} + +#[test] +fn remove_overlay() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + overlay remove spam; + foo + "# + )); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn remove_last_overlay() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + overlay remove; + foo + "# + )); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn remove_overlay_scoped() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + do { + overlay remove spam + }; + foo + "# + )); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn remove_overlay_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export env FOO { "foo" } }; + overlay add spam; + overlay remove spam; + $env.FOO + "# + )); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn remove_overlay_scoped_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export env FOO { "foo" } }; + overlay add spam; + do { + overlay remove spam + }; + $env.FOO + "# + )); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn list_default_overlay() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay list | last + "#, + )); + + assert_eq!(actual.out, "zero"); +} + +#[test] +fn list_last_overlay() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + overlay list | last + "#, + )); + + assert_eq!(actual.out, "spam"); +} + +#[test] +fn list_overlay_scoped() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + module spam { export def foo [] { "foo" } }; + overlay add spam; + do { overlay list | last } + "# + )); + + assert_eq!(actual.out, "spam"); +} + +#[test] +fn remove_overlay_discard_decl() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + def bagr [] { "bagr" }; + overlay remove spam; + bagr + "# + )); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn remove_overlay_discard_alias() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + alias bagr = "bagr"; + overlay remove spam; + bagr + "# + )); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn remove_overlay_discard_env() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + let-env BAGR = "bagr"; + overlay remove spam; + $env.bagr + "# + )); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn preserve_overrides() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + def foo [] { "new-foo" }; + overlay remove spam; + overlay add spam; + foo + "# + )); + + assert_eq!(actual.out, "new-foo"); +} + +#[test] +fn reset_overrides() { + let actual = nu!( + cwd: "tests/overlays", pipeline( + r#" + overlay add samples/spam.nu; + def foo [] { "new-foo" }; + overlay remove spam; + overlay add samples/spam.nu; + foo + "# + )); + + assert_eq!(actual.out, "foo"); +} diff --git a/tests/overlays/samples/spam.nu b/tests/overlays/samples/spam.nu new file mode 100644 index 0000000000..1e54127281 --- /dev/null +++ b/tests/overlays/samples/spam.nu @@ -0,0 +1,5 @@ +export def foo [] { "foo" } + +export alias bar = "bar" + +export env BAZ { "baz" }