From 9b99b2f6ac398ff505e1ec7834c32289a5d62d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 7 May 2022 22:39:22 +0300 Subject: [PATCH] Overlays (#5375) * WIP: Start laying overlays * Rename Overlay->Module; Start adding overlay * Revamp adding overlay * Add overlay add tests; Disable debug print * Fix overlay add; Add overlay remove * Add overlay remove tests * Add missing overlay remove file * Add overlay list command * (WIP?) Enable overlays for env vars * Move OverlayFrames to ScopeFrames * (WIP) Move everything to overlays only ScopeFrame contains nothing but overlays now * Fix predecls * Fix wrong overlay id translation and aliases * Fix broken env lookup logic * Remove TODOs * Add overlay add + remove for environment * Add a few overlay tests; Fix overlay add name * Some cleanup; Fix overlay add/remove names * Clippy * Fmt * Remove walls of comments * List overlays from stack; Add debugging flag Currently, the engine state ordering is somehow broken. * Fix (?) overlay list test * Fix tests on Windows * Fix activated overlay ordering * Check for active overlays equality in overlay list This removes the -p flag: Either both parser and engine will have the same overlays, or the command will fail. * Add merging on overlay remove * Change help message and comment * Add some remove-merge/discard tests * (WIP) Track removed overlays properly * Clippy; Fmt * Fix getting last overlay; Fix predecls in overlays * Remove merging; Fix re-add overwriting stuff Also some error message tweaks. * Fix overlay error in the engine * Update variable_completions.rs * Adds flags and optional arguments to view-source (#5446) * added flags and optional arguments to view-source * removed redundant code * removed redundant code * fmt * fix bug in shell_integration (#5450) * fix bug in shell_integration * add some comments * enable cd to work with directory abbreviations (#5452) * enable cd to work with abbreviations * add abbreviation example * fix tests * make it configurable * make cd recornize symblic link (#5454) * implement seq char command to generate single character sequence (#5453) * add tmp code * add seq char command * Add split number flag in `split row` (#5434) Signed-off-by: Yuheng Su * Add two more overlay tests * Add ModuleId to OverlayFrame * Fix env conversion accidentally activating overlay It activated overlay from permanent state prematurely which would cause `overlay add` to misbehave. * Remove unused parameter; Add overlay list test * Remove added traces * Add overlay commands examples * Modify TODO * Fix $nu.scope iteration * Disallow removing default overlay * Refactor some parser errors * Remove last overlay if no argument * Diversify overlay examples * Make it possible to update overlay's module In case the origin module updates, the overlay add loads the new module, makes it overlay's origin and applies the changes. Before, it was impossible to update the overlay if the module changed. Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> Co-authored-by: WindSoilder Co-authored-by: Yuheng Su --- crates/nu-cli/src/commands.rs | 4 +- .../src/completions/command_completions.rs | 4 +- .../src/completions/directory_completions.rs | 2 +- .../src/completions/dotnu_completions.rs | 4 +- .../src/completions/file_completions.rs | 2 +- .../src/completions/variable_completions.rs | 43 +- crates/nu-cli/src/config_files.rs | 4 +- crates/nu-cli/src/eval_file.rs | 2 +- crates/nu-cli/src/reedline_config.rs | 4 +- crates/nu-cli/src/repl.rs | 2 +- crates/nu-cli/src/util.rs | 18 +- crates/nu-command/src/core_commands/hide.rs | 16 +- crates/nu-command/src/core_commands/mod.rs | 2 + .../src/core_commands/overlay/add.rs | 139 ++ .../src/core_commands/overlay/command.rs | 58 + .../src/core_commands/overlay/list.rs | 85 ++ .../src/core_commands/overlay/mod.rs | 9 + .../src/core_commands/overlay/remove.rs | 82 ++ crates/nu-command/src/core_commands/tutor.rs | 2 +- crates/nu-command/src/core_commands/use_.rs | 18 +- crates/nu-command/src/dataframe/eager/list.rs | 32 +- crates/nu-command/src/default_context.rs | 4 + .../src/experimental/view_source.rs | 10 +- crates/nu-command/src/filesystem/open.rs | 2 +- crates/nu-command/src/filesystem/save.rs | 2 +- crates/nu-command/src/network/fetch.rs | 2 +- crates/nu-command/src/network/post.rs | 2 +- crates/nu-command/src/system/which_.rs | 4 +- crates/nu-engine/src/documentation.rs | 2 +- crates/nu-engine/src/env.rs | 26 +- crates/nu-engine/src/eval.rs | 58 +- crates/nu-parser/src/errors.rs | 42 + crates/nu-parser/src/known_external.rs | 2 +- crates/nu-parser/src/parse_keywords.rs | 544 ++++++- crates/nu-parser/src/parser.rs | 41 +- crates/nu-protocol/src/ast/import_pattern.rs | 4 +- crates/nu-protocol/src/engine/engine_state.rs | 1253 +++++++++++++---- crates/nu-protocol/src/engine/stack.rs | 327 +++-- crates/nu-protocol/src/id.rs | 1 + crates/nu-protocol/src/lib.rs | 4 +- .../nu-protocol/src/{overlay.rs => module.rs} | 12 +- crates/nu-protocol/src/pipeline_data.rs | 2 +- crates/nu-protocol/src/shell_error.rs | 53 +- tests/main.rs | 1 + tests/overlays/mod.rs | 310 ++++ tests/overlays/samples/spam.nu | 5 + 46 files changed, 2638 insertions(+), 607 deletions(-) create mode 100644 crates/nu-command/src/core_commands/overlay/add.rs create mode 100644 crates/nu-command/src/core_commands/overlay/command.rs create mode 100644 crates/nu-command/src/core_commands/overlay/list.rs create mode 100644 crates/nu-command/src/core_commands/overlay/mod.rs create mode 100644 crates/nu-command/src/core_commands/overlay/remove.rs rename crates/nu-protocol/src/{overlay.rs => module.rs} (97%) create mode 100644 tests/overlays/mod.rs create mode 100644 tests/overlays/samples/spam.nu 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" }