From 8833d3f89f114f3fdfaa4022e35153e9d167921c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 5 Jul 2024 07:16:03 -0500 Subject: [PATCH 01/63] change duration mod duration to duration instead of float (#13300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description closes #13298 so that duration mod duration / duration = duration ### Before ```nushell (92sec mod 1min) / 1sec Error: nu::parser::unsupported_operation × division is not supported between float and duration. ╭─[entry #5:1:1] 1 │ (92sec mod 1min) / 1sec · ────────┬─────── ┬ ──┬─ · │ │ ╰── duration · │ ╰── doesn't support these values · ╰── float ╰──── ``` ### After ```nushell ❯ (92sec mod 1min) / 1sec 32 ``` # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-parser/src/type_check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 1c76afb621..872bb6a94c 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -292,7 +292,7 @@ pub fn math_result_type( (Type::Filesize, Type::Filesize) => (Type::Float, None), (Type::Filesize, Type::Int) => (Type::Filesize, None), (Type::Filesize, Type::Float) => (Type::Filesize, None), - (Type::Duration, Type::Duration) => (Type::Float, None), + (Type::Duration, Type::Duration) => (Type::Duration, None), (Type::Duration, Type::Int) => (Type::Duration, None), (Type::Duration, Type::Float) => (Type::Duration, None), From afaa019faeb67f7f501b51682dd5551385d68c72 Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:46:21 +0530 Subject: [PATCH 02/63] feat: replace `unfold` with `from_fn` for the generate command (#13299) This PR should close #13247 # Description - The deprecated `itertools::unfold` function is replaced with `std::iter::from_fn` for the generate command. - The mutable iterator state is no longer passed as an argument to `from_fn` but it gets captured with the closure's `move`. # User-Facing Changes No user facing changes # Tests + Formatting Tests for the generate command are passing locally. # After Submitting --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-command/src/generators/generate.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index aeb375aab0..44bca993d0 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -1,4 +1,3 @@ -use itertools::unfold; use nu_engine::{command_prelude::*, ClosureEval}; use nu_protocol::engine::Closure; @@ -81,7 +80,8 @@ used as the next argument to the closure, otherwise generation stops. // A type of Option is used to represent state. Invocation // will stop on None. Using Option allows functions to output // one final value before stopping. - let iter = unfold(Some(initial), move |state| { + let mut state = Some(initial); + let iter = std::iter::from_fn(move || { let arg = state.take()?; let (output, next_input) = match closure.run_with_value(arg) { @@ -160,7 +160,7 @@ used as the next argument to the closure, otherwise generation stops. // We use `state` to control when to stop, not `output`. By wrapping // it in a `Some`, we allow the generator to output `None` as a valid output // value. - *state = next_input; + state = next_input; Some(output) }); From b27cd70fd1875d301c8b577536c6fee46ad02e5c Mon Sep 17 00:00:00 2001 From: Andy Gayton Date: Fri, 5 Jul 2024 08:16:50 -0400 Subject: [PATCH 03/63] remove the deprecated `register` command (#13297) # Description This PR removes the `register` command which has been [deprecated](https://www.nushell.sh/blog/2024-04-30-nushell_0_93_0.html#register-toc) in favor of [`plugin add`](https://www.nushell.sh/blog/2024-04-30-nushell_0_93_0.html#redesigned-plugin-management-commands-toc) # User-Facing Changes `register` is no longer available --- crates/nu-cmd-plugin/src/commands/mod.rs | 2 - .../nu-cmd-plugin/src/commands/plugin/stop.rs | 2 +- crates/nu-cmd-plugin/src/commands/register.rs | 80 ------ crates/nu-cmd-plugin/src/default_context.rs | 1 - crates/nu-parser/src/lib.rs | 3 - crates/nu-parser/src/parse_keywords.rs | 239 ------------------ crates/nu-parser/src/parser.rs | 11 - crates/nu-plugin-engine/src/context.rs | 4 +- crates/nu-plugin/src/plugin/command.rs | 2 +- crates/nu-plugin/src/plugin/mod.rs | 2 +- crates/nu-protocol/src/config/mod.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 4 +- crates/nu-protocol/src/plugin/registered.rs | 2 +- crates/nu_plugin_example/README.md | 6 +- crates/nu_plugin_formats/README.md | 4 +- crates/nu_plugin_gstat/README.md | 4 +- crates/nu_plugin_inc/README.md | 4 +- .../nu_plugin_python_example.py | 4 +- crates/nu_plugin_query/README.md | 4 +- docker/Dockerfile | 2 +- scripts/README.md | 1 - scripts/register-plugins.nu | 55 ---- tests/repl/test_parser.rs | 33 --- 23 files changed, 24 insertions(+), 447 deletions(-) delete mode 100644 crates/nu-cmd-plugin/src/commands/register.rs delete mode 100644 scripts/register-plugins.nu diff --git a/crates/nu-cmd-plugin/src/commands/mod.rs b/crates/nu-cmd-plugin/src/commands/mod.rs index 3e927747f1..17ff32faa1 100644 --- a/crates/nu-cmd-plugin/src/commands/mod.rs +++ b/crates/nu-cmd-plugin/src/commands/mod.rs @@ -1,5 +1,3 @@ mod plugin; -mod register; pub use plugin::*; -pub use register::Register; diff --git a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs index d74ee59a3f..f90eac8411 100644 --- a/crates/nu-cmd-plugin/src/commands/plugin/stop.rs +++ b/crates/nu-cmd-plugin/src/commands/plugin/stop.rs @@ -72,7 +72,7 @@ impl Command for PluginStop { error: format!("Failed to stop the `{}` plugin", name.item), msg: "couldn't find a plugin with this name".into(), span: Some(name.span), - help: Some("you may need to `register` the plugin first".into()), + help: Some("you may need to `plugin add` the plugin first".into()), inner: vec![], }) } diff --git a/crates/nu-cmd-plugin/src/commands/register.rs b/crates/nu-cmd-plugin/src/commands/register.rs deleted file mode 100644 index 2c10456db7..0000000000 --- a/crates/nu-cmd-plugin/src/commands/register.rs +++ /dev/null @@ -1,80 +0,0 @@ -use nu_engine::command_prelude::*; -use nu_protocol::engine::CommandType; - -#[derive(Clone)] -pub struct Register; - -impl Command for Register { - fn name(&self) -> &str { - "register" - } - - fn usage(&self) -> &str { - "Register a plugin." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("register") - .input_output_types(vec![(Type::Nothing, Type::Nothing)]) - .required( - "plugin", - SyntaxShape::Filepath, - "Path of executable for plugin.", - ) - .optional( - "signature", - SyntaxShape::Any, - "Block with signature description as json object.", - ) - .named( - "shell", - SyntaxShape::Filepath, - "path of shell used to run plugin (cmd, sh, python, etc)", - Some('s'), - ) - .category(Category::Plugin) - } - - fn extra_usage(&self) -> &str { - r#" -Deprecated in favor of `plugin add` and `plugin use`. - -This command is a parser keyword. For details, check: - https://www.nushell.sh/book/thinking_in_nu.html -"# - .trim() - } - - fn search_terms(&self) -> Vec<&str> { - vec!["add"] - } - - fn command_type(&self) -> CommandType { - CommandType::Keyword - } - - fn run( - &self, - _engine_state: &EngineState, - _stack: &mut Stack, - _call: &Call, - _input: PipelineData, - ) -> Result { - Ok(PipelineData::empty()) - } - - fn examples(&self) -> Vec { - vec![ - Example { - description: "Register `nu_plugin_query` plugin from ~/.cargo/bin/ dir", - example: r#"register ~/.cargo/bin/nu_plugin_query"#, - result: None, - }, - Example { - description: "Register `nu_plugin_query` plugin from `nu -c` (writes/updates $nu.plugin-path)", - example: r#"let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version'"#, - result: None, - }, - ] - } -} diff --git a/crates/nu-cmd-plugin/src/default_context.rs b/crates/nu-cmd-plugin/src/default_context.rs index 601dd52cfc..1ef6a905dd 100644 --- a/crates/nu-cmd-plugin/src/default_context.rs +++ b/crates/nu-cmd-plugin/src/default_context.rs @@ -18,7 +18,6 @@ pub fn add_plugin_command_context(mut engine_state: EngineState) -> EngineState PluginRm, PluginStop, PluginUse, - Register, ); working_set.render() diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 89cba8d243..3c97012e8a 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -24,6 +24,3 @@ pub use parser::{ is_math_expression_like, parse, parse_block, parse_expression, parse_external_call, parse_unit_value, trim_quotes, trim_quotes_str, unescape_unquote_string, DURATION_UNIT_GROUPS, }; - -#[cfg(feature = "plugin")] -pub use parse_keywords::parse_register; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 70d217c564..4ac1d20699 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -82,7 +82,6 @@ pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[ b"source-env", b"source", b"where", - b"register", b"plugin use", ]; @@ -3627,244 +3626,6 @@ pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand } } -/// `register` is deprecated and will be removed in 0.94. Use `plugin add` and `plugin use` instead. -#[cfg(feature = "plugin")] -pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline { - use nu_plugin_engine::PluginDeclaration; - use nu_protocol::{ - engine::Stack, ErrSpan, ParseWarning, PluginIdentity, PluginRegistryItem, PluginSignature, - RegisteredPlugin, - }; - - let spans = &lite_command.parts; - - #[allow(deprecated)] - let cwd = working_set.get_cwd(); - - // Checking that the function is used with the correct name - // Maybe this is not necessary but it is a sanity check - if working_set.get_span_contents(spans[0]) != b"register" { - working_set.error(ParseError::UnknownState( - "internal error: Wrong call name for 'register' function".into(), - Span::concat(spans), - )); - return garbage_pipeline(working_set, spans); - } - if let Some(redirection) = lite_command.redirection.as_ref() { - working_set.error(redirecting_builtin_error("register", redirection)); - return garbage_pipeline(working_set, spans); - } - - // Parsing the spans and checking that they match the register signature - // Using a parsed call makes more sense than checking for how many spans are in the call - // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(b"register") { - None => { - working_set.error(ParseError::UnknownState( - "internal error: Register declaration not found".into(), - Span::concat(spans), - )); - return garbage_pipeline(working_set, spans); - } - Some(decl_id) => { - let ParsedInternalCall { call, output } = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); - let decl = working_set.get_decl(decl_id); - - let call_span = Span::concat(spans); - - let starting_error_count = working_set.parse_errors.len(); - check_call(working_set, call_span, &decl.signature(), &call); - - let Ok(is_help) = has_flag_const(working_set, &call, "help") else { - return garbage_pipeline(working_set, spans); - }; - - if starting_error_count != working_set.parse_errors.len() || is_help { - return Pipeline::from_vec(vec![Expression::new( - working_set, - Expr::Call(call), - call_span, - output, - )]); - } - - (call, call_span) - } - }; - - // Now that the call is parsed, add the deprecation warning - working_set - .parse_warnings - .push(ParseWarning::DeprecatedWarning { - old_command: "register".into(), - new_suggestion: "use `plugin add` and `plugin use`".into(), - span: call.head, - url: "https://www.nushell.sh/book/plugins.html".into(), - }); - - // Extracting the required arguments from the call and keeping them together in a tuple - let arguments = call - .positional_nth(0) - .map(|expr| { - let val = - eval_constant(working_set, expr).map_err(|err| err.wrap(working_set, call.head))?; - let filename = val - .coerce_into_string() - .map_err(|err| err.wrap(working_set, call.head))?; - - let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(PLUGIN_DIRS_VAR)) - else { - return Err(ParseError::RegisteredFileNotFound(filename, expr.span)); - }; - - if path.exists() && path.is_file() { - Ok((path, expr.span)) - } else { - Err(ParseError::RegisteredFileNotFound(filename, expr.span)) - } - }) - .expect("required positional has being checked"); - - // Signature is an optional value from the call and will be used to decide if - // the plugin is called to get the signatures or to use the given signature - let signature = call.positional_nth(1).map(|expr| { - let signature = working_set.get_span_contents(expr.span); - serde_json::from_slice::(signature).map_err(|e| { - ParseError::LabeledError( - "Signature deserialization error".into(), - format!("unable to deserialize signature: {e}"), - spans[0], - ) - }) - }); - - // Shell is another optional value used as base to call shell to plugins - let shell = call.get_flag_expr("shell").map(|expr| { - let shell_expr = working_set.get_span_contents(expr.span); - - String::from_utf8(shell_expr.to_vec()) - .map_err(|_| ParseError::NonUtf8(expr.span)) - .and_then(|name| { - canonicalize_with(&name, cwd) - .map_err(|_| ParseError::RegisteredFileNotFound(name, expr.span)) - }) - .and_then(|path| { - if path.exists() & path.is_file() { - Ok(path) - } else { - Err(ParseError::RegisteredFileNotFound( - format!("{path:?}"), - expr.span, - )) - } - }) - }); - - let shell = match shell { - None => None, - Some(path) => match path { - Ok(path) => Some(path), - Err(err) => { - working_set.error(err); - return Pipeline::from_vec(vec![Expression::new( - working_set, - Expr::Call(call), - call_span, - Type::Any, - )]); - } - }, - }; - - // We need the current environment variables for `python` based plugins - // Or we'll likely have a problem when a plugin is implemented in a virtual Python environment. - let get_envs = || { - let stack = Stack::new().capture(); - nu_engine::env::env_to_strings(working_set.permanent_state, &stack) - }; - - let error = arguments.and_then(|(path, path_span)| { - let path = path.path_buf(); - - // Create the plugin identity. This validates that the plugin name starts with `nu_plugin_` - let identity = PluginIdentity::new(path, shell).err_span(path_span)?; - - let plugin = nu_plugin_engine::add_plugin_to_working_set(working_set, &identity) - .map_err(|err| err.wrap(working_set, call.head))?; - - let signatures = signature.map_or_else( - || { - // It's important that the plugin is restarted if we're going to get signatures - // - // The user would expect that `register` would always run the binary to get new - // signatures, in case it was replaced with an updated binary - plugin.reset().map_err(|err| { - ParseError::LabeledError( - "Failed to restart plugin to get new signatures".into(), - err.to_string(), - spans[0], - ) - })?; - - let metadata_and_signatures = plugin - .clone() - .get(get_envs) - .and_then(|p| { - let meta = p.get_metadata()?; - let sigs = p.get_signature()?; - Ok((meta, sigs)) - }) - .map_err(|err| { - log::warn!("Error getting metadata and signatures: {err:?}"); - ParseError::LabeledError( - "Error getting metadata and signatures".into(), - err.to_string(), - spans[0], - ) - }); - - match metadata_and_signatures { - Ok((meta, sigs)) => { - // Set the metadata on the plugin - plugin.set_metadata(Some(meta.clone())); - // Add the loaded plugin to the delta - working_set.update_plugin_registry(PluginRegistryItem::new( - &identity, - meta, - sigs.clone(), - )); - Ok(sigs) - } - Err(err) => Err(err), - } - }, - |sig| sig.map(|sig| vec![sig]), - )?; - - for signature in signatures { - // create plugin command declaration (need struct impl Command) - // store declaration in working set - let plugin_decl = PluginDeclaration::new(plugin.clone(), signature); - - working_set.add_decl(Box::new(plugin_decl)); - } - - Ok(()) - }); - - if let Err(err) = error { - working_set.error(err); - } - - Pipeline::from_vec(vec![Expression::new( - working_set, - Expr::Call(call), - call_span, - Type::Nothing, - )]) -} - #[cfg(feature = "plugin")] pub fn parse_plugin_use(working_set: &mut StateWorkingSet, call: Box) -> Pipeline { use nu_protocol::{FromValue, PluginRegistryFile}; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index c3d18b55ac..b274d77404 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -5275,15 +5275,6 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex } b"where" => parse_where_expr(working_set, &spans[pos..]), #[cfg(feature = "plugin")] - b"register" => { - working_set.error(ParseError::BuiltinCommandInPipeline( - "register".into(), - spans[0], - )); - - parse_call(working_set, &spans[pos..], spans[0]) - } - #[cfg(feature = "plugin")] b"plugin" => { if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"use" { // only 'plugin use' is banned @@ -5419,8 +5410,6 @@ pub fn parse_builtin_commands( b"export" => parse_export_in_block(working_set, lite_command), b"hide" => parse_hide(working_set, lite_command), b"where" => parse_where(working_set, lite_command), - #[cfg(feature = "plugin")] - b"register" => parse_register(working_set, lite_command), // Only "plugin use" is a keyword #[cfg(feature = "plugin")] b"plugin" diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index d5be6ad4b6..c87e64aa48 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -95,8 +95,8 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { fn get_plugin_config(&self) -> Result, ShellError> { // Fetch the configuration for a plugin // - // The `plugin` must match the registered name of a plugin. For - // `register nu_plugin_example` the plugin config lookup uses `"example"` + // The `plugin` must match the registered name of a plugin. For `plugin add + // nu_plugin_example` the plugin config lookup uses `"example"` Ok(self .get_config()? .plugins diff --git a/crates/nu-plugin/src/plugin/command.rs b/crates/nu-plugin/src/plugin/command.rs index 7ccedfd4e5..d33fc445e2 100644 --- a/crates/nu-plugin/src/plugin/command.rs +++ b/crates/nu-plugin/src/plugin/command.rs @@ -340,7 +340,7 @@ where /// Build a [`PluginSignature`] from the signature-related methods on [`PluginCommand`]. /// -/// This is sent to the engine on `register`. +/// This is sent to the engine on `plugin add`. /// /// This is not a public API. #[doc(hidden)] diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 997381f97b..2bf7f9fc07 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -38,7 +38,7 @@ pub(crate) const OUTPUT_BUFFER_SIZE: usize = 16384; /// The API for a Nushell plugin /// /// A plugin defines multiple commands, which are added to the engine when the user calls -/// `register`. +/// `plugin add`. /// /// The plugin must be able to be safely shared between threads, so that multiple invocations can /// be run in parallel. If interior mutability is desired, consider synchronization primitives such diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index 5aa611937b..e804e3f024 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -104,7 +104,7 @@ pub struct Config { /// Configuration for plugins. /// /// Users can provide configuration for a plugin through this entry. The entry name must - /// match the registered plugin name so `register nu_plugin_example` will be able to place + /// match the registered plugin name so `plugin add nu_plugin_example` will be able to place /// its configuration under a `nu_plugin_example` column. pub plugins: HashMap, /// Configuration for plugin garbage collection. diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 3960f6d9d0..c436621627 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -741,8 +741,8 @@ impl EngineState { /// Fetch the configuration for a plugin /// - /// The `plugin` must match the registered name of a plugin. For `register nu_plugin_example` - /// the plugin name to use will be `"example"` + /// The `plugin` must match the registered name of a plugin. For `plugin add + /// nu_plugin_example` the plugin name to use will be `"example"` pub fn get_plugin_config(&self, plugin: &str) -> Option<&Value> { self.config.plugins.get(plugin) } diff --git a/crates/nu-protocol/src/plugin/registered.rs b/crates/nu-protocol/src/plugin/registered.rs index abd75b4dc6..56d7f4079a 100644 --- a/crates/nu-protocol/src/plugin/registered.rs +++ b/crates/nu-protocol/src/plugin/registered.rs @@ -26,7 +26,7 @@ pub trait RegisteredPlugin: Send + Sync { fn stop(&self) -> Result<(), ShellError>; /// Stop the plugin and reset any state so that we don't make any assumptions about the plugin - /// next time it launches. This is used on `register`. + /// next time it launches. This is used on `plugin add`. fn reset(&self) -> Result<(), ShellError>; /// Cast the pointer to an [`Any`] so that its concrete type can be retrieved. diff --git a/crates/nu_plugin_example/README.md b/crates/nu_plugin_example/README.md index d4bc6dbe29..98283e9958 100644 --- a/crates/nu_plugin_example/README.md +++ b/crates/nu_plugin_example/README.md @@ -7,10 +7,12 @@ in order to create a binary that can be registered into nushell declaration list This subcommand demonstrates sending configuration from the nushell `$env.config` to a plugin. -To register from after building `nushell` run: +To make use of the plugin after building `nushell` run: ```nushell -register target/debug/nu_plugin_example +plugin add target/debug/nu_plugin_example +# or then either restart your current nushell session or run: +plugin use target/debug/nu_plugin_example ``` The configuration for the plugin lives in `$env.config.plugins.example`: diff --git a/crates/nu_plugin_formats/README.md b/crates/nu_plugin_formats/README.md index 2b5fc2d829..a7ee046f99 100644 --- a/crates/nu_plugin_formats/README.md +++ b/crates/nu_plugin_formats/README.md @@ -12,7 +12,7 @@ A nushell plugin to convert data to nushell tables. # Usage 1. compile the binary: `cargo build` -2. register plugin(assume it's compiled in ./target/debug/): +2. plugin add plugin(assume it's compiled in ./target/debug/): ``` -register ./target/debug/nu_plugin_formats +plugin add ./target/debug/nu_plugin_formats ``` diff --git a/crates/nu_plugin_gstat/README.md b/crates/nu_plugin_gstat/README.md index 88db33cbf8..7b9ab158ec 100644 --- a/crates/nu_plugin_gstat/README.md +++ b/crates/nu_plugin_gstat/README.md @@ -8,6 +8,6 @@ To install: > cargo install --path . ``` -To register (from inside Nushell): +To add the plugin (from inside Nushell): ``` -> register +> plugin add diff --git a/crates/nu_plugin_inc/README.md b/crates/nu_plugin_inc/README.md index e989aba0b9..d97443a189 100644 --- a/crates/nu_plugin_inc/README.md +++ b/crates/nu_plugin_inc/README.md @@ -8,6 +8,6 @@ To install: cargo install --path . ``` -To register (from inside Nushell): +To add the plugin (from inside Nushell): ``` -register +> plugin add diff --git a/crates/nu_plugin_python/nu_plugin_python_example.py b/crates/nu_plugin_python/nu_plugin_python_example.py index 34e54fea71..993e619366 100755 --- a/crates/nu_plugin_python/nu_plugin_python_example.py +++ b/crates/nu_plugin_python/nu_plugin_python_example.py @@ -7,7 +7,7 @@ # decode and encode information that is read and written to stdin and stdout # # To register the plugin use: -# register +# plugin add # # Be careful with the spans. Miette will crash if a span is outside the # size of the contents vector. We strongly suggest using the span found in the @@ -258,4 +258,4 @@ if __name__ == "__main__": if len(sys.argv) == 2 and sys.argv[1] == "--stdio": plugin() else: - print("Run me from inside nushell!") \ No newline at end of file + print("Run me from inside nushell!") diff --git a/crates/nu_plugin_query/README.md b/crates/nu_plugin_query/README.md index a3abd18082..621a004806 100644 --- a/crates/nu_plugin_query/README.md +++ b/crates/nu_plugin_query/README.md @@ -8,6 +8,6 @@ To install: > cargo install --path . ``` -To register (from inside Nushell): +To add the plugin (from inside Nushell): ``` -> register +> plugin add diff --git a/docker/Dockerfile b/docker/Dockerfile index 209b17d419..64c37f6230 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,7 +24,7 @@ RUN echo '/usr/bin/nu' >> /etc/shells \ && chmod +x /usr/bin/nu \ && chown -R nushell:nushell /home/nushell/.config/nushell \ && ls /usr/bin/nu_plugin* \ - | xargs -I{} su -c 'register {}' nushell \ + | xargs -I{} su -c 'plugin add {}' nushell \ && rm -rf /tmp/* USER nushell diff --git a/scripts/README.md b/scripts/README.md index 366ff76375..37388e74b5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -23,5 +23,4 @@ | `coverage-local.sh` | x | x | x | | `install-all.ps1` | ? | x | ? | | `install-all.sh` | x | x | x | -| `register-plugins.nu` | x | x | x | | `uninstall-all.sh` | x | x | x | diff --git a/scripts/register-plugins.nu b/scripts/register-plugins.nu deleted file mode 100644 index 0447ffb0c1..0000000000 --- a/scripts/register-plugins.nu +++ /dev/null @@ -1,55 +0,0 @@ -use std log warning - -warning "./scripts/register-plugin.nu will be deprecated, please use the `toolkit plugin register` command instead" - -# are we on windows or not? -def windows? [] { - $nu.os-info.name == windows -} - -# filter out files that end in .d -def keep-plugin-executables [] { - if (windows?) { where name ends-with '.exe' } else { where name !~ '\.d' } -} - -# get list of all plugin files from their installed directory -let plugins = (ls ((which nu).path.0 | path dirname) | where name =~ nu_plugin | keep-plugin-executables) -for plugin in $plugins { - print -n $"registering ($plugin.name), " - nu -c $"register '($plugin.name)'" - print "success!" -} - -# print helpful message -print "\nplugins registered, please restart nushell" - -# Plugin Location -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_custom_values -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_example -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_gstat -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_inc -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_python -# https://github.com/nushell/nushell/tree/main/crates/nu_plugin_query -# https://github.com/fdncred/nu_plugin_from_parquet -# https://github.com/fdncred/nu_plugin_from_regex -# https://github.com/fdncred/nu_plugin_pnet -# https://github.com/JosephTLyons/nu_plugin_periodic_table -# https://github.com/Euphrasiologist/nu_plugin_bio -# https://github.com/realcundo/nu_plugin_dcm -# https://github.com/enerdgumen/nu_plugin_dotenv -# https://github.com/bluk/nu_plugin_from_bencode - -# Older plugins -# https://github.com/notryanb/nu_plugin_id3 -# https://github.com/notryanb/nu_plugin_weather -# https://github.com/tiffany352/nu-plugins/tree/main/from_nbt -# https://github.com/tiffany352/nu-plugins/tree/main/file_exists -# https://github.com/potan/nu_plugin_wifiscan -# https://github.com/autophagy/nu_plugin_from_dhall -# https://github.com/yanganto/nu_plugin_s3 -# https://github.com/lukasreuter/nu_plugin_unity -# https://github.com/filaretov/nu_plugin_path_temp -# https://github.com/cdecompilador/nu_plugin_bg -# https://github.com/aJuvan/nu_plugin_kubectl -# https://github.com/hedonihilist/nu_plugin_df - diff --git a/tests/repl/test_parser.rs b/tests/repl/test_parser.rs index 0efb1d087a..a7a25e1c67 100644 --- a/tests/repl/test_parser.rs +++ b/tests/repl/test_parser.rs @@ -572,39 +572,6 @@ fn unbalanced_parens2() -> TestResult { fail_test(r#"("("))"#, "unbalanced ( and )") } -#[test] -fn register_with_string_literal() -> TestResult { - fail_test(r#"register 'nu-plugin-math'"#, "File not found") -} - -#[test] -fn register_with_string_constant() -> TestResult { - let input = "\ -const file = 'nu-plugin-math' -register $file -"; - // should not fail with `not a constant` - fail_test(input, "File not found") -} - -#[test] -fn register_with_string_variable() -> TestResult { - let input = "\ -let file = 'nu-plugin-math' -register $file -"; - fail_test(input, "Value is not a parse-time constant") -} - -#[test] -fn register_with_non_string_constant() -> TestResult { - let input = "\ -const file = 6 -register $file -"; - fail_test(input, "expected string, found int") -} - #[test] fn plugin_use_with_string_literal() -> TestResult { fail_test( From 1514b9fbefdea8c6a4d001853b1876f2875fac8d Mon Sep 17 00:00:00 2001 From: Wind Date: Fri, 5 Jul 2024 20:17:07 +0800 Subject: [PATCH 04/63] don't show result in error make examples (#13296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes: #13189 The issue is caused `error make` returns a `Value::Errror`, and when nushell pass it to `table -e` in `std help`, it directly stop and render the error message. To solve it, I think it's safe to make these examples return None directly, it doesn't change the reult of `help error make`. # User-Facing Changes ## Before ```nushell ~> help "error make" Error: nu::shell::eval_block_with_input × Eval block failed with pipeline input ╭─[NU_STDLIB_VIRTUAL_DIR/std/help.nu:692:21] 691 │ ] { 692 │ let commands = (scope commands | sort-by name) · ───────┬────── · ╰── source value 693 │ ╰──── Error: × my custom error message ``` ## After ```nushell Create an error. Search terms: panic, crash, throw Category: core This command: - does not create a scope. - is a built-in command. - is a subcommand. - is not part of a plugin. - is not a custom command. - is not a keyword. Usage: > error make {flags} Flags: -u, --unspanned - remove the origin label from the error -h, --help - Display the help message for this command Signatures: | error make[ ] -> Parameters: error_struct: The error to create. Examples: Create a simple custom error > error make {msg: "my custom error message"} Create a more complex custom error > error make { msg: "my custom error message" label: { text: "my custom label text" # not mandatory unless $.label exists # optional span: { # if $.label.span exists, both start and end must be present start: 123 end: 456 } } help: "A help string, suggesting a fix to the user" # optional } Create a custom error for a custom command that shows the span of the argument > def foo [x] { error make { msg: "this is fishy" label: { text: "fish right here" span: (metadata $x).span } } } ``` # Tests + Formatting Added 1 test --- .../src/core_commands/error_make.rs | 22 ++----------------- crates/nu-std/tests/test_help.nu | 5 +++++ 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/crates/nu-cmd-lang/src/core_commands/error_make.rs b/crates/nu-cmd-lang/src/core_commands/error_make.rs index 987e083cbc..07efcd7885 100644 --- a/crates/nu-cmd-lang/src/core_commands/error_make.rs +++ b/crates/nu-cmd-lang/src/core_commands/error_make.rs @@ -56,16 +56,7 @@ impl Command for ErrorMake { Example { description: "Create a simple custom error", example: r#"error make {msg: "my custom error message"}"#, - result: Some(Value::error( - ShellError::GenericError { - error: "my custom error message".into(), - msg: "".into(), - span: None, - help: None, - inner: vec![], - }, - Span::unknown(), - )), + result: None, }, Example { description: "Create a more complex custom error", @@ -82,16 +73,7 @@ impl Command for ErrorMake { } help: "A help string, suggesting a fix to the user" # optional }"#, - result: Some(Value::error( - ShellError::GenericError { - error: "my custom error message".into(), - msg: "my custom label text".into(), - span: Some(Span::new(123, 456)), - help: Some("A help string, suggesting a fix to the user".into()), - inner: vec![], - }, - Span::unknown(), - )), + result: None, }, Example { description: diff --git a/crates/nu-std/tests/test_help.nu b/crates/nu-std/tests/test_help.nu index 1608950886..b82e5072ef 100644 --- a/crates/nu-std/tests/test_help.nu +++ b/crates/nu-std/tests/test_help.nu @@ -7,3 +7,8 @@ def show_help_on_commands [] { assert ("item not found" not-in $help_result) } +#[test] +def show_help_on_error_make [] { + let help_result = (help error make) + assert ("Error: nu::shell::eval_block_with_input" not-in $help_result) +} From 8707d14f952b51a99449f6ce6946f3ad400e4a04 Mon Sep 17 00:00:00 2001 From: Reilly Wood <26268125+rgwood@users.noreply.github.com> Date: Fri, 5 Jul 2024 05:18:25 -0700 Subject: [PATCH 05/63] Limit drilling down inside `explore` (#13293) This PR fixes an issue with `explore` where you can "drill down" into the same value forever. For example: 1. Run `ls | explore` 2. Press Enter to enter cursor mode 3. Press Enter again to open the selected string in a new layer 4. Press Enter again to open that string in a new layer 5. Press Enter again to open that string in a new layer 6. Repeat and eventually you have a bajillion layers open with the same string IMO it only makes sense to "drill down" into lists and records. In a separate commit I also did a little refactoring, cleaning up naming and comments. --- crates/nu-explore/src/commands/nu.rs | 2 +- crates/nu-explore/src/commands/table.rs | 4 +- crates/nu-explore/src/lib.rs | 2 +- crates/nu-explore/src/views/record/mod.rs | 120 +++++++++++----------- crates/nu-explore/src/views/try.rs | 6 +- 5 files changed, 68 insertions(+), 66 deletions(-) diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index f8aaf44c24..a0d479fd92 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -65,7 +65,7 @@ impl ViewCommand for NuCmd { let mut view = RecordView::new(columns, values); if is_record { - view.set_orientation_current(Orientation::Left); + view.set_top_layer_orientation(Orientation::Left); } Ok(NuView::Records(view)) diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index e5a3ae5599..1079cf6cea 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -58,11 +58,11 @@ impl ViewCommand for TableCmd { let mut view = RecordView::new(columns, data); if is_record { - view.set_orientation_current(Orientation::Left); + view.set_top_layer_orientation(Orientation::Left); } if let Some(o) = self.settings.orientation { - view.set_orientation_current(o); + view.set_top_layer_orientation(o); } if self.settings.turn_on_cursor_mode { diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 562602c394..d347321354 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -75,7 +75,7 @@ fn create_record_view( ) -> Option { let mut view = RecordView::new(columns, data); if is_record { - view.set_orientation_current(Orientation::Left); + view.set_top_layer_orientation(Orientation::Left); } if config.tail { diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index 87b0952a68..bd3c7e8e0a 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -20,7 +20,7 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use nu_color_config::StyleComputer; use nu_protocol::{ engine::{EngineState, Stack}, - Config, Record, Span, Value, + Config, Record, Value, }; use ratatui::{layout::Rect, widgets::Block}; use std::collections::HashMap; @@ -54,27 +54,26 @@ impl RecordView { } pub fn transpose(&mut self) { - let layer = self.get_layer_last_mut(); + let layer = self.get_top_layer_mut(); transpose_table(layer); layer.reset_cursor(); } - // todo: rename to get_layer - pub fn get_layer_last(&self) -> &RecordLayer { + pub fn get_top_layer(&self) -> &RecordLayer { self.layer_stack .last() .expect("we guarantee that 1 entry is always in a list") } - pub fn get_layer_last_mut(&mut self) -> &mut RecordLayer { + pub fn get_top_layer_mut(&mut self) -> &mut RecordLayer { self.layer_stack .last_mut() .expect("we guarantee that 1 entry is always in a list") } - pub fn get_orientation_current(&mut self) -> Orientation { - self.get_layer_last().orientation + pub fn get_top_layer_orientation(&mut self) -> Orientation { + self.get_top_layer().orientation } pub fn set_orientation(&mut self, orientation: Orientation) { @@ -90,27 +89,27 @@ impl RecordView { } } - pub fn set_orientation_current(&mut self, orientation: Orientation) { - let layer = self.get_layer_last_mut(); + pub fn set_top_layer_orientation(&mut self, orientation: Orientation) { + let layer = self.get_top_layer_mut(); layer.orientation = orientation; layer.reset_cursor(); } /// Get the current position of the cursor in the table as a whole pub fn get_cursor_position(&self) -> Position { - let layer = self.get_layer_last(); + let layer = self.get_top_layer(); layer.cursor.position() } /// Get the current position of the cursor in the window being shown pub fn get_cursor_position_in_window(&self) -> Position { - let layer = self.get_layer_last(); + let layer = self.get_top_layer(); layer.cursor.window_relative_position() } /// Get the origin of the window being shown. (0,0), top left corner. pub fn get_window_origin(&self) -> Position { - let layer = self.get_layer_last(); + let layer = self.get_top_layer(); layer.cursor.window_origin() } @@ -122,22 +121,20 @@ impl RecordView { self.mode = UIMode::View; } - pub fn get_current_value(&self) -> Value { + pub fn get_current_value(&self) -> &Value { let Position { row, column } = self.get_cursor_position(); - let layer = self.get_layer_last(); + let layer = self.get_top_layer(); let (row, column) = match layer.orientation { Orientation::Top => (row, column), Orientation::Left => (column, row), }; - if row >= layer.record_values.len() || column >= layer.column_names.len() { - // actually must never happen; unless cursor works incorrectly - // if being sure about cursor it can be deleted; - return Value::nothing(Span::unknown()); - } + // These should never happen as long as the cursor is working correctly + assert!(row < layer.record_values.len(), "row out of bounds"); + assert!(column < layer.column_names.len(), "column out of bounds"); - layer.record_values[row][column].clone() + &layer.record_values[row][column] } fn create_table_widget<'a>(&'a mut self, cfg: ViewConfig<'a>) -> TableWidget<'a> { @@ -145,7 +142,7 @@ impl RecordView { let style_computer = cfg.style_computer; let Position { row, column } = self.get_window_origin(); - let layer = self.get_layer_last_mut(); + let layer = self.get_top_layer_mut(); if layer.record_text.is_none() { let mut data = convert_records_to_string(&layer.record_values, cfg.nu_config, cfg.style_computer); @@ -169,17 +166,17 @@ impl RecordView { } fn update_cursors(&mut self, rows: usize, columns: usize) { - match self.get_layer_last().orientation { + match self.get_top_layer().orientation { Orientation::Top => { let _ = self - .get_layer_last_mut() + .get_top_layer_mut() .cursor .set_window_size(rows, columns); } Orientation::Left => { let _ = self - .get_layer_last_mut() + .get_top_layer_mut() .cursor .set_window_size(rows, columns); } @@ -187,7 +184,7 @@ impl RecordView { } fn create_records_report(&self) -> Report { - let layer = self.get_layer_last(); + let layer = self.get_top_layer(); let covered_percent = report_row_position(layer.cursor); let cursor = report_cursor_position(self.mode, layer.cursor); let message = layer.name.clone().unwrap_or_default(); @@ -204,9 +201,6 @@ impl RecordView { impl View for RecordView { fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) { let mut table_layout = TableWidgetState::default(); - // TODO: creating the table widget is O(N) where N is the number of cells in the grid. - // Way too slow to do on every draw call! - // To make explore work for larger data sets, this needs to be improved. let table = self.create_table_widget(cfg); f.render_stateful_widget(table, area, &mut table_layout); @@ -221,7 +215,7 @@ impl View for RecordView { row, column, table_layout.count_rows, - self.get_layer_last().orientation, + self.get_top_layer().orientation, self.cfg.table.show_header, ); @@ -269,7 +263,7 @@ impl View for RecordView { let style_computer = StyleComputer::new(&dummy_engine_state, &dummy_stack, HashMap::new()); let data = convert_records_to_string( - &self.get_layer_last().record_values, + &self.get_top_layer().record_values, &nu_protocol::Config::default(), &style_computer, ); @@ -278,7 +272,7 @@ impl View for RecordView { } fn show_data(&mut self, pos: usize) -> bool { - let data = &self.get_layer_last().record_values; + let data = &self.get_top_layer().record_values; let mut i = 0; for (row, cells) in data.iter().enumerate() { @@ -289,7 +283,7 @@ impl View for RecordView { for (column, _) in cells.iter().enumerate() { if i == pos { - self.get_layer_last_mut() + self.get_top_layer_mut() .cursor .set_window_start_position(row, column); return true; @@ -413,7 +407,7 @@ fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option { - view.get_layer_last_mut().cursor.prev_row_page(); + view.get_top_layer_mut().cursor.prev_row_page(); return Some(Transition::Ok); } @@ -426,7 +420,7 @@ fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option { - view.get_layer_last_mut().cursor.next_row_page(); + view.get_top_layer_mut().cursor.next_row_page(); return Some(Transition::Ok); } @@ -456,32 +450,32 @@ fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option Some(Transition::Cmd(String::from("expand"))), KeyCode::Up | KeyCode::Char('k') => { - view.get_layer_last_mut().cursor.prev_row_i(); + view.get_top_layer_mut().cursor.prev_row_i(); Some(Transition::Ok) } KeyCode::Down | KeyCode::Char('j') => { - view.get_layer_last_mut().cursor.next_row_i(); + view.get_top_layer_mut().cursor.next_row_i(); Some(Transition::Ok) } KeyCode::Left | KeyCode::Char('h') => { - view.get_layer_last_mut().cursor.prev_column_i(); + view.get_top_layer_mut().cursor.prev_column_i(); Some(Transition::Ok) } KeyCode::Right | KeyCode::Char('l') => { - view.get_layer_last_mut().cursor.next_column_i(); + view.get_top_layer_mut().cursor.next_column_i(); Some(Transition::Ok) } KeyCode::Home | KeyCode::Char('g') => { - view.get_layer_last_mut().cursor.row_move_to_start(); + view.get_top_layer_mut().cursor.row_move_to_start(); Some(Transition::Ok) } KeyCode::End | KeyCode::Char('G') => { - view.get_layer_last_mut().cursor.row_move_to_end(); + view.get_top_layer_mut().cursor.row_move_to_end(); Some(Transition::Ok) } @@ -503,7 +497,7 @@ fn handle_key_event_cursor_mode( code: KeyCode::PageUp, .. } => { - view.get_layer_last_mut().cursor.prev_row_page(); + view.get_top_layer_mut().cursor.prev_row_page(); return Ok(Some(Transition::Ok)); } @@ -516,7 +510,7 @@ fn handle_key_event_cursor_mode( code: KeyCode::PageDown, .. } => { - view.get_layer_last_mut().cursor.next_row_page(); + view.get_top_layer_mut().cursor.next_row_page(); return Ok(Some(Transition::Ok)); } @@ -530,47 +524,55 @@ fn handle_key_event_cursor_mode( Ok(Some(Transition::Ok)) } KeyCode::Up | KeyCode::Char('k') => { - view.get_layer_last_mut().cursor.prev_row(); + view.get_top_layer_mut().cursor.prev_row(); Ok(Some(Transition::Ok)) } KeyCode::Down | KeyCode::Char('j') => { - view.get_layer_last_mut().cursor.next_row(); + view.get_top_layer_mut().cursor.next_row(); Ok(Some(Transition::Ok)) } KeyCode::Left | KeyCode::Char('h') => { - view.get_layer_last_mut().cursor.prev_column(); + view.get_top_layer_mut().cursor.prev_column(); Ok(Some(Transition::Ok)) } KeyCode::Right | KeyCode::Char('l') => { - view.get_layer_last_mut().cursor.next_column(); + view.get_top_layer_mut().cursor.next_column(); Ok(Some(Transition::Ok)) } KeyCode::Home | KeyCode::Char('g') => { - view.get_layer_last_mut().cursor.row_move_to_start(); + view.get_top_layer_mut().cursor.row_move_to_start(); Ok(Some(Transition::Ok)) } KeyCode::End | KeyCode::Char('G') => { - view.get_layer_last_mut().cursor.row_move_to_end(); + view.get_top_layer_mut().cursor.row_move_to_end(); Ok(Some(Transition::Ok)) } + // Try to "drill down" into the selected value KeyCode::Enter => { let value = view.get_current_value(); + + // ...but it only makes sense to drill down into a few types of values + if !matches!( + value, + Value::Record { .. } | Value::List { .. } | Value::Custom { .. } + ) { + return Ok(None); + } + let is_record = matches!(value, Value::Record { .. }); - let next_layer = create_layer(value)?; + let next_layer = create_layer(value.clone())?; push_layer(view, next_layer); if is_record { - view.set_orientation_current(Orientation::Left); - } else if view.orientation == view.get_layer_last().orientation { - view.get_layer_last_mut().orientation = view.orientation; + view.set_top_layer_orientation(Orientation::Left); } else { - view.set_orientation_current(view.orientation); + view.set_top_layer_orientation(view.orientation); } Ok(Some(Transition::Ok)) @@ -586,7 +588,7 @@ fn create_layer(value: Value) -> Result { } fn push_layer(view: &mut RecordView, mut next_layer: RecordLayer) { - let layer = view.get_layer_last(); + let layer = view.get_top_layer(); let header = layer.get_column_header(); if let Some(header) = header { @@ -609,7 +611,7 @@ fn estimate_page_size(area: Rect, show_head: bool) -> u16 { /// scroll to the end of the data fn tail_data(state: &mut RecordView, page_size: usize) { - let layer = state.get_layer_last_mut(); + let layer = state.get_top_layer_mut(); let count_rows = layer.record_values.len(); if count_rows > page_size { layer @@ -648,8 +650,8 @@ fn highlight_selected_cell(f: &mut Frame, info: ElementInfo, cfg: &ExploreConfig fn build_last_value(v: &RecordView) -> Value { if v.mode == UIMode::Cursor { - v.get_current_value() - } else if v.get_layer_last().count_rows() < 2 { + v.get_current_value().clone() + } else if v.get_top_layer().count_rows() < 2 { build_table_as_record(v) } else { build_table_as_list(v) @@ -657,7 +659,7 @@ fn build_last_value(v: &RecordView) -> Value { } fn build_table_as_list(v: &RecordView) -> Value { - let layer = v.get_layer_last(); + let layer = v.get_top_layer(); let vals = layer .record_values @@ -677,7 +679,7 @@ fn build_table_as_list(v: &RecordView) -> Value { } fn build_table_as_record(v: &RecordView) -> Value { - let layer = v.get_layer_last(); + let layer = v.get_top_layer(); let mut record = Record::new(); if let Some(row) = layer.record_values.first() { diff --git a/crates/nu-explore/src/views/try.rs b/crates/nu-explore/src/views/try.rs index 7ad6d38aef..b23ca3e9f4 100644 --- a/crates/nu-explore/src/views/try.rs +++ b/crates/nu-explore/src/views/try.rs @@ -242,8 +242,8 @@ impl View for TryView { if let Some(view) = &mut self.table { view.setup(config); - view.set_orientation(r.get_orientation_current()); - view.set_orientation_current(r.get_orientation_current()); + view.set_orientation(r.get_top_layer_orientation()); + view.set_top_layer_orientation(r.get_top_layer_orientation()); } } } @@ -262,7 +262,7 @@ fn run_command( let mut view = RecordView::new(columns, values); if is_record { - view.set_orientation_current(Orientation::Left); + view.set_top_layer_orientation(Orientation::Left); } Ok(view) From 34da26d039b85581332e58893cd539951a888c4f Mon Sep 17 00:00:00 2001 From: Himadri Bhattacharjee <107522312+lavafroth@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:31:27 +0530 Subject: [PATCH 06/63] fix: exotic types return float on division, self on modulo (#13301) Related to #13298 # Description Exotic types like `Duration` and `Filesize` return a float on division by the same type, i.e., the unit is gone since division results in a scalar. When using the modulo operator, the output type has the same unit. # User-Facing Changes Division results in a float like the following: ```sh ~/Public/nushell> 512sec / 3sec 170.66666666666666 ``` Modulo results in an output with the same unit: ```sh ~/Public/nushell> 512sec mod 3sec 2sec ``` Type checking isn't confused with output types: ```sh ~/Public/nushell> (512sec mod 3sec) / 0.5sec 4 ``` # Tests + Formatting Existing tests are passing. # After Submitting --- crates/nu-parser/src/type_check.rs | 52 ++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 872bb6a94c..6999a0469b 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -278,8 +278,7 @@ pub fn math_result_type( ) } }, - Operator::Math(Math::Divide) | Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty) - { + Operator::Math(Math::Divide) => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), @@ -292,6 +291,55 @@ pub fn math_result_type( (Type::Filesize, Type::Filesize) => (Type::Float, None), (Type::Filesize, Type::Int) => (Type::Filesize, None), (Type::Filesize, Type::Float) => (Type::Filesize, None), + (Type::Duration, Type::Duration) => (Type::Float, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Duration, Type::Float) => (Type::Duration, None), + + (Type::Custom(a), Type::Custom(b)) if a == b => (Type::Custom(a.clone()), None), + (Type::Custom(a), _) => (Type::Custom(a.clone()), None), + + (Type::Any, _) => (Type::Any, None), + (_, Type::Any) => (Type::Any, None), + (Type::Int | Type::Float | Type::Filesize | Type::Duration, _) => { + *op = Expression::garbage(working_set, op.span); + ( + Type::Any, + Some(ParseError::UnsupportedOperationRHS( + "division".into(), + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + _ => { + *op = Expression::garbage(working_set, op.span); + ( + Type::Any, + Some(ParseError::UnsupportedOperationLHS( + "division".into(), + op.span, + lhs.span, + lhs.ty.clone(), + )), + ) + } + }, + Operator::Math(Math::Modulo) => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Number, Type::Number) => (Type::Number, None), + (Type::Number, Type::Int) => (Type::Number, None), + (Type::Int, Type::Number) => (Type::Number, None), + (Type::Number, Type::Float) => (Type::Number, None), + (Type::Float, Type::Number) => (Type::Number, None), + (Type::Filesize, Type::Filesize) => (Type::Filesize, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Filesize, Type::Float) => (Type::Filesize, None), (Type::Duration, Type::Duration) => (Type::Duration, None), (Type::Duration, Type::Int) => (Type::Duration, None), (Type::Duration, Type::Float) => (Type::Duration, None), From 948b90299db6db1ef5ea8dcb503af8ee046db211 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 5 Jul 2024 14:10:41 -0700 Subject: [PATCH 07/63] Preserve attributes on external ByteStreams (#13305) # Description Bug fix: `PipelineData::check_external_failed()` was not preserving the original `type_` and `known_size` attributes of the stream passed in for streams that come from children, so `external-command | into binary` did not work properly and always ended up still being unknown type. # User-Facing Changes The following test case now works as expected: ```nushell > head -c 2 /dev/urandom | into binary # Expected: pretty hex dump of binary # Previous behavior: just raw binary in the terminal ``` # Tests + Formatting Added a test to cover this to `into binary` --- .../tests/commands/conversions/into/binary.rs | 13 +++++++++++++ .../tests/commands/conversions/into/mod.rs | 1 + crates/nu-protocol/src/pipeline/pipeline_data.rs | 9 +++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/tests/commands/conversions/into/binary.rs diff --git a/crates/nu-command/tests/commands/conversions/into/binary.rs b/crates/nu-command/tests/commands/conversions/into/binary.rs new file mode 100644 index 0000000000..c8fd24df77 --- /dev/null +++ b/crates/nu-command/tests/commands/conversions/into/binary.rs @@ -0,0 +1,13 @@ +use nu_test_support::nu; + +#[test] +fn sets_stream_from_internal_command_as_binary() { + let result = nu!("seq 1 10 | to text | into binary | describe"); + assert_eq!("binary (stream)", result.out); +} + +#[test] +fn sets_stream_from_external_command_as_binary() { + let result = nu!("^nu --testbin cococo | into binary | describe"); + assert_eq!("binary (stream)", result.out); +} diff --git a/crates/nu-command/tests/commands/conversions/into/mod.rs b/crates/nu-command/tests/commands/conversions/into/mod.rs index a7a829445a..ad10199b5c 100644 --- a/crates/nu-command/tests/commands/conversions/into/mod.rs +++ b/crates/nu-command/tests/commands/conversions/into/mod.rs @@ -1 +1,2 @@ +mod binary; mod int; diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index 03fbc64d21..c0c0bd1c33 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -453,7 +453,10 @@ impl PipelineData { /// Currently this will consume an external command to completion. pub fn check_external_failed(self) -> Result<(Self, bool), ShellError> { if let PipelineData::ByteStream(stream, metadata) = self { + // Preserve stream attributes let span = stream.span(); + let type_ = stream.type_(); + let known_size = stream.known_size(); match stream.into_child() { Ok(mut child) => { // Only check children without stdout. This means that nothing @@ -485,10 +488,12 @@ impl PipelineData { child.stderr = Some(ChildPipe::Tee(Box::new(Cursor::new(stderr)))); } child.set_exit_code(code); - let stream = ByteStream::child(child, span); + let stream = ByteStream::child(child, span).with_type(type_); Ok((PipelineData::ByteStream(stream, metadata), code != 0)) } else { - let stream = ByteStream::child(child, span); + let stream = ByteStream::child(child, span) + .with_type(type_) + .with_known_size(known_size); Ok((PipelineData::ByteStream(stream, metadata), false)) } } From de2b752771f5d1d5672983a00a3676a246196295 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:58:35 -0400 Subject: [PATCH 08/63] Fix variable completion sort order (#13306) # Description My last PR (https://github.com/nushell/nushell/pull/13242) made it so that the last branch in the variable completer doesn't sort suggestions. Sorry about that. This should fix it. # User-Facing Changes Variables will now be sorted properly. # Tests + Formatting Added one test case to verify this won't happen again. # After Submitting --- crates/nu-cli/src/completions/variable_completions.rs | 2 ++ crates/nu-cli/tests/completions/mod.rs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/completions/variable_completions.rs b/crates/nu-cli/src/completions/variable_completions.rs index ab04da13ea..72a69e942c 100644 --- a/crates/nu-cli/src/completions/variable_completions.rs +++ b/crates/nu-cli/src/completions/variable_completions.rs @@ -229,6 +229,8 @@ impl Completer for VariableCompletion { } } + output = sort_suggestions(&prefix_str, output, SortBy::Ascending); + output.dedup(); // TODO: Removes only consecutive duplicates, is it intended? output diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 03f37925f5..a7f88dc3b0 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -107,7 +107,7 @@ fn subcommand_completer() -> NuCompleter { } #[test] -fn variables_dollar_sign_with_varialblecompletion() { +fn variables_dollar_sign_with_variablecompletion() { let (_, _, engine, stack) = new_engine(); let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); @@ -907,6 +907,11 @@ fn variables_completions() { // Match results match_suggestions(expected, suggestions); + + let suggestions = completer.complete("$", 1); + let expected: Vec = vec!["$actor".into(), "$env".into(), "$in".into(), "$nu".into()]; + + match_suggestions(expected, suggestions); } #[test] From d2a1f96dbd95ce55dc95c43ad4a65b0dd3f26c58 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 6 Jul 2024 12:04:19 -0500 Subject: [PATCH 09/63] update to latest reedline commit (#13313) # Description Update to the latest reedline version. (don't ask me why libloading changed. `cargo update -p reedline` sometimes does weird things) # User-Facing Changes # Tests + Formatting # After Submitting /cc @YizhePKU (this include your reedline pwd change) --- Cargo.lock | 7 +++---- Cargo.toml | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72b7f72b3b..d4f759aed0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2382,9 +2382,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets 0.52.5", @@ -4988,8 +4988,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf59e4c97b5049ba96b052cdb652368305a2eddcbce9bf1c16f9d003139eeea" +source = "git+https://github.com/nushell/reedline?branch=main#480059a3f52cf919341cda88e8c544edd846bc73" dependencies = [ "arboard", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 573ace795f..5701fe3958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,7 +197,6 @@ nu-protocol = { path = "./crates/nu-protocol", version = "0.95.1" } nu-std = { path = "./crates/nu-std", version = "0.95.1" } nu-system = { path = "./crates/nu-system", version = "0.95.1" } nu-utils = { path = "./crates/nu-utils", version = "0.95.1" } - reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } @@ -304,7 +303,7 @@ bench = false # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious [patch.crates-io] -# reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # Run all benchmarks with `cargo bench` From fa183b66699bbd9ed05f6b0a34a129cc981e93e0 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sat, 6 Jul 2024 18:09:12 +0000 Subject: [PATCH 10/63] `help operators` refactor (#13307) # Description Refactors `help operators` so that its output is always up to date with the parser. # User-Facing Changes - The order of output rows for `help operators` was changed. - `not` is now listed as a boolean operator instead of a comparison operator. - Edited some of the descriptions for the operators. --- crates/nu-command/src/help/help_operators.rs | 401 +++++++------------ crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/ast/expression.rs | 37 +- crates/nu-protocol/src/ast/operator.rs | 40 +- 4 files changed, 174 insertions(+), 306 deletions(-) diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index a7beee3dd5..12803fad8e 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::ast::{Assignment, Bits, Boolean, Comparison, Math, Operator}; #[derive(Clone)] pub struct HelpOperators; @@ -27,282 +28,148 @@ impl Command for HelpOperators { _input: PipelineData, ) -> Result { let head = call.head; - let op_info = generate_operator_info(); - let mut recs = vec![]; - - for op in op_info { - recs.push(Value::record( + let mut operators = [ + Operator::Assignment(Assignment::Assign), + Operator::Assignment(Assignment::PlusAssign), + Operator::Assignment(Assignment::AppendAssign), + Operator::Assignment(Assignment::MinusAssign), + Operator::Assignment(Assignment::MultiplyAssign), + Operator::Assignment(Assignment::DivideAssign), + Operator::Comparison(Comparison::Equal), + Operator::Comparison(Comparison::NotEqual), + Operator::Comparison(Comparison::LessThan), + Operator::Comparison(Comparison::GreaterThan), + Operator::Comparison(Comparison::LessThanOrEqual), + Operator::Comparison(Comparison::GreaterThanOrEqual), + Operator::Comparison(Comparison::RegexMatch), + Operator::Comparison(Comparison::NotRegexMatch), + Operator::Comparison(Comparison::In), + Operator::Comparison(Comparison::NotIn), + Operator::Comparison(Comparison::StartsWith), + Operator::Comparison(Comparison::EndsWith), + Operator::Math(Math::Plus), + Operator::Math(Math::Append), + Operator::Math(Math::Minus), + Operator::Math(Math::Multiply), + Operator::Math(Math::Divide), + Operator::Math(Math::Modulo), + Operator::Math(Math::FloorDivision), + Operator::Math(Math::Pow), + Operator::Bits(Bits::BitOr), + Operator::Bits(Bits::BitXor), + Operator::Bits(Bits::BitAnd), + Operator::Bits(Bits::ShiftLeft), + Operator::Bits(Bits::ShiftRight), + Operator::Boolean(Boolean::And), + Operator::Boolean(Boolean::Or), + Operator::Boolean(Boolean::Xor), + ] + .into_iter() + .map(|op| { + Value::record( record! { - "type" => Value::string(op.op_type, head), - "operator" => Value::string(op.operator, head), - "name" => Value::string(op.name, head), - "description" => Value::string(op.description, head), - "precedence" => Value::int(op.precedence, head), + "type" => Value::string(op_type(&op), head), + "operator" => Value::string(op.to_string(), head), + "name" => Value::string(name(&op), head), + "description" => Value::string(description(&op), head), + "precedence" => Value::int(op.precedence().into(), head), }, head, - )); - } + ) + }) + .collect::>(); - Ok(Value::list(recs, head).into_pipeline_data()) + operators.push(Value::record( + record! { + "type" => Value::string("Boolean", head), + "operator" => Value::string("not", head), + "name" => Value::string("Not", head), + "description" => Value::string("Negates a value or expression.", head), + "precedence" => Value::int(0, head), + }, + head, + )); + + Ok(Value::list(operators, head).into_pipeline_data()) } } -struct OperatorInfo { - op_type: String, - operator: String, - name: String, - description: String, - precedence: i64, +fn op_type(operator: &Operator) -> &'static str { + match operator { + Operator::Comparison(_) => "Comparison", + Operator::Math(_) => "Math", + Operator::Boolean(_) => "Boolean", + Operator::Bits(_) => "Bitwise", + Operator::Assignment(_) => "Assignment", + } } -fn generate_operator_info() -> Vec { - vec![ - OperatorInfo { - op_type: "Assignment".into(), - operator: "=".into(), - name: "Assign".into(), - description: "Assigns a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "+=".into(), - name: "PlusAssign".into(), - description: "Adds a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "++=".into(), - name: "AppendAssign".into(), - description: "Appends a list or a value to a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "-=".into(), - name: "MinusAssign".into(), - description: "Subtracts a value from a variable.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "*=".into(), - name: "MultiplyAssign".into(), - description: "Multiplies a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Assignment".into(), - operator: "/=".into(), - name: "DivideAssign".into(), - description: "Divides a variable by a value.".into(), - precedence: 10, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "==".into(), - name: "Equal".into(), - description: "Checks if two values are equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!=".into(), - name: "NotEqual".into(), - description: "Checks if two values are not equal.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<".into(), - name: "LessThan".into(), - description: "Checks if a value is less than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "<=".into(), - name: "LessThanOrEqual".into(), - description: "Checks if a value is less than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">".into(), - name: "GreaterThan".into(), - description: "Checks if a value is greater than another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: ">=".into(), - name: "GreaterThanOrEqual".into(), - description: "Checks if a value is greater than or equal to another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "=~".into(), - name: "RegexMatch".into(), - description: "Checks if a value matches a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "!~".into(), - name: "NotRegexMatch".into(), - description: "Checks if a value does not match a regular expression.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "in".into(), - name: "In".into(), - description: "Checks if a value is in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not-in".into(), - name: "NotIn".into(), - description: "Checks if a value is not in a list or string.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "starts-with".into(), - name: "StartsWith".into(), - description: "Checks if a string starts with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "ends-with".into(), - name: "EndsWith".into(), - description: "Checks if a string ends with another.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Comparison".into(), - operator: "not".into(), - name: "UnaryNot".into(), - description: "Negates a value or expression.".into(), - precedence: 0, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "+".into(), - name: "Plus".into(), - description: "Adds two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "++".into(), - name: "Append".into(), - description: "Appends two lists or a list and a value.".into(), - precedence: 80, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "-".into(), - name: "Minus".into(), - description: "Subtracts two values.".into(), - precedence: 90, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "*".into(), - name: "Multiply".into(), - description: "Multiplies two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "/".into(), - name: "Divide".into(), - description: "Divides two values.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "//".into(), - name: "FloorDivision".into(), - description: "Divides two values and floors the result.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "mod".into(), - name: "Modulo".into(), - description: "Divides two values and returns the remainder.".into(), - precedence: 95, - }, - OperatorInfo { - op_type: "Math".into(), - operator: "**".into(), - name: "Pow ".into(), - description: "Raises one value to the power of another.".into(), - precedence: 100, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-or".into(), - name: "BitOr".into(), - description: "Performs a bitwise OR on two values.".into(), - precedence: 60, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-xor".into(), - name: "BitXor".into(), - description: "Performs a bitwise XOR on two values.".into(), - precedence: 70, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-and".into(), - name: "BitAnd".into(), - description: "Performs a bitwise AND on two values.".into(), - precedence: 75, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shl".into(), - name: "ShiftLeft".into(), - description: "Shifts a value left by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Bitwise".into(), - operator: "bit-shr".into(), - name: "ShiftRight".into(), - description: "Shifts a value right by another.".into(), - precedence: 85, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "and".into(), - name: "And".into(), - description: "Checks if two values are true.".into(), - precedence: 50, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "or".into(), - name: "Or".into(), - description: "Checks if either value is true.".into(), - precedence: 40, - }, - OperatorInfo { - op_type: "Boolean".into(), - operator: "xor".into(), - name: "Xor".into(), - description: "Checks if one value is true and the other is false.".into(), - precedence: 45, - }, - ] +fn name(operator: &Operator) -> String { + match operator { + Operator::Comparison(op) => format!("{op:?}"), + Operator::Math(op) => format!("{op:?}"), + Operator::Boolean(op) => format!("{op:?}"), + Operator::Bits(op) => format!("{op:?}"), + Operator::Assignment(op) => format!("{op:?}"), + } +} + +fn description(operator: &Operator) -> &'static str { + // NOTE: if you have to add an operator here, also add it to the operator list above. + match operator { + Operator::Comparison(Comparison::Equal) => "Checks if two values are equal.", + Operator::Comparison(Comparison::NotEqual) => "Checks if two values are not equal.", + Operator::Comparison(Comparison::LessThan) => "Checks if a value is less than another.", + Operator::Comparison(Comparison::GreaterThan) => { + "Checks if a value is greater than another." + } + Operator::Comparison(Comparison::LessThanOrEqual) => { + "Checks if a value is less than or equal to another." + } + Operator::Comparison(Comparison::GreaterThanOrEqual) => { + "Checks if a value is greater than or equal to another." + } + Operator::Comparison(Comparison::RegexMatch) => { + "Checks if a value matches a regular expression." + } + Operator::Comparison(Comparison::NotRegexMatch) => { + "Checks if a value does not match a regular expression." + } + Operator::Comparison(Comparison::In) => { + "Checks if a value is in a list, is part of a string, or is a key in a record." + } + Operator::Comparison(Comparison::NotIn) => { + "Checks if a value is not in a list, is not part of a string, or is not a key in a record." + } + Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.", + Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.", + Operator::Math(Math::Plus) => "Adds two values.", + Operator::Math(Math::Append) => { + "Appends two lists, a list and a value, two strings, or two binary values." + } + Operator::Math(Math::Minus) => "Subtracts two values.", + Operator::Math(Math::Multiply) => "Multiplies two values.", + Operator::Math(Math::Divide) => "Divides two values.", + Operator::Math(Math::Modulo) => "Divides two values and returns the remainder.", + Operator::Math(Math::FloorDivision) => "Divides two values and floors the result.", + Operator::Math(Math::Pow) => "Raises one value to the power of another.", + Operator::Boolean(Boolean::And) => "Checks if both values are true.", + Operator::Boolean(Boolean::Or) => "Checks if either value is true.", + Operator::Boolean(Boolean::Xor) => "Checks if one value is true and the other is false.", + Operator::Bits(Bits::BitOr) => "Performs a bitwise OR on two values.", + Operator::Bits(Bits::BitXor) => "Performs a bitwise XOR on two values.", + Operator::Bits(Bits::BitAnd) => "Performs a bitwise AND on two values.", + Operator::Bits(Bits::ShiftLeft) => "Bitwise shifts a value left by another.", + Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.", + Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.", + Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.", + Operator::Assignment(Assignment::AppendAssign) => { + "Appends a list, a value, a string, or a binary value to a variable." + } + Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.", + Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.", + Operator::Assignment(Assignment::DivideAssign) => "Divides a variable by a value.", + } } #[cfg(test)] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index b274d77404..51935214f5 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -4950,7 +4950,7 @@ pub fn parse_math_expression( let mut expr_stack: Vec = vec![]; let mut idx = 0; - let mut last_prec = 1000000; + let mut last_prec = u8::MAX; let first_span = working_set.get_span_contents(spans[0]); diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 040e140cab..906fce3fe1 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -27,42 +27,9 @@ impl Expression { } } - pub fn precedence(&self) -> usize { + pub fn precedence(&self) -> u8 { match &self.expr { - Expr::Operator(operator) => { - use super::operator::*; - // Higher precedence binds tighter - - match operator { - Operator::Math(Math::Pow) => 100, - Operator::Math(Math::Multiply) - | Operator::Math(Math::Divide) - | Operator::Math(Math::Modulo) - | Operator::Math(Math::FloorDivision) => 95, - Operator::Math(Math::Plus) | Operator::Math(Math::Minus) => 90, - Operator::Bits(Bits::ShiftLeft) | Operator::Bits(Bits::ShiftRight) => 85, - Operator::Comparison(Comparison::NotRegexMatch) - | Operator::Comparison(Comparison::RegexMatch) - | Operator::Comparison(Comparison::StartsWith) - | Operator::Comparison(Comparison::EndsWith) - | Operator::Comparison(Comparison::LessThan) - | Operator::Comparison(Comparison::LessThanOrEqual) - | Operator::Comparison(Comparison::GreaterThan) - | Operator::Comparison(Comparison::GreaterThanOrEqual) - | Operator::Comparison(Comparison::Equal) - | Operator::Comparison(Comparison::NotEqual) - | Operator::Comparison(Comparison::In) - | Operator::Comparison(Comparison::NotIn) - | Operator::Math(Math::Append) => 80, - Operator::Bits(Bits::BitAnd) => 75, - Operator::Bits(Bits::BitXor) => 70, - Operator::Bits(Bits::BitOr) => 60, - Operator::Boolean(Boolean::And) => 50, - Operator::Boolean(Boolean::Xor) => 45, - Operator::Boolean(Boolean::Or) => 40, - Operator::Assignment(_) => 10, - } - } + Expr::Operator(operator) => operator.precedence(), _ => 0, } } diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 46484761bc..713c6c0060 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -68,6 +68,40 @@ pub enum Operator { Assignment(Assignment), } +impl Operator { + pub fn precedence(&self) -> u8 { + match self { + Self::Math(Math::Pow) => 100, + Self::Math(Math::Multiply) + | Self::Math(Math::Divide) + | Self::Math(Math::Modulo) + | Self::Math(Math::FloorDivision) => 95, + Self::Math(Math::Plus) | Self::Math(Math::Minus) => 90, + Self::Bits(Bits::ShiftLeft) | Self::Bits(Bits::ShiftRight) => 85, + Self::Comparison(Comparison::NotRegexMatch) + | Self::Comparison(Comparison::RegexMatch) + | Self::Comparison(Comparison::StartsWith) + | Self::Comparison(Comparison::EndsWith) + | Self::Comparison(Comparison::LessThan) + | Self::Comparison(Comparison::LessThanOrEqual) + | Self::Comparison(Comparison::GreaterThan) + | Self::Comparison(Comparison::GreaterThanOrEqual) + | Self::Comparison(Comparison::Equal) + | Self::Comparison(Comparison::NotEqual) + | Self::Comparison(Comparison::In) + | Self::Comparison(Comparison::NotIn) + | Self::Math(Math::Append) => 80, + Self::Bits(Bits::BitAnd) => 75, + Self::Bits(Bits::BitXor) => 70, + Self::Bits(Bits::BitOr) => 60, + Self::Boolean(Boolean::And) => 50, + Self::Boolean(Boolean::Xor) => 45, + Self::Boolean(Boolean::Or) => 40, + Self::Assignment(_) => 10, + } + } +} + impl Display for Operator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -95,10 +129,10 @@ impl Display for Operator { Operator::Math(Math::Multiply) => write!(f, "*"), Operator::Math(Math::Divide) => write!(f, "/"), Operator::Math(Math::Modulo) => write!(f, "mod"), - Operator::Math(Math::FloorDivision) => write!(f, "fdiv"), + Operator::Math(Math::FloorDivision) => write!(f, "//"), Operator::Math(Math::Pow) => write!(f, "**"), - Operator::Boolean(Boolean::And) => write!(f, "&&"), - Operator::Boolean(Boolean::Or) => write!(f, "||"), + Operator::Boolean(Boolean::And) => write!(f, "and"), + Operator::Boolean(Boolean::Or) => write!(f, "or"), Operator::Boolean(Boolean::Xor) => write!(f, "xor"), Operator::Bits(Bits::BitOr) => write!(f, "bit-or"), Operator::Bits(Bits::BitXor) => write!(f, "bit-xor"), From 32db5d3aa31f7d0ac5494c78086d9ff01b3d2190 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Jul 2024 22:47:39 +0300 Subject: [PATCH 11/63] Fix issue with head on separation lines (#13291) Hi there, Seems to work Though I haven't done a lot of testing. ![image](https://github.com/nushell/nushell/assets/20165848/c95aa8d4-a8d2-462c-afc9-35c48f8825f4) ![image](https://github.com/nushell/nushell/assets/20165848/1859dfe5-4a76-4776-a4e0-d3f53fc86862) ![image](https://github.com/nushell/nushell/assets/20165848/b46bb62b-a951-412d-b8fa-65cebcfbfed6) ![image](https://github.com/nushell/nushell/assets/20165848/bff0762e-42d4-41bf-b2c2-641c0436ca2e) ![image](https://github.com/nushell/nushell/assets/20165848/2c3c5664-9b90-44e4-befc-c250174cb630) close #13287 cc: @fdncred PS: Yessssss I do remember about emojie issue..... :disappointed: --- crates/nu-table/src/table.rs | 354 +++++++++++++++++++++++++++++------ 1 file changed, 300 insertions(+), 54 deletions(-) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index bdaf7fb6c3..e1b1269874 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -12,13 +12,17 @@ use tabled::{ config::{AlignmentHorizontal, ColoredConfig, Entity, EntityMap, Position}, dimension::CompleteDimensionVecRecords, records::{ - vec_records::{CellInfo, VecRecords}, + vec_records::{Cell, CellInfo, VecRecords}, ExactRecords, PeekableRecords, Records, Resizable, }, }, settings::{ - formatting::AlignmentStrategy, object::Segment, peaker::Peaker, themes::ColumnNames, Color, - Modify, Padding, Settings, TableOption, Width, + formatting::AlignmentStrategy, + object::{Columns, Segment}, + peaker::Peaker, + themes::ColumnNames, + width::Truncate, + Color, Modify, Padding, Settings, TableOption, Width, }, Table, }; @@ -216,7 +220,7 @@ fn build_table( } let pad = indent.0 + indent.1; - let widths = maybe_truncate_columns(&mut data, &cfg.theme, termwidth, pad); + let widths = maybe_truncate_columns(&mut data, &cfg, termwidth, pad); if widths.is_empty() { return None; } @@ -269,20 +273,51 @@ fn set_indent(table: &mut Table, left: usize, right: usize) { fn set_border_head(table: &mut Table, with_footer: bool, wctrl: TableWidthCtrl) { if with_footer { let count_rows = table.count_rows(); + let last_row_index = count_rows - 1; + + // note: funnily last and row must be equal at this point but we do not rely on it just in case. + + let mut first_row = GetRow(0, Vec::new()); + let mut head_settings = GetRowSettings(0, AlignmentHorizontal::Left, None); + let mut last_row = GetRow(last_row_index, Vec::new()); + + table.with(&mut first_row); + table.with(&mut head_settings); + table.with(&mut last_row); + table.with( Settings::default() .with(wctrl) .with(StripColorFromRow(0)) .with(StripColorFromRow(count_rows - 1)) - .with(HeaderMove((0, 0), true)) - .with(HeaderMove((count_rows - 1 - 1, count_rows - 1), false)), + .with(MoveRowNext::new(0, 0)) + .with(MoveRowPrev::new(last_row_index - 1, last_row_index)) + .with(SetLineHeaders::new( + 0, + first_row.1, + head_settings.1, + head_settings.2.clone(), + )) + .with(SetLineHeaders::new( + last_row_index - 1, + last_row.1, + head_settings.1, + head_settings.2, + )), ); } else { + let mut row = GetRow(0, Vec::new()); + let mut row_opts = GetRowSettings(0, AlignmentHorizontal::Left, None); + + table.with(&mut row); + table.with(&mut row_opts); + table.with( Settings::default() .with(wctrl) .with(StripColorFromRow(0)) - .with(HeaderMove((0, 0), true)), + .with(MoveRowNext::new(0, 0)) + .with(SetLineHeaders::new(0, row.1, row_opts.1, row_opts.2)), ); } } @@ -324,11 +359,14 @@ impl TableOption, ColoredConfig> for max: self.max, strategy: self.cfg.trim, width: self.width, + do_as_head: self.cfg.header_on_border, } .change(rec, cfg, dim); } else if self.cfg.expand && self.max > total_width { Settings::new(SetDimensions(self.width), Width::increase(self.max)) .change(rec, cfg, dim) + } else { + SetDimensions(self.width).change(rec, cfg, dim); } } } @@ -337,6 +375,7 @@ struct TableTrim { width: Vec, strategy: TrimStrategy, max: usize, + do_as_head: bool, } impl TableOption, ColoredConfig> for TableTrim { @@ -346,6 +385,44 @@ impl TableOption, ColoredConfig> for cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { + // we already must have been estimated that it's safe to do. + // and all dims will be suffitient + if self.do_as_head { + if recs.is_empty() { + return; + } + + let headers = recs[0].to_owned(); + for (i, head) in headers.into_iter().enumerate() { + let head_width = CellInfo::width(&head); + + match &self.strategy { + TrimStrategy::Wrap { try_to_keep_words } => { + let mut wrap = Width::wrap(head_width); + if *try_to_keep_words { + wrap = wrap.keep_words(); + } + + Modify::new(Columns::single(i)) + .with(wrap) + .change(recs, cfg, dims); + } + TrimStrategy::Truncate { suffix } => { + let mut truncate = Width::truncate(self.max); + if let Some(suffix) = suffix { + truncate = truncate.suffix(suffix).suffix_try_color(true); + } + + Modify::new(Columns::single(i)) + .with(truncate) + .change(recs, cfg, dims); + } + } + } + + return; + } + match self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { let mut wrap = Width::wrap(self.max).priority::(); @@ -460,19 +537,22 @@ fn load_theme( fn maybe_truncate_columns( data: &mut NuRecords, - theme: &TableTheme, + cfg: &NuTableConfig, termwidth: usize, pad: usize, ) -> Vec { const TERMWIDTH_THRESHOLD: usize = 120; - let truncate = if termwidth > TERMWIDTH_THRESHOLD { + let preserve_content = termwidth > TERMWIDTH_THRESHOLD; + let truncate = if cfg.header_on_border { + truncate_columns_by_head + } else if preserve_content { truncate_columns_by_columns } else { truncate_columns_by_content }; - truncate(data, theme, pad, termwidth) + truncate(data, &cfg.theme, pad, termwidth) } // VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE. @@ -627,6 +707,83 @@ fn truncate_columns_by_columns( widths } +// VERSION where we are showing AS LITTLE COLUMNS AS POSSIBLE but WITH AS MUCH CONTENT AS POSSIBLE. +fn truncate_columns_by_head( + data: &mut NuRecords, + theme: &TableTheme, + pad: usize, + termwidth: usize, +) -> Vec { + const TRAILING_COLUMN_WIDTH: usize = 5; + + let config = get_config(theme, false, None); + let mut widths = build_width(&*data, pad); + let total_width = get_total_width2(&widths, &config); + if total_width <= termwidth { + return widths; + } + + if data.is_empty() { + return widths; + } + + let head = &data[0]; + + let borders = config.get_borders(); + let has_vertical = borders.has_vertical(); + + let mut width = borders.has_left() as usize + borders.has_right() as usize; + let mut truncate_pos = 0; + for (i, column_header) in head.iter().enumerate() { + let column_header_width = Cell::width(column_header); + width += column_header_width; + + if i > 0 { + width += has_vertical as usize; + } + + if width >= termwidth { + width -= column_header_width + (i > 0 && has_vertical) as usize; + break; + } + + truncate_pos += 1; + } + + // we don't need any truncation then (is it possible?) + if truncate_pos == head.len() { + return widths; + } + + if truncate_pos == 0 { + return vec![]; + } + + truncate_columns(data, truncate_pos); + widths.truncate(truncate_pos); + + // Append columns with a trailing column + + let min_width = width; + + let diff = termwidth - min_width; + let can_trailing_column_be_pushed = diff > TRAILING_COLUMN_WIDTH + has_vertical as usize; + + if !can_trailing_column_be_pushed { + if data.count_columns() == 1 { + return vec![]; + } + + truncate_columns(data, data.count_columns() - 1); + widths.pop(); + } + + push_empty_column(data); + widths.push(3 + pad); + + widths +} + /// The same as [`tabled::peaker::PriorityMax`] but prioritizes left columns first in case of equal width. #[derive(Debug, Default, Clone)] pub struct PriorityMax; @@ -714,9 +871,8 @@ impl TableOption, ColoredConfig> for SetDi } // it assumes no spans is used. +// todo: Could be replaced by Dimension impl usage fn build_width(records: &NuRecords, pad: usize) -> Vec { - use tabled::grid::records::vec_records::Cell; - let count_columns = records.count_columns(); let mut widths = vec![0; count_columns]; for columns in records.iter_rows() { @@ -729,50 +885,156 @@ fn build_width(records: &NuRecords, pad: usize) -> Vec { widths } -struct HeaderMove((usize, usize), bool); +struct GetRow(usize, Vec); -impl TableOption, ColoredConfig> for HeaderMove { +impl TableOption, ColoredConfig> for &mut GetRow { + fn change( + self, + recs: &mut NuRecords, + _: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + let row = self.0; + self.1 = recs[row].iter().map(|c| c.as_ref().to_owned()).collect(); + } +} + +struct GetRowSettings(usize, AlignmentHorizontal, Option); + +impl TableOption, ColoredConfig> + for &mut GetRowSettings +{ + fn change( + self, + _: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + let row = self.0; + self.1 = *cfg.get_alignment_horizontal(Entity::Row(row)); + self.2 = cfg + .get_colors() + .get_color((row, 0)) + .cloned() + .map(Color::from); + } +} + +struct SetLineHeaders { + line: usize, + columns: Vec, + alignment: AlignmentHorizontal, + color: Option, +} + +impl SetLineHeaders { + fn new( + line: usize, + columns: Vec, + alignment: AlignmentHorizontal, + color: Option, + ) -> Self { + Self { + line, + columns, + alignment, + color, + } + } +} + +impl TableOption, ColoredConfig> for SetLineHeaders { fn change( self, recs: &mut NuRecords, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { - let (row, line) = self.0; - if self.1 { - move_header_on_next(recs, cfg, dims, row, line); - } else { - move_header_on_prev(recs, cfg, dims, row, line); - } + let mut columns = self.columns; + match dims.get_widths() { + Some(widths) => { + columns = columns + .into_iter() + .zip(widths.iter().map(|w| w.checked_sub(2).unwrap_or(*w))) // exclude padding; which is generally 2 + .map(|(s, width)| Truncate::truncate_text(&s, width).into_owned()) + .collect(); + } + None => { + // we don't have widths cached; which means that NO widtds adjustmens were done + // which means we are OK to leave columns as they are. + // + // but we are actually always got to have widths at this point + } + }; + + set_column_names( + recs, + cfg, + dims, + columns, + self.line, + self.alignment, + self.color, + ) } } -fn move_header_on_next( - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, +struct MoveRowNext { row: usize, line: usize, -) { +} + +impl MoveRowNext { + fn new(row: usize, line: usize) -> Self { + Self { row, line } + } +} + +struct MoveRowPrev { + row: usize, + line: usize, +} + +impl MoveRowPrev { + fn new(row: usize, line: usize) -> Self { + Self { row, line } + } +} + +impl TableOption, ColoredConfig> for MoveRowNext { + fn change( + self, + recs: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + row_shift_next(recs, cfg, self.row, self.line); + } +} + +impl TableOption, ColoredConfig> for MoveRowPrev { + fn change( + self, + recs: &mut NuRecords, + cfg: &mut ColoredConfig, + _: &mut CompleteDimensionVecRecords<'_>, + ) { + row_shift_prev(recs, cfg, self.row, self.line); + } +} + +fn row_shift_next(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) { let count_rows = recs.count_rows(); let count_columns = recs.count_columns(); let has_line = cfg.has_horizontal(line, count_rows); let has_next_line = cfg.has_horizontal(line + 1, count_rows); - let align = *cfg.get_alignment_horizontal(Entity::Row(row)); - let color = cfg - .get_colors() - .get_color((row, 0)) - .cloned() - .map(Color::from); - if !has_line && !has_next_line { return; } if !has_line { - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = recs.count_rows(); - set_column_names(recs, cfg, dims, head, line, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); shift_lines_up(cfg, count_rows, &[line + 1]); @@ -780,47 +1042,31 @@ fn move_header_on_next( return; } - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = recs.count_rows(); - set_column_names(recs, cfg, dims, head, line, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); remove_lines(cfg, count_rows, &[line + 1]); shift_lines_up(cfg, count_rows, &[count_rows]); } -fn move_header_on_prev( - recs: &mut NuRecords, - cfg: &mut ColoredConfig, - dims: &mut CompleteDimensionVecRecords<'_>, - row: usize, - line: usize, -) { +fn row_shift_prev(recs: &mut NuRecords, cfg: &mut ColoredConfig, row: usize, line: usize) { let count_rows = recs.count_rows(); let count_columns = recs.count_columns(); let has_line = cfg.has_horizontal(line, count_rows); let has_prev_line = cfg.has_horizontal(line - 1, count_rows); - let align = *cfg.get_alignment_horizontal(Entity::Row(row)); - let color = cfg - .get_colors() - .get_color((row, 0)) - .cloned() - .map(Color::from); - if !has_line && !has_prev_line { return; } if !has_line { - let head = remove_row(recs, row); + let _ = remove_row(recs, row); // shift_lines_down(table, &[line - 1]); - set_column_names(recs, cfg, dims, head, line - 1, align, color); return; } - let head = remove_row(recs, row); + let _ = remove_row(recs, row); let count_rows = count_rows - 1; - set_column_names(recs, cfg, dims, head, line - 1, align, color); shift_alignments_down(cfg, row, count_rows, count_columns); shift_colors_down(cfg, row, count_rows, count_columns); remove_lines(cfg, count_rows, &[line - 1]); From 5af8d6266600ceec4305121afe652a3da2d36f31 Mon Sep 17 00:00:00 2001 From: goldfish <37319612+ito-hiroki@users.noreply.github.com> Date: Sun, 7 Jul 2024 21:55:06 +0900 Subject: [PATCH 12/63] Fix `from toml` to handle toml datetime correctly (#13315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description fixed #12699 When bare dates or naive times are specified in toml files, `from toml` returns invalid dates or times. This PR fixes the problem to correctly handle toml datetime. The current version command returns the default datetime (`chrono::DateTime::default()`) if the datetime parse fails. However, I felt that this behavior was a bit unfriendly, so I changed it to return `Value::string`. # User-Facing Changes The command returns a date with default time and timezone if a bare date is specified. ``` ~/Development/nushell> "dob = 2023-05-27" | from toml ╭─────┬────────────╮ │ dob │ a year ago │ ╰─────┴────────────╯ ~/Development/nushell> "dob = 2023-05-27" | from toml | Sat, 27 May 2023 00:00:00 +0000 (a year ago) ~/Development/nushell> ``` If a bare time is given, a time string is returned. ``` ~/Development/nushell> "tm = 11:00:00" | from toml ╭────┬──────────╮ │ tm │ 11:00:00 │ ╰────┴──────────╯ ~/Development/nushell> "tm = 11:00:00" | from toml | get tm 11:00:00 ~/Development/nushell> ``` # Tests + Formatting When I ran tests, `commands::touch::change_file_mtime_to_reference` failed with the following error. The error also occurs in the master branch, so it's probably unrelated to these changes. (maybe a problem with my dev environment) ``` $ ~/Development/nushell> toolkit check pr ~~~~~~~~ test usage_start_uppercase ... ok test format_conversions::yaml::convert_dict_to_yaml_with_integer_floats_key ... ok test format_conversions::yaml::convert_dict_to_yaml_with_boolean_key ... ok test format_conversions::yaml::table_to_yaml_text_and_from_yaml_text_back_into_table ... ok test quickcheck_parse ... ok test format_conversions::yaml::convert_dict_to_yaml_with_integer_key ... ok failures: ---- commands::touch::change_file_mtime_to_reference stdout ---- === stderr thread 'commands::touch::change_file_mtime_to_reference' panicked at crates/nu-command/tests/commands/touch.rs:298:9: assertion `left == right` failed left: SystemTime { tv_sec: 1720344745, tv_nsec: 862392750 } right: SystemTime { tv_sec: 1720344745, tv_nsec: 887670417 } failures: commands::touch::change_file_mtime_to_reference test result: FAILED. 1542 passed; 1 failed; 32 ignored; 0 measured; 0 filtered out; finished in 12.04s error: test failed, to rerun pass `-p nu-command --test main` - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :red_circle: `toolkit test` - :black_circle: `toolkit test stdlib` ~/Development/nushell> toolkit test stdlib Compiling nu v0.95.1 (/Users/hiroki/Development/nushell) Compiling nu-cmd-lang v0.95.1 (/Users/hiroki/Development/nushell/crates/nu-cmd-lang) Finished dev [unoptimized + debuginfo] target(s) in 6.64s Running `target/debug/nu --no-config-file -c ' use crates/nu-std/testing.nu testing run-tests --path crates/nu-std '` 2024-07-07T19:00:20.423|INF|Running from_jsonl_invalid_object in module test_formats 2024-07-07T19:00:20.436|INF|Running env_log-prefix in module test_logger_env ~~~~~~~~~~~ 2024-07-07T19:00:22.196|INF|Running debug_short in module test_basic_commands ~/Development/nushell> ``` # After Submitting nothing --- crates/nu-command/src/formats/from/toml.rs | 193 +++++++++++++++++---- 1 file changed, 159 insertions(+), 34 deletions(-) diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index e1ddca3164..266911f50a 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use std::str::FromStr; +use toml::value::{Datetime, Offset}; #[derive(Clone)] pub struct FromToml; @@ -56,6 +56,54 @@ b = [1, 2]' | from toml", } } +fn convert_toml_datetime_to_value(dt: &Datetime, span: Span) -> Value { + match &dt.clone() { + toml::value::Datetime { + date: Some(_), + time: _, + offset: _, + } => (), + _ => return Value::string(dt.to_string(), span), + } + + let date = match dt.date { + Some(date) => { + chrono::NaiveDate::from_ymd_opt(date.year.into(), date.month.into(), date.day.into()) + } + None => Some(chrono::NaiveDate::default()), + }; + + let time = match dt.time { + Some(time) => chrono::NaiveTime::from_hms_nano_opt( + time.hour.into(), + time.minute.into(), + time.second.into(), + time.nanosecond, + ), + None => Some(chrono::NaiveTime::default()), + }; + + let tz = match dt.offset { + Some(offset) => match offset { + Offset::Z => chrono::FixedOffset::east_opt(0), + Offset::Custom { minutes: min } => chrono::FixedOffset::east_opt(min as i32 * 60), + }, + None => chrono::FixedOffset::east_opt(0), + }; + + let datetime = match (date, time, tz) { + (Some(date), Some(time), Some(tz)) => chrono::NaiveDateTime::new(date, time) + .and_local_timezone(tz) + .earliest(), + _ => None, + }; + + match datetime { + Some(datetime) => Value::date(datetime, span), + None => Value::string(dt.to_string(), span), + } +} + fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { match value { toml::Value::Array(array) => { @@ -76,13 +124,7 @@ fn convert_toml_to_value(value: &toml::Value, span: Span) -> Value { span, ), toml::Value::String(s) => Value::string(s.clone(), span), - toml::Value::Datetime(d) => match chrono::DateTime::from_str(&d.to_string()) { - Ok(nushell_date) => Value::date(nushell_date, span), - // in the unlikely event that parsing goes wrong, this function still returns a valid - // nushell date (however the default one). This decision was made to make the output of - // this function uniform amongst all eventualities - Err(_) => Value::date(chrono::DateTime::default(), span), - }, + toml::Value::Datetime(dt) => convert_toml_datetime_to_value(dt, span), } } @@ -113,32 +155,6 @@ mod tests { test_examples(FromToml {}) } - #[test] - fn from_toml_creates_nushell_date() { - let toml_date = toml::Value::Datetime(Datetime { - date: Option::from(toml::value::Date { - year: 1980, - month: 10, - day: 12, - }), - time: Option::from(toml::value::Time { - hour: 10, - minute: 12, - second: 44, - nanosecond: 0, - }), - offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), - }); - - let span = Span::test_data(); - let reference_date = Value::date(Default::default(), Span::test_data()); - - let result = convert_toml_to_value(&toml_date, span); - - //positive test (from toml returns a nushell date) - assert_eq!(result.get_type(), reference_date.get_type()); - } - #[test] fn from_toml_creates_correct_date() { let toml_date = toml::Value::Datetime(Datetime { @@ -206,4 +222,113 @@ mod tests { assert!(result.is_err()); } + + #[test] + fn convert_toml_datetime_to_value_date_time_offset() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: Option::from(toml::value::Offset::Custom { minutes: 120 }), + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(60 * 120) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date_time() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 12, 12, 12) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_date() { + let toml_date = Datetime { + date: Option::from(toml::value::Date { + year: 2000, + month: 1, + day: 1, + }), + time: None, + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::date( + chrono::FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(2000, 1, 1, 0, 0, 0) + .unwrap(), + span, + ); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } + + #[test] + fn convert_toml_datetime_to_value_only_time() { + let toml_date = Datetime { + date: None, + time: Option::from(toml::value::Time { + hour: 12, + minute: 12, + second: 12, + nanosecond: 0, + }), + offset: None, + }; + + let span = Span::test_data(); + let reference_date = Value::string(toml_date.to_string(), span); + + let result = convert_toml_datetime_to_value(&toml_date, span); + + assert_eq!(result, reference_date); + } } From 6ce5530fc2d6fc746bb32a3e20e1ab5d1b4c0c1b Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sun, 7 Jul 2024 06:00:57 -0700 Subject: [PATCH 13/63] Make `into bits` produce bitstring stream (#13310) # Description Fix `into bits` to have consistent behavior when passed a byte stream. # User-Facing Changes Previously, it was returning a binary on stream, even though its input/output types don't describe this possibility. We don't need this since we have `into binary` anyway. # Tests + Formatting Tests added --- crates/nu-cmd-extra/src/extra/bits/into.rs | 34 +++++++++++++++++-- .../nu-cmd-extra/tests/commands/bits/into.rs | 13 +++++++ .../nu-cmd-extra/tests/commands/bits/mod.rs | 1 + crates/nu-cmd-extra/tests/commands/mod.rs | 1 + 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 crates/nu-cmd-extra/tests/commands/bits/into.rs create mode 100644 crates/nu-cmd-extra/tests/commands/bits/mod.rs diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index cf85f92ac5..9891a971f6 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -1,3 +1,5 @@ +use std::io::{self, Read, Write}; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; @@ -118,15 +120,41 @@ fn into_bits( let cell_paths = call.rest(engine_state, stack, 0)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - if let PipelineData::ByteStream(stream, ..) = input { - // TODO: in the future, we may want this to stream out, converting each to bytes - Ok(Value::binary(stream.into_bytes()?, head).into_pipeline_data()) + if let PipelineData::ByteStream(stream, metadata) = input { + Ok(PipelineData::ByteStream( + byte_stream_to_bits(stream, head), + metadata, + )) } else { let args = Arguments { cell_paths }; operate(action, args, input, call.head, engine_state.ctrlc.clone()) } } +fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { + if let Some(mut reader) = stream.reader() { + let mut is_first = true; + ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) + } else { + // EOF + Ok(false) + } + }) + } else { + ByteStream::read(io::empty(), head, None, ByteStreamType::String) + } +} + fn convert_to_smallest_number_type(num: i64, span: Span) -> Value { if let Some(v) = num.to_i8() { let bytes = v.to_ne_bytes(); diff --git a/crates/nu-cmd-extra/tests/commands/bits/into.rs b/crates/nu-cmd-extra/tests/commands/bits/into.rs new file mode 100644 index 0000000000..b7e7700583 --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/into.rs @@ -0,0 +1,13 @@ +use nu_test_support::nu; + +#[test] +fn byte_stream_into_bits() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits"); + assert_eq!("00000001 00000010 00000011", result.out); +} + +#[test] +fn byte_stream_into_bits_is_stream() { + let result = nu!("[0x[01] 0x[02 03]] | bytes collect | into bits | describe"); + assert_eq!("string (stream)", result.out); +} diff --git a/crates/nu-cmd-extra/tests/commands/bits/mod.rs b/crates/nu-cmd-extra/tests/commands/bits/mod.rs new file mode 100644 index 0000000000..0d4ee04b0d --- /dev/null +++ b/crates/nu-cmd-extra/tests/commands/bits/mod.rs @@ -0,0 +1 @@ +mod into; diff --git a/crates/nu-cmd-extra/tests/commands/mod.rs b/crates/nu-cmd-extra/tests/commands/mod.rs index 2354122e35..fd216cecb6 100644 --- a/crates/nu-cmd-extra/tests/commands/mod.rs +++ b/crates/nu-cmd-extra/tests/commands/mod.rs @@ -1 +1,2 @@ +mod bits; mod bytes; From 83081f9852dfc6f743fb569c084690d325526502 Mon Sep 17 00:00:00 2001 From: Reilly Wood <26268125+rgwood@users.noreply.github.com> Date: Sun, 7 Jul 2024 06:09:59 -0700 Subject: [PATCH 14/63] `explore`: pass config to views at creation time (#13312) cc: @zhiburt This is an internal refactoring for `explore`. Previously, views inside `explore` were created with default/incorrect configuration and then the correct configuration was passed to them using a function called `setup()`. I believe this was because configuration was dynamic and could change while `explore` was running. After https://github.com/nushell/nushell/pull/10259, configuration can no longer be changed on the fly. So we can clean this up by removing `setup()` and passing configuration to views when they are created. --- crates/nu-explore/src/commands/expand.rs | 3 +- crates/nu-explore/src/commands/help.rs | 10 ++- crates/nu-explore/src/commands/mod.rs | 3 + crates/nu-explore/src/commands/nu.rs | 10 +-- crates/nu-explore/src/commands/table.rs | 5 +- crates/nu-explore/src/commands/try.rs | 5 +- crates/nu-explore/src/lib.rs | 9 +- crates/nu-explore/src/pager/mod.rs | 105 +++++++++------------- crates/nu-explore/src/registry/command.rs | 5 +- crates/nu-explore/src/views/binary/mod.rs | 19 ++-- crates/nu-explore/src/views/mod.rs | 6 -- crates/nu-explore/src/views/record/mod.rs | 28 +----- crates/nu-explore/src/views/try.rs | 35 ++++---- 13 files changed, 93 insertions(+), 150 deletions(-) diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index d7d75af6c5..cbb86336af 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -1,7 +1,7 @@ use super::ViewCommand; use crate::{ nu_common::{self, collect_input}, - views::Preview, + views::{Preview, ViewConfig}, }; use anyhow::Result; use nu_color_config::StyleComputer; @@ -43,6 +43,7 @@ impl ViewCommand for ExpandCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + _: &ViewConfig, ) -> Result { if let Some(value) = value { let value_as_string = convert_value_to_string(value, engine_state, stack)?; diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 8be468383e..684e713939 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -1,5 +1,5 @@ use super::ViewCommand; -use crate::views::Preview; +use crate::views::{Preview, ViewConfig}; use anyhow::Result; use nu_ansi_term::Color; use nu_protocol::{ @@ -99,7 +99,13 @@ impl ViewCommand for HelpCmd { Ok(()) } - fn spawn(&mut self, _: &EngineState, _: &mut Stack, _: Option) -> Result { + fn spawn( + &mut self, + _: &EngineState, + _: &mut Stack, + _: Option, + _: &ViewConfig, + ) -> Result { Ok(HelpCmd::view()) } } diff --git a/crates/nu-explore/src/commands/mod.rs b/crates/nu-explore/src/commands/mod.rs index 141dc25f56..a748ddaaa3 100644 --- a/crates/nu-explore/src/commands/mod.rs +++ b/crates/nu-explore/src/commands/mod.rs @@ -1,3 +1,5 @@ +use crate::views::ViewConfig; + use super::pager::{Pager, Transition}; use anyhow::Result; use nu_protocol::{ @@ -49,5 +51,6 @@ pub trait ViewCommand { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result; } diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index a0d479fd92..84e1fb9706 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -48,6 +48,7 @@ impl ViewCommand for NuCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); @@ -62,7 +63,7 @@ impl ViewCommand for NuCmd { return Ok(NuView::Preview(Preview::new(&text))); } - let mut view = RecordView::new(columns, values); + let mut view = RecordView::new(columns, values, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); @@ -119,11 +120,4 @@ impl View for NuView { NuView::Preview(v) => v.exit(), } } - - fn setup(&mut self, config: ViewConfig<'_>) { - match self { - NuView::Records(v) => v.setup(config), - NuView::Preview(v) => v.setup(config), - } - } } diff --git a/crates/nu-explore/src/commands/table.rs b/crates/nu-explore/src/commands/table.rs index 1079cf6cea..9ab1e39b98 100644 --- a/crates/nu-explore/src/commands/table.rs +++ b/crates/nu-explore/src/commands/table.rs @@ -1,7 +1,7 @@ use super::ViewCommand; use crate::{ nu_common::collect_input, - views::{Orientation, RecordView}, + views::{Orientation, RecordView, ViewConfig}, }; use anyhow::Result; use nu_protocol::{ @@ -49,13 +49,14 @@ impl ViewCommand for TableCmd { _: &EngineState, _: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); let is_record = matches!(value, Value::Record { .. }); let (columns, data) = collect_input(value)?; - let mut view = RecordView::new(columns, data); + let mut view = RecordView::new(columns, data, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); diff --git a/crates/nu-explore/src/commands/try.rs b/crates/nu-explore/src/commands/try.rs index e70b4237d9..e1290651c8 100644 --- a/crates/nu-explore/src/commands/try.rs +++ b/crates/nu-explore/src/commands/try.rs @@ -1,5 +1,5 @@ use super::ViewCommand; -use crate::views::TryView; +use crate::views::{TryView, ViewConfig}; use anyhow::Result; use nu_protocol::{ engine::{EngineState, Stack}, @@ -43,9 +43,10 @@ impl ViewCommand for TryCmd { engine_state: &EngineState, stack: &mut Stack, value: Option, + config: &ViewConfig, ) -> Result { let value = value.unwrap_or_default(); - let mut view = TryView::new(value); + let mut view = TryView::new(value, config.explore_config.clone()); view.init(self.command.clone()); view.try_run(engine_state, stack)?; diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index d347321354..30b3e5cf87 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -10,6 +10,7 @@ use anyhow::Result; use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; pub use default_context::add_explore_context; pub use explore::Explore; +use explore::ExploreConfig; use nu_common::{collect_pipeline, has_simple_value, CtrlC}; use nu_protocol::{ engine::{EngineState, Stack}, @@ -43,7 +44,7 @@ fn run_pager( if is_binary { p.show_message("For help type :help"); - let view = binary_view(input)?; + let view = binary_view(input, config.explore_config)?; return p.run(engine_state, stack, ctrlc, Some(view), commands); } @@ -73,7 +74,7 @@ fn create_record_view( is_record: bool, config: PagerConfig, ) -> Option { - let mut view = RecordView::new(columns, data); + let mut view = RecordView::new(columns, data, config.explore_config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); } @@ -91,14 +92,14 @@ fn help_view() -> Option { Some(Page::new(HelpCmd::view(), false)) } -fn binary_view(input: PipelineData) -> Result { +fn binary_view(input: PipelineData, config: &ExploreConfig) -> Result { let data = match input { PipelineData::Value(Value::Binary { val, .. }, _) => val, PipelineData::ByteStream(bs, _) => bs.into_bytes()?, _ => unreachable!("checked beforehand"), }; - let view = BinaryView::new(data); + let view = BinaryView::new(data, config); Ok(Page::new(view, true)) } diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index a56df95bc7..3114543f7c 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -90,19 +90,42 @@ impl<'a> Pager<'a> { engine_state: &EngineState, stack: &mut Stack, ctrlc: CtrlC, - mut view: Option, + view: Option, commands: CommandRegistry, ) -> Result> { - if let Some(page) = &mut view { - page.view.setup(ViewConfig::new( - self.config.nu_config, - self.config.explore_config, - self.config.style_computer, - self.config.lscolors, - )) + // setup terminal + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?; + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let mut info = ViewInfo { + status: Some(Report::default()), + ..Default::default() + }; + + if let Some(text) = self.message.take() { + info.status = Some(Report::message(text, Severity::Info)); } - run_pager(engine_state, stack, ctrlc, self, view, commands) + let result = render_ui( + &mut terminal, + engine_state, + stack, + ctrlc, + self, + &mut info, + view, + commands, + )?; + + // restore terminal + disable_raw_mode()?; + execute!(io::stdout(), LeaveAlternateScreen)?; + + Ok(result) } } @@ -145,49 +168,6 @@ impl<'a> PagerConfig<'a> { } } -fn run_pager( - engine_state: &EngineState, - stack: &mut Stack, - ctrlc: CtrlC, - pager: &mut Pager, - view: Option, - commands: CommandRegistry, -) -> Result> { - // setup terminal - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, Clear(ClearType::All))?; - - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - let mut info = ViewInfo { - status: Some(Report::default()), - ..Default::default() - }; - - if let Some(text) = pager.message.take() { - info.status = Some(Report::message(text, Severity::Info)); - } - - let result = render_ui( - &mut terminal, - engine_state, - stack, - ctrlc, - pager, - &mut info, - view, - commands, - )?; - - // restore terminal - disable_raw_mode()?; - execute!(io::stdout(), LeaveAlternateScreen)?; - - Ok(result) -} - #[allow(clippy::too_many_arguments)] fn render_ui( term: &mut Terminal, @@ -440,15 +420,20 @@ fn run_command( Command::View { mut cmd, stackable } => { // what we do we just replace the view. let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit()); - let mut new_view = cmd.spawn(engine_state, stack, value)?; + let view_cfg = ViewConfig::new( + pager.config.nu_config, + pager.config.explore_config, + pager.config.style_computer, + pager.config.lscolors, + ); + + let new_view = cmd.spawn(engine_state, stack, value, &view_cfg)?; if let Some(view) = view_stack.curr_view.take() { if !view.stackable { view_stack.stack.push(view); } } - setup_view(&mut new_view, &pager.config); - view_stack.curr_view = Some(Page::raw(new_view, stackable)); Ok(CmdResult::new(false, true, cmd.name().to_owned())) @@ -456,16 +441,6 @@ fn run_command( } } -fn setup_view(view: &mut Box, cfg: &PagerConfig<'_>) { - let cfg = ViewConfig::new( - cfg.nu_config, - cfg.explore_config, - cfg.style_computer, - cfg.lscolors, - ); - view.setup(cfg); -} - fn set_cursor_cmd_bar(f: &mut Frame, area: Rect, pager: &Pager) { if pager.cmd_buf.is_cmd_input { // todo: deal with a situation where we exceed the bar width diff --git a/crates/nu-explore/src/registry/command.rs b/crates/nu-explore/src/registry/command.rs index 7e0bc131b5..6a3e9a128f 100644 --- a/crates/nu-explore/src/registry/command.rs +++ b/crates/nu-explore/src/registry/command.rs @@ -1,6 +1,6 @@ use crate::{ commands::{SimpleCommand, ViewCommand}, - views::View, + views::{View, ViewConfig}, }; use anyhow::Result; @@ -78,8 +78,9 @@ where engine_state: &nu_protocol::engine::EngineState, stack: &mut nu_protocol::engine::Stack, value: Option, + cfg: &ViewConfig, ) -> Result { - let view = self.0.spawn(engine_state, stack, value)?; + let view = self.0.spawn(engine_state, stack, value, cfg)?; Ok(Box::new(view) as Box) } } diff --git a/crates/nu-explore/src/views/binary/mod.rs b/crates/nu-explore/src/views/binary/mod.rs index f288861be8..2a35b7c180 100644 --- a/crates/nu-explore/src/views/binary/mod.rs +++ b/crates/nu-explore/src/views/binary/mod.rs @@ -40,11 +40,15 @@ struct Settings { } impl BinaryView { - pub fn new(data: Vec) -> Self { + pub fn new(data: Vec, cfg: &ExploreConfig) -> Self { + let settings = settings_from_config(cfg); + // There's gotta be a nicer way of doing this than creating a widget just to count lines + let count_rows = BinaryWidget::new(&data, settings.opts, Default::default()).count_lines(); + Self { data, - cursor: WindowCursor2D::default(), - settings: Settings::default(), + cursor: WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor"), + settings, } } } @@ -87,15 +91,6 @@ impl View for BinaryView { // todo: impl Cursor + peek of a value None } - - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.settings = settings_from_config(cfg.explore_config); - - let count_rows = - BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines(); - // TODO: refactor View so setup() is fallible and we don't have to panic here - self.cursor = WindowCursor2D::new(count_rows, 1).expect("Failed to create XYCursor"); - } } fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 7aab197eb5..bdbf3129db 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -99,8 +99,6 @@ pub trait View { fn exit(&mut self) -> Option { None } - - fn setup(&mut self, _: ViewConfig<'_>) {} } impl View for Box { @@ -131,8 +129,4 @@ impl View for Box { fn show_data(&mut self, i: usize) -> bool { self.as_mut().show_data(i) } - - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.as_mut().setup(cfg) - } } diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index bd3c7e8e0a..4f617686f1 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -36,14 +36,12 @@ pub struct RecordView { } impl RecordView { - pub fn new(columns: Vec, records: Vec>) -> Self { + pub fn new(columns: Vec, records: Vec>, cfg: ExploreConfig) -> Self { Self { layer_stack: vec![RecordLayer::new(columns, records)], mode: UIMode::View, orientation: Orientation::Top, - // TODO: It's kind of gross how this temporarily has an incorrect/default config. - // See if we can pass correct config in through the constructor - cfg: ExploreConfig::default(), + cfg, } } @@ -72,23 +70,6 @@ impl RecordView { .expect("we guarantee that 1 entry is always in a list") } - pub fn get_top_layer_orientation(&mut self) -> Orientation { - self.get_top_layer().orientation - } - - pub fn set_orientation(&mut self, orientation: Orientation) { - self.orientation = orientation; - - // we need to reset all indexes as we can't no more use them. - self.reset_cursors(); - } - - fn reset_cursors(&mut self) { - for layer in &mut self.layer_stack { - layer.reset_cursor(); - } - } - pub fn set_top_layer_orientation(&mut self, orientation: Orientation) { let layer = self.get_top_layer_mut(); layer.orientation = orientation; @@ -299,11 +280,6 @@ impl View for RecordView { fn exit(&mut self) -> Option { Some(build_last_value(self)) } - - // todo: move the method to Command? - fn setup(&mut self, cfg: ViewConfig<'_>) { - self.cfg = cfg.explore_config.clone(); - } } fn get_element_info( diff --git a/crates/nu-explore/src/views/try.rs b/crates/nu-explore/src/views/try.rs index b23ca3e9f4..099e792e38 100644 --- a/crates/nu-explore/src/views/try.rs +++ b/crates/nu-explore/src/views/try.rs @@ -1,5 +1,6 @@ use super::{record::RecordView, util::nu_style_to_tui, Layout, Orientation, View, ViewConfig}; use crate::{ + explore::ExploreConfig, nu_common::{collect_pipeline, run_command_with_value}, pager::{report::Report, Frame, Transition, ViewInfo}, }; @@ -23,17 +24,19 @@ pub struct TryView { table: Option, view_mode: bool, border_color: Style, + config: ExploreConfig, } impl TryView { - pub fn new(input: Value) -> Self { + pub fn new(input: Value, config: ExploreConfig) -> Self { Self { input, table: None, - immediate: false, - border_color: Style::default(), + immediate: config.try_reactive, + border_color: nu_style_to_tui(config.table.separator_style), view_mode: false, command: String::new(), + config, } } @@ -42,7 +45,13 @@ impl TryView { } pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> { - let view = run_command(&self.command, &self.input, engine_state, stack)?; + let view = run_command( + &self.command, + &self.input, + engine_state, + stack, + &self.config, + )?; self.table = Some(view); Ok(()) } @@ -122,7 +131,6 @@ impl View for TryView { f.render_widget(table_block, table_area); if let Some(table) = &mut self.table { - table.setup(cfg); let area = Rect::new( area.x + 2, area.y + 4, @@ -232,20 +240,6 @@ impl View for TryView { fn show_data(&mut self, i: usize) -> bool { self.table.as_mut().map_or(false, |v| v.show_data(i)) } - - fn setup(&mut self, config: ViewConfig<'_>) { - self.border_color = nu_style_to_tui(config.explore_config.table.separator_style); - self.immediate = config.explore_config.try_reactive; - - let mut r = RecordView::new(vec![], vec![]); - r.setup(config); - - if let Some(view) = &mut self.table { - view.setup(config); - view.set_orientation(r.get_top_layer_orientation()); - view.set_top_layer_orientation(r.get_top_layer_orientation()); - } - } } fn run_command( @@ -253,6 +247,7 @@ fn run_command( input: &Value, engine_state: &EngineState, stack: &mut Stack, + config: &ExploreConfig, ) -> Result { let pipeline = run_command_with_value(command, input, engine_state, stack)?; @@ -260,7 +255,7 @@ fn run_command( let (columns, values) = collect_pipeline(pipeline)?; - let mut view = RecordView::new(columns, values); + let mut view = RecordView::new(columns, values, config.clone()); if is_record { view.set_top_layer_orientation(Orientation::Left); } From 152fb5be39dc9e37f3dd5ede2bc7eba11cc6d810 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Mon, 8 Jul 2024 00:43:22 +0800 Subject: [PATCH 15/63] Fix PWD-aware command hints (#13024) This PR fixes PWD-aware command hints by sending PWD to the Reedline state in every REPL loop. This PR should be merged along with https://github.com/nushell/reedline/pull/796. Fixes https://github.com/nushell/nushell/issues/12951. --- crates/nu-cli/src/repl.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 251876022a..d2de8f4bea 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -336,6 +336,13 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { .with_quick_completions(config.quick_completions) .with_partial_completions(config.partial_completions) .with_ansi_colors(config.use_ansi_coloring) + .with_cwd(Some( + engine_state + .cwd(None) + .unwrap_or_default() + .to_string_lossy() + .to_string(), + )) .with_cursor_config(cursor_config); perf!("reedline builder", start_time, use_color); @@ -666,13 +673,14 @@ fn prepare_history_metadata( line_editor: &mut Reedline, ) { if !s.is_empty() && line_editor.has_last_command_context() { - #[allow(deprecated)] let result = line_editor .update_last_command_context(&|mut c| { c.start_timestamp = Some(chrono::Utc::now()); c.hostname = hostname.map(str::to_string); - - c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd()); + c.cwd = engine_state + .cwd(None) + .ok() + .map(|path| path.to_string_lossy().to_string()); c }) .into_diagnostic(); From c6b6b1b7a87481305d502d1932a19322bfaaaf0a Mon Sep 17 00:00:00 2001 From: Justin Ma Date: Mon, 8 Jul 2024 06:15:03 +0800 Subject: [PATCH 16/63] Upgrade Ubuntu runners to 22.04 to fix nightly build errors, fix #13255 (#13273) # Description Upgrade Ubuntu runners to 22.04 to fix nightly build errors related to `GLIBC` versions like: https://github.com/nushell/nushell/actions/runs/9727979044/job/26848189346 Should close #13255 BTW: The [workflow of `nushell/nightly`](https://github.com/nushell/nightly/blob/6faf3c3aed728b641701add604cf58f28a87e67f/.github/workflows/nightly-build.yml#L108) has been upgraded a long time ago # User-Facing Changes Older linux system users can download `*-x86_64-unknown-linux-musl.tar.gz` binaries if any `GLIBC` error found while starting `nu` binary --- .github/workflows/nightly-build.yml | 8 ++++---- .github/workflows/release.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 8097b02fe6..9b7f44feba 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -99,13 +99,13 @@ jobs: extra: msi os: windows-latest - target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: x86_64-unknown-linux-musl - os: ubuntu-20.04 + os: ubuntu-22.04 - target: aarch64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: armv7-unknown-linux-gnueabihf - os: ubuntu-20.04 + os: ubuntu-22.04 - target: riscv64gc-unknown-linux-gnu os: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd3371e0d3..34cad24fbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,13 +49,13 @@ jobs: extra: msi os: windows-latest - target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: x86_64-unknown-linux-musl - os: ubuntu-20.04 + os: ubuntu-22.04 - target: aarch64-unknown-linux-gnu - os: ubuntu-20.04 + os: ubuntu-22.04 - target: armv7-unknown-linux-gnueabihf - os: ubuntu-20.04 + os: ubuntu-22.04 - target: riscv64gc-unknown-linux-gnu os: ubuntu-latest From 399a7c8836488db9fd2601404eb154fddf55e48a Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Sun, 7 Jul 2024 22:29:01 +0000 Subject: [PATCH 17/63] Add and use new `Signals` struct (#13314) # Description This PR introduces a new `Signals` struct to replace our adhoc passing around of `ctrlc: Option>`. Doing so has a few benefits: - We can better enforce when/where resetting or triggering an interrupt is allowed. - Consolidates `nu_utils::ctrl_c::was_pressed` and other ad-hoc re-implementations into a single place: `Signals::check`. - This allows us to add other types of signals later if we want. E.g., exiting or suspension. - Similarly, we can more easily change the underlying implementation if we need to in the future. - Places that used to have a `ctrlc` of `None` now use `Signals::empty()`, so we can double check these usages for correctness in the future. --- benches/benchmarks.rs | 21 ++- .../nu-cli/src/commands/history/history_.rs | 6 +- crates/nu-cli/src/nu_highlight.rs | 4 +- crates/nu-cli/src/repl.rs | 9 +- crates/nu-cmd-base/src/input_handler.rs | 10 +- crates/nu-cmd-extra/src/extra/bits/and.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/into.rs | 38 ++-- crates/nu-cmd-extra/src/extra/bits/not.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/or.rs | 2 +- .../src/extra/bits/rotate_left.rs | 2 +- .../src/extra/bits/rotate_right.rs | 2 +- .../nu-cmd-extra/src/extra/bits/shift_left.rs | 2 +- .../src/extra/bits/shift_right.rs | 2 +- crates/nu-cmd-extra/src/extra/bits/xor.rs | 2 +- .../nu-cmd-extra/src/extra/conversions/fmt.rs | 2 +- .../src/extra/filters/each_while.rs | 4 +- .../src/extra/filters/update_cells.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arccos.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arccosh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/arcsin.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arcsinh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/arctan.rs | 2 +- crates/nu-cmd-extra/src/extra/math/arctanh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/cos.rs | 2 +- crates/nu-cmd-extra/src/extra/math/cosh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/exp.rs | 5 +- crates/nu-cmd-extra/src/extra/math/ln.rs | 5 +- crates/nu-cmd-extra/src/extra/math/sin.rs | 2 +- crates/nu-cmd-extra/src/extra/math/sinh.rs | 5 +- crates/nu-cmd-extra/src/extra/math/tan.rs | 2 +- crates/nu-cmd-extra/src/extra/math/tanh.rs | 5 +- .../src/extra/platform/ansi/gradient.rs | 2 +- .../src/extra/strings/encode_decode/hex.rs | 2 +- .../src/extra/strings/format/command.rs | 2 +- .../src/extra/strings/str_/case/mod.rs | 2 +- crates/nu-cmd-lang/src/core_commands/for_.rs | 10 +- crates/nu-cmd-lang/src/core_commands/loop_.rs | 5 +- .../nu-cmd-lang/src/core_commands/while_.rs | 5 +- crates/nu-command/src/bytes/add.rs | 2 +- crates/nu-command/src/bytes/at.rs | 2 +- crates/nu-command/src/bytes/collect.rs | 7 +- crates/nu-command/src/bytes/ends_with.rs | 2 +- crates/nu-command/src/bytes/index_of.rs | 2 +- crates/nu-command/src/bytes/length.rs | 2 +- crates/nu-command/src/bytes/remove.rs | 2 +- crates/nu-command/src/bytes/replace.rs | 2 +- crates/nu-command/src/bytes/reverse.rs | 2 +- crates/nu-command/src/bytes/starts_with.rs | 2 +- crates/nu-command/src/conversions/fill.rs | 2 +- .../nu-command/src/conversions/into/binary.rs | 2 +- .../nu-command/src/conversions/into/bool.rs | 2 +- .../src/conversions/into/datetime.rs | 2 +- .../src/conversions/into/duration.rs | 2 +- .../src/conversions/into/filesize.rs | 2 +- .../nu-command/src/conversions/into/float.rs | 2 +- .../nu-command/src/conversions/into/glob.rs | 2 +- crates/nu-command/src/conversions/into/int.rs | 2 +- .../nu-command/src/conversions/into/record.rs | 2 +- .../nu-command/src/conversions/into/string.rs | 2 +- .../nu-command/src/conversions/into/value.rs | 8 +- .../src/database/commands/into_sqlite.rs | 46 ++--- .../nu-command/src/database/values/sqlite.rs | 149 +++++++-------- crates/nu-command/src/date/humanize.rs | 2 +- crates/nu-command/src/date/list_timezone.rs | 2 +- crates/nu-command/src/date/to_record.rs | 2 +- crates/nu-command/src/date/to_table.rs | 2 +- crates/nu-command/src/date/to_timezone.rs | 2 +- crates/nu-command/src/debug/debug_.rs | 2 +- crates/nu-command/src/debug/metadata_set.rs | 12 +- crates/nu-command/src/filesystem/du.rs | 27 ++- crates/nu-command/src/filesystem/glob.rs | 30 ++- crates/nu-command/src/filesystem/ls.rs | 23 +-- crates/nu-command/src/filesystem/open.rs | 13 +- crates/nu-command/src/filesystem/rm.rs | 7 +- crates/nu-command/src/filesystem/save.rs | 35 ++-- crates/nu-command/src/filesystem/watch.rs | 3 +- crates/nu-command/src/filters/append.rs | 2 +- crates/nu-command/src/filters/compact.rs | 2 +- crates/nu-command/src/filters/default.rs | 6 +- crates/nu-command/src/filters/drop/column.rs | 6 +- crates/nu-command/src/filters/drop/nth.rs | 4 +- crates/nu-command/src/filters/each.rs | 6 +- crates/nu-command/src/filters/enumerate.rs | 3 +- crates/nu-command/src/filters/every.rs | 2 +- crates/nu-command/src/filters/filter.rs | 6 +- crates/nu-command/src/filters/find.rs | 17 +- crates/nu-command/src/filters/first.rs | 14 +- crates/nu-command/src/filters/flatten.rs | 2 +- crates/nu-command/src/filters/get.rs | 5 +- crates/nu-command/src/filters/group.rs | 9 +- crates/nu-command/src/filters/insert.rs | 6 +- crates/nu-command/src/filters/interleave.rs | 2 +- crates/nu-command/src/filters/items.rs | 2 +- crates/nu-command/src/filters/last.rs | 6 +- crates/nu-command/src/filters/lines.rs | 3 +- crates/nu-command/src/filters/merge.rs | 7 +- crates/nu-command/src/filters/move_.rs | 7 +- crates/nu-command/src/filters/par_each.rs | 15 +- crates/nu-command/src/filters/prepend.rs | 2 +- crates/nu-command/src/filters/range.rs | 4 +- crates/nu-command/src/filters/reduce.rs | 5 +- crates/nu-command/src/filters/rename.rs | 2 +- crates/nu-command/src/filters/reverse.rs | 2 +- crates/nu-command/src/filters/select.rs | 10 +- crates/nu-command/src/filters/shuffle.rs | 6 +- crates/nu-command/src/filters/skip/skip_.rs | 16 +- .../nu-command/src/filters/skip/skip_until.rs | 2 +- .../nu-command/src/filters/skip/skip_while.rs | 2 +- crates/nu-command/src/filters/sort.rs | 2 +- crates/nu-command/src/filters/sort_by.rs | 2 +- crates/nu-command/src/filters/take/take_.rs | 18 +- .../nu-command/src/filters/take/take_until.rs | 2 +- .../nu-command/src/filters/take/take_while.rs | 2 +- crates/nu-command/src/filters/tee.rs | 40 ++-- crates/nu-command/src/filters/transpose.rs | 7 +- crates/nu-command/src/filters/uniq.rs | 4 +- crates/nu-command/src/filters/update.rs | 6 +- crates/nu-command/src/filters/upsert.rs | 6 +- crates/nu-command/src/filters/utils.rs | 5 +- crates/nu-command/src/filters/values.rs | 10 +- crates/nu-command/src/filters/where_.rs | 2 +- crates/nu-command/src/filters/window.rs | 7 +- crates/nu-command/src/filters/wrap.rs | 2 +- crates/nu-command/src/filters/zip.rs | 2 +- .../nu-command/src/formats/from/delimited.rs | 8 +- crates/nu-command/src/formats/from/json.rs | 20 +- crates/nu-command/src/formats/from/msgpack.rs | 10 +- .../nu-command/src/formats/from/msgpackz.rs | 2 +- crates/nu-command/src/formats/to/delimited.rs | 63 ++++--- crates/nu-command/src/formats/to/msgpack.rs | 4 +- crates/nu-command/src/formats/to/text.rs | 2 +- crates/nu-command/src/generators/generate.rs | 2 +- crates/nu-command/src/generators/seq.rs | 4 +- crates/nu-command/src/hash/generic_digest.rs | 2 +- crates/nu-command/src/math/abs.rs | 5 +- crates/nu-command/src/math/ceil.rs | 5 +- crates/nu-command/src/math/floor.rs | 5 +- crates/nu-command/src/math/log.rs | 2 +- crates/nu-command/src/math/round.rs | 2 +- crates/nu-command/src/math/sqrt.rs | 5 +- crates/nu-command/src/math/utils.rs | 4 +- crates/nu-command/src/network/http/client.rs | 68 ++++--- crates/nu-command/src/network/http/delete.rs | 9 +- crates/nu-command/src/network/http/get.rs | 9 +- crates/nu-command/src/network/http/head.rs | 13 +- crates/nu-command/src/network/http/options.rs | 9 +- crates/nu-command/src/network/http/patch.rs | 9 +- crates/nu-command/src/network/http/post.rs | 9 +- crates/nu-command/src/network/http/put.rs | 9 +- crates/nu-command/src/network/url/decode.rs | 2 +- crates/nu-command/src/network/url/encode.rs | 10 +- crates/nu-command/src/path/basename.rs | 4 +- crates/nu-command/src/path/dirname.rs | 4 +- crates/nu-command/src/path/exists.rs | 4 +- crates/nu-command/src/path/expand.rs | 4 +- crates/nu-command/src/path/parse.rs | 4 +- crates/nu-command/src/path/relative_to.rs | 4 +- crates/nu-command/src/path/split.rs | 4 +- crates/nu-command/src/path/type.rs | 4 +- crates/nu-command/src/platform/ansi/ansi_.rs | 25 +-- crates/nu-command/src/platform/ansi/link.rs | 4 +- crates/nu-command/src/platform/ansi/strip.rs | 2 +- crates/nu-command/src/platform/dir_info.rs | 31 ++- crates/nu-command/src/platform/sleep.rs | 7 +- crates/nu-command/src/random/dice.rs | 2 +- crates/nu-command/src/stor/create.rs | 27 ++- crates/nu-command/src/stor/delete.rs | 6 +- crates/nu-command/src/stor/export.rs | 6 +- crates/nu-command/src/stor/import.rs | 6 +- crates/nu-command/src/stor/insert.rs | 36 +++- crates/nu-command/src/stor/open.rs | 6 +- crates/nu-command/src/stor/reset.rs | 6 +- crates/nu-command/src/stor/update.rs | 6 +- crates/nu-command/src/strings/char_.rs | 18 +- .../nu-command/src/strings/detect_columns.rs | 7 +- .../src/strings/encode_decode/base64.rs | 2 +- crates/nu-command/src/strings/format/date.rs | 2 +- .../nu-command/src/strings/format/duration.rs | 4 +- .../nu-command/src/strings/format/filesize.rs | 4 +- crates/nu-command/src/strings/parse.rs | 23 +-- crates/nu-command/src/strings/split/chars.rs | 2 +- crates/nu-command/src/strings/split/column.rs | 2 +- crates/nu-command/src/strings/split/list.rs | 4 +- crates/nu-command/src/strings/split/row.rs | 2 +- crates/nu-command/src/strings/split/words.rs | 2 +- .../src/strings/str_/case/capitalize.rs | 2 +- .../src/strings/str_/case/downcase.rs | 2 +- .../nu-command/src/strings/str_/case/mod.rs | 2 +- .../src/strings/str_/case/upcase.rs | 2 +- .../nu-command/src/strings/str_/contains.rs | 4 +- .../nu-command/src/strings/str_/deunicode.rs | 4 +- .../nu-command/src/strings/str_/distance.rs | 4 +- .../nu-command/src/strings/str_/ends_with.rs | 4 +- crates/nu-command/src/strings/str_/expand.rs | 2 +- .../nu-command/src/strings/str_/index_of.rs | 4 +- crates/nu-command/src/strings/str_/join.rs | 50 ++--- crates/nu-command/src/strings/str_/length.rs | 2 +- crates/nu-command/src/strings/str_/replace.rs | 4 +- crates/nu-command/src/strings/str_/reverse.rs | 4 +- .../src/strings/str_/starts_with.rs | 4 +- crates/nu-command/src/strings/str_/stats.rs | 2 +- .../nu-command/src/strings/str_/substring.rs | 4 +- .../nu-command/src/strings/str_/trim/trim_.rs | 2 +- crates/nu-command/src/system/ps.rs | 2 +- .../nu-command/src/system/registry_query.rs | 2 +- crates/nu-command/src/system/run_external.rs | 29 ++- crates/nu-command/src/system/which_.rs | 5 +- crates/nu-command/src/viewers/table.rs | 145 +++++++------- crates/nu-engine/src/eval.rs | 4 +- crates/nu-explore/src/commands/expand.rs | 3 +- crates/nu-explore/src/explore.rs | 3 +- crates/nu-explore/src/lib.rs | 11 +- crates/nu-explore/src/nu_common/mod.rs | 2 - crates/nu-explore/src/nu_common/table.rs | 17 +- crates/nu-explore/src/pager/mod.rs | 13 +- crates/nu-lsp/src/lib.rs | 19 +- crates/nu-plugin-core/src/interface/mod.rs | 10 +- crates/nu-plugin-core/src/interface/tests.rs | 20 +- crates/nu-plugin-engine/src/context.rs | 20 +- crates/nu-plugin-engine/src/interface/mod.rs | 28 +-- .../nu-plugin-engine/src/interface/tests.rs | 30 +-- crates/nu-plugin-test-support/src/lib.rs | 8 +- .../nu-plugin-test-support/src/plugin_test.rs | 10 +- .../tests/lowercase/mod.rs | 9 +- crates/nu-plugin/src/plugin/command.rs | 4 +- crates/nu-plugin/src/plugin/interface/mod.rs | 8 +- .../nu-plugin/src/plugin/interface/tests.rs | 18 +- crates/nu-protocol/src/engine/engine_state.rs | 18 +- crates/nu-protocol/src/errors/shell_error.rs | 7 + .../nu-protocol/src/pipeline/byte_stream.rs | 178 ++++++++---------- .../nu-protocol/src/pipeline/list_stream.rs | 29 ++- crates/nu-protocol/src/pipeline/mod.rs | 2 + .../nu-protocol/src/pipeline/pipeline_data.rs | 112 +++++------ crates/nu-protocol/src/pipeline/signals.rs | 76 ++++++++ crates/nu-protocol/src/value/mod.rs | 7 +- crates/nu-protocol/src/value/range.rs | 53 ++---- crates/nu-table/src/types/expanded.rs | 16 +- crates/nu-table/src/types/general.rs | 12 +- crates/nu-table/src/types/mod.rs | 9 +- crates/nu-utils/src/ctrl_c.rs | 13 -- crates/nu-utils/src/lib.rs | 1 - .../src/commands/collect_bytes.rs | 6 +- .../src/commands/generate.rs | 6 +- crates/nu_plugin_example/src/commands/seq.rs | 5 +- src/main.rs | 11 +- src/signals.rs | 13 +- 246 files changed, 1332 insertions(+), 1234 deletions(-) create mode 100644 crates/nu-protocol/src/pipeline/signals.rs delete mode 100644 crates/nu-utils/src/ctrl_c.rs diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 7a23715c8d..ea296f0d06 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -4,11 +4,14 @@ use nu_plugin_protocol::{PluginCallResponse, PluginOutput}; use nu_protocol::{ engine::{EngineState, Stack}, - PipelineData, Span, Spanned, Value, + PipelineData, Signals, Span, Spanned, Value, }; use nu_std::load_standard_library; use nu_utils::{get_default_config, get_default_env}; -use std::rc::Rc; +use std::{ + rc::Rc, + sync::{atomic::AtomicBool, Arc}, +}; use std::hint::black_box; @@ -248,14 +251,12 @@ fn bench_eval_interleave(n: i32) -> impl IntoBenchmarks { ) } -fn bench_eval_interleave_with_ctrlc(n: i32) -> impl IntoBenchmarks { +fn bench_eval_interleave_with_interrupt(n: i32) -> impl IntoBenchmarks { let mut engine = setup_engine(); - engine.ctrlc = Some(std::sync::Arc::new(std::sync::atomic::AtomicBool::new( - false, - ))); + engine.set_signals(Signals::new(Arc::new(AtomicBool::new(false)))); let stack = Stack::new(); bench_command( - &format!("eval_interleave_with_ctrlc_{n}"), + &format!("eval_interleave_with_interrupt_{n}"), &format!("seq 1 {n} | wrap a | interleave {{ seq 1 {n} | wrap b }} | ignore"), stack, engine, @@ -443,9 +444,9 @@ tango_benchmarks!( bench_eval_interleave(100), bench_eval_interleave(1_000), bench_eval_interleave(10_000), - bench_eval_interleave_with_ctrlc(100), - bench_eval_interleave_with_ctrlc(1_000), - bench_eval_interleave_with_ctrlc(10_000), + bench_eval_interleave_with_interrupt(100), + bench_eval_interleave_with_interrupt(1_000), + bench_eval_interleave_with_interrupt(10_000), // For bench_eval_for(1), bench_eval_for(10), diff --git a/crates/nu-cli/src/commands/history/history_.rs b/crates/nu-cli/src/commands/history/history_.rs index 8b0714216e..cdf85eea72 100644 --- a/crates/nu-cli/src/commands/history/history_.rs +++ b/crates/nu-cli/src/commands/history/history_.rs @@ -47,7 +47,7 @@ impl Command for History { if let Some(config_path) = nu_path::config_dir() { let clear = call.has_flag(engine_state, stack, "clear")?; let long = call.has_flag(engine_state, stack, "long")?; - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals().clone(); let mut history_path = config_path; history_path.push("nushell"); @@ -107,7 +107,7 @@ impl Command for History { file: history_path.display().to_string(), span: head, })? - .into_pipeline_data(head, ctrlc)), + .into_pipeline_data(head, signals)), HistoryFileFormat::Sqlite => Ok(history_reader .and_then(|h| { h.search(SearchQuery::everything(SearchDirection::Forward, None)) @@ -122,7 +122,7 @@ impl Command for History { file: history_path.display().to_string(), span: head, })? - .into_pipeline_data(head, ctrlc)), + .into_pipeline_data(head, signals)), } } } else { diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs index 07084c6258..f4f38296de 100644 --- a/crates/nu-cli/src/nu_highlight.rs +++ b/crates/nu-cli/src/nu_highlight.rs @@ -32,7 +32,7 @@ impl Command for NuHighlight { ) -> Result { let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals(); let engine_state = std::sync::Arc::new(engine_state.clone()); let config = engine_state.get_config().clone(); @@ -50,7 +50,7 @@ impl Command for NuHighlight { } Err(err) => Value::error(err, head), }, - ctrlc, + signals, ) } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index d2de8f4bea..5b0db96741 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -43,7 +43,7 @@ use std::{ io::{self, IsTerminal, Write}, panic::{catch_unwind, AssertUnwindSafe}, path::{Path, PathBuf}, - sync::{atomic::Ordering, Arc}, + sync::Arc, time::{Duration, Instant}, }; use sysinfo::System; @@ -271,11 +271,8 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("merge env", start_time, use_color); start_time = std::time::Instant::now(); - // Reset the ctrl-c handler - if let Some(ctrlc) = &mut engine_state.ctrlc { - ctrlc.store(false, Ordering::SeqCst); - } - perf!("reset ctrlc", start_time, use_color); + engine_state.reset_signals(); + perf!("reset signals", start_time, use_color); start_time = std::time::Instant::now(); // Right before we start our prompt and take input from the user, diff --git a/crates/nu-cmd-base/src/input_handler.rs b/crates/nu-cmd-base/src/input_handler.rs index d81193e190..7d61f90cb0 100644 --- a/crates/nu-cmd-base/src/input_handler.rs +++ b/crates/nu-cmd-base/src/input_handler.rs @@ -1,5 +1,5 @@ -use nu_protocol::{ast::CellPath, PipelineData, ShellError, Span, Value}; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::{ast::CellPath, PipelineData, ShellError, Signals, Span, Value}; +use std::sync::Arc; pub trait CmdArgument { fn take_cell_paths(&mut self) -> Option>; @@ -40,7 +40,7 @@ pub fn operate( mut arg: A, input: PipelineData, span: Span, - ctrlc: Option>, + signals: &Signals, ) -> Result where A: CmdArgument + Send + Sync + 'static, @@ -55,7 +55,7 @@ where _ => cmd(&v, &arg, span), } }, - ctrlc, + signals, ), Some(column_paths) => { let arg = Arc::new(arg); @@ -79,7 +79,7 @@ where } v }, - ctrlc, + signals, ) } } diff --git a/crates/nu-cmd-extra/src/extra/bits/and.rs b/crates/nu-cmd-extra/src/extra/bits/and.rs index 538cf6e60f..234b4e5cc1 100644 --- a/crates/nu-cmd-extra/src/extra/bits/and.rs +++ b/crates/nu-cmd-extra/src/extra/bits/and.rs @@ -79,7 +79,7 @@ impl Command for BitsAnd { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l & r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index 9891a971f6..9ff8bd0a05 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -3,6 +3,7 @@ use std::io::{self, Read, Write}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use num_traits::ToPrimitive; pub struct Arguments { @@ -127,31 +128,36 @@ fn into_bits( )) } else { let args = Arguments { cell_paths }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } fn byte_stream_to_bits(stream: ByteStream, head: Span) -> ByteStream { if let Some(mut reader) = stream.reader() { let mut is_first = true; - ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { - let mut byte = [0]; - if reader.read(&mut byte[..]).err_span(head)? > 0 { - // Format the byte as bits - if is_first { - is_first = false; + ByteStream::from_fn( + head, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + let mut byte = [0]; + if reader.read(&mut byte[..]).err_span(head)? > 0 { + // Format the byte as bits + if is_first { + is_first = false; + } else { + buffer.push(b' '); + } + write!(buffer, "{:08b}", byte[0]).expect("format failed"); + Ok(true) } else { - buffer.push(b' '); + // EOF + Ok(false) } - write!(buffer, "{:08b}", byte[0]).expect("format failed"); - Ok(true) - } else { - // EOF - Ok(false) - } - }) + }, + ) } else { - ByteStream::read(io::empty(), head, None, ByteStreamType::String) + ByteStream::read(io::empty(), head, Signals::empty(), ByteStreamType::String) } } diff --git a/crates/nu-cmd-extra/src/extra/bits/not.rs b/crates/nu-cmd-extra/src/extra/bits/not.rs index e4c344f137..405cc79d7e 100644 --- a/crates/nu-cmd-extra/src/extra/bits/not.rs +++ b/crates/nu-cmd-extra/src/extra/bits/not.rs @@ -82,7 +82,7 @@ impl Command for BitsNot { number_size, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/or.rs b/crates/nu-cmd-extra/src/extra/bits/or.rs index 2352d65c23..a0af2dc8d0 100644 --- a/crates/nu-cmd-extra/src/extra/bits/or.rs +++ b/crates/nu-cmd-extra/src/extra/bits/or.rs @@ -80,7 +80,7 @@ impl Command for BitsOr { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l | r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs index cbd9d17eb5..5bb9e42f6b 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_left.rs @@ -86,7 +86,7 @@ impl Command for BitsRol { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs index 0aea603ce1..31e17891a3 100644 --- a/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/rotate_right.rs @@ -86,7 +86,7 @@ impl Command for BitsRor { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs index 049408c24a..6a67a45e0e 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_left.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_left.rs @@ -88,7 +88,7 @@ impl Command for BitsShl { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs index d66db68ee5..e45e10ac94 100644 --- a/crates/nu-cmd-extra/src/extra/bits/shift_right.rs +++ b/crates/nu-cmd-extra/src/extra/bits/shift_right.rs @@ -88,7 +88,7 @@ impl Command for BitsShr { bits, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/bits/xor.rs b/crates/nu-cmd-extra/src/extra/bits/xor.rs index 65c3be4e1a..4a71487137 100644 --- a/crates/nu-cmd-extra/src/extra/bits/xor.rs +++ b/crates/nu-cmd-extra/src/extra/bits/xor.rs @@ -80,7 +80,7 @@ impl Command for BitsXor { input.map( move |value| binary_op(&value, &target, little_endian, |(l, r)| l ^ r, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs index fec0745dac..3b682291e7 100644 --- a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs +++ b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs @@ -59,7 +59,7 @@ fn fmt( ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { diff --git a/crates/nu-cmd-extra/src/extra/filters/each_while.rs b/crates/nu-cmd-extra/src/extra/filters/each_while.rs index 58679c8eea..1f30e5b9b9 100644 --- a/crates/nu-cmd-extra/src/extra/filters/each_while.rs +++ b/crates/nu-cmd-extra/src/extra/filters/each_while.rs @@ -89,7 +89,7 @@ impl Command for EachWhile { } }) .fuse() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { let span = stream.span(); @@ -107,7 +107,7 @@ impl Command for EachWhile { } }) .fuse() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } diff --git a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs index c90e933410..b102c13c0d 100644 --- a/crates/nu-cmd-extra/src/extra/filters/update_cells.rs +++ b/crates/nu-cmd-extra/src/extra/filters/update_cells.rs @@ -108,7 +108,7 @@ impl Command for UpdateCells { columns, span: head, } - .into_pipeline_data(head, engine_state.ctrlc.clone()) + .into_pipeline_data(head, engine_state.signals().clone()) .set_metadata(metadata)) } } diff --git a/crates/nu-cmd-extra/src/extra/math/arccos.rs b/crates/nu-cmd-extra/src/extra/math/arccos.rs index 120fc4df98..5d0a659380 100644 --- a/crates/nu-cmd-extra/src/extra/math/arccos.rs +++ b/crates/nu-cmd-extra/src/extra/math/arccos.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arccosh.rs b/crates/nu-cmd-extra/src/extra/math/arccosh.rs index 30e0d2cfb6..532a388b3f 100644 --- a/crates/nu-cmd-extra/src/extra/math/arccosh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arccosh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/arcsin.rs b/crates/nu-cmd-extra/src/extra/math/arcsin.rs index a68e0648ef..a85c438367 100644 --- a/crates/nu-cmd-extra/src/extra/math/arcsin.rs +++ b/crates/nu-cmd-extra/src/extra/math/arcsin.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arcsinh.rs b/crates/nu-cmd-extra/src/extra/math/arcsinh.rs index 67addfdba2..91cb814a32 100644 --- a/crates/nu-cmd-extra/src/extra/math/arcsinh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arcsinh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/arctan.rs b/crates/nu-cmd-extra/src/extra/math/arctan.rs index 9c14203312..f52c8bd40c 100644 --- a/crates/nu-cmd-extra/src/extra/math/arctan.rs +++ b/crates/nu-cmd-extra/src/extra/math/arctan.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/arctanh.rs b/crates/nu-cmd-extra/src/extra/math/arctanh.rs index 920e56eeb6..7791b56948 100644 --- a/crates/nu-cmd-extra/src/extra/math/arctanh.rs +++ b/crates/nu-cmd-extra/src/extra/math/arctanh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/cos.rs b/crates/nu-cmd-extra/src/extra/math/cos.rs index 633c131b8b..252c7dbbd6 100644 --- a/crates/nu-cmd-extra/src/extra/math/cos.rs +++ b/crates/nu-cmd-extra/src/extra/math/cos.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/cosh.rs b/crates/nu-cmd-extra/src/extra/math/cosh.rs index a772540b5c..e46d3c4df8 100644 --- a/crates/nu-cmd-extra/src/extra/math/cosh.rs +++ b/crates/nu-cmd-extra/src/extra/math/cosh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/exp.rs b/crates/nu-cmd-extra/src/extra/math/exp.rs index b89d6f553f..d8f6a52899 100644 --- a/crates/nu-cmd-extra/src/extra/math/exp.rs +++ b/crates/nu-cmd-extra/src/extra/math/exp.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/ln.rs b/crates/nu-cmd-extra/src/extra/math/ln.rs index dd9782b467..694192bc8e 100644 --- a/crates/nu-cmd-extra/src/extra/math/ln.rs +++ b/crates/nu-cmd-extra/src/extra/math/ln.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/sin.rs b/crates/nu-cmd-extra/src/extra/math/sin.rs index 883007d1ed..0caedbabe7 100644 --- a/crates/nu-cmd-extra/src/extra/math/sin.rs +++ b/crates/nu-cmd-extra/src/extra/math/sin.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/sinh.rs b/crates/nu-cmd-extra/src/extra/math/sinh.rs index c768dba739..d40db3bcb5 100644 --- a/crates/nu-cmd-extra/src/extra/math/sinh.rs +++ b/crates/nu-cmd-extra/src/extra/math/sinh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/math/tan.rs b/crates/nu-cmd-extra/src/extra/math/tan.rs index e10807279d..97c5d2ff93 100644 --- a/crates/nu-cmd-extra/src/extra/math/tan.rs +++ b/crates/nu-cmd-extra/src/extra/math/tan.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, use_degrees), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/math/tanh.rs b/crates/nu-cmd-extra/src/extra/math/tanh.rs index 4d09f93cf4..6679d04fe5 100644 --- a/crates/nu-cmd-extra/src/extra/math/tanh.rs +++ b/crates/nu-cmd-extra/src/extra/math/tanh.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs index 21c7e42a61..5934b57a5d 100644 --- a/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs +++ b/crates/nu-cmd-extra/src/extra/platform/ansi/gradient.rs @@ -140,7 +140,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs b/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs index 7628d6e240..be681b382a 100644 --- a/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs +++ b/crates/nu-cmd-extra/src/extra/strings/encode_decode/hex.rs @@ -88,7 +88,7 @@ pub fn operate( cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action( diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 50e89e61e3..08df92ef40 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -220,7 +220,7 @@ fn format( } } - Ok(ListStream::new(list.into_iter(), head_span, engine_state.ctrlc.clone()).into()) + Ok(ListStream::new(list.into_iter(), head_span, engine_state.signals().clone()).into()) } // Unwrapping this ShellError is a bit unfortunate. // Ideally, its Span would be preserved. diff --git a/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs b/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs index 980a1b83fc..bfa54921e2 100644 --- a/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs +++ b/crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs @@ -44,7 +44,7 @@ where case_operation, cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, head: Span) -> Value diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index 9410be74c7..1e90e5f06d 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -1,5 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression}; -use nu_protocol::engine::CommandType; +use nu_protocol::{engine::CommandType, Signals}; #[derive(Clone)] pub struct For; @@ -72,7 +72,6 @@ impl Command for For { let value = eval_expression(engine_state, stack, keyword_expr)?; - let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id); @@ -82,9 +81,7 @@ impl Command for For { match value { Value::List { vals, .. } => { for x in vals.into_iter() { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - break; - } + engine_state.signals().check(head)?; // with_env() is used here to ensure that each iteration uses // a different set of environment variables. @@ -116,7 +113,8 @@ impl Command for For { } } Value::Range { val, .. } => { - for x in val.into_range_iter(span, ctrlc) { + for x in val.into_range_iter(span, Signals::empty()) { + engine_state.signals().check(head)?; stack.add_var(var_id, x); match eval_block(&engine_state, stack, block, PipelineData::empty()) { diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index a9c642ca3c..86e18389de 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -37,6 +37,7 @@ impl Command for Loop { call: &Call, _input: PipelineData, ) -> Result { + let head = call.head; let block_id = call .positional_nth(0) .expect("checked through parser") @@ -49,9 +50,7 @@ impl Command for Loop { let stack = &mut stack.push_redirection(None, None); loop { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(head)?; match eval_block(engine_state, stack, block, PipelineData::empty()) { Err(ShellError::Break { .. }) => { diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index 646b95c82e..22bb4c5dbd 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -46,6 +46,7 @@ impl Command for While { call: &Call, _input: PipelineData, ) -> Result { + let head = call.head; let cond = call.positional_nth(0).expect("checked through parser"); let block_id = call .positional_nth(1) @@ -59,9 +60,7 @@ impl Command for While { let stack = &mut stack.push_redirection(None, None); loop { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(head)?; let result = eval_expression(engine_state, stack, cond)?; diff --git a/crates/nu-command/src/bytes/add.rs b/crates/nu-command/src/bytes/add.rs index 8514718cfd..ab31f74b12 100644 --- a/crates/nu-command/src/bytes/add.rs +++ b/crates/nu-command/src/bytes/add.rs @@ -78,7 +78,7 @@ impl Command for BytesAdd { end, cell_paths, }; - operate(add, arg, input, call.head, engine_state.ctrlc.clone()) + operate(add, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/at.rs b/crates/nu-command/src/bytes/at.rs index c5164c3550..5e95f4fd62 100644 --- a/crates/nu-command/src/bytes/at.rs +++ b/crates/nu-command/src/bytes/at.rs @@ -83,7 +83,7 @@ impl Command for BytesAt { cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/collect.rs b/crates/nu-command/src/bytes/collect.rs index 74ea3e5d14..afb70bfb36 100644 --- a/crates/nu-command/src/bytes/collect.rs +++ b/crates/nu-command/src/bytes/collect.rs @@ -60,7 +60,12 @@ impl Command for BytesCollect { ) .flatten(); - let output = ByteStream::from_result_iter(iter, span, None, ByteStreamType::Binary); + let output = ByteStream::from_result_iter( + iter, + span, + engine_state.signals().clone(), + ByteStreamType::Binary, + ); Ok(PipelineData::ByteStream(output, metadata)) } diff --git a/crates/nu-command/src/bytes/ends_with.rs b/crates/nu-command/src/bytes/ends_with.rs index d6174a189c..8e3966716c 100644 --- a/crates/nu-command/src/bytes/ends_with.rs +++ b/crates/nu-command/src/bytes/ends_with.rs @@ -102,7 +102,7 @@ impl Command for BytesEndsWith { pattern, cell_paths, }; - operate(ends_with, arg, input, head, engine_state.ctrlc.clone()) + operate(ends_with, arg, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/bytes/index_of.rs b/crates/nu-command/src/bytes/index_of.rs index bdf51b24d9..e10bd6c200 100644 --- a/crates/nu-command/src/bytes/index_of.rs +++ b/crates/nu-command/src/bytes/index_of.rs @@ -71,7 +71,7 @@ impl Command for BytesIndexOf { all: call.has_flag(engine_state, stack, "all")?, cell_paths, }; - operate(index_of, arg, input, call.head, engine_state.ctrlc.clone()) + operate(index_of, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/length.rs b/crates/nu-command/src/bytes/length.rs index aaaf23e0a5..78b3d31eac 100644 --- a/crates/nu-command/src/bytes/length.rs +++ b/crates/nu-command/src/bytes/length.rs @@ -46,7 +46,7 @@ impl Command for BytesLen { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 1)?; let arg = CellPathOnlyArgs::from(cell_paths); - operate(length, arg, input, call.head, engine_state.ctrlc.clone()) + operate(length, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/remove.rs b/crates/nu-command/src/bytes/remove.rs index 9afef07e8b..34d3c427f4 100644 --- a/crates/nu-command/src/bytes/remove.rs +++ b/crates/nu-command/src/bytes/remove.rs @@ -73,7 +73,7 @@ impl Command for BytesRemove { all: call.has_flag(engine_state, stack, "all")?, }; - operate(remove, arg, input, call.head, engine_state.ctrlc.clone()) + operate(remove, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/replace.rs b/crates/nu-command/src/bytes/replace.rs index ab7ede7588..db2b6fb790 100644 --- a/crates/nu-command/src/bytes/replace.rs +++ b/crates/nu-command/src/bytes/replace.rs @@ -73,7 +73,7 @@ impl Command for BytesReplace { all: call.has_flag(engine_state, stack, "all")?, }; - operate(replace, arg, input, call.head, engine_state.ctrlc.clone()) + operate(replace, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/reverse.rs b/crates/nu-command/src/bytes/reverse.rs index 171add213d..fd769eaa40 100644 --- a/crates/nu-command/src/bytes/reverse.rs +++ b/crates/nu-command/src/bytes/reverse.rs @@ -42,7 +42,7 @@ impl Command for BytesReverse { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let arg = CellPathOnlyArgs::from(cell_paths); - operate(reverse, arg, input, call.head, engine_state.ctrlc.clone()) + operate(reverse, arg, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/bytes/starts_with.rs b/crates/nu-command/src/bytes/starts_with.rs index 92cc16f02c..89cc4f7afc 100644 --- a/crates/nu-command/src/bytes/starts_with.rs +++ b/crates/nu-command/src/bytes/starts_with.rs @@ -79,7 +79,7 @@ impl Command for BytesStartsWith { pattern, cell_paths, }; - operate(starts_with, arg, input, head, engine_state.ctrlc.clone()) + operate(starts_with, arg, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/fill.rs b/crates/nu-command/src/conversions/fill.rs index 6507e4a368..eaf8c05da1 100644 --- a/crates/nu-command/src/conversions/fill.rs +++ b/crates/nu-command/src/conversions/fill.rs @@ -165,7 +165,7 @@ fn fill( cell_paths, }; - operate(action, arg, input, call.head, engine_state.ctrlc.clone()) + operate(action, arg, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, span: Span) -> Value { diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 8eb7715754..e5c34ae6bc 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -138,7 +138,7 @@ fn into_binary( cell_paths, compact: call.has_flag(engine_state, stack, "compact")?, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/bool.rs b/crates/nu-command/src/conversions/into/bool.rs index b1d433cb93..0fcd33b4a3 100644 --- a/crates/nu-command/src/conversions/into/bool.rs +++ b/crates/nu-command/src/conversions/into/bool.rs @@ -107,7 +107,7 @@ fn into_bool( ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn string_to_boolean(s: &str, span: Span) -> Result { diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index b928bebe23..8dc0340ba1 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -141,7 +141,7 @@ impl Command for SubCommand { zone_options, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/duration.rs b/crates/nu-command/src/conversions/into/duration.rs index 21494f3bcc..b459dd04b1 100644 --- a/crates/nu-command/src/conversions/into/duration.rs +++ b/crates/nu-command/src/conversions/into/duration.rs @@ -166,7 +166,7 @@ fn into_duration( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 010c031b2a..5be167e30c 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/float.rs b/crates/nu-command/src/conversions/into/float.rs index 9ccd7ea03f..43556bb5ef 100644 --- a/crates/nu-command/src/conversions/into/float.rs +++ b/crates/nu-command/src/conversions/into/float.rs @@ -49,7 +49,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/glob.rs b/crates/nu-command/src/conversions/into/glob.rs index e5d03093f4..ffc3655330 100644 --- a/crates/nu-command/src/conversions/into/glob.rs +++ b/crates/nu-command/src/conversions/into/glob.rs @@ -87,7 +87,7 @@ fn glob_helper( Ok(Value::glob(stream.into_string()?, false, head).into_pipeline_data()) } else { let args = Arguments { cell_paths }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index a3f1c92a4f..d4bfa61639 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -158,7 +158,7 @@ impl Command for SubCommand { signed, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/conversions/into/record.rs b/crates/nu-command/src/conversions/into/record.rs index e867f06e15..1f332d9c85 100644 --- a/crates/nu-command/src/conversions/into/record.rs +++ b/crates/nu-command/src/conversions/into/record.rs @@ -125,7 +125,7 @@ fn into_record( ), }, Value::Range { val, .. } => Value::record( - val.into_range_iter(span, engine_state.ctrlc.clone()) + val.into_range_iter(span, engine_state.signals().clone()) .enumerate() .map(|(idx, val)| (format!("{idx}"), val)) .collect(), diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 7c7d69cf4e..c394f9fd21 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -180,7 +180,7 @@ fn string_helper( cell_paths, config, }; - operate(action, args, input, head, engine_state.ctrlc.clone()) + operate(action, args, input, head, engine_state.signals()) } } diff --git a/crates/nu-command/src/conversions/into/value.rs b/crates/nu-command/src/conversions/into/value.rs index 4bf7e68f53..e8787e75bb 100644 --- a/crates/nu-command/src/conversions/into/value.rs +++ b/crates/nu-command/src/conversions/into/value.rs @@ -57,14 +57,12 @@ impl Command for IntoValue { call: &Call, input: PipelineData, ) -> Result { - let engine_state = engine_state.clone(); let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); let span = call.head; - let display_as_filesizes = call.has_flag(&engine_state, stack, "prefer-filesizes")?; + let display_as_filesizes = call.has_flag(engine_state, stack, "prefer-filesizes")?; // the columns to update - let columns: Option = call.get_flag(&engine_state, stack, "columns")?; + let columns: Option = call.get_flag(engine_state, stack, "columns")?; let columns: Option> = match columns { Some(val) => Some( val.into_list()? @@ -81,7 +79,7 @@ impl Command for IntoValue { display_as_filesizes, span, } - .into_pipeline_data(span, ctrlc) + .into_pipeline_data(span, engine_state.signals().clone()) .set_metadata(metadata)) } } diff --git a/crates/nu-command/src/database/commands/into_sqlite.rs b/crates/nu-command/src/database/commands/into_sqlite.rs index f3a2e3622a..88a6114ab3 100644 --- a/crates/nu-command/src/database/commands/into_sqlite.rs +++ b/crates/nu-command/src/database/commands/into_sqlite.rs @@ -2,13 +2,8 @@ use crate::database::values::sqlite::{open_sqlite_db, values_to_sql}; use nu_engine::command_prelude::*; use itertools::Itertools; -use std::{ - path::Path, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; +use nu_protocol::Signals; +use std::path::Path; pub const DEFAULT_TABLE_NAME: &str = "main"; @@ -188,23 +183,18 @@ fn operate( let file_name: Spanned = call.req(engine_state, stack, 0)?; let table_name: Option> = call.get_flag(engine_state, stack, "table-name")?; let table = Table::new(&file_name, table_name)?; - let ctrl_c = engine_state.ctrlc.clone(); - - match action(input, table, span, ctrl_c) { - Ok(val) => Ok(val.into_pipeline_data()), - Err(e) => Err(e), - } + Ok(action(input, table, span, engine_state.signals())?.into_pipeline_data()) } fn action( input: PipelineData, table: Table, span: Span, - ctrl_c: Option>, + signals: &Signals, ) -> Result { match input { PipelineData::ListStream(stream, _) => { - insert_in_transaction(stream.into_iter(), span, table, ctrl_c) + insert_in_transaction(stream.into_iter(), span, table, signals) } PipelineData::Value( Value::List { @@ -212,9 +202,9 @@ fn action( internal_span, }, _, - ) => insert_in_transaction(vals.into_iter(), internal_span, table, ctrl_c), + ) => insert_in_transaction(vals.into_iter(), internal_span, table, signals), PipelineData::Value(val, _) => { - insert_in_transaction(std::iter::once(val), span, table, ctrl_c) + insert_in_transaction(std::iter::once(val), span, table, signals) } _ => Err(ShellError::OnlySupportsThisInputType { exp_input_type: "list".into(), @@ -229,7 +219,7 @@ fn insert_in_transaction( stream: impl Iterator, span: Span, mut table: Table, - ctrl_c: Option>, + signals: &Signals, ) -> Result { let mut stream = stream.peekable(); let first_val = match stream.peek() { @@ -251,17 +241,15 @@ fn insert_in_transaction( let tx = table.try_init(&first_val)?; for stream_value in stream { - if let Some(ref ctrlc) = ctrl_c { - if ctrlc.load(Ordering::Relaxed) { - tx.rollback().map_err(|e| ShellError::GenericError { - error: "Failed to rollback SQLite transaction".into(), - msg: e.to_string(), - span: None, - help: None, - inner: Vec::new(), - })?; - return Err(ShellError::InterruptedByUser { span: None }); - } + if let Err(err) = signals.check(span) { + tx.rollback().map_err(|e| ShellError::GenericError { + error: "Failed to rollback SQLite transaction".into(), + msg: e.to_string(), + span: None, + help: None, + inner: Vec::new(), + })?; + return Err(err); } let val = stream_value.as_record()?; diff --git a/crates/nu-command/src/database/values/sqlite.rs b/crates/nu-command/src/database/values/sqlite.rs index 483da7672e..f253ba1cbd 100644 --- a/crates/nu-command/src/database/values/sqlite.rs +++ b/crates/nu-command/src/database/values/sqlite.rs @@ -2,7 +2,7 @@ use super::definitions::{ db_column::DbColumn, db_constraint::DbConstraint, db_foreignkey::DbForeignKey, db_index::DbIndex, db_table::DbTable, }; -use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Span, Spanned, Value}; +use nu_protocol::{CustomValue, PipelineData, Record, ShellError, Signals, Span, Spanned, Value}; use rusqlite::{ types::ValueRef, Connection, DatabaseName, Error as SqliteError, OpenFlags, Row, Statement, ToSql, @@ -12,7 +12,6 @@ use std::{ fs::File, io::Read, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, }; const SQLITE_MAGIC_BYTES: &[u8] = "SQLite format 3\0".as_bytes(); @@ -24,25 +23,21 @@ pub struct SQLiteDatabase { // 1) YAGNI, 2) it's not obvious how cloning a connection could work, 3) state // management gets tricky quick. Revisit this approach if we find a compelling use case. pub path: PathBuf, - #[serde(skip)] + #[serde(skip, default = "Signals::empty")] // this understandably can't be serialized. think that's OK, I'm not aware of a // reason why a CustomValue would be serialized outside of a plugin - ctrlc: Option>, + signals: Signals, } impl SQLiteDatabase { - pub fn new(path: &Path, ctrlc: Option>) -> Self { + pub fn new(path: &Path, signals: Signals) -> Self { Self { path: PathBuf::from(path), - ctrlc, + signals, } } - pub fn try_from_path( - path: &Path, - span: Span, - ctrlc: Option>, - ) -> Result { + pub fn try_from_path(path: &Path, span: Span, signals: Signals) -> Result { let mut file = File::open(path).map_err(|e| ShellError::ReadingFile { msg: e.to_string(), span, @@ -56,7 +51,7 @@ impl SQLiteDatabase { }) .and_then(|_| { if buf == SQLITE_MAGIC_BYTES { - Ok(SQLiteDatabase::new(path, ctrlc)) + Ok(SQLiteDatabase::new(path, signals)) } else { Err(ShellError::ReadingFile { msg: "Not a SQLite file".into(), @@ -72,7 +67,7 @@ impl SQLiteDatabase { Value::Custom { val, .. } => match val.as_any().downcast_ref::() { Some(db) => Ok(Self { path: db.path.clone(), - ctrlc: db.ctrlc.clone(), + signals: db.signals.clone(), }), None => Err(ShellError::CantConvert { to_type: "database".into(), @@ -107,16 +102,8 @@ impl SQLiteDatabase { call_span: Span, ) -> Result { let conn = open_sqlite_db(&self.path, call_span)?; - - let stream = run_sql_query(conn, sql, params, self.ctrlc.clone()).map_err(|e| { - ShellError::GenericError { - error: "Failed to query SQLite database".into(), - msg: e.to_string(), - span: Some(sql.span), - help: None, - inner: vec![], - } - })?; + let stream = run_sql_query(conn, sql, params, &self.signals) + .map_err(|e| e.into_shell_error(sql.span, "Failed to query SQLite database"))?; Ok(stream) } @@ -352,12 +339,7 @@ impl SQLiteDatabase { impl CustomValue for SQLiteDatabase { fn clone_value(&self, span: Span) -> Value { - let cloned = SQLiteDatabase { - path: self.path.clone(), - ctrlc: self.ctrlc.clone(), - }; - - Value::custom(Box::new(cloned), span) + Value::custom(Box::new(self.clone()), span) } fn type_name(&self) -> String { @@ -366,13 +348,8 @@ impl CustomValue for SQLiteDatabase { fn to_base_value(&self, span: Span) -> Result { let db = open_sqlite_db(&self.path, span)?; - read_entire_sqlite_db(db, span, self.ctrlc.clone()).map_err(|e| ShellError::GenericError { - error: "Failed to read from SQLite database".into(), - msg: e.to_string(), - span: Some(span), - help: None, - inner: vec![], - }) + read_entire_sqlite_db(db, span, &self.signals) + .map_err(|e| e.into_shell_error(span, "Failed to read from SQLite database")) } fn as_any(&self) -> &dyn std::any::Any { @@ -396,20 +373,12 @@ impl CustomValue for SQLiteDatabase { fn follow_path_string( &self, _self_span: Span, - _column_name: String, + column_name: String, path_span: Span, ) -> Result { let db = open_sqlite_db(&self.path, path_span)?; - - read_single_table(db, _column_name, path_span, self.ctrlc.clone()).map_err(|e| { - ShellError::GenericError { - error: "Failed to read from SQLite database".into(), - msg: e.to_string(), - span: Some(path_span), - help: None, - inner: vec![], - } - }) + read_single_table(db, column_name, path_span, &self.signals) + .map_err(|e| e.into_shell_error(path_span, "Failed to read from SQLite database")) } fn typetag_name(&self) -> &'static str { @@ -426,12 +395,12 @@ pub fn open_sqlite_db(path: &Path, call_span: Span) -> Result, params: NuSqlParams, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let stmt = conn.prepare(&sql.item)?; - - prepared_statement_to_nu_list(stmt, params, sql.span, ctrlc) + prepared_statement_to_nu_list(stmt, params, sql.span, signals) } // This is taken from to text local_into_string but tweaks it a bit so that certain formatting does not happen @@ -534,23 +502,56 @@ pub fn nu_value_to_params(value: Value) -> Result { } } +#[derive(Debug)] +enum SqliteOrShellError { + SqliteError(SqliteError), + ShellError(ShellError), +} + +impl From for SqliteOrShellError { + fn from(error: SqliteError) -> Self { + Self::SqliteError(error) + } +} + +impl From for SqliteOrShellError { + fn from(error: ShellError) -> Self { + Self::ShellError(error) + } +} + +impl SqliteOrShellError { + fn into_shell_error(self, span: Span, msg: &str) -> ShellError { + match self { + Self::SqliteError(err) => ShellError::GenericError { + error: msg.into(), + msg: err.to_string(), + span: Some(span), + help: None, + inner: Vec::new(), + }, + Self::ShellError(err) => err, + } + } +} + fn read_single_table( conn: Connection, table_name: String, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { // TODO: Should use params here? let stmt = conn.prepare(&format!("SELECT * FROM [{table_name}]"))?; - prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, ctrlc) + prepared_statement_to_nu_list(stmt, NuSqlParams::default(), call_span, signals) } fn prepared_statement_to_nu_list( mut stmt: Statement, params: NuSqlParams, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let column_names = stmt .column_names() .into_iter() @@ -576,11 +577,7 @@ fn prepared_statement_to_nu_list( let mut row_values = vec![]; for row_result in row_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - // return whatever we have so far, let the caller decide whether to use it - return Ok(Value::list(row_values, call_span)); - } - + signals.check(call_span)?; if let Ok(row_value) = row_result { row_values.push(row_value); } @@ -606,11 +603,7 @@ fn prepared_statement_to_nu_list( let mut row_values = vec![]; for row_result in row_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - // return whatever we have so far, let the caller decide whether to use it - return Ok(Value::list(row_values, call_span)); - } - + signals.check(call_span)?; if let Ok(row_value) = row_result { row_values.push(row_value); } @@ -626,8 +619,8 @@ fn prepared_statement_to_nu_list( fn read_entire_sqlite_db( conn: Connection, call_span: Span, - ctrlc: Option>, -) -> Result { + signals: &Signals, +) -> Result { let mut tables = Record::new(); let mut get_table_names = @@ -638,12 +631,8 @@ fn read_entire_sqlite_db( let table_name: String = row?; // TODO: Should use params here? let table_stmt = conn.prepare(&format!("select * from [{table_name}]"))?; - let rows = prepared_statement_to_nu_list( - table_stmt, - NuSqlParams::default(), - call_span, - ctrlc.clone(), - )?; + let rows = + prepared_statement_to_nu_list(table_stmt, NuSqlParams::default(), call_span, signals)?; tables.push(table_name, rows); } @@ -710,7 +699,7 @@ mod test { #[test] fn can_read_empty_db() { let db = open_connection_in_memory().unwrap(); - let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap(); + let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap(); let expected = Value::test_record(Record::new()); @@ -730,7 +719,7 @@ mod test { [], ) .unwrap(); - let converted_db = read_entire_sqlite_db(db, Span::test_data(), None).unwrap(); + let converted_db = read_entire_sqlite_db(db, Span::test_data(), &Signals::empty()).unwrap(); let expected = Value::test_record(record! { "person" => Value::test_list(vec![]), @@ -759,7 +748,7 @@ mod test { db.execute("INSERT INTO item (id, name) VALUES (456, 'foo bar')", []) .unwrap(); - let converted_db = read_entire_sqlite_db(db, span, None).unwrap(); + let converted_db = read_entire_sqlite_db(db, span, &Signals::empty()).unwrap(); let expected = Value::test_record(record! { "item" => Value::test_list( diff --git a/crates/nu-command/src/date/humanize.rs b/crates/nu-command/src/date/humanize.rs index 2815571520..d8542a540c 100644 --- a/crates/nu-command/src/date/humanize.rs +++ b/crates/nu-command/src/date/humanize.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/list_timezone.rs b/crates/nu-command/src/date/list_timezone.rs index 6f9267947d..56f7fe5376 100644 --- a/crates/nu-command/src/date/list_timezone.rs +++ b/crates/nu-command/src/date/list_timezone.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { head, ) }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_record.rs b/crates/nu-command/src/date/to_record.rs index f9c0ceff1b..c0b09c040b 100644 --- a/crates/nu-command/src/date/to_record.rs +++ b/crates/nu-command/src/date/to_record.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_table.rs b/crates/nu-command/src/date/to_table.rs index 36c3f4a94a..7ce8bc171b 100644 --- a/crates/nu-command/src/date/to_table.rs +++ b/crates/nu-command/src/date/to_table.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map(move |value| helper(value, head), engine_state.ctrlc.clone()) + input.map(move |value| helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/date/to_timezone.rs b/crates/nu-command/src/date/to_timezone.rs index 5f41d287ae..3d08d7271b 100644 --- a/crates/nu-command/src/date/to_timezone.rs +++ b/crates/nu-command/src/date/to_timezone.rs @@ -55,7 +55,7 @@ impl Command for SubCommand { } input.map( move |value| helper(value, head, &timezone), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/debug/debug_.rs b/crates/nu-command/src/debug/debug_.rs index c766081410..f4e5707491 100644 --- a/crates/nu-command/src/debug/debug_.rs +++ b/crates/nu-command/src/debug/debug_.rs @@ -46,7 +46,7 @@ impl Command for Debug { Value::string(x.to_expanded_string(", ", &config), head) } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/debug/metadata_set.rs b/crates/nu-command/src/debug/metadata_set.rs index e4ee97a76b..96f50cfdba 100644 --- a/crates/nu-command/src/debug/metadata_set.rs +++ b/crates/nu-command/src/debug/metadata_set.rs @@ -48,7 +48,7 @@ impl Command for MetadataSet { let ds_fp: Option = call.get_flag(engine_state, stack, "datasource-filepath")?; let ds_ls = call.has_flag(engine_state, stack, "datasource-ls")?; let content_type: Option = call.get_flag(engine_state, stack, "content-type")?; - + let signals = engine_state.signals().clone(); let metadata = input .metadata() .clone() @@ -58,19 +58,15 @@ impl Command for MetadataSet { match (ds_fp, ds_ls) { (Some(path), false) => Ok(input.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + signals, metadata.with_data_source(DataSource::FilePath(path.into())), )), (None, true) => Ok(input.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + signals, metadata.with_data_source(DataSource::Ls), )), - _ => Ok(input.into_pipeline_data_with_metadata( - head, - engine_state.ctrlc.clone(), - metadata, - )), + _ => Ok(input.into_pipeline_data_with_metadata(head, signals, metadata)), } } diff --git a/crates/nu-command/src/filesystem/du.rs b/crates/nu-command/src/filesystem/du.rs index c48a1f7ac1..93f08f7785 100644 --- a/crates/nu-command/src/filesystem/du.rs +++ b/crates/nu-command/src/filesystem/du.rs @@ -3,10 +3,9 @@ use crate::{DirBuilder, DirInfo, FileInfo}; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; use nu_glob::Pattern; -use nu_protocol::NuGlob; +use nu_protocol::{NuGlob, Signals}; use serde::Deserialize; use std::path::Path; -use std::sync::{atomic::AtomicBool, Arc}; #[derive(Clone)] pub struct Du; @@ -120,8 +119,8 @@ impl Command for Du { min_size, }; Ok( - du_for_one_pattern(args, ¤t_dir, tag, engine_state.ctrlc.clone())? - .into_pipeline_data(tag, engine_state.ctrlc.clone()), + du_for_one_pattern(args, ¤t_dir, tag, engine_state.signals())? + .into_pipeline_data(tag, engine_state.signals().clone()), ) } Some(paths) => { @@ -139,7 +138,7 @@ impl Command for Du { args, ¤t_dir, tag, - engine_state.ctrlc.clone(), + engine_state.signals(), )?) } @@ -147,7 +146,7 @@ impl Command for Du { Ok(result_iters .into_iter() .flatten() - .into_pipeline_data(tag, engine_state.ctrlc.clone())) + .into_pipeline_data(tag, engine_state.signals().clone())) } } } @@ -164,8 +163,8 @@ impl Command for Du { fn du_for_one_pattern( args: DuArgs, current_dir: &Path, - call_span: Span, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result + Send, ShellError> { let exclude = args.exclude.map_or(Ok(None), move |x| { Pattern::new(x.item.as_ref()) @@ -178,7 +177,7 @@ fn du_for_one_pattern( let include_files = args.all; let mut paths = match args.path { - Some(p) => nu_engine::glob_from(&p, current_dir, call_span, None), + Some(p) => nu_engine::glob_from(&p, current_dir, span, None), // The * pattern should never fail. None => nu_engine::glob_from( &Spanned { @@ -186,7 +185,7 @@ fn du_for_one_pattern( span: Span::unknown(), }, current_dir, - call_span, + span, None, ), } @@ -205,7 +204,7 @@ fn du_for_one_pattern( let min_size = args.min_size.map(|f| f.item as u64); let params = DirBuilder { - tag: call_span, + tag: span, min: min_size, deref, exclude, @@ -217,13 +216,13 @@ fn du_for_one_pattern( match p { Ok(a) => { if a.is_dir() { - output.push(DirInfo::new(a, ¶ms, max_depth, ctrl_c.clone()).into()); - } else if let Ok(v) = FileInfo::new(a, deref, call_span) { + output.push(DirInfo::new(a, ¶ms, max_depth, span, signals)?.into()); + } else if let Ok(v) = FileInfo::new(a, deref, span) { output.push(v.into()); } } Err(e) => { - output.push(Value::error(e, call_span)); + output.push(Value::error(e, span)); } } } diff --git a/crates/nu-command/src/filesystem/glob.rs b/crates/nu-command/src/filesystem/glob.rs index b10e8893a0..212c6ceb92 100644 --- a/crates/nu-command/src/filesystem/glob.rs +++ b/crates/nu-command/src/filesystem/glob.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::Signals; use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry}; #[derive(Clone)] @@ -125,7 +125,6 @@ impl Command for Glob { call: &Call, _input: PipelineData, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); let span = call.head; let glob_pattern: Spanned = call.req(engine_state, stack, 0)?; let depth = call.get_flag(engine_state, stack, "depth")?; @@ -216,7 +215,14 @@ impl Command for Glob { inner: vec![], })? .flatten(); - glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span) + glob_to_value( + engine_state.signals(), + glob_results, + no_dirs, + no_files, + no_symlinks, + span, + ) } else { let glob_results = glob .walk_with_behavior( @@ -227,12 +233,19 @@ impl Command for Glob { }, ) .flatten(); - glob_to_value(ctrlc, glob_results, no_dirs, no_files, no_symlinks, span) + glob_to_value( + engine_state.signals(), + glob_results, + no_dirs, + no_files, + no_symlinks, + span, + ) }?; Ok(result .into_iter() - .into_pipeline_data(span, engine_state.ctrlc.clone())) + .into_pipeline_data(span, engine_state.signals().clone())) } } @@ -252,7 +265,7 @@ fn convert_patterns(columns: &[Value]) -> Result, ShellError> { } fn glob_to_value<'a>( - ctrlc: Option>, + signals: &Signals, glob_results: impl Iterator>, no_dirs: bool, no_files: bool, @@ -261,10 +274,7 @@ fn glob_to_value<'a>( ) -> Result, ShellError> { let mut result: Vec = Vec::new(); for entry in glob_results { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { - result.clear(); - return Err(ShellError::InterruptedByUser { span: None }); - } + signals.check(span)?; let file_type = entry.file_type(); if !(no_dirs && file_type.is_dir() diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index a3ab1eca54..f465a93dc1 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -6,14 +6,13 @@ use nu_engine::glob_from; use nu_engine::{command_prelude::*, env::current_dir}; use nu_glob::MatchOptions; use nu_path::expand_to_real_path; -use nu_protocol::{DataSource, NuGlob, PipelineMetadata}; +use nu_protocol::{DataSource, NuGlob, PipelineMetadata, Signals}; use pathdiff::diff_paths; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::{ path::PathBuf, - sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -93,7 +92,6 @@ impl Command for Ls { let du = call.has_flag(engine_state, stack, "du")?; let directory = call.has_flag(engine_state, stack, "directory")?; let use_mime_type = call.has_flag(engine_state, stack, "mime-type")?; - let ctrl_c = engine_state.ctrlc.clone(); let call_span = call.head; #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; @@ -116,10 +114,10 @@ impl Command for Ls { Some(pattern_arg) }; match input_pattern_arg { - None => Ok(ls_for_one_pattern(None, args, ctrl_c.clone(), cwd)? + None => Ok(ls_for_one_pattern(None, args, engine_state.signals(), cwd)? .into_pipeline_data_with_metadata( call_span, - ctrl_c, + engine_state.signals().clone(), PipelineMetadata { data_source: DataSource::Ls, content_type: None, @@ -131,7 +129,7 @@ impl Command for Ls { result_iters.push(ls_for_one_pattern( Some(pat), args, - ctrl_c.clone(), + engine_state.signals(), cwd.clone(), )?) } @@ -143,7 +141,7 @@ impl Command for Ls { .flatten() .into_pipeline_data_with_metadata( call_span, - ctrl_c, + engine_state.signals().clone(), PipelineMetadata { data_source: DataSource::Ls, content_type: None, @@ -215,7 +213,7 @@ impl Command for Ls { fn ls_for_one_pattern( pattern_arg: Option>, args: Args, - ctrl_c: Option>, + signals: &Signals, cwd: PathBuf, ) -> Result + Send>, ShellError> { let Args { @@ -342,7 +340,7 @@ fn ls_for_one_pattern( let mut hidden_dirs = vec![]; - let one_ctrl_c = ctrl_c.clone(); + let signals = signals.clone(); Ok(Box::new(paths_peek.filter_map(move |x| match x { Ok(path) => { let metadata = match std::fs::symlink_metadata(&path) { @@ -412,7 +410,7 @@ fn ls_for_one_pattern( call_span, long, du, - one_ctrl_c.clone(), + &signals, use_mime_type, ); match entry { @@ -474,7 +472,6 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use std::path::Path; -use std::sync::atomic::AtomicBool; pub fn get_file_type(md: &std::fs::Metadata, display_name: &str, use_mime_type: bool) -> String { let ft = md.file_type(); @@ -523,7 +520,7 @@ pub(crate) fn dir_entry_dict( span: Span, long: bool, du: bool, - ctrl_c: Option>, + signals: &Signals, use_mime_type: bool, ) -> Result { #[cfg(windows)] @@ -618,7 +615,7 @@ pub(crate) fn dir_entry_dict( if md.is_dir() { if du { let params = DirBuilder::new(Span::new(0, 2), None, false, None, false); - let dir_size = DirInfo::new(filename, ¶ms, None, ctrl_c).get_size(); + let dir_size = DirInfo::new(filename, ¶ms, None, span, signals)?.get_size(); Value::filesize(dir_size as i64, span) } else { diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 9000359450..e654b27f05 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -51,7 +51,6 @@ impl Command for Open { ) -> Result { let raw = call.has_flag(engine_state, stack, "raw")?; let call_span = call.head; - let ctrlc = engine_state.ctrlc.clone(); #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; @@ -122,8 +121,12 @@ impl Command for Open { } else { #[cfg(feature = "sqlite")] if !raw { - let res = SQLiteDatabase::try_from_path(path, arg_span, ctrlc.clone()) - .map(|db| db.into_value(call.head).into_pipeline_data()); + let res = SQLiteDatabase::try_from_path( + path, + arg_span, + engine_state.signals().clone(), + ) + .map(|db| db.into_value(call.head).into_pipeline_data()); if res.is_ok() { return res; @@ -144,7 +147,7 @@ impl Command for Open { }; let stream = PipelineData::ByteStream( - ByteStream::file(file, call_span, ctrlc.clone()), + ByteStream::file(file, call_span, engine_state.signals().clone()), Some(PipelineMetadata { data_source: DataSource::FilePath(path.to_path_buf()), content_type: None, @@ -203,7 +206,7 @@ impl Command for Open { Ok(output .into_iter() .flatten() - .into_pipeline_data(call_span, ctrlc)) + .into_pipeline_data(call_span, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 9696ae0c2f..d67046ffe1 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -451,12 +451,7 @@ fn rm( }); for result in iter { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { - span: Some(call.head), - }); - } - + engine_state.signals().check(call.head)?; match result { Ok(None) => {} Ok(Some(msg)) => eprintln!("{msg}"), diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 1cfbbc67b4..6ca5c09559 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -5,15 +5,14 @@ use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; use nu_protocol::{ ast::{Expr, Expression}, - byte_stream::copy_with_interrupt, + byte_stream::copy_with_signals, process::ChildPipe, - ByteStreamSource, DataSource, OutDest, PipelineMetadata, + ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals, }; use std::{ fs::File, io::{self, BufRead, BufReader, Read, Write}, path::{Path, PathBuf}, - sync::{atomic::AtomicBool, Arc}, thread, }; @@ -120,30 +119,30 @@ impl Command for Save { )?; let size = stream.known_size(); - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals(); match stream.into_source() { ByteStreamSource::Read(read) => { - stream_to_file(read, size, ctrlc, file, span, progress)?; + stream_to_file(read, size, signals, file, span, progress)?; } ByteStreamSource::File(source) => { - stream_to_file(source, size, ctrlc, file, span, progress)?; + stream_to_file(source, size, signals, file, span, progress)?; } ByteStreamSource::Child(mut child) => { fn write_or_consume_stderr( stderr: ChildPipe, file: Option, span: Span, - ctrlc: Option>, + signals: &Signals, progress: bool, ) -> Result<(), ShellError> { if let Some(file) = file { match stderr { ChildPipe::Pipe(pipe) => { - stream_to_file(pipe, None, ctrlc, file, span, progress) + stream_to_file(pipe, None, signals, file, span, progress) } ChildPipe::Tee(tee) => { - stream_to_file(tee, None, ctrlc, file, span, progress) + stream_to_file(tee, None, signals, file, span, progress) } }? } else { @@ -163,14 +162,14 @@ impl Command for Save { // delegate a thread to redirect stderr to result. let handler = stderr .map(|stderr| { - let ctrlc = ctrlc.clone(); + let signals = signals.clone(); thread::Builder::new().name("stderr saver".into()).spawn( move || { write_or_consume_stderr( stderr, stderr_file, span, - ctrlc, + &signals, progress, ) }, @@ -181,10 +180,10 @@ impl Command for Save { let res = match stdout { ChildPipe::Pipe(pipe) => { - stream_to_file(pipe, None, ctrlc, file, span, progress) + stream_to_file(pipe, None, signals, file, span, progress) } ChildPipe::Tee(tee) => { - stream_to_file(tee, None, ctrlc, file, span, progress) + stream_to_file(tee, None, signals, file, span, progress) } }; if let Some(h) = handler { @@ -202,7 +201,7 @@ impl Command for Save { stderr, stderr_file, span, - ctrlc, + signals, progress, )?; } @@ -510,7 +509,7 @@ fn get_files( fn stream_to_file( source: impl Read, known_size: Option, - ctrlc: Option>, + signals: &Signals, mut file: File, span: Span, progress: bool, @@ -526,9 +525,9 @@ fn stream_to_file( let mut reader = BufReader::new(source); let res = loop { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if let Err(err) = signals.check(span) { bar.abandoned_msg("# Cancelled #".to_owned()); - return Ok(()); + return Err(err); } match reader.fill_buf() { @@ -555,7 +554,7 @@ fn stream_to_file( Ok(()) } } else { - copy_with_interrupt(source, file, span, ctrlc.as_deref())?; + copy_with_signals(source, file, span, signals)?; Ok(()) } } diff --git a/crates/nu-command/src/filesystem/watch.rs b/crates/nu-command/src/filesystem/watch.rs index fda542c8a8..c9817de250 100644 --- a/crates/nu-command/src/filesystem/watch.rs +++ b/crates/nu-command/src/filesystem/watch.rs @@ -143,7 +143,6 @@ impl Command for Watch { None => RecursiveMode::Recursive, }; - let ctrlc_ref = &engine_state.ctrlc.clone(); let (tx, rx) = channel(); let mut debouncer = match new_debouncer(debounce_duration, None, tx) { @@ -256,7 +255,7 @@ impl Command for Watch { } Err(RecvTimeoutError::Timeout) => {} } - if nu_utils::ctrl_c::was_pressed(ctrlc_ref) { + if engine_state.signals().interrupted() { break; } } diff --git a/crates/nu-command/src/filters/append.rs b/crates/nu-command/src/filters/append.rs index af5bd49283..2064bdf1e8 100644 --- a/crates/nu-command/src/filters/append.rs +++ b/crates/nu-command/src/filters/append.rs @@ -116,7 +116,7 @@ only unwrap the outer list, and leave the variable's contents untouched."# Ok(input .into_iter() .chain(other.into_pipeline_data()) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/compact.rs b/crates/nu-command/src/filters/compact.rs index 9a4e968e9b..f43d738ed1 100644 --- a/crates/nu-command/src/filters/compact.rs +++ b/crates/nu-command/src/filters/compact.rs @@ -140,7 +140,7 @@ pub fn compact( _ => true, } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|m| m.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index 3eaa8d342e..a12d78f0b7 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -80,8 +80,6 @@ fn default( let value: Value = call.req(engine_state, stack, 0)?; let column: Option> = call.opt(engine_state, stack, 1)?; - let ctrlc = engine_state.ctrlc.clone(); - if let Some(column) = column { input .map( @@ -109,7 +107,7 @@ fn default( } _ => item, }, - ctrlc, + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } else if input.is_nothing() { @@ -121,7 +119,7 @@ fn default( Value::Nothing { .. } => value.clone(), x => x, }, - ctrlc, + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs index 94c0308ea8..f168e6c2ca 100644 --- a/crates/nu-command/src/filters/drop/column.rs +++ b/crates/nu-command/src/filters/drop/column.rs @@ -102,7 +102,11 @@ fn drop_cols( Err(e) => Value::error(e, head), } })) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else { Ok(PipelineData::Empty) } diff --git a/crates/nu-command/src/filters/drop/nth.rs b/crates/nu-command/src/filters/drop/nth.rs index a530de5aa1..a76c4f0d92 100644 --- a/crates/nu-command/src/filters/drop/nth.rs +++ b/crates/nu-command/src/filters/drop/nth.rs @@ -156,7 +156,7 @@ impl Command for DropNth { .take(start) .into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -177,7 +177,7 @@ impl Command for DropNth { rows, current: 0, } - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index a074f63abb..fa1bf390b8 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -140,7 +140,7 @@ with 'transpose' first."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -171,7 +171,7 @@ with 'transpose' first."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } @@ -185,7 +185,7 @@ with 'transpose' first."# .and_then(|x| { x.filter( move |x| if !keep_empty { !x.is_nothing() } else { true }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) }) .map(|data| data.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/enumerate.rs b/crates/nu-command/src/filters/enumerate.rs index 1034780657..df0f4e2fca 100644 --- a/crates/nu-command/src/filters/enumerate.rs +++ b/crates/nu-command/src/filters/enumerate.rs @@ -52,7 +52,6 @@ impl Command for Enumerate { ) -> Result { let head = call.head; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); Ok(input .into_iter() @@ -66,7 +65,7 @@ impl Command for Enumerate { head, ) }) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/every.rs b/crates/nu-command/src/filters/every.rs index 1202b4f7c9..664be33bed 100644 --- a/crates/nu-command/src/filters/every.rs +++ b/crates/nu-command/src/filters/every.rs @@ -78,7 +78,7 @@ impl Command for Every { None } }) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/filter.rs b/crates/nu-command/src/filters/filter.rs index 1ba1508839..f2efaa3af3 100644 --- a/crates/nu-command/src/filters/filter.rs +++ b/crates/nu-command/src/filters/filter.rs @@ -72,7 +72,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -97,7 +97,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::Empty) } @@ -117,7 +117,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# Some(Value::error(err, span)) } } - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } } .map(|data| data.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index af626c1e75..7669190f7e 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -213,7 +213,6 @@ fn find_with_regex( input: PipelineData, ) -> Result { let span = call.head; - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config().clone(); let insensitive = call.has_flag(engine_state, stack, "ignore-case")?; @@ -246,7 +245,7 @@ fn find_with_regex( Value::List { vals, .. } => values_match_find(vals, &re, &config, invert), _ => false, }, - ctrlc, + engine_state.signals(), ) } @@ -349,18 +348,16 @@ fn find_with_rest_and_highlight( input: PipelineData, ) -> Result { let span = call.head; - let ctrlc = engine_state.ctrlc.clone(); - let engine_state = engine_state.clone(); let config = engine_state.get_config().clone(); let filter_config = engine_state.get_config().clone(); - let invert = call.has_flag(&engine_state, stack, "invert")?; - let terms = call.rest::(&engine_state, stack, 0)?; + let invert = call.has_flag(engine_state, stack, "invert")?; + let terms = call.rest::(engine_state, stack, 0)?; let lower_terms = terms .iter() .map(|v| Value::string(v.to_expanded_string("", &config).to_lowercase(), span)) .collect::>(); - let style_computer = StyleComputer::from_config(&engine_state, stack); + let style_computer = StyleComputer::from_config(engine_state, stack); // Currently, search results all use the same style. // Also note that this sample string is passed into user-written code (the closure that may or may not be // defined for "string"). @@ -369,7 +366,7 @@ fn find_with_rest_and_highlight( style_computer.compute("search_result", &Value::string("search result", span)); let cols_to_search_in_map: Vec<_> = call - .get_flag(&engine_state, stack, "columns")? + .get_flag(engine_state, stack, "columns")? .unwrap_or_default(); let cols_to_search_in_filter = cols_to_search_in_map.clone(); @@ -401,7 +398,7 @@ fn find_with_rest_and_highlight( _ => x, } }, - ctrlc.clone(), + engine_state.signals(), )? .filter( move |value| { @@ -414,7 +411,7 @@ fn find_with_rest_and_highlight( invert, ) }, - ctrlc, + engine_state.signals(), ), PipelineData::ListStream(stream, metadata) => { let stream = stream.modify(|iter| { diff --git a/crates/nu-command/src/filters/first.rs b/crates/nu-command/src/filters/first.rs index 97c5159b97..8f2e8db1b9 100644 --- a/crates/nu-command/src/filters/first.rs +++ b/crates/nu-command/src/filters/first.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Read; #[derive(Clone)] @@ -133,8 +134,7 @@ fn first_helper( } } Value::Range { val, .. } => { - let ctrlc = engine_state.ctrlc.clone(); - let mut iter = val.into_range_iter(span, ctrlc.clone()); + let mut iter = val.into_range_iter(span, Signals::empty()); if return_single_element { if let Some(v) = iter.next() { Ok(v.into_pipeline_data()) @@ -142,9 +142,11 @@ fn first_helper( Err(ShellError::AccessEmptyContent { span: head }) } } else { - Ok(iter - .take(rows) - .into_pipeline_data_with_metadata(span, ctrlc, metadata)) + Ok(iter.take(rows).into_pipeline_data_with_metadata( + span, + engine_state.signals().clone(), + metadata, + )) } } // Propagate errors by explicitly matching them before the final case. @@ -189,7 +191,7 @@ fn first_helper( ByteStream::read( reader.take(rows as u64), head, - None, + Signals::empty(), ByteStreamType::Binary, ), metadata, diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index ec86677af4..b4faec9589 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -127,7 +127,7 @@ fn flatten( input .flat_map( move |item| flat_value(&columns, item, flatten_all), - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 07f0ea9440..9f7d76277d 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -62,7 +62,6 @@ If multiple cell paths are given, this will produce a list of values."# let mut rest: Vec = call.rest(engine_state, stack, 1)?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let sensitive = call.has_flag(engine_state, stack, "sensitive")?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); if ignore_errors { @@ -89,7 +88,9 @@ If multiple cell paths are given, this will produce a list of values."# output.push(val?); } - Ok(output.into_iter().into_pipeline_data(span, ctrlc)) + Ok(output + .into_iter() + .into_pipeline_data(span, engine_state.signals().clone())) } .map(|x| x.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/group.rs b/crates/nu-command/src/filters/group.rs index 821f35f34e..13b53850d2 100644 --- a/crates/nu-command/src/filters/group.rs +++ b/crates/nu-command/src/filters/group.rs @@ -55,18 +55,19 @@ impl Command for Group { ) -> Result { let head = call.head; let group_size: Spanned = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); - //FIXME: add in support for external redirection when engine-q supports it generally - let each_group_iterator = EachGroupIterator { group_size: group_size.item, input: Box::new(input.into_iter()), span: head, }; - Ok(each_group_iterator.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(each_group_iterator.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index 5f1380b2ac..3b47c4e6a9 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -222,7 +222,11 @@ fn insert( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/interleave.rs b/crates/nu-command/src/filters/interleave.rs index 85a92741a1..9890fede1e 100644 --- a/crates/nu-command/src/filters/interleave.rs +++ b/crates/nu-command/src/filters/interleave.rs @@ -147,7 +147,7 @@ interleave // Now that threads are writing to the channel, we just return it as a stream Ok(rx .into_iter() - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/filters/items.rs b/crates/nu-command/src/filters/items.rs index ed30486bee..04a4c6d672 100644 --- a/crates/nu-command/src/filters/items.rs +++ b/crates/nu-command/src/filters/items.rs @@ -67,7 +67,7 @@ impl Command for Items { } } }) - .into_pipeline_data(head, engine_state.ctrlc.clone())) + .into_pipeline_data(head, engine_state.signals().clone())) } Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { diff --git a/crates/nu-command/src/filters/last.rs b/crates/nu-command/src/filters/last.rs index efb300db01..bb5a75fecd 100644 --- a/crates/nu-command/src/filters/last.rs +++ b/crates/nu-command/src/filters/last.rs @@ -99,14 +99,10 @@ impl Command for Last { let mut buf = VecDeque::new(); for row in iterator { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { span: Some(head) }); - } - + engine_state.signals().check(head)?; if buf.len() == rows { buf.pop_front(); } - buf.push_back(row); } diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index 0b037dcaac..fc957e1c0e 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -26,7 +26,6 @@ impl Command for Lines { input: PipelineData, ) -> Result { let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); let skip_empty = call.has_flag(engine_state, stack, "skip-empty")?; let span = input.span().unwrap_or(call.head); @@ -91,7 +90,7 @@ impl Command for Lines { Ok(line) => Value::string(line, head), Err(err) => Value::error(err, head), }) - .into_pipeline_data(head, ctrlc)) + .into_pipeline_data(head, engine_state.signals().clone())) } else { Ok(PipelineData::empty()) } diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs index 8a3e613c56..191ed1706c 100644 --- a/crates/nu-command/src/filters/merge.rs +++ b/crates/nu-command/src/filters/merge.rs @@ -88,7 +88,6 @@ repeating this process with row 1, and so on."# let head = call.head; let merge_value: Value = call.req(engine_state, stack, 0)?; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); match (&input, merge_value) { // table (list of records) @@ -110,7 +109,11 @@ repeating this process with row 1, and so on."# (Err(error), _) => Value::error(error, head), }); - Ok(res.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(res.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } // record ( diff --git a/crates/nu-command/src/filters/move_.rs b/crates/nu-command/src/filters/move_.rs index d93368f291..6e826af050 100644 --- a/crates/nu-command/src/filters/move_.rs +++ b/crates/nu-command/src/filters/move_.rs @@ -144,7 +144,6 @@ impl Command for Move { }; let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); match input { PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => { @@ -158,7 +157,11 @@ impl Command for Move { Err(error) => Value::error(error, head), }); - Ok(res.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(res.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } PipelineData::Value(Value::Record { val, .. }, ..) => { Ok(move_record_columns(&val, &columns, &before_or_after, head)? diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index af72895df6..958a7fbc76 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -1,6 +1,6 @@ use super::utils::chain_error_with_input; use nu_engine::{command_prelude::*, ClosureEvalOnce}; -use nu_protocol::engine::Closure; +use nu_protocol::{engine::Closure, Signals}; use rayon::prelude::*; #[derive(Clone)] @@ -158,12 +158,11 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(span, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(span, engine_state.signals().clone()) })), Value::Range { val, .. } => Ok(create_pool(max_threads)?.install(|| { - let ctrlc = engine_state.ctrlc.clone(); let vec = val - .into_range_iter(span, ctrlc.clone()) + .into_range_iter(span, Signals::empty()) .enumerate() .par_bridge() .map(move |(index, value)| { @@ -184,7 +183,7 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(span, ctrlc) + apply_order(vec).into_pipeline_data(span, engine_state.signals().clone()) })), // This match allows non-iterables to be accepted, // which is currently considered undesirable (Nov 2022). @@ -212,7 +211,7 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(head, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(head, engine_state.signals().clone()) })), PipelineData::ByteStream(stream, ..) => { if let Some(chunks) = stream.chunks() { @@ -236,14 +235,14 @@ impl Command for ParEach { }) .collect::>(); - apply_order(vec).into_pipeline_data(head, engine_state.ctrlc.clone()) + apply_order(vec).into_pipeline_data(head, engine_state.signals().clone()) })) } else { Ok(PipelineData::empty()) } } } - .and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.ctrlc.clone())) + .and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.signals())) .map(|data| data.set_metadata(metadata)) } } diff --git a/crates/nu-command/src/filters/prepend.rs b/crates/nu-command/src/filters/prepend.rs index f017420595..eeae0ef656 100644 --- a/crates/nu-command/src/filters/prepend.rs +++ b/crates/nu-command/src/filters/prepend.rs @@ -117,7 +117,7 @@ only unwrap the outer list, and leave the variable's contents untouched."# .into_pipeline_data() .into_iter() .chain(input) - .into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(call.head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/range.rs b/crates/nu-command/src/filters/range.rs index c89a0b11f2..0d4e703bb1 100644 --- a/crates/nu-command/src/filters/range.rs +++ b/crates/nu-command/src/filters/range.rs @@ -106,7 +106,7 @@ impl Command for Range { Ok(PipelineData::Value(Value::nothing(head), None)) } else { let iter = v.into_iter().skip(from).take(to - from + 1); - Ok(iter.into_pipeline_data(head, engine_state.ctrlc.clone())) + Ok(iter.into_pipeline_data(head, engine_state.signals().clone())) } } else { let from = start as usize; @@ -116,7 +116,7 @@ impl Command for Range { Ok(PipelineData::Value(Value::nothing(head), None)) } else { let iter = input.into_iter().skip(from).take(to - from + 1); - Ok(iter.into_pipeline_data(head, engine_state.ctrlc.clone())) + Ok(iter.into_pipeline_data(head, engine_state.signals().clone())) } } .map(|x| x.set_metadata(metadata)) diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index fc808ca9af..87fbe3b23e 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -107,10 +107,7 @@ impl Command for Reduce { let mut closure = ClosureEval::new(engine_state, stack, closure); for value in iter { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } - + engine_state.signals().check(head)?; acc = closure .add_arg(value) .add_arg(acc) diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs index b803bd8567..1e7ce34028 100644 --- a/crates/nu-command/src/filters/rename.rs +++ b/crates/nu-command/src/filters/rename.rs @@ -221,7 +221,7 @@ fn rename( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) .map(|data| data.set_metadata(metadata)) } diff --git a/crates/nu-command/src/filters/reverse.rs b/crates/nu-command/src/filters/reverse.rs index f40e5f3be4..18891637ab 100644 --- a/crates/nu-command/src/filters/reverse.rs +++ b/crates/nu-command/src/filters/reverse.rs @@ -63,7 +63,7 @@ impl Command for Reverse { let metadata = input.metadata(); let values = input.into_iter_strict(head)?.collect::>(); let iter = values.into_iter().rev(); - Ok(iter.into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 3514ac6be7..2ca04b3999 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -215,7 +215,11 @@ fn select( rows: unique_rows.into_iter().peekable(), current: 0, } - .into_pipeline_data_with_metadata(call_span, engine_state.ctrlc.clone(), metadata) + .into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + ) } else { input }; @@ -255,7 +259,7 @@ fn select( Ok(output.into_iter().into_pipeline_data_with_metadata( call_span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -304,7 +308,7 @@ fn select( Ok(values.into_pipeline_data_with_metadata( call_span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } diff --git a/crates/nu-command/src/filters/shuffle.rs b/crates/nu-command/src/filters/shuffle.rs index 9e023b86c6..ec4bf8c454 100644 --- a/crates/nu-command/src/filters/shuffle.rs +++ b/crates/nu-command/src/filters/shuffle.rs @@ -33,7 +33,11 @@ impl Command for Shuffle { let mut values = input.into_iter_strict(call.head)?.collect::>(); values.shuffle(&mut thread_rng()); let iter = values.into_iter(); - Ok(iter.into_pipeline_data_with_metadata(call.head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata( + call.head, + engine_state.signals().clone(), + metadata, + )) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/skip/skip_.rs b/crates/nu-command/src/filters/skip/skip_.rs index d293a743da..b64f438858 100644 --- a/crates/nu-command/src/filters/skip/skip_.rs +++ b/crates/nu-command/src/filters/skip/skip_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::{self, Read}; #[derive(Clone)] @@ -90,8 +91,6 @@ impl Command for Skip { } None => 1, }; - - let ctrlc = engine_state.ctrlc.clone(); let input_span = input.span().unwrap_or(call.head); match input { PipelineData::ByteStream(stream, metadata) => { @@ -102,7 +101,12 @@ impl Command for Skip { io::copy(&mut (&mut reader).take(n as u64), &mut io::sink()) .err_span(span)?; Ok(PipelineData::ByteStream( - ByteStream::read(reader, call.head, None, ByteStreamType::Binary), + ByteStream::read( + reader, + call.head, + Signals::empty(), + ByteStreamType::Binary, + ), metadata, )) } else { @@ -124,7 +128,11 @@ impl Command for Skip { _ => Ok(input .into_iter_strict(call.head)? .skip(n) - .into_pipeline_data_with_metadata(input_span, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + input_span, + engine_state.signals().clone(), + metadata, + )), } } } diff --git a/crates/nu-command/src/filters/skip/skip_until.rs b/crates/nu-command/src/filters/skip/skip_until.rs index bb36785e00..72cae739af 100644 --- a/crates/nu-command/src/filters/skip/skip_until.rs +++ b/crates/nu-command/src/filters/skip/skip_until.rs @@ -89,7 +89,7 @@ impl Command for SkipUntil { .map(|cond| cond.is_false()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/skip/skip_while.rs b/crates/nu-command/src/filters/skip/skip_while.rs index 2747ea6f97..ea9c12bf6a 100644 --- a/crates/nu-command/src/filters/skip/skip_while.rs +++ b/crates/nu-command/src/filters/skip/skip_while.rs @@ -94,7 +94,7 @@ impl Command for SkipWhile { .map(|cond| cond.is_true()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/sort.rs b/crates/nu-command/src/filters/sort.rs index 965b997355..179235302d 100644 --- a/crates/nu-command/src/filters/sort.rs +++ b/crates/nu-command/src/filters/sort.rs @@ -173,7 +173,7 @@ impl Command for Sort { let iter = vec.into_iter(); Ok(iter.into_pipeline_data_with_metadata( head, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index f3f715bc9d..e832255a26 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -100,7 +100,7 @@ impl Command for SortBy { } let iter = vec.into_iter(); - Ok(iter.into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + Ok(iter.into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/take/take_.rs b/crates/nu-command/src/filters/take/take_.rs index 7ebe22f914..1876244bdd 100644 --- a/crates/nu-command/src/filters/take/take_.rs +++ b/crates/nu-command/src/filters/take/take_.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Read; #[derive(Clone)] @@ -46,7 +47,6 @@ impl Command for Take { let head = call.head; let rows_desired: usize = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); match input { @@ -56,15 +56,23 @@ impl Command for Take { Value::List { vals, .. } => Ok(vals .into_iter() .take(rows_desired) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )), Value::Binary { val, .. } => { let slice: Vec = val.into_iter().take(rows_desired).collect(); Ok(PipelineData::Value(Value::binary(slice, span), metadata)) } Value::Range { val, .. } => Ok(val - .into_range_iter(span, ctrlc.clone()) + .into_range_iter(span, Signals::empty()) .take(rows_desired) - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )), // Propagate errors by explicitly matching them before the final case. Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { @@ -87,7 +95,7 @@ impl Command for Take { ByteStream::read( reader.take(rows_desired as u64), head, - None, + Signals::empty(), ByteStreamType::Binary, ), metadata, diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index 0df2407cb1..c7debf5dee 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -85,7 +85,7 @@ impl Command for TakeUntil { .map(|cond| cond.is_false()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/take/take_while.rs b/crates/nu-command/src/filters/take/take_while.rs index 7c282ac38a..b8045080ea 100644 --- a/crates/nu-command/src/filters/take/take_while.rs +++ b/crates/nu-command/src/filters/take/take_while.rs @@ -85,7 +85,7 @@ impl Command for TakeWhile { .map(|cond| cond.is_true()) .unwrap_or(false) }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index 2251f6646a..663b2f19af 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -1,15 +1,11 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; use nu_protocol::{ - byte_stream::copy_with_interrupt, engine::Closure, process::ChildPipe, ByteStream, - ByteStreamSource, OutDest, PipelineMetadata, + byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, ByteStream, + ByteStreamSource, OutDest, PipelineMetadata, Signals, }; use std::{ io::{self, Read, Write}, - sync::{ - atomic::AtomicBool, - mpsc::{self, Sender}, - Arc, - }, + sync::mpsc::{self, Sender}, thread::{self, JoinHandle}, }; @@ -103,12 +99,11 @@ use it in your pipeline."# if let PipelineData::ByteStream(stream, metadata) = input { let span = stream.span(); - let ctrlc = engine_state.ctrlc.clone(); let type_ = stream.type_(); let info = StreamInfo { span, - ctrlc: ctrlc.clone(), + signals: engine_state.signals().clone(), type_, metadata: metadata.clone(), }; @@ -123,7 +118,7 @@ use it in your pipeline."# let tee = IoTee::new(read, tee_thread); Ok(PipelineData::ByteStream( - ByteStream::read(tee, span, ctrlc, type_), + ByteStream::read(tee, span, engine_state.signals().clone(), type_), metadata, )) } @@ -136,7 +131,7 @@ use it in your pipeline."# let tee = IoTee::new(file, tee_thread); Ok(PipelineData::ByteStream( - ByteStream::read(tee, span, ctrlc, type_), + ByteStream::read(tee, span, engine_state.signals().clone(), type_), metadata, )) } @@ -234,19 +229,19 @@ use it in your pipeline."# } let span = input.span().unwrap_or(head); - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let metadata_clone = metadata.clone(); + let signals = engine_state.signals().clone(); Ok(tee(input.into_iter(), move |rx| { - let input = rx.into_pipeline_data_with_metadata(span, ctrlc, metadata_clone); + let input = rx.into_pipeline_data_with_metadata(span, signals, metadata_clone); eval_block(input) }) .err_span(call.head)? .map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span))) .into_pipeline_data_with_metadata( span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), metadata, )) } @@ -386,8 +381,13 @@ fn spawn_tee( let thread = thread::Builder::new() .name("tee".into()) .spawn(move || { - // We don't use ctrlc here because we assume it already has it on the other side - let stream = ByteStream::from_iter(receiver.into_iter(), info.span, None, info.type_); + // We use Signals::empty() here because we assume there already is a Signals on the other side + let stream = ByteStream::from_iter( + receiver.into_iter(), + info.span, + Signals::empty(), + info.type_, + ); eval_block(PipelineData::ByteStream(stream, info.metadata)) }) .err_span(info.span)?; @@ -398,13 +398,13 @@ fn spawn_tee( #[derive(Clone)] struct StreamInfo { span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, metadata: Option, } fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> { - copy_with_interrupt(src, dest, info.span, info.ctrlc.as_deref())?; + copy_with_signals(src, dest, info.span, &info.signals)?; Ok(()) } @@ -421,11 +421,11 @@ fn copy_on_thread( info: &StreamInfo, ) -> Result>, ShellError> { let span = info.span; - let ctrlc = info.ctrlc.clone(); + let signals = info.signals.clone(); thread::Builder::new() .name("stderr copier".into()) .spawn(move || { - copy_with_interrupt(src, dest, span, ctrlc.as_deref())?; + copy_with_signals(src, dest, span, &signals)?; Ok(()) }) .map_err(|e| e.into_spanned(span).into()) diff --git a/crates/nu-command/src/filters/transpose.rs b/crates/nu-command/src/filters/transpose.rs index 49caa56d18..95aa382e4f 100644 --- a/crates/nu-command/src/filters/transpose.rs +++ b/crates/nu-command/src/filters/transpose.rs @@ -173,7 +173,6 @@ pub fn transpose( }); } - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let input: Vec<_> = input.into_iter().collect(); @@ -284,7 +283,11 @@ pub fn transpose( metadata, )) } else { - Ok(result_data.into_pipeline_data_with_metadata(name, ctrlc, metadata)) + Ok(result_data.into_pipeline_data_with_metadata( + name, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/uniq.rs b/crates/nu-command/src/filters/uniq.rs index 7d71d868f0..99abb4e152 100644 --- a/crates/nu-command/src/filters/uniq.rs +++ b/crates/nu-command/src/filters/uniq.rs @@ -241,18 +241,18 @@ pub fn uniq( item_mapper: Box ValueCounter>, metadata: Option, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); let head = call.head; let flag_show_count = call.has_flag(engine_state, stack, "count")?; let flag_show_repeated = call.has_flag(engine_state, stack, "repeated")?; let flag_ignore_case = call.has_flag(engine_state, stack, "ignore-case")?; let flag_only_uniques = call.has_flag(engine_state, stack, "unique")?; + let signals = engine_state.signals().clone(); let uniq_values = input .into_iter() .enumerate() .map_while(|(index, item)| { - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { return None; } Some(item_mapper(ItemMapperState { diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index e724ae77ad..d5ef0825ec 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -187,7 +187,11 @@ fn update( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/upsert.rs b/crates/nu-command/src/filters/upsert.rs index e3678972fb..af2e6c74b1 100644 --- a/crates/nu-command/src/filters/upsert.rs +++ b/crates/nu-command/src/filters/upsert.rs @@ -247,7 +247,11 @@ fn upsert( Ok(pre_elems .into_iter() .chain(stream) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } else if let Value::Closure { val, .. } = replacement { let mut closure = ClosureEval::new(engine_state, stack, *val); let stream = stream.map(move |mut value| { diff --git a/crates/nu-command/src/filters/utils.rs b/crates/nu-command/src/filters/utils.rs index 8d9b1300f6..3ebd4bafbd 100644 --- a/crates/nu-command/src/filters/utils.rs +++ b/crates/nu-command/src/filters/utils.rs @@ -32,10 +32,7 @@ pub fn boolean_fold( let mut closure = ClosureEval::new(engine_state, stack, closure); for value in input { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } - + engine_state.signals().check(head)?; let pred = closure.run_with_value(value)?.into_value(head)?.is_true(); if pred == accumulator { diff --git a/crates/nu-command/src/filters/values.rs b/crates/nu-command/src/filters/values.rs index f6ff8cda2e..9a08f1168c 100644 --- a/crates/nu-command/src/filters/values.rs +++ b/crates/nu-command/src/filters/values.rs @@ -134,7 +134,7 @@ fn values( head: Span, input: PipelineData, ) -> Result { - let ctrlc = engine_state.ctrlc.clone(); + let signals = engine_state.signals().clone(); let metadata = input.metadata(); match input { PipelineData::Empty => Ok(PipelineData::Empty), @@ -144,7 +144,7 @@ fn values( Value::List { vals, .. } => match get_values(&vals, head, span) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), }, Value::Custom { val, .. } => { @@ -152,7 +152,7 @@ fn values( match get_values(&[input_as_base_value], head, span) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), } } @@ -160,7 +160,7 @@ fn values( .values() .cloned() .collect::>() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), // Propagate errors Value::Error { error, .. } => Err(*error), other => Err(ShellError::OnlySupportsThisInputType { @@ -176,7 +176,7 @@ fn values( match get_values(&vals, head, head) { Ok(cols) => Ok(cols .into_iter() - .into_pipeline_data_with_metadata(head, ctrlc, metadata)), + .into_pipeline_data_with_metadata(head, signals, metadata)), Err(err) => Err(err), } } diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index 922879d09b..0a8c8c9ef3 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -70,7 +70,7 @@ not supported."# Err(err) => Some(Value::error(err, head)), } }) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/window.rs b/crates/nu-command/src/filters/window.rs index 5b386b9f84..3c470f478d 100644 --- a/crates/nu-command/src/filters/window.rs +++ b/crates/nu-command/src/filters/window.rs @@ -113,7 +113,6 @@ impl Command for Window { ) -> Result { let head = call.head; let group_size: Spanned = call.req(engine_state, stack, 0)?; - let ctrlc = engine_state.ctrlc.clone(); let metadata = input.metadata(); let stride: Option = call.get_flag(engine_state, stack, "stride")?; let remainder = call.has_flag(engine_state, stack, "remainder")?; @@ -131,7 +130,11 @@ impl Command for Window { remainder, }; - Ok(each_group_iterator.into_pipeline_data_with_metadata(head, ctrlc, metadata)) + Ok(each_group_iterator.into_pipeline_data_with_metadata( + head, + engine_state.signals().clone(), + metadata, + )) } } diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index 52a0fb22c3..dfe1c11d03 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -42,7 +42,7 @@ impl Command for Wrap { | PipelineData::ListStream { .. } => Ok(input .into_iter() .map(move |x| Value::record(record! { name.clone() => x }, span)) - .into_pipeline_data_with_metadata(span, engine_state.ctrlc.clone(), metadata)), + .into_pipeline_data_with_metadata(span, engine_state.signals().clone(), metadata)), PipelineData::ByteStream(stream, ..) => Ok(Value::record( record! { name => stream.into_value()? }, span, diff --git a/crates/nu-command/src/filters/zip.rs b/crates/nu-command/src/filters/zip.rs index 9d81451ed4..59e2b4ac98 100644 --- a/crates/nu-command/src/filters/zip.rs +++ b/crates/nu-command/src/filters/zip.rs @@ -112,7 +112,7 @@ impl Command for Zip { .into_iter() .zip(other) .map(move |(x, y)| Value::list(vec![x, y], head)) - .into_pipeline_data_with_metadata(head, engine_state.ctrlc.clone(), metadata)) + .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata)) } } diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 8f84890645..0fea7e082b 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -1,5 +1,5 @@ use csv::{ReaderBuilder, Trim}; -use nu_protocol::{ByteStream, ListStream, PipelineData, ShellError, Span, Value}; +use nu_protocol::{ByteStream, ListStream, PipelineData, ShellError, Signals, Span, Value}; fn from_csv_error(err: csv::Error, span: Span) -> ShellError { ShellError::DelimiterError { @@ -25,7 +25,7 @@ fn from_delimited_stream( let input_reader = if let Some(stream) = input.reader() { stream } else { - return Ok(ListStream::new(std::iter::empty(), span, None)); + return Ok(ListStream::new(std::iter::empty(), span, Signals::empty())); }; let mut reader = ReaderBuilder::new() @@ -83,7 +83,7 @@ fn from_delimited_stream( Value::record(columns.zip(values).collect(), span) }); - Ok(ListStream::new(iter, span, None)) + Ok(ListStream::new(iter, span, Signals::empty())) } pub(super) struct DelimitedReaderConfig { @@ -106,7 +106,7 @@ pub(super) fn from_delimited_data( PipelineData::Empty => Ok(PipelineData::Empty), PipelineData::Value(value, metadata) => { let string = value.into_string()?; - let byte_stream = ByteStream::read_string(string, name, None); + let byte_stream = ByteStream::read_string(string, name, Signals::empty()); Ok(PipelineData::ListStream( from_delimited_stream(config, byte_stream, name)?, metadata, diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index a1b43abb0a..6252afff4c 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -1,10 +1,7 @@ -use std::{ - io::{BufRead, Cursor}, - sync::{atomic::AtomicBool, Arc}, -}; +use std::io::{BufRead, Cursor}; use nu_engine::command_prelude::*; -use nu_protocol::{ListStream, PipelineMetadata}; +use nu_protocol::{ListStream, PipelineMetadata, Signals}; #[derive(Clone)] pub struct FromJson; @@ -80,7 +77,12 @@ impl Command for FromJson { match input { PipelineData::Value(Value::String { val, .. }, metadata) => { Ok(PipelineData::ListStream( - read_json_lines(Cursor::new(val), span, strict, engine_state.ctrlc.clone()), + read_json_lines( + Cursor::new(val), + span, + strict, + engine_state.signals().clone(), + ), update_metadata(metadata), )) } @@ -89,7 +91,7 @@ impl Command for FromJson { { if let Some(reader) = stream.reader() { Ok(PipelineData::ListStream( - read_json_lines(reader, span, strict, None), + read_json_lines(reader, span, strict, Signals::empty()), update_metadata(metadata), )) } else { @@ -127,7 +129,7 @@ fn read_json_lines( input: impl BufRead + Send + 'static, span: Span, strict: bool, - interrupt: Option>, + signals: Signals, ) -> ListStream { let iter = input .lines() @@ -142,7 +144,7 @@ fn read_json_lines( }) .map(move |result| result.unwrap_or_else(|err| Value::error(err, span))); - ListStream::new(iter, span, interrupt) + ListStream::new(iter, span, signals) } fn convert_nujson_to_value(value: nu_json::Value, span: Span) -> Value { diff --git a/crates/nu-command/src/formats/from/msgpack.rs b/crates/nu-command/src/formats/from/msgpack.rs index 4d8ea5e320..d3d0d710f1 100644 --- a/crates/nu-command/src/formats/from/msgpack.rs +++ b/crates/nu-command/src/formats/from/msgpack.rs @@ -5,12 +5,12 @@ use std::{ error::Error, io::{self, Cursor, ErrorKind}, string::FromUtf8Error, - sync::{atomic::AtomicBool, Arc}, }; use byteorder::{BigEndian, ReadBytesExt}; use chrono::{TimeZone, Utc}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use rmp::decode::{self as mp, ValueReadError}; /// Max recursion depth @@ -111,7 +111,7 @@ MessagePack: https://msgpack.org/ let opts = Opts { span: call.head, objects, - ctrlc: engine_state.ctrlc.clone(), + signals: engine_state.signals().clone(), }; match input { // Deserialize from a byte buffer @@ -227,7 +227,7 @@ impl From for ShellError { pub(crate) struct Opts { pub span: Span, pub objects: bool, - pub ctrlc: Option>, + pub signals: Signals, } /// Read single or multiple values into PipelineData @@ -238,7 +238,7 @@ pub(crate) fn read_msgpack( let Opts { span, objects, - ctrlc, + signals, } = opts; if objects { // Make an iterator that reads multiple values from the reader @@ -262,7 +262,7 @@ pub(crate) fn read_msgpack( None } }) - .into_pipeline_data(span, ctrlc)) + .into_pipeline_data(span, signals)) } else { // Read a single value and then make sure it's EOF let result = read_value(&mut input, span, 0)?; diff --git a/crates/nu-command/src/formats/from/msgpackz.rs b/crates/nu-command/src/formats/from/msgpackz.rs index 7960f3f97a..c98b72cdd6 100644 --- a/crates/nu-command/src/formats/from/msgpackz.rs +++ b/crates/nu-command/src/formats/from/msgpackz.rs @@ -41,7 +41,7 @@ impl Command for FromMsgpackz { let opts = Opts { span, objects, - ctrlc: engine_state.ctrlc.clone(), + signals: engine_state.signals().clone(), }; match input { // Deserialize from a byte buffer diff --git a/crates/nu-command/src/formats/to/delimited.rs b/crates/nu-command/src/formats/to/delimited.rs index a7a2480a34..34bb06b8a2 100644 --- a/crates/nu-command/src/formats/to/delimited.rs +++ b/crates/nu-command/src/formats/to/delimited.rs @@ -1,7 +1,7 @@ use csv::WriterBuilder; use nu_cmd_base::formats::to::delimited::merge_descriptors; use nu_protocol::{ - ByteStream, ByteStreamType, Config, PipelineData, ShellError, Span, Spanned, Value, + ByteStream, ByteStreamType, Config, PipelineData, ShellError, Signals, Span, Spanned, Value, }; use std::{iter, sync::Arc}; @@ -128,37 +128,42 @@ pub fn to_delimited_data( // If we're configured to generate a header, we generate it first, then set this false let mut is_header = !noheaders; - let stream = ByteStream::from_fn(head, None, ByteStreamType::String, move |buffer| { - let mut wtr = WriterBuilder::new() - .delimiter(separator) - .from_writer(buffer); + let stream = ByteStream::from_fn( + head, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + let mut wtr = WriterBuilder::new() + .delimiter(separator) + .from_writer(buffer); - if is_header { - // Unless we are configured not to write a header, we write the header row now, once, - // before everything else. - wtr.write_record(&columns) - .map_err(|err| make_csv_error(err, format_name, head))?; - is_header = false; - Ok(true) - } else if let Some(row) = iter.next() { - // Write each column of a normal row, in order - let record = row.into_record()?; - for column in &columns { - let field = record - .get(column) - .map(|v| to_string_tagged_value(v, &config, format_name)) - .unwrap_or(Ok(String::new()))?; - wtr.write_field(field) + if is_header { + // Unless we are configured not to write a header, we write the header row now, once, + // before everything else. + wtr.write_record(&columns) .map_err(|err| make_csv_error(err, format_name, head))?; + is_header = false; + Ok(true) + } else if let Some(row) = iter.next() { + // Write each column of a normal row, in order + let record = row.into_record()?; + for column in &columns { + let field = record + .get(column) + .map(|v| to_string_tagged_value(v, &config, format_name)) + .unwrap_or(Ok(String::new()))?; + wtr.write_field(field) + .map_err(|err| make_csv_error(err, format_name, head))?; + } + // End the row + wtr.write_record(iter::empty::()) + .map_err(|err| make_csv_error(err, format_name, head))?; + Ok(true) + } else { + Ok(false) } - // End the row - wtr.write_record(iter::empty::()) - .map_err(|err| make_csv_error(err, format_name, head))?; - Ok(true) - } else { - Ok(false) - } - }); + }, + ); Ok(PipelineData::ByteStream(stream, metadata)) } diff --git a/crates/nu-command/src/formats/to/msgpack.rs b/crates/nu-command/src/formats/to/msgpack.rs index bfeb428e3e..02515e26fe 100644 --- a/crates/nu-command/src/formats/to/msgpack.rs +++ b/crates/nu-command/src/formats/to/msgpack.rs @@ -5,7 +5,7 @@ use std::io; use byteorder::{BigEndian, WriteBytesExt}; use nu_engine::command_prelude::*; -use nu_protocol::{ast::PathMember, Spanned}; +use nu_protocol::{ast::PathMember, Signals, Spanned}; use rmp::encode as mp; /// Max recursion depth @@ -189,7 +189,7 @@ pub(crate) fn write_value( // Convert range to list write_value( out, - &Value::list(val.into_range_iter(span, None).collect(), span), + &Value::list(val.into_range_iter(span, Signals::empty()).collect(), span), depth, )?; } diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 1aa1114bce..82b853248c 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -60,7 +60,7 @@ impl Command for ToText { ByteStream::from_iter( iter, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ByteStreamType::String, ), update_metadata(meta), diff --git a/crates/nu-command/src/generators/generate.rs b/crates/nu-command/src/generators/generate.rs index 44bca993d0..8d4f48d3fb 100644 --- a/crates/nu-command/src/generators/generate.rs +++ b/crates/nu-command/src/generators/generate.rs @@ -166,7 +166,7 @@ used as the next argument to the closure, otherwise generation stops. Ok(iter .flatten() - .into_pipeline_data(call.head, engine_state.ctrlc.clone())) + .into_pipeline_data(call.head, engine_state.signals().clone())) } } diff --git a/crates/nu-command/src/generators/seq.rs b/crates/nu-command/src/generators/seq.rs index 359d479ca0..3636d89d4c 100644 --- a/crates/nu-command/src/generators/seq.rs +++ b/crates/nu-command/src/generators/seq.rs @@ -129,7 +129,7 @@ pub fn run_seq( span, }, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ) } else { ListStream::new( @@ -141,7 +141,7 @@ pub fn run_seq( span, }, span, - engine_state.ctrlc.clone(), + engine_state.signals().clone(), ) }; diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs index ab15ccae7a..f098f8a0cb 100644 --- a/crates/nu-command/src/hash/generic_digest.rs +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -96,7 +96,7 @@ where } } else { let args = Arguments { binary, cell_paths }; - operate(action::, args, input, head, engine_state.ctrlc.clone()) + operate(action::, args, input, head, engine_state.signals()) } } } diff --git a/crates/nu-command/src/math/abs.rs b/crates/nu-command/src/math/abs.rs index 8f653142fa..b16097ba8d 100644 --- a/crates/nu-command/src/math/abs.rs +++ b/crates/nu-command/src/math/abs.rs @@ -42,10 +42,7 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let head = call.head; - input.map( - move |value| abs_helper(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| abs_helper(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/ceil.rs b/crates/nu-command/src/math/ceil.rs index 0b0f6d1696..0886ff0bc5 100644 --- a/crates/nu-command/src/math/ceil.rs +++ b/crates/nu-command/src/math/ceil.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/floor.rs b/crates/nu-command/src/math/floor.rs index e85e3ca674..c2b34c2cf9 100644 --- a/crates/nu-command/src/math/floor.rs +++ b/crates/nu-command/src/math/floor.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/log.rs b/crates/nu-command/src/math/log.rs index 90fad17daf..7a7e4bd27b 100644 --- a/crates/nu-command/src/math/log.rs +++ b/crates/nu-command/src/math/log.rs @@ -59,7 +59,7 @@ impl Command for SubCommand { let base = base.item; input.map( move |value| operate(value, head, base), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/math/round.rs b/crates/nu-command/src/math/round.rs index 7693c9a316..eafe669cf1 100644 --- a/crates/nu-command/src/math/round.rs +++ b/crates/nu-command/src/math/round.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { } input.map( move |value| operate(value, head, precision_param), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/math/sqrt.rs b/crates/nu-command/src/math/sqrt.rs index c9c9765912..a3ab5fbbd8 100644 --- a/crates/nu-command/src/math/sqrt.rs +++ b/crates/nu-command/src/math/sqrt.rs @@ -41,10 +41,7 @@ impl Command for SubCommand { if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); } - input.map( - move |value| operate(value, head), - engine_state.ctrlc.clone(), - ) + input.map(move |value| operate(value, head), engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/math/utils.rs b/crates/nu-command/src/math/utils.rs index 9d2c15e15f..765c1f42fb 100644 --- a/crates/nu-command/src/math/utils.rs +++ b/crates/nu-command/src/math/utils.rs @@ -1,6 +1,6 @@ use core::slice; use indexmap::IndexMap; -use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Span, Value}; +use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; pub fn run_with_function( call: &Call, @@ -92,7 +92,7 @@ pub fn calculate( } PipelineData::Value(Value::Range { val, .. }, ..) => { let new_vals: Result, ShellError> = val - .into_range_iter(span, None) + .into_range_iter(span, Signals::empty()) .map(|val| mf(&[val], span, name)) .collect(); diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index 2c02d7be33..deeb768eea 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -5,16 +5,12 @@ use base64::{ Engine, }; use nu_engine::command_prelude::*; -use nu_protocol::ByteStream; +use nu_protocol::{ByteStream, Signals}; use std::{ collections::HashMap, path::PathBuf, str::FromStr, - sync::{ - atomic::AtomicBool, - mpsc::{self, RecvTimeoutError}, - Arc, - }, + sync::mpsc::{self, RecvTimeoutError}, time::Duration, }; use ureq::{Error, ErrorKind, Request, Response}; @@ -129,7 +125,7 @@ pub fn response_to_buffer( let reader = response.into_reader(); PipelineData::ByteStream( - ByteStream::read(reader, span, engine_state.ctrlc.clone(), response_type) + ByteStream::read(reader, span, engine_state.signals().clone(), response_type) .with_known_size(buffer_size), None, ) @@ -192,13 +188,14 @@ pub fn send_request( request: Request, http_body: HttpBody, content_type: Option, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let request_url = request.url().to_string(); match http_body { HttpBody::None => { - send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c) + send_cancellable_request(&request_url, Box::new(|| request.call()), span, signals) } HttpBody::ByteStream(byte_stream) => { let req = if let Some(content_type) = content_type { @@ -207,7 +204,7 @@ pub fn send_request( request }; - send_cancellable_request_bytes(&request_url, req, byte_stream, ctrl_c) + send_cancellable_request_bytes(&request_url, req, byte_stream, span, signals) } HttpBody::Value(body) => { let (body_type, req) = match content_type { @@ -224,20 +221,32 @@ pub fn send_request( Value::Binary { val, .. } => send_cancellable_request( &request_url, Box::new(move || req.send_bytes(&val)), - ctrl_c, + span, + signals, ), Value::String { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } Value::String { val, .. } => send_cancellable_request( &request_url, Box::new(move || req.send_string(&val)), - ctrl_c, + span, + signals, ), Value::Record { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } Value::Record { val, .. } if body_type == BodyType::Form => { let mut data: Vec<(String, String)> = Vec::with_capacity(val.len()); @@ -254,7 +263,7 @@ pub fn send_request( .collect::>(); req.send_form(&data) }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + send_cancellable_request(&request_url, Box::new(request_fn), span, signals) } Value::List { vals, .. } if body_type == BodyType::Form => { if vals.len() % 2 != 0 { @@ -276,11 +285,16 @@ pub fn send_request( .collect::>(); req.send_form(&data) }; - send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c) + send_cancellable_request(&request_url, Box::new(request_fn), span, signals) } Value::List { .. } if body_type == BodyType::Json => { let data = value_to_json_value(&body)?; - send_cancellable_request(&request_url, Box::new(|| req.send_json(data)), ctrl_c) + send_cancellable_request( + &request_url, + Box::new(|| req.send_json(data)), + span, + signals, + ) } _ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError { msg: "unsupported body input".into(), @@ -295,7 +309,8 @@ pub fn send_request( fn send_cancellable_request( request_url: &str, request_fn: Box Result + Sync + Send>, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let (tx, rx) = mpsc::channel::>(); @@ -310,12 +325,7 @@ fn send_cancellable_request( // ...and poll the channel for responses loop { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - // Return early and give up on the background thread. The connection will either time out or be disconnected - return Err(ShellErrorOrRequestError::ShellError( - ShellError::InterruptedByUser { span: None }, - )); - } + signals.check(span)?; // 100ms wait time chosen arbitrarily match rx.recv_timeout(Duration::from_millis(100)) { @@ -336,7 +346,8 @@ fn send_cancellable_request_bytes( request_url: &str, request: Request, byte_stream: ByteStream, - ctrl_c: Option>, + span: Span, + signals: &Signals, ) -> Result { let (tx, rx) = mpsc::channel::>(); let request_url_string = request_url.to_string(); @@ -369,12 +380,7 @@ fn send_cancellable_request_bytes( // ...and poll the channel for responses loop { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - // Return early and give up on the background thread. The connection will either time out or be disconnected - return Err(ShellErrorOrRequestError::ShellError( - ShellError::InterruptedByUser { span: None }, - )); - } + signals.check(span)?; // 100ms wait time chosen arbitrarily match rx.recv_timeout(Duration::from_millis(100)) { diff --git a/crates/nu-command/src/network/http/delete.rs b/crates/nu-command/src/network/http/delete.rs index c2ef774a01..f32c9d15bb 100644 --- a/crates/nu-command/src/network/http/delete.rs +++ b/crates/nu-command/src/network/http/delete.rs @@ -203,7 +203,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -214,7 +213,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/get.rs b/crates/nu-command/src/network/http/get.rs index 04582d5f00..3b702404f4 100644 --- a/crates/nu-command/src/network/http/get.rs +++ b/crates/nu-command/src/network/http/get.rs @@ -171,7 +171,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -182,7 +181,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); + let response = send_request( + request.clone(), + HttpBody::None, + None, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/head.rs b/crates/nu-command/src/network/http/head.rs index 57dfafcba2..797fa138e3 100644 --- a/crates/nu-command/src/network/http/head.rs +++ b/crates/nu-command/src/network/http/head.rs @@ -1,13 +1,11 @@ +use super::client::HttpBody; use crate::network::http::client::{ check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header, request_add_custom_headers, request_handle_response_headers, request_set_timeout, send_request, }; use nu_engine::command_prelude::*; - -use std::sync::{atomic::AtomicBool, Arc}; - -use super::client::HttpBody; +use nu_protocol::Signals; #[derive(Clone)] pub struct SubCommand; @@ -133,9 +131,8 @@ fn run_head( timeout: call.get_flag(engine_state, stack, "max-time")?, redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; - let ctrl_c = engine_state.ctrlc.clone(); - helper(engine_state, stack, call, args, ctrl_c) + helper(engine_state, stack, call, args, engine_state.signals()) } // Helper function that actually goes to retrieve the resource from the url given @@ -145,7 +142,7 @@ fn helper( stack: &mut Stack, call: &Call, args: Arguments, - ctrlc: Option>, + signals: &Signals, ) -> Result { let span = args.url.span(); let (requested_url, _) = http_parse_url(call, span, args.url)?; @@ -158,7 +155,7 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request, HttpBody::None, None, ctrlc); + let response = send_request(request, HttpBody::None, None, call.head, signals); check_response_redirection(redirect_mode, span, &response)?; request_handle_response_headers(span, response) } diff --git a/crates/nu-command/src/network/http/options.rs b/crates/nu-command/src/network/http/options.rs index 91ecac02d5..cd9a1f79c1 100644 --- a/crates/nu-command/src/network/http/options.rs +++ b/crates/nu-command/src/network/http/options.rs @@ -151,7 +151,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?; @@ -161,7 +160,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), HttpBody::None, None, ctrl_c); + let response = send_request( + request.clone(), + HttpBody::None, + None, + call.head, + engine_state.signals(), + ); // http options' response always showed in header, so we set full to true. // And `raw` is useless too because options method doesn't return body, here we set to true diff --git a/crates/nu-command/src/network/http/patch.rs b/crates/nu-command/src/network/http/patch.rs index 2cf66a2a82..7f49781284 100644 --- a/crates/nu-command/src/network/http/patch.rs +++ b/crates/nu-command/src/network/http/patch.rs @@ -205,7 +205,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -216,7 +215,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/post.rs b/crates/nu-command/src/network/http/post.rs index d48bbcc9fe..ea4ec093f3 100644 --- a/crates/nu-command/src/network/http/post.rs +++ b/crates/nu-command/src/network/http/post.rs @@ -203,7 +203,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -214,7 +213,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/http/put.rs b/crates/nu-command/src/network/http/put.rs index dbd3b245cb..e2118ea359 100644 --- a/crates/nu-command/src/network/http/put.rs +++ b/crates/nu-command/src/network/http/put.rs @@ -204,7 +204,6 @@ fn helper( args: Arguments, ) -> Result { let span = args.url.span(); - let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; let redirect_mode = http_parse_redirect_mode(args.redirect)?; @@ -215,7 +214,13 @@ fn helper( request = request_add_authorization_header(args.user, args.password, request); request = request_add_custom_headers(args.headers, request)?; - let response = send_request(request.clone(), args.data, args.content_type, ctrl_c); + let response = send_request( + request.clone(), + args.data, + args.content_type, + call.head, + engine_state.signals(), + ); let request_flags = RequestFlags { raw: args.raw, diff --git a/crates/nu-command/src/network/url/decode.rs b/crates/nu-command/src/network/url/decode.rs index 8789eb13ca..b98e50a56e 100644 --- a/crates/nu-command/src/network/url/decode.rs +++ b/crates/nu-command/src/network/url/decode.rs @@ -48,7 +48,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/network/url/encode.rs b/crates/nu-command/src/network/url/encode.rs index 845487963b..96e0289903 100644 --- a/crates/nu-command/src/network/url/encode.rs +++ b/crates/nu-command/src/network/url/encode.rs @@ -50,15 +50,9 @@ impl Command for SubCommand { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); if call.has_flag(engine_state, stack, "all")? { - operate( - action_all, - args, - input, - call.head, - engine_state.ctrlc.clone(), - ) + operate(action_all, args, input, call.head, engine_state.signals()) } else { - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } } diff --git a/crates/nu-command/src/path/basename.rs b/crates/nu-command/src/path/basename.rs index 04f7cf380e..8b7082048e 100644 --- a/crates/nu-command/src/path/basename.rs +++ b/crates/nu-command/src/path/basename.rs @@ -61,7 +61,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_basename, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -82,7 +82,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_basename, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/dirname.rs b/crates/nu-command/src/path/dirname.rs index 2eb864215e..218091ee34 100644 --- a/crates/nu-command/src/path/dirname.rs +++ b/crates/nu-command/src/path/dirname.rs @@ -69,7 +69,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_dirname, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -91,7 +91,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&get_dirname, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/exists.rs b/crates/nu-command/src/path/exists.rs index a99f04113e..86b00c6024 100644 --- a/crates/nu-command/src/path/exists.rs +++ b/crates/nu-command/src/path/exists.rs @@ -65,7 +65,7 @@ If you need to distinguish dirs and files, please use `path type`."# } input.map( move |value| super::operate(&exists, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -87,7 +87,7 @@ If you need to distinguish dirs and files, please use `path type`."# } input.map( move |value| super::operate(&exists, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 18497c6426..ac51978810 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -70,7 +70,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&expand, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -93,7 +93,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&expand, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/parse.rs b/crates/nu-command/src/path/parse.rs index 039f1012ed..cec2f5c6ac 100644 --- a/crates/nu-command/src/path/parse.rs +++ b/crates/nu-command/src/path/parse.rs @@ -63,7 +63,7 @@ On Windows, an extra 'prefix' column is added."# } input.map( move |value| super::operate(&parse, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -84,7 +84,7 @@ On Windows, an extra 'prefix' column is added."# } input.map( move |value| super::operate(&parse, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/relative_to.rs b/crates/nu-command/src/path/relative_to.rs index 6533f6fa79..35df385d74 100644 --- a/crates/nu-command/src/path/relative_to.rs +++ b/crates/nu-command/src/path/relative_to.rs @@ -67,7 +67,7 @@ path."# } input.map( move |value| super::operate(&relative_to, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -88,7 +88,7 @@ path."# } input.map( move |value| super::operate(&relative_to, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/split.rs b/crates/nu-command/src/path/split.rs index ea8e7e1b1f..80d86fb998 100644 --- a/crates/nu-command/src/path/split.rs +++ b/crates/nu-command/src/path/split.rs @@ -51,7 +51,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&split, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -70,7 +70,7 @@ impl Command for SubCommand { } input.map( move |value| super::operate(&split, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index e58e697604..bf66c8cb3b 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -64,7 +64,7 @@ If the path does not exist, null will be returned."# } input.map( move |value| super::operate(&path_type, &args, value, head), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -85,7 +85,7 @@ If the path does not exist, null will be returned."# } input.map( move |value| super::operate(&path_type, &args, value, head), - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 9e0b0bba66..29603be9e7 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -1,11 +1,8 @@ use nu_ansi_term::*; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; +use nu_protocol::{engine::StateWorkingSet, Signals}; use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - sync::{atomic::AtomicBool, Arc}, -}; +use std::collections::HashMap; #[derive(Clone)] pub struct AnsiCommand; @@ -657,10 +654,13 @@ Operating system commands: let escape: bool = call.has_flag(engine_state, stack, "escape")?; let osc: bool = call.has_flag(engine_state, stack, "osc")?; let use_ansi_coloring = engine_state.get_config().use_ansi_coloring; - let ctrlc = engine_state.ctrlc.clone(); if list { - return Ok(generate_ansi_code_list(ctrlc, call.head, use_ansi_coloring)); + return Ok(generate_ansi_code_list( + engine_state.signals().clone(), + call.head, + use_ansi_coloring, + )); } // The code can now be one of the ansi abbreviations like green_bold @@ -691,10 +691,13 @@ Operating system commands: let escape: bool = call.has_flag_const(working_set, "escape")?; let osc: bool = call.has_flag_const(working_set, "osc")?; let use_ansi_coloring = working_set.get_config().use_ansi_coloring; - let ctrlc = working_set.permanent().ctrlc.clone(); if list { - return Ok(generate_ansi_code_list(ctrlc, call.head, use_ansi_coloring)); + return Ok(generate_ansi_code_list( + working_set.permanent().signals().clone(), + call.head, + use_ansi_coloring, + )); } // The code can now be one of the ansi abbreviations like green_bold @@ -827,7 +830,7 @@ pub fn str_to_ansi(s: &str) -> Option { } fn generate_ansi_code_list( - ctrlc: Option>, + signals: Signals, call_span: Span, use_ansi_coloring: bool, ) -> PipelineData { @@ -862,7 +865,7 @@ fn generate_ansi_code_list( Value::record(record, call_span) }) - .into_pipeline_data(call_span, ctrlc) + .into_pipeline_data(call_span, signals.clone()) } fn build_ansi_hashmap(v: &[AnsiCode]) -> HashMap<&str, &str> { diff --git a/crates/nu-command/src/platform/ansi/link.rs b/crates/nu-command/src/platform/ansi/link.rs index 68fc17977b..b45b365d3f 100644 --- a/crates/nu-command/src/platform/ansi/link.rs +++ b/crates/nu-command/src/platform/ansi/link.rs @@ -91,12 +91,12 @@ fn operate( if column_paths.is_empty() { input.map( move |v| process_value(&v, text.as_deref()), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } else { input.map( move |v| process_each_path(v, &column_paths, text.as_deref(), command_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } } diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index 35d410161c..3d59da6a10 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -56,7 +56,7 @@ impl Command for SubCommand { cell_paths, config: config.clone(), }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs index e4a3039672..10ce4f5420 100644 --- a/crates/nu-command/src/platform/dir_info.rs +++ b/crates/nu-command/src/platform/dir_info.rs @@ -1,10 +1,7 @@ use filesize::file_real_size_fast; use nu_glob::Pattern; -use nu_protocol::{record, ShellError, Span, Value}; -use std::{ - path::PathBuf, - sync::{atomic::AtomicBool, Arc}, -}; +use nu_protocol::{record, ShellError, Signals, Span, Value}; +use std::path::PathBuf; #[derive(Debug, Clone)] pub struct DirBuilder { @@ -82,8 +79,9 @@ impl DirInfo { path: impl Into, params: &DirBuilder, depth: Option, - ctrl_c: Option>, - ) -> Self { + span: Span, + signals: &Signals, + ) -> Result { let path = path.into(); let mut s = Self { @@ -107,14 +105,12 @@ impl DirInfo { match std::fs::read_dir(&s.path) { Ok(d) => { for f in d { - if nu_utils::ctrl_c::was_pressed(&ctrl_c) { - break; - } + signals.check(span)?; match f { Ok(i) => match i.file_type() { Ok(t) if t.is_dir() => { - s = s.add_dir(i.path(), depth, params, ctrl_c.clone()) + s = s.add_dir(i.path(), depth, params, span, signals)? } Ok(_t) => s = s.add_file(i.path(), params), Err(e) => s = s.add_error(e.into()), @@ -125,7 +121,7 @@ impl DirInfo { } Err(e) => s = s.add_error(e.into()), } - s + Ok(s) } fn add_dir( @@ -133,21 +129,22 @@ impl DirInfo { path: impl Into, mut depth: Option, params: &DirBuilder, - ctrl_c: Option>, - ) -> Self { + span: Span, + signals: &Signals, + ) -> Result { if let Some(current) = depth { if let Some(new) = current.checked_sub(1) { depth = Some(new); } else { - return self; + return Ok(self); } } - let d = DirInfo::new(path, params, depth, ctrl_c); + let d = DirInfo::new(path, params, depth, span, signals)?; self.size += d.size; self.blocks += d.blocks; self.dirs.push(d); - self + Ok(self) } fn add_file(mut self, f: impl Into, params: &DirBuilder) -> Self { diff --git a/crates/nu-command/src/platform/sleep.rs b/crates/nu-command/src/platform/sleep.rs index ddf429509c..4d5f6ec827 100644 --- a/crates/nu-command/src/platform/sleep.rs +++ b/crates/nu-command/src/platform/sleep.rs @@ -56,12 +56,7 @@ impl Command for Sleep { break; } thread::sleep(CTRL_C_CHECK_INTERVAL.min(time_until_deadline)); - // exit early if Ctrl+C was pressed - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Err(ShellError::InterruptedByUser { - span: Some(call.head), - }); - } + engine_state.signals().check(call.head)?; } Ok(Value::nothing(call.head).into_pipeline_data()) diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index 2fb659ad1e..5e3a1b98b6 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -78,7 +78,7 @@ fn dice( Value::int(thread_rng.gen_range(1..sides + 1) as i64, span) }); - Ok(ListStream::new(iter, span, engine_state.ctrlc.clone()).into()) + Ok(ListStream::new(iter, span, engine_state.signals().clone()).into()) } #[cfg(test)] diff --git a/crates/nu-command/src/stor/create.rs b/crates/nu-command/src/stor/create.rs index 630718489b..1e1219889d 100644 --- a/crates/nu-command/src/stor/create.rs +++ b/crates/nu-command/src/stor/create.rs @@ -54,7 +54,10 @@ impl Command for StorCreate { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let columns: Option = call.get_flag(engine_state, stack, "columns")?; - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + engine_state.signals().clone(), + )); process(table_name, span, &db, columns)?; // dbg!(db.clone()); @@ -141,6 +144,8 @@ fn process( #[cfg(test)] mod test { + use nu_protocol::Signals; + use super::*; #[test] @@ -154,7 +159,10 @@ mod test { fn test_process_with_valid_parameters() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); columns.insert( "int_column".to_string(), @@ -170,7 +178,10 @@ mod test { fn test_process_with_missing_table_name() { let table_name = None; let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); columns.insert( "int_column".to_string(), @@ -190,7 +201,10 @@ mod test { fn test_process_with_missing_columns() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let result = process(table_name, span, &db, None); @@ -205,7 +219,10 @@ mod test { fn test_process_with_unsupported_column_data_type() { let table_name = Some("test_table".to_string()); let span = Span::unknown(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let mut columns = Record::new(); let column_datatype = "bogus_data_type".to_string(); columns.insert( diff --git a/crates/nu-command/src/stor/delete.rs b/crates/nu-command/src/stor/delete.rs index 4de4874140..676e0490b0 100644 --- a/crates/nu-command/src/stor/delete.rs +++ b/crates/nu-command/src/stor/delete.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorDelete; @@ -82,7 +83,10 @@ impl Command for StorDelete { } // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Some(new_table_name) = table_name_opt { if let Ok(conn) = db.open_connection() { diff --git a/crates/nu-command/src/stor/export.rs b/crates/nu-command/src/stor/export.rs index 95c5ee9f35..f8255eec49 100644 --- a/crates/nu-command/src/stor/export.rs +++ b/crates/nu-command/src/stor/export.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorExport; @@ -58,7 +59,10 @@ impl Command for StorExport { }; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(conn) = db.open_connection() { // This uses vacuum. I'm not really sure if this is the best way to do this. diff --git a/crates/nu-command/src/stor/import.rs b/crates/nu-command/src/stor/import.rs index 682694e8bb..20b5b64f2d 100644 --- a/crates/nu-command/src/stor/import.rs +++ b/crates/nu-command/src/stor/import.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorImport; @@ -58,7 +59,10 @@ impl Command for StorImport { }; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(mut conn) = db.open_connection() { db.restore_database_from_file(&mut conn, file_name) diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index 4b9677941b..b6b8d50906 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -1,5 +1,6 @@ use crate::database::{values_to_sql, SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use rusqlite::params_from_iter; #[derive(Clone)] @@ -65,7 +66,10 @@ impl Command for StorInsert { let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; // let config = engine_state.get_config(); - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // Check if the record is being passed as input or using the data record parameter let columns = handle(span, data_record, input)?; @@ -198,7 +202,10 @@ mod test { #[test] fn test_process_with_simple_parameters() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_process_with_simple_parameters ( int_column INTEGER, real_column REAL, @@ -237,7 +244,10 @@ mod test { #[test] fn test_process_string_with_space() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_process_string_with_space ( str_column VARCHAR(255) )"; @@ -262,7 +272,10 @@ mod test { #[test] fn test_no_errors_when_string_too_long() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_string_too_long ( str_column VARCHAR(8) )"; @@ -287,7 +300,10 @@ mod test { #[test] fn test_no_errors_when_param_is_wrong_type() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_param_is_wrong_type ( int_column INT )"; @@ -312,7 +328,10 @@ mod test { #[test] fn test_errors_when_column_doesnt_exist() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let create_stmt = "CREATE TABLE test_errors_when_column_doesnt_exist ( int_column INT )"; @@ -337,7 +356,10 @@ mod test { #[test] fn test_errors_when_table_doesnt_exist() { - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); let table_name = Some("test_errors_when_table_doesnt_exist".to_string()); let span = Span::unknown(); diff --git a/crates/nu-command/src/stor/open.rs b/crates/nu-command/src/stor/open.rs index c7f6f9f746..ba0f17c2af 100644 --- a/crates/nu-command/src/stor/open.rs +++ b/crates/nu-command/src/stor/open.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorOpen; @@ -54,7 +55,10 @@ impl Command for StorOpen { // It returns the output of `select * from my_table_name` // Just create an empty database with MEMORY_DB and nothing else - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // dbg!(db.clone()); Ok(db.into_value(call.head).into_pipeline_data()) diff --git a/crates/nu-command/src/stor/reset.rs b/crates/nu-command/src/stor/reset.rs index d4489fb702..ba9e2a9681 100644 --- a/crates/nu-command/src/stor/reset.rs +++ b/crates/nu-command/src/stor/reset.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorReset; @@ -42,7 +43,10 @@ impl Command for StorReset { let span = call.head; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); if let Ok(conn) = db.open_connection() { db.drop_all_tables(&conn) diff --git a/crates/nu-command/src/stor/update.rs b/crates/nu-command/src/stor/update.rs index d731207a3f..18cf6f9f0f 100644 --- a/crates/nu-command/src/stor/update.rs +++ b/crates/nu-command/src/stor/update.rs @@ -1,5 +1,6 @@ use crate::database::{SQLiteDatabase, MEMORY_DB}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; #[derive(Clone)] pub struct StorUpdate; @@ -79,7 +80,10 @@ impl Command for StorUpdate { call.get_flag(engine_state, stack, "where-clause")?; // Open the in-mem database - let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); + let db = Box::new(SQLiteDatabase::new( + std::path::Path::new(MEMORY_DB), + Signals::empty(), + )); // Check if the record is being passed as input or using the update record parameter let columns = handle(span, update_record, input)?; diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 6834134b26..7737210254 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -1,8 +1,8 @@ use indexmap::{indexmap, IndexMap}; use nu_engine::command_prelude::*; +use nu_protocol::Signals; use once_cell::sync::Lazy; -use std::sync::{atomic::AtomicBool, Arc}; // Character used to separate directories in a Path Environment variable on windows is ";" #[cfg(target_family = "windows")] @@ -229,11 +229,13 @@ impl Command for Char { let list = call.has_flag_const(working_set, "list")?; let integer = call.has_flag_const(working_set, "integer")?; let unicode = call.has_flag_const(working_set, "unicode")?; - let ctrlc = working_set.permanent().ctrlc.clone(); // handle -l flag if list { - return Ok(generate_character_list(ctrlc, call.head)); + return Ok(generate_character_list( + working_set.permanent().signals().clone(), + call.head, + )); } // handle -i flag @@ -264,11 +266,13 @@ impl Command for Char { let list = call.has_flag(engine_state, stack, "list")?; let integer = call.has_flag(engine_state, stack, "integer")?; let unicode = call.has_flag(engine_state, stack, "unicode")?; - let ctrlc = engine_state.ctrlc.clone(); // handle -l flag if list { - return Ok(generate_character_list(ctrlc, call_span)); + return Ok(generate_character_list( + engine_state.signals().clone(), + call_span, + )); } // handle -i flag @@ -289,7 +293,7 @@ impl Command for Char { } } -fn generate_character_list(ctrlc: Option>, call_span: Span) -> PipelineData { +fn generate_character_list(signals: Signals, call_span: Span) -> PipelineData { CHAR_MAP .iter() .map(move |(name, s)| { @@ -308,7 +312,7 @@ fn generate_character_list(ctrlc: Option>, call_span: Span) -> P Value::record(record, call_span) }) - .into_pipeline_data(call_span, ctrlc) + .into_pipeline_data(call_span, signals) } fn handle_integer_flag( diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 62a2d8bbcc..2f1f713b33 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -199,7 +199,7 @@ fn guess_width( Err(e) => Value::error(e, input_span), } }) - .into_pipeline_data(input_span, engine_state.ctrlc.clone())) + .into_pipeline_data(input_span, engine_state.signals().clone())) } else { let length = result[0].len(); let columns: Vec = (0..length).map(|n| format!("column{n}")).collect(); @@ -224,7 +224,7 @@ fn guess_width( Err(e) => Value::error(e, input_span), } }) - .into_pipeline_data(input_span, engine_state.ctrlc.clone())) + .into_pipeline_data(input_span, engine_state.signals().clone())) } } @@ -235,7 +235,6 @@ fn detect_columns( args: Arguments, ) -> Result { let name_span = call.head; - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let input = input.collect_string("", config)?; @@ -316,7 +315,7 @@ fn detect_columns( None => Value::record(record, name_span), } }) - .into_pipeline_data(call.head, ctrlc)) + .into_pipeline_data(call.head, engine_state.signals().clone())) } else { Ok(PipelineData::empty()) } diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index 8050193212..afc143983e 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -75,7 +75,7 @@ pub fn operate( cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action( diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index 1aaf1fb851..dd005b9216 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -127,7 +127,7 @@ fn run( Some(format) => format_helper(value, format.item.as_str(), format.span, head), None => format_helper_rfc2822(value, head), }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/format/duration.rs b/crates/nu-command/src/strings/format/duration.rs index 281542f49a..fbbe192048 100644 --- a/crates/nu-command/src/strings/format/duration.rs +++ b/crates/nu-command/src/strings/format/duration.rs @@ -81,7 +81,7 @@ impl Command for FormatDuration { arg, input, call.head, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -108,7 +108,7 @@ impl Command for FormatDuration { arg, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/format/filesize.rs b/crates/nu-command/src/strings/format/filesize.rs index ebd43d90b1..63865ac2ac 100644 --- a/crates/nu-command/src/strings/format/filesize.rs +++ b/crates/nu-command/src/strings/format/filesize.rs @@ -76,7 +76,7 @@ impl Command for FormatFilesize { arg, input, call.head, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } @@ -101,7 +101,7 @@ impl Command for FormatFilesize { arg, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 4318b3da8b..9bf31de73c 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -1,10 +1,7 @@ use fancy_regex::{Captures, Regex}; use nu_engine::command_prelude::*; -use nu_protocol::{engine::StateWorkingSet, ListStream}; -use std::{ - collections::VecDeque, - sync::{atomic::AtomicBool, Arc}, -}; +use nu_protocol::{engine::StateWorkingSet, ListStream, Signals}; +use std::collections::VecDeque; #[derive(Clone)] pub struct Parse; @@ -163,8 +160,6 @@ fn operate( }) .collect::>(); - let ctrlc = engine_state.ctrlc.clone(); - match input { PipelineData::Empty => Ok(PipelineData::Empty), PipelineData::Value(value, ..) => match value { @@ -192,10 +187,10 @@ fn operate( columns, iter, span: head, - ctrlc, + signals: engine_state.signals().clone(), }; - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } value => Err(ShellError::PipelineMismatch { exp_input_type: "string".into(), @@ -220,7 +215,7 @@ fn operate( columns, iter, span: head, - ctrlc, + signals: engine_state.signals().clone(), } }) .into()), @@ -232,10 +227,10 @@ fn operate( columns, iter: lines, span: head, - ctrlc, + signals: engine_state.signals().clone(), }; - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } else { Ok(PipelineData::Empty) } @@ -302,7 +297,7 @@ struct ParseIter>> { columns: Vec, iter: I, span: Span, - ctrlc: Option>, + signals: Signals, } impl>> ParseIter { @@ -320,7 +315,7 @@ impl>> Iterator for ParseIter { fn next(&mut self) -> Option { loop { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.signals.interrupted() { return None; } diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index 08e73b9830..370df262ea 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -124,7 +124,7 @@ fn split_chars( let span = call.head; input.map( move |x| split_chars_helper(&x, span, graphemes), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index 02eff7845f..540cfabe54 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -170,7 +170,7 @@ fn split_column( input.flat_map( move |x| split_column_helper(&x, ®ex, &args.rest, args.collapse_empty, name_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/list.rs b/crates/nu-command/src/strings/split/list.rs index d52bb5401a..eb874841a2 100644 --- a/crates/nu-command/src/strings/split/list.rs +++ b/crates/nu-command/src/strings/split/list.rs @@ -215,9 +215,7 @@ fn split_list( let matcher = Matcher::new(has_regex, separator)?; for val in input { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - break; - } + engine_state.signals().check(call.head)?; if matcher.compare(&val)? { if !temp_list.is_empty() { diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs index 8bc0003cb6..1f427a06e0 100644 --- a/crates/nu-command/src/strings/split/row.rs +++ b/crates/nu-command/src/strings/split/row.rs @@ -170,7 +170,7 @@ fn split_row( })?; input.flat_map( move |x| split_row_helper(&x, ®ex, args.max_split, name_span), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/split/words.rs b/crates/nu-command/src/strings/split/words.rs index 0dd5dc9383..6cb5562a70 100644 --- a/crates/nu-command/src/strings/split/words.rs +++ b/crates/nu-command/src/strings/split/words.rs @@ -177,7 +177,7 @@ fn split_words( input.map( move |x| split_words_helper(&x, args.word_length, span, args.graphemes), - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/capitalize.rs b/crates/nu-command/src/strings/str_/case/capitalize.rs index 20f334976c..862ca127c2 100644 --- a/crates/nu-command/src/strings/str_/case/capitalize.rs +++ b/crates/nu-command/src/strings/str_/case/capitalize.rs @@ -108,7 +108,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/downcase.rs b/crates/nu-command/src/strings/str_/case/downcase.rs index 316050d501..0493a663aa 100644 --- a/crates/nu-command/src/strings/str_/case/downcase.rs +++ b/crates/nu-command/src/strings/str_/case/downcase.rs @@ -116,7 +116,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/case/mod.rs b/crates/nu-command/src/strings/str_/case/mod.rs index 32b50c4bc6..3390ff097e 100644 --- a/crates/nu-command/src/strings/str_/case/mod.rs +++ b/crates/nu-command/src/strings/str_/case/mod.rs @@ -38,7 +38,7 @@ where case_operation, cell_paths, }; - general_operate(action, args, input, call.head, engine_state.ctrlc.clone()) + general_operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, args: &Arguments, head: Span) -> Value diff --git a/crates/nu-command/src/strings/str_/case/upcase.rs b/crates/nu-command/src/strings/str_/case/upcase.rs index 9e55f25f64..557239ec91 100644 --- a/crates/nu-command/src/strings/str_/case/upcase.rs +++ b/crates/nu-command/src/strings/str_/case/upcase.rs @@ -93,7 +93,7 @@ fn operate( ret } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index ff9cb85fcb..27d8a8e313 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -103,7 +103,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/deunicode.rs b/crates/nu-command/src/strings/str_/deunicode.rs index 0b70cf2003..ef732571f9 100644 --- a/crates/nu-command/src/strings/str_/deunicode.rs +++ b/crates/nu-command/src/strings/str_/deunicode.rs @@ -39,7 +39,7 @@ impl Command for SubCommand { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -56,7 +56,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/distance.rs b/crates/nu-command/src/strings/str_/distance.rs index bd666e53f5..9b88f8e3e0 100644 --- a/crates/nu-command/src/strings/str_/distance.rs +++ b/crates/nu-command/src/strings/str_/distance.rs @@ -67,7 +67,7 @@ impl Command for SubCommand { compare_string, cell_paths, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -88,7 +88,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/ends_with.rs b/crates/nu-command/src/strings/str_/ends_with.rs index 40f643ea8e..d4841b74e0 100644 --- a/crates/nu-command/src/strings/str_/ends_with.rs +++ b/crates/nu-command/src/strings/str_/ends_with.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -89,7 +89,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/expand.rs b/crates/nu-command/src/strings/str_/expand.rs index 70fb51ec4c..b9759ef6a1 100644 --- a/crates/nu-command/src/strings/str_/expand.rs +++ b/crates/nu-command/src/strings/str_/expand.rs @@ -233,7 +233,7 @@ fn run( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/index_of.rs b/crates/nu-command/src/strings/str_/index_of.rs index 5b40f80d3d..a6b06ef9b7 100644 --- a/crates/nu-command/src/strings/str_/index_of.rs +++ b/crates/nu-command/src/strings/str_/index_of.rs @@ -93,7 +93,7 @@ impl Command for SubCommand { cell_paths, graphemes: grapheme_flags(engine_state, stack, call)?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -117,7 +117,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/join.rs b/crates/nu-command/src/strings/str_/join.rs index 1d36f216d0..d297ea4cbc 100644 --- a/crates/nu-command/src/strings/str_/join.rs +++ b/crates/nu-command/src/strings/str_/join.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Signals; use std::io::Write; @@ -88,30 +89,35 @@ fn run( let mut iter = input.into_iter(); let mut first = true; - let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Write each input to the buffer - if let Some(value) = iter.next() { - // Write the separator if this is not the first - if first { - first = false; - } else if let Some(separator) = &separator { - write!(buffer, "{}", separator)?; - } - - match value { - Value::Error { error, .. } => { - return Err(*error); + let output = ByteStream::from_fn( + span, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + // Write each input to the buffer + if let Some(value) = iter.next() { + // Write the separator if this is not the first + if first { + first = false; + } else if let Some(separator) = &separator { + write!(buffer, "{}", separator)?; } - // Hmm, not sure what we actually want. - // `to_expanded_string` formats dates as human readable which feels funny. - Value::Date { val, .. } => write!(buffer, "{val:?}")?, - value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + + match value { + Value::Error { error, .. } => { + return Err(*error); + } + // Hmm, not sure what we actually want. + // `to_expanded_string` formats dates as human readable which feels funny. + Value::Date { val, .. } => write!(buffer, "{val:?}")?, + value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + } + Ok(true) + } else { + Ok(false) } - Ok(true) - } else { - Ok(false) - } - }); + }, + ); Ok(PipelineData::ByteStream(output, metadata)) } diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index f456fe9467..ab0ba8db49 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -130,7 +130,7 @@ fn run( cell_paths: (!cell_paths.is_empty()).then_some(cell_paths), graphemes, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn action(input: &Value, arg: &Arguments, head: Span) -> Value { diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index 58e2574681..342dd0693e 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -102,7 +102,7 @@ impl Command for SubCommand { no_regex, multiline, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -134,7 +134,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index cc3772db7c..9a339f7bcc 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let args = CellPathOnlyArgs::from(cell_paths); - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -66,7 +66,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/starts_with.rs b/crates/nu-command/src/strings/str_/starts_with.rs index aec12f6f77..bac451466c 100644 --- a/crates/nu-command/src/strings/str_/starts_with.rs +++ b/crates/nu-command/src/strings/str_/starts_with.rs @@ -70,7 +70,7 @@ impl Command for SubCommand { cell_paths, case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -92,7 +92,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/stats.rs b/crates/nu-command/src/strings/str_/stats.rs index 28c65afe2b..eb091ae4f6 100644 --- a/crates/nu-command/src/strings/str_/stats.rs +++ b/crates/nu-command/src/strings/str_/stats.rs @@ -122,7 +122,7 @@ fn stats( ), } }, - engine_state.ctrlc.clone(), + engine_state.signals(), ) } diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index 5ad7a967da..10464580c7 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -103,7 +103,7 @@ impl Command for SubCommand { cell_paths, graphemes: grapheme_flags(engine_state, stack, call)?, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } fn run_const( @@ -133,7 +133,7 @@ impl Command for SubCommand { args, input, call.head, - working_set.permanent().ctrlc.clone(), + working_set.permanent().signals(), ) } diff --git a/crates/nu-command/src/strings/str_/trim/trim_.rs b/crates/nu-command/src/strings/str_/trim/trim_.rs index 52c4017cda..76d886a010 100644 --- a/crates/nu-command/src/strings/str_/trim/trim_.rs +++ b/crates/nu-command/src/strings/str_/trim/trim_.rs @@ -190,7 +190,7 @@ fn run( cell_paths, mode, }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + operate(action, args, input, call.head, engine_state.signals()) } #[derive(Debug, Copy, Clone)] diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index c64549a44d..859575abbd 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -182,5 +182,5 @@ fn run_ps( Ok(output .into_iter() - .into_pipeline_data(span, engine_state.ctrlc.clone())) + .into_pipeline_data(span, engine_state.signals().clone())) } diff --git a/crates/nu-command/src/system/registry_query.rs b/crates/nu-command/src/system/registry_query.rs index 1e2a328356..9b68059ffa 100644 --- a/crates/nu-command/src/system/registry_query.rs +++ b/crates/nu-command/src/system/registry_query.rs @@ -106,7 +106,7 @@ fn registry_query( *registry_key_span, )) } - Ok(reg_values.into_pipeline_data(call_span, engine_state.ctrlc.clone())) + Ok(reg_values.into_pipeline_data(call_span, engine_state.signals().clone())) } else { match registry_value { Some(value) => { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index f82c23a0e0..e475833747 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -2,7 +2,7 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; use nu_path::{dots::expand_ndots, expand_tilde}; use nu_protocol::{ - ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, + ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, }; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; @@ -13,7 +13,7 @@ use std::{ io::Write, path::{Path, PathBuf}, process::Stdio, - sync::{atomic::AtomicBool, Arc}, + sync::Arc, thread, }; @@ -221,7 +221,6 @@ pub fn eval_arguments_from_call( stack: &mut Stack, call: &Call, ) -> Result>, ShellError> { - let ctrlc = &engine_state.ctrlc; let cwd = engine_state.cwd(Some(stack))?; let mut args: Vec> = vec![]; for (expr, spread) in call.rest_iter(1) { @@ -229,7 +228,7 @@ pub fn eval_arguments_from_call( match arg { // Expand globs passed to run-external Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, &cwd, expr.span, ctrlc)? + expand_glob(&val, &cwd, expr.span, engine_state.signals())? .into_iter() .map(|s| s.into_spanned(expr.span)), ), @@ -289,7 +288,7 @@ fn expand_glob( arg: &str, cwd: &Path, span: Span, - interrupt: &Option>, + signals: &Signals, ) -> Result, ShellError> { const GLOB_CHARS: &[char] = &['*', '?', '[']; @@ -307,9 +306,7 @@ fn expand_glob( let mut result: Vec = vec![]; for m in matches { - if nu_utils::ctrl_c::was_pressed(interrupt) { - return Err(ShellError::InterruptedByUser { span: Some(span) }); - } + signals.check(span)?; if let Ok(arg) = m { let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd); result.push(arg.into()); @@ -611,30 +608,30 @@ mod test { let cwd = dirs.test(); - let actual = expand_glob("*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["a.txt", "b.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("./*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); assert_eq!(actual, expected); - let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["'*.txt'"]; assert_eq!(actual, expected); - let actual = expand_glob(".", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob(".", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["."]; assert_eq!(actual, expected); - let actual = expand_glob("./a.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("./a.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["./a.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("[*.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("[*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["[*.txt"]; assert_eq!(actual, expected); - let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &None).unwrap(); + let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let home = dirs_next::home_dir().expect("failed to get home dir"); let expected: Vec = vec![home.join("foo.txt").into()]; assert_eq!(actual, expected); @@ -666,7 +663,7 @@ mod test { ByteStream::read( b"foo".as_slice(), Span::unknown(), - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu-command/src/system/which_.rs b/crates/nu-command/src/system/which_.rs index f0cf4ece39..fd5b0beb18 100644 --- a/crates/nu-command/src/system/which_.rs +++ b/crates/nu-command/src/system/which_.rs @@ -188,7 +188,6 @@ fn which( applications: call.rest(engine_state, stack, 0)?, all: call.has_flag(engine_state, stack, "all")?, }; - let ctrlc = engine_state.ctrlc.clone(); if which_args.applications.is_empty() { return Err(ShellError::MissingParameter { @@ -214,7 +213,9 @@ fn which( output.extend(values); } - Ok(output.into_iter().into_pipeline_data(head, ctrlc)) + Ok(output + .into_iter() + .into_pipeline_data(head, engine_state.signals().clone())) } #[cfg(test)] diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index f0cc90fa9f..e3738a3952 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -7,7 +7,7 @@ use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_engine::{command_prelude::*, env::get_config, env_to_string}; use nu_pretty_hex::HexConfig; use nu_protocol::{ - ByteStream, Config, DataSource, ListStream, PipelineMetadata, TableMode, ValueIterator, + ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator, }; use nu_table::{ common::create_nu_table_config, CollapsedTable, ExpandedTable, JustTable, NuTable, NuTableCell, @@ -19,7 +19,6 @@ use std::{ io::{IsTerminal, Read}, path::PathBuf, str::FromStr, - sync::{atomic::AtomicBool, Arc}, time::Instant, }; use terminal_size::{Height, Width}; @@ -377,8 +376,8 @@ fn handle_table_command( ), PipelineData::ByteStream(..) => Ok(input.data), PipelineData::Value(Value::Binary { val, .. }, ..) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ByteStream::read_binary(val, input.call.head, ctrlc); + let signals = input.engine_state.signals().clone(); + let stream = ByteStream::read_binary(val, input.call.head, signals); Ok(PipelineData::ByteStream( pretty_hex_stream(stream, input.call.head), None, @@ -386,8 +385,8 @@ fn handle_table_command( } // None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack. PipelineData::Value(Value::List { vals, .. }, metadata) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ListStream::new(vals.into_iter(), span, ctrlc); + let signals = input.engine_state.signals().clone(); + let stream = ListStream::new(vals.into_iter(), span, signals); input.data = PipelineData::Empty; handle_row_stream(input, cfg, stream, metadata) @@ -410,8 +409,9 @@ fn handle_table_command( Table.run(input.engine_state, input.stack, input.call, base_pipeline) } PipelineData::Value(Value::Range { val, .. }, metadata) => { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = ListStream::new(val.into_range_iter(span, ctrlc), span, None); + let signals = input.engine_state.signals().clone(); + let stream = + ListStream::new(val.into_range_iter(span, Signals::empty()), span, signals); input.data = PipelineData::Empty; handle_row_stream(input, cfg, stream, metadata) } @@ -437,50 +437,55 @@ fn pretty_hex_stream(stream: ByteStream, span: Span) -> ByteStream { reader } else { // No stream to read from - return ByteStream::read_string("".into(), span, None); + return ByteStream::read_string("".into(), span, Signals::empty()); }; - ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Turn the buffer into a String we can write to - let mut write_buf = std::mem::take(buffer); - write_buf.clear(); - // SAFETY: we just truncated it empty - let mut write_buf = unsafe { String::from_utf8_unchecked(write_buf) }; + ByteStream::from_fn( + span, + Signals::empty(), + ByteStreamType::String, + move |buffer| { + // Turn the buffer into a String we can write to + let mut write_buf = std::mem::take(buffer); + write_buf.clear(); + // SAFETY: we just truncated it empty + let mut write_buf = unsafe { String::from_utf8_unchecked(write_buf) }; - // Write the title at the beginning - if cfg.title { - nu_pretty_hex::write_title(&mut write_buf, cfg, true).expect("format error"); - cfg.title = false; - - // Put the write_buf back into buffer - *buffer = write_buf.into_bytes(); - - Ok(true) - } else { - // Read up to `cfg.width` bytes - read_buf.clear(); - (&mut reader) - .take(cfg.width as u64) - .read_to_end(&mut read_buf) - .err_span(span)?; - - if !read_buf.is_empty() { - nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) - .expect("format error"); - write_buf.push('\n'); - - // Advance the address offset for next time - cfg.address_offset += read_buf.len(); + // Write the title at the beginning + if cfg.title { + nu_pretty_hex::write_title(&mut write_buf, cfg, true).expect("format error"); + cfg.title = false; // Put the write_buf back into buffer *buffer = write_buf.into_bytes(); Ok(true) } else { - Ok(false) + // Read up to `cfg.width` bytes + read_buf.clear(); + (&mut reader) + .take(cfg.width as u64) + .read_to_end(&mut read_buf) + .err_span(span)?; + + if !read_buf.is_empty() { + nu_pretty_hex::hex_write(&mut write_buf, &read_buf, cfg, Some(true)) + .expect("format error"); + write_buf.push('\n'); + + // Advance the address offset for next time + cfg.address_offset += read_buf.len(); + + // Put the write_buf back into buffer + *buffer = write_buf.into_bytes(); + + Ok(true) + } else { + Ok(false) + } } - } - }) + }, + ) } fn handle_record( @@ -491,8 +496,6 @@ fn handle_record( let config = get_config(input.engine_state, input.stack); let span = input.data.span().unwrap_or(input.call.head); let styles = &StyleComputer::from_config(input.engine_state, input.stack); - let ctrlc = input.engine_state.ctrlc.clone(); - let ctrlc1 = ctrlc.clone(); if record.is_empty() { let value = @@ -517,7 +520,7 @@ fn handle_record( let opts = TableOpts::new( &config, styles, - ctrlc, + input.engine_state.signals(), span, cfg.term_width, indent, @@ -529,7 +532,7 @@ fn handle_record( let result = match result { Some(output) => maybe_strip_color(output, &config), - None => report_unsuccessful_output(ctrlc1, cfg.term_width), + None => report_unsuccessful_output(input.engine_state.signals(), cfg.term_width), }; let val = Value::string(result, span); @@ -537,8 +540,8 @@ fn handle_record( Ok(val.into_pipeline_data()) } -fn report_unsuccessful_output(ctrlc1: Option>, term_width: usize) -> String { - if nu_utils::ctrl_c::was_pressed(&ctrlc1) { +fn report_unsuccessful_output(signals: &Signals, term_width: usize) -> String { + if signals.interrupted() { "".into() } else { // assume this failed because the table was too wide @@ -599,8 +602,6 @@ fn handle_row_stream( stream: ListStream, metadata: Option, ) -> Result { - let ctrlc = input.engine_state.ctrlc.clone(); - let stream = match metadata.as_ref() { // First, `ls` sources: Some(PipelineMetadata { @@ -680,11 +681,14 @@ fn handle_row_stream( // for the values it outputs. Because engine_state is passed in, config doesn't need to. input.engine_state.clone(), input.stack.clone(), - ctrlc.clone(), cfg, ); - let stream = - ByteStream::from_result_iter(paginator, input.call.head, None, ByteStreamType::String); + let stream = ByteStream::from_result_iter( + paginator, + input.call.head, + Signals::empty(), + ByteStreamType::String, + ); Ok(PipelineData::ByteStream(stream, None)) } @@ -717,7 +721,6 @@ struct PagingTableCreator { stream: ValueIterator, engine_state: EngineState, stack: Stack, - ctrlc: Option>, elements_displayed: usize, reached_end: bool, cfg: TableConfig, @@ -730,7 +733,6 @@ impl PagingTableCreator { stream: ListStream, engine_state: EngineState, stack: Stack, - ctrlc: Option>, cfg: TableConfig, ) -> Self { PagingTableCreator { @@ -738,7 +740,6 @@ impl PagingTableCreator { stream: stream.into_inner(), engine_state, stack, - ctrlc, cfg, elements_displayed: 0, reached_end: false, @@ -790,14 +791,14 @@ impl PagingTableCreator { } fn create_table_opts<'a>( - &self, + &'a self, cfg: &'a Config, style_comp: &'a StyleComputer<'a>, ) -> TableOpts<'a> { TableOpts::new( cfg, style_comp, - self.ctrlc.clone(), + self.engine_state.signals(), self.head, self.cfg.term_width, (cfg.table_indent.left, cfg.table_indent.right), @@ -830,12 +831,15 @@ impl Iterator for PagingTableCreator { match self.cfg.abbreviation { Some(abbr) => { (batch, _, end) = - stream_collect_abbriviated(&mut self.stream, abbr, self.ctrlc.clone()); + stream_collect_abbriviated(&mut self.stream, abbr, self.engine_state.signals()); } None => { // Pull from stream until time runs out or we have enough items - (batch, end) = - stream_collect(&mut self.stream, STREAM_PAGE_SIZE, self.ctrlc.clone()); + (batch, end) = stream_collect( + &mut self.stream, + STREAM_PAGE_SIZE, + self.engine_state.signals(), + ); } } @@ -869,14 +873,19 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; let config = get_config(&self.engine_state, &self.stack); - convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width) + convert_table_to_output( + table, + &config, + self.engine_state.signals(), + self.cfg.term_width, + ) } } fn stream_collect( stream: impl Iterator, size: usize, - ctrlc: Option>, + signals: &Signals, ) -> (Vec, bool) { let start_time = Instant::now(); let mut end = true; @@ -896,7 +905,7 @@ fn stream_collect( break; } - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { break; } } @@ -907,7 +916,7 @@ fn stream_collect( fn stream_collect_abbriviated( stream: impl Iterator, size: usize, - ctrlc: Option>, + signals: &Signals, ) -> (Vec, usize, bool) { let mut end = true; let mut read = 0; @@ -930,7 +939,7 @@ fn stream_collect_abbriviated( tail.push_back(item); } - if nu_utils::ctrl_c::was_pressed(&ctrlc) { + if signals.interrupted() { end = false; break; } @@ -1062,7 +1071,7 @@ fn create_empty_placeholder( fn convert_table_to_output( table: Result, ShellError>, config: &Config, - ctrlc: &Option>, + signals: &Signals, term_width: usize, ) -> Option, ShellError>> { match table { @@ -1075,7 +1084,7 @@ fn convert_table_to_output( Some(Ok(bytes)) } Ok(None) => { - let msg = if nu_utils::ctrl_c::was_pressed(ctrlc) { + let msg = if signals.interrupted() { String::from("") } else { // assume this failed because the table was too wide diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 044bf86980..1460dec464 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -21,9 +21,7 @@ pub fn eval_call( call: &Call, input: PipelineData, ) -> Result { - if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { - return Ok(Value::nothing(call.head).into_pipeline_data()); - } + engine_state.signals().check(call.head)?; let decl = engine_state.get_decl(call.decl_id); if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") { diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index cbb86336af..b748ce2b89 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -67,12 +67,11 @@ fn convert_value_to_string( let config = engine_state.get_config(); Ok(vals[0][0].to_abbreviated_string(config)) } else { - let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); Ok(nu_common::try_build_table( - ctrlc, + engine_state.signals(), config, &style_computer, value, diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index e5498ce5b3..8d59591beb 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -63,7 +63,6 @@ impl Command for Explore { let tail: bool = call.has_flag(engine_state, stack, "tail")?; let peek_value: bool = call.has_flag(engine_state, stack, "peek")?; - let ctrlc = engine_state.ctrlc.clone(); let nu_config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); @@ -83,7 +82,7 @@ impl Command for Explore { tail, ); - let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config); + let result = run_pager(engine_state, &mut stack.clone(), input, config); match result { Ok(Some(value)) => Ok(PipelineData::Value(value, None)), diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 30b3e5cf87..043c85eb02 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -11,7 +11,7 @@ use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; pub use default_context::add_explore_context; pub use explore::Explore; use explore::ExploreConfig; -use nu_common::{collect_pipeline, has_simple_value, CtrlC}; +use nu_common::{collect_pipeline, has_simple_value}; use nu_protocol::{ engine::{EngineState, Stack}, PipelineData, Value, @@ -28,7 +28,6 @@ mod util { fn run_pager( engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, input: PipelineData, config: PagerConfig, ) -> Result> { @@ -45,14 +44,14 @@ fn run_pager( p.show_message("For help type :help"); let view = binary_view(input, config.explore_config)?; - return p.run(engine_state, stack, ctrlc, Some(view), commands); + return p.run(engine_state, stack, Some(view), commands); } let (columns, data) = collect_pipeline(input)?; let has_no_input = columns.is_empty() && data.is_empty(); if has_no_input { - return p.run(engine_state, stack, ctrlc, help_view(), commands); + return p.run(engine_state, stack, help_view(), commands); } p.show_message("For help type :help"); @@ -60,11 +59,11 @@ fn run_pager( if let Some(value) = has_simple_value(&data) { let text = value.to_abbreviated_string(config.nu_config); let view = Some(Page::new(Preview::new(&text), false)); - return p.run(engine_state, stack, ctrlc, view, commands); + return p.run(engine_state, stack, view, commands); } let view = create_record_view(columns, data, is_record, config); - p.run(engine_state, stack, ctrlc, view, commands) + p.run(engine_state, stack, view, commands) } fn create_record_view( diff --git a/crates/nu-explore/src/nu_common/mod.rs b/crates/nu-explore/src/nu_common/mod.rs index 091a7d6f36..42ba2f0896 100644 --- a/crates/nu-explore/src/nu_common/mod.rs +++ b/crates/nu-explore/src/nu_common/mod.rs @@ -6,13 +6,11 @@ mod value; use nu_color_config::TextStyle; use nu_protocol::Value; -use std::sync::{atomic::AtomicBool, Arc}; pub use nu_ansi_term::{Color as NuColor, Style as NuStyle}; pub use nu_protocol::{Config as NuConfig, Span as NuSpan}; pub type NuText = (String, TextStyle); -pub type CtrlC = Option>; pub use command::run_command_with_value; pub use lscolor::{create_lscolors, lscolorize}; diff --git a/crates/nu-explore/src/nu_common/table.rs b/crates/nu-explore/src/nu_common/table.rs index cb580e404b..164c837bf9 100644 --- a/crates/nu-explore/src/nu_common/table.rs +++ b/crates/nu-explore/src/nu_common/table.rs @@ -1,22 +1,21 @@ use crate::nu_common::NuConfig; use nu_color_config::StyleComputer; -use nu_protocol::{Record, Span, Value}; +use nu_protocol::{Record, Signals, Span, Value}; use nu_table::{ common::{nu_value_to_string, nu_value_to_string_clean}, ExpandedTable, TableOpts, }; -use std::sync::{atomic::AtomicBool, Arc}; pub fn try_build_table( - ctrlc: Option>, + signals: &Signals, config: &NuConfig, style_computer: &StyleComputer, value: Value, ) -> String { let span = value.span(); match value { - Value::List { vals, .. } => try_build_list(vals, ctrlc, config, span, style_computer), - Value::Record { val, .. } => try_build_map(&val, span, style_computer, ctrlc, config), + Value::List { vals, .. } => try_build_list(vals, signals, config, span, style_computer), + Value::Record { val, .. } => try_build_map(&val, span, style_computer, signals, config), val if matches!(val, Value::String { .. }) => { nu_value_to_string_clean(&val, config, style_computer).0 } @@ -28,13 +27,13 @@ fn try_build_map( record: &Record, span: Span, style_computer: &StyleComputer, - ctrlc: Option>, + signals: &Signals, config: &NuConfig, ) -> String { let opts = TableOpts::new( config, style_computer, - ctrlc, + signals, Span::unknown(), usize::MAX, (config.table_indent.left, config.table_indent.right), @@ -53,7 +52,7 @@ fn try_build_map( fn try_build_list( vals: Vec, - ctrlc: Option>, + signals: &Signals, config: &NuConfig, span: Span, style_computer: &StyleComputer, @@ -61,7 +60,7 @@ fn try_build_list( let opts = TableOpts::new( config, style_computer, - ctrlc, + signals, Span::unknown(), usize::MAX, (config.table_indent.left, config.table_indent.right), diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 3114543f7c..4a043476bb 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -11,7 +11,7 @@ use self::{ use super::views::{Layout, View}; use crate::{ explore::ExploreConfig, - nu_common::{CtrlC, NuColor, NuConfig, NuStyle}, + nu_common::{NuColor, NuConfig, NuStyle}, registry::{Command, CommandRegistry}, views::{util::nu_style_to_tui, ViewConfig}, }; @@ -36,7 +36,6 @@ use std::{ cmp::min, io::{self, Stdout}, result, - sync::atomic::Ordering, }; pub type Frame<'a> = ratatui::Frame<'a>; @@ -89,7 +88,6 @@ impl<'a> Pager<'a> { &mut self, engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, view: Option, commands: CommandRegistry, ) -> Result> { @@ -114,7 +112,6 @@ impl<'a> Pager<'a> { &mut terminal, engine_state, stack, - ctrlc, self, &mut info, view, @@ -173,7 +170,6 @@ fn render_ui( term: &mut Terminal, engine_state: &EngineState, stack: &mut Stack, - ctrlc: CtrlC, pager: &mut Pager<'_>, info: &mut ViewInfo, view: Option, @@ -183,11 +179,8 @@ fn render_ui( let mut view_stack = ViewStack::new(view, Vec::new()); loop { - // handle CTRLC event - if let Some(ctrlc) = ctrlc.clone() { - if ctrlc.load(Ordering::SeqCst) { - break Ok(None); - } + if engine_state.signals().interrupted() { + break Ok(None); } let mut layout = Layout::default(); diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index 44eeeb5756..fdeededac5 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -17,10 +17,7 @@ use ropey::Rope; use std::{ collections::BTreeMap, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::Arc, time::Duration, }; @@ -57,11 +54,7 @@ impl LanguageServer { }) } - pub fn serve_requests( - mut self, - engine_state: EngineState, - ctrlc: Arc, - ) -> Result<()> { + pub fn serve_requests(mut self, engine_state: EngineState) -> Result<()> { let server_capabilities = serde_json::to_value(ServerCapabilities { text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind( TextDocumentSyncKind::INCREMENTAL, @@ -75,10 +68,12 @@ impl LanguageServer { let _initialization_params = self .connection - .initialize_while(server_capabilities, || !ctrlc.load(Ordering::SeqCst)) + .initialize_while(server_capabilities, || { + !engine_state.signals().interrupted() + }) .into_diagnostic()?; - while !ctrlc.load(Ordering::SeqCst) { + while !engine_state.signals().interrupted() { let msg = match self .connection .receiver @@ -631,7 +626,7 @@ mod tests { std::thread::spawn(move || { let engine_state = nu_cmd_lang::create_default_context(); let engine_state = nu_command::add_shell_command_context(engine_state); - send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false)))) + send.send(lsp_server.serve_requests(engine_state)) }); client_connection diff --git a/crates/nu-plugin-core/src/interface/mod.rs b/crates/nu-plugin-core/src/interface/mod.rs index 4f287f39c0..89aa2b15e5 100644 --- a/crates/nu-plugin-core/src/interface/mod.rs +++ b/crates/nu-plugin-core/src/interface/mod.rs @@ -1,10 +1,10 @@ //! Implements the stream multiplexing interface for both the plugin side and the engine side. use nu_plugin_protocol::{ByteStreamInfo, ListStreamInfo, PipelineDataHeader, StreamMessage}; -use nu_protocol::{ByteStream, IntoSpanned, ListStream, PipelineData, Reader, ShellError}; +use nu_protocol::{ByteStream, IntoSpanned, ListStream, PipelineData, Reader, ShellError, Signals}; use std::{ io::{Read, Write}, - sync::{atomic::AtomicBool, Arc, Mutex}, + sync::Mutex, thread, }; @@ -170,7 +170,7 @@ pub trait InterfaceManager { fn read_pipeline_data( &self, header: PipelineDataHeader, - ctrlc: Option<&Arc>, + signals: &Signals, ) -> Result { self.prepare_pipeline_data(match header { PipelineDataHeader::Empty => PipelineData::Empty, @@ -178,12 +178,12 @@ pub trait InterfaceManager { PipelineDataHeader::ListStream(info) => { let handle = self.stream_manager().get_handle(); let reader = handle.read_stream(info.id, self.get_interface())?; - ListStream::new(reader, info.span, ctrlc.cloned()).into() + ListStream::new(reader, info.span, signals.clone()).into() } PipelineDataHeader::ByteStream(info) => { let handle = self.stream_manager().get_handle(); let reader = handle.read_stream(info.id, self.get_interface())?; - ByteStream::from_result_iter(reader, info.span, ctrlc.cloned(), info.type_).into() + ByteStream::from_result_iter(reader, info.span, signals.clone(), info.type_).into() } }) } diff --git a/crates/nu-plugin-core/src/interface/tests.rs b/crates/nu-plugin-core/src/interface/tests.rs index 6b86aba97d..456eb547b5 100644 --- a/crates/nu-plugin-core/src/interface/tests.rs +++ b/crates/nu-plugin-core/src/interface/tests.rs @@ -11,7 +11,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ ByteStream, ByteStreamSource, ByteStreamType, DataSource, ListStream, PipelineData, - PipelineMetadata, ShellError, Span, Value, + PipelineMetadata, ShellError, Signals, Span, Value, }; use std::{path::Path, sync::Arc}; @@ -129,7 +129,7 @@ fn read_pipeline_data_empty() -> Result<(), ShellError> { let header = PipelineDataHeader::Empty; assert!(matches!( - manager.read_pipeline_data(header, None)?, + manager.read_pipeline_data(header, &Signals::empty())?, PipelineData::Empty )); Ok(()) @@ -141,7 +141,7 @@ fn read_pipeline_data_value() -> Result<(), ShellError> { let value = Value::test_int(4); let header = PipelineDataHeader::Value(value.clone()); - match manager.read_pipeline_data(header, None)? { + match manager.read_pipeline_data(header, &Signals::empty())? { PipelineData::Value(read_value, ..) => assert_eq!(value, read_value), PipelineData::ListStream(..) => panic!("unexpected ListStream"), PipelineData::ByteStream(..) => panic!("unexpected ByteStream"), @@ -168,7 +168,7 @@ fn read_pipeline_data_list_stream() -> Result<(), ShellError> { span: Span::test_data(), }); - let pipe = manager.read_pipeline_data(header, None)?; + let pipe = manager.read_pipeline_data(header, &Signals::empty())?; assert!( matches!(pipe, PipelineData::ListStream(..)), "unexpected PipelineData: {pipe:?}" @@ -212,7 +212,7 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> { type_: ByteStreamType::Unknown, }); - let pipe = manager.read_pipeline_data(header, None)?; + let pipe = manager.read_pipeline_data(header, &Signals::empty())?; // need to consume input manager.consume_all()?; @@ -257,7 +257,7 @@ fn read_pipeline_data_prepared_properly() -> Result<(), ShellError> { id: 0, span: Span::test_data(), }); - match manager.read_pipeline_data(header, None)? { + match manager.read_pipeline_data(header, &Signals::empty())? { PipelineData::ListStream(_, meta) => match meta { Some(PipelineMetadata { data_source, .. }) => match data_source { DataSource::FilePath(path) => { @@ -353,7 +353,11 @@ fn write_pipeline_data_list_stream() -> Result<(), ShellError> { // Set up pipeline data for a list stream let pipe = PipelineData::ListStream( - ListStream::new(values.clone().into_iter(), Span::test_data(), None), + ListStream::new( + values.clone().into_iter(), + Span::test_data(), + Signals::empty(), + ), None, ); @@ -406,7 +410,7 @@ fn write_pipeline_data_byte_stream() -> Result<(), ShellError> { ByteStream::read( std::io::Cursor::new(expected), span, - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index c87e64aa48..b026d21b23 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -3,23 +3,21 @@ use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce use nu_protocol::{ ast::Call, engine::{Closure, EngineState, Redirection, Stack}, - Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Span, Spanned, Value, + Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned, + Value, }; use std::{ borrow::Cow, collections::HashMap, - sync::{ - atomic::{AtomicBool, AtomicU32}, - Arc, - }, + sync::{atomic::AtomicU32, Arc}, }; /// Object safe trait for abstracting operations required of the plugin context. pub trait PluginExecutionContext: Send + Sync { /// A span pointing to the command being executed fn span(&self) -> Span; - /// The interrupt signal, if present - fn ctrlc(&self) -> Option<&Arc>; + /// The [`Signals`] struct, if present + fn signals(&self) -> &Signals; /// The pipeline externals state, for tracking the foreground process group, if present fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>; /// Get engine configuration @@ -80,8 +78,8 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { self.call.head } - fn ctrlc(&self) -> Option<&Arc> { - self.engine_state.ctrlc.as_ref() + fn signals(&self) -> &Signals { + self.engine_state.signals() } fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> { @@ -234,8 +232,8 @@ impl PluginExecutionContext for PluginExecutionBogusContext { Span::test_data() } - fn ctrlc(&self) -> Option<&Arc> { - None + fn signals(&self) -> &Signals { + &Signals::EMPTY } fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> { diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index 9a4c72d2c1..79ab7f7720 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -12,11 +12,11 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature, - ShellError, Span, Spanned, Value, + ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap}, - sync::{atomic::AtomicBool, mpsc, Arc, OnceLock}, + sync::{mpsc, Arc, OnceLock}, }; use crate::{ @@ -103,8 +103,8 @@ struct PluginCallState { /// Don't try to send the plugin call response. This is only used for `Dropped` to avoid an /// error dont_send_response: bool, - /// Interrupt signal to be used for stream iterators - ctrlc: Option>, + /// Signals to be used for stream iterators + signals: Signals, /// Channel to receive context on to be used if needed context_rx: Option>, /// Span associated with the call, if any @@ -231,14 +231,14 @@ impl PluginInterfaceManager { } } - /// Find the ctrlc signal corresponding to the given plugin call id - fn get_ctrlc(&mut self, id: PluginCallId) -> Result>, ShellError> { + /// Find the [`Signals`] struct corresponding to the given plugin call id + fn get_signals(&mut self, id: PluginCallId) -> Result { // Make sure we're up to date self.receive_plugin_call_subscriptions(); // Find the subscription and return the context self.plugin_call_states .get(&id) - .map(|state| state.ctrlc.clone()) + .map(|state| state.signals.clone()) .ok_or_else(|| ShellError::PluginFailedToDecode { msg: format!("Unknown plugin call ID: {id}"), }) @@ -517,14 +517,14 @@ impl InterfaceManager for PluginInterfaceManager { // Handle reading the pipeline data, if any let response = response .map_data(|data| { - let ctrlc = self.get_ctrlc(id)?; + let signals = self.get_signals(id)?; // Register the stream in the response if let Some(stream_id) = data.stream_id() { self.recv_stream_started(id, stream_id); } - self.read_pipeline_data(data, ctrlc.as_ref()) + self.read_pipeline_data(data, &signals) }) .unwrap_or_else(|err| { // If there's an error with initializing this stream, change it to a plugin @@ -544,8 +544,8 @@ impl InterfaceManager for PluginInterfaceManager { let call = call // Handle reading the pipeline data, if any .map_data(|input| { - let ctrlc = self.get_ctrlc(context)?; - self.read_pipeline_data(input, ctrlc.as_ref()) + let signals = self.get_signals(context)?; + self.read_pipeline_data(input, &signals) }) // Do anything extra needed for each engine call setup .and_then(|mut engine_call| { @@ -698,7 +698,9 @@ impl PluginInterface { context: Option<&dyn PluginExecutionContext>, ) -> Result { let id = self.state.plugin_call_id_sequence.next()?; - let ctrlc = context.and_then(|c| c.ctrlc().cloned()); + let signals = context + .map(|c| c.signals().clone()) + .unwrap_or_else(Signals::empty); let (tx, rx) = mpsc::channel(); let (context_tx, context_rx) = mpsc::channel(); let keep_plugin_custom_values = mpsc::channel(); @@ -746,7 +748,7 @@ impl PluginInterface { PluginCallState { sender: Some(tx).filter(|_| !dont_send_response), dont_send_response, - ctrlc, + signals, context_rx: Some(context_rx), span: call.span(), keep_plugin_custom_values, diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index 5665beb92b..2bab67f25f 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -18,7 +18,7 @@ use nu_protocol::{ ast::{Math, Operator}, engine::Closure, ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData, - PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, + PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; use std::{ @@ -56,7 +56,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; // and an interface... @@ -112,7 +112,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; manager @@ -159,7 +159,7 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell span: Span::test_data(), type_: ByteStreamType::Unknown, }), - None, + &Signals::empty(), )?; manager @@ -190,7 +190,7 @@ fn fake_plugin_call( PluginCallState { sender: Some(tx), dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -493,7 +493,7 @@ fn manager_handle_engine_call_after_response_received() -> Result<(), ShellError PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: Some(context_rx), span: None, keep_plugin_custom_values: mpsc::channel(), @@ -559,7 +559,7 @@ fn manager_send_plugin_call_response_removes_context_only_if_no_streams_to_read( PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -595,7 +595,7 @@ fn manager_consume_stream_end_removes_context_only_if_last_stream() -> Result<() PluginCallState { sender: None, dont_send_response: false, - ctrlc: None, + signals: Signals::empty(), context_rx: None, span: None, keep_plugin_custom_values: mpsc::channel(), @@ -678,7 +678,7 @@ fn manager_prepare_pipeline_data_adds_source_to_list_streams() -> Result<(), She [Value::test_custom_value(Box::new( test_plugin_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -852,7 +852,9 @@ fn interface_write_plugin_call_writes_run_with_stream_input() -> Result<(), Shel positional: vec![], named: vec![], }, - input: values.clone().into_pipeline_data(Span::test_data(), None), + input: values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), }), None, )?; @@ -1148,7 +1150,9 @@ fn interface_prepare_pipeline_data_accepts_normal_streams() -> Result<(), ShellE let values = normal_values(&interface); let state = CurrentCallState::default(); let data = interface.prepare_pipeline_data( - values.clone().into_pipeline_data(Span::test_data(), None), + values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), &state, )?; @@ -1211,7 +1215,9 @@ fn interface_prepare_pipeline_data_rejects_bad_custom_value_in_a_stream() -> Res let values = bad_custom_values(); let state = CurrentCallState::default(); let data = interface.prepare_pipeline_data( - values.clone().into_pipeline_data(Span::test_data(), None), + values + .clone() + .into_pipeline_data(Span::test_data(), Signals::empty()), &state, )?; diff --git a/crates/nu-plugin-test-support/src/lib.rs b/crates/nu-plugin-test-support/src/lib.rs index a53b353981..998c31ac0d 100644 --- a/crates/nu-plugin-test-support/src/lib.rs +++ b/crates/nu-plugin-test-support/src/lib.rs @@ -8,8 +8,8 @@ //! use nu_plugin::*; //! use nu_plugin_test_support::PluginTest; //! use nu_protocol::{ -//! Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature, -//! Span, Type, Value, +//! Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signals, +//! Signature, Span, Type, Value, //! }; //! //! struct LowercasePlugin; @@ -60,7 +60,7 @@ //! // Errors in a stream should be returned as values. //! .unwrap_or_else(|err| Value::error(err, span)) //! }, -//! None, +//! &Signals::empty(), //! )?) //! } //! } @@ -83,7 +83,7 @@ //! //! // #[test] //! fn test_lowercase() -> Result<(), ShellError> { -//! let input = vec![Value::test_string("FooBar")].into_pipeline_data(Span::test_data(), None); +//! let input = vec![Value::test_string("FooBar")].into_pipeline_data(Span::test_data(), Signals::empty()); //! let output = PluginTest::new("lowercase", LowercasePlugin.into())? //! .eval_with("lowercase", input)? //! .into_value(Span::test_data())?; diff --git a/crates/nu-plugin-test-support/src/plugin_test.rs b/crates/nu-plugin-test-support/src/plugin_test.rs index 3d6b3eec23..5e3334c78b 100644 --- a/crates/nu-plugin-test-support/src/plugin_test.rs +++ b/crates/nu-plugin-test-support/src/plugin_test.rs @@ -11,7 +11,7 @@ use nu_protocol::{ debugger::WithoutDebug, engine::{EngineState, Stack, StateWorkingSet}, report_error_new, CustomValue, Example, IntoSpanned as _, LabeledError, PipelineData, - ShellError, Span, Value, + ShellError, Signals, Span, Value, }; use crate::{diff::diff_by_line, fake_register::fake_register}; @@ -85,13 +85,13 @@ impl PluginTest { /// /// ```rust,no_run /// # use nu_plugin_test_support::PluginTest; - /// # use nu_protocol::{ShellError, Span, Value, IntoInterruptiblePipelineData}; + /// # use nu_protocol::{IntoInterruptiblePipelineData, ShellError, Signals, Span, Value}; /// # use nu_plugin::*; /// # fn test(MyPlugin: impl Plugin + Send + 'static) -> Result<(), ShellError> { /// let result = PluginTest::new("my_plugin", MyPlugin.into())? /// .eval_with( /// "my-command", - /// vec![Value::test_int(42)].into_pipeline_data(Span::test_data(), None) + /// vec![Value::test_int(42)].into_pipeline_data(Span::test_data(), Signals::empty()) /// )? /// .into_value(Span::test_data())?; /// assert_eq!(Value::test_string("42"), result); @@ -151,7 +151,7 @@ impl PluginTest { Err(err) => Value::error(err, value.span()), } }, - None, + &Signals::empty(), )? }; @@ -171,7 +171,7 @@ impl PluginTest { Err(err) => Value::error(err, value.span()), } }, - None, + &Signals::empty(), ) } } diff --git a/crates/nu-plugin-test-support/tests/lowercase/mod.rs b/crates/nu-plugin-test-support/tests/lowercase/mod.rs index 50271a8cc2..5be127e7f5 100644 --- a/crates/nu-plugin-test-support/tests/lowercase/mod.rs +++ b/crates/nu-plugin-test-support/tests/lowercase/mod.rs @@ -1,8 +1,8 @@ use nu_plugin::*; use nu_plugin_test_support::PluginTest; use nu_protocol::{ - Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signature, - Span, Type, Value, + Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, ShellError, Signals, + Signature, Span, Type, Value, }; struct LowercasePlugin; @@ -53,7 +53,7 @@ impl PluginCommand for Lowercase { // Errors in a stream should be returned as values. .unwrap_or_else(|err| Value::error(err, span)) }, - None, + &Signals::empty(), )?) } } @@ -72,7 +72,8 @@ impl Plugin for LowercasePlugin { fn test_lowercase_using_eval_with() -> Result<(), ShellError> { let result = PluginTest::new("lowercase", LowercasePlugin.into())?.eval_with( "lowercase", - vec![Value::test_string("HeLlO wOrLd")].into_pipeline_data(Span::test_data(), None), + vec![Value::test_string("HeLlO wOrLd")] + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; assert_eq!( diff --git a/crates/nu-plugin/src/plugin/command.rs b/crates/nu-plugin/src/plugin/command.rs index d33fc445e2..a90555ac40 100644 --- a/crates/nu-plugin/src/plugin/command.rs +++ b/crates/nu-plugin/src/plugin/command.rs @@ -22,7 +22,7 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; /// Basic usage: /// ``` /// # use nu_plugin::*; -/// # use nu_protocol::{Signature, PipelineData, Type, Value, LabeledError}; +/// # use nu_protocol::{LabeledError, PipelineData, Signals, Signature, Type, Value}; /// struct LowercasePlugin; /// struct Lowercase; /// @@ -55,7 +55,7 @@ use crate::{EngineInterface, EvaluatedCall, Plugin}; /// .map(|string| Value::string(string.to_lowercase(), span)) /// // Errors in a stream should be returned as values. /// .unwrap_or_else(|err| Value::error(err, span)) -/// }, None)?) +/// }, &Signals::empty())?) /// } /// } /// diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index fc9c9e85b3..dcb4119dba 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -12,7 +12,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, - ShellError, Span, Spanned, Value, + ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::{btree_map, BTreeMap, HashMap}, @@ -274,7 +274,9 @@ impl InterfaceManager for EngineInterfaceManager { PluginInput::Call(id, call) => { let interface = self.interface_for_context(id); // Read streams in the input - let call = match call.map_data(|input| self.read_pipeline_data(input, None)) { + let call = match call + .map_data(|input| self.read_pipeline_data(input, &Signals::empty())) + { Ok(call) => call, Err(err) => { // If there's an error with initialization of the input stream, just send @@ -320,7 +322,7 @@ impl InterfaceManager for EngineInterfaceManager { } PluginInput::EngineCallResponse(id, response) => { let response = response - .map_data(|header| self.read_pipeline_data(header, None)) + .map_data(|header| self.read_pipeline_data(header, &Signals::empty())) .unwrap_or_else(|err| { // If there's an error with initializing this stream, change it to an engine // call error response, but send it anyway diff --git a/crates/nu-plugin/src/plugin/interface/tests.rs b/crates/nu-plugin/src/plugin/interface/tests.rs index b195b43197..7065c6f64d 100644 --- a/crates/nu-plugin/src/plugin/interface/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/tests.rs @@ -10,7 +10,7 @@ use nu_plugin_protocol::{ }; use nu_protocol::{ engine::Closure, ByteStreamType, Config, CustomValue, IntoInterruptiblePipelineData, - LabeledError, PipelineData, PluginSignature, ShellError, Span, Spanned, Value, + LabeledError, PipelineData, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; use std::{ collections::HashMap, @@ -59,7 +59,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; // and an interface... @@ -115,7 +115,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError id: 0, span: Span::test_data(), }), - None, + &Signals::empty(), )?; manager @@ -162,7 +162,7 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell span: Span::test_data(), type_: ByteStreamType::Unknown, }), - None, + &Signals::empty(), )?; manager @@ -615,7 +615,7 @@ fn manager_prepare_pipeline_data_deserializes_custom_values_in_streams() -> Resu [Value::test_custom_value(Box::new( test_plugin_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -647,7 +647,7 @@ fn manager_prepare_pipeline_data_embeds_deserialization_errors_in_streams() -> R let span = Span::new(20, 30); let data = manager.prepare_pipeline_data( [Value::custom(Box::new(invalid_custom_value), span)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), )?; let value = data @@ -730,7 +730,7 @@ fn interface_write_response_with_stream() -> Result<(), ShellError> { interface .write_response(Ok::<_, ShellError>( [Value::test_int(3), Value::test_int(4), Value::test_int(5)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), ))? .write()?; @@ -1132,7 +1132,7 @@ fn interface_prepare_pipeline_data_serializes_custom_values_in_streams() -> Resu [Value::test_custom_value(Box::new( expected_test_custom_value(), ))] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), &(), )?; @@ -1191,7 +1191,7 @@ fn interface_prepare_pipeline_data_embeds_serialization_errors_in_streams() -> R let span = Span::new(40, 60); let data = interface.prepare_pipeline_data( [Value::custom(Box::new(CantSerialize::BadVariant), span)] - .into_pipeline_data(Span::test_data(), None), + .into_pipeline_data(Span::test_data(), Signals::empty()), &(), )?; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index c436621627..e393d78d41 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -8,7 +8,7 @@ use crate::{ }, eval_const::create_nu_constant, BlockId, Category, Config, DeclId, FileId, GetSpan, HistoryConfig, Module, ModuleId, OverlayId, - ShellError, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId, + ShellError, Signals, Signature, Span, SpanId, Type, Value, VarId, VirtualPathId, }; use fancy_regex::Regex; use lru::LruCache; @@ -84,7 +84,7 @@ pub struct EngineState { pub spans: Vec, usage: Usage, pub scope: ScopeFrame, - pub ctrlc: Option>, + signals: Signals, pub env_vars: Arc, pub previous_env_vars: Arc>, pub config: Arc, @@ -144,7 +144,7 @@ impl EngineState { 0, false, ), - ctrlc: None, + signals: Signals::empty(), env_vars: Arc::new( [(DEFAULT_OVERLAY_NAME.to_string(), HashMap::new())] .into_iter() @@ -177,6 +177,18 @@ impl EngineState { } } + pub fn signals(&self) -> &Signals { + &self.signals + } + + pub fn reset_signals(&mut self) { + self.signals.reset() + } + + pub fn set_signals(&mut self, signals: Signals) { + self.signals = signals; + } + /// Merges a `StateDelta` onto the current state. These deltas come from a system, like the parser, that /// creates a new set of definitions and visible symbols in the current scope. We make this transactional /// as there are times when we want to run the parser and immediately throw away the results (namely: diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index 30752d5c9e..be24fd093e 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1187,6 +1187,13 @@ pub enum ShellError { span: Option, }, + /// Operation interrupted + #[error("Operation interrupted")] + Interrupted { + #[label("This operation was interrupted")] + span: Span, + }, + /// Operation interrupted by user #[error("Operation interrupted by user")] InterruptedByUser { diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index babd195a9e..6226f1d8db 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,9 +1,8 @@ -use serde::{Deserialize, Serialize}; - use crate::{ process::{ChildPipe, ChildProcess, ExitStatus}, - ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Span, Type, Value, + ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value, }; +use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::os::fd::OwnedFd; #[cfg(windows)] @@ -13,10 +12,6 @@ use std::{ fs::File, io::{self, BufRead, BufReader, Cursor, ErrorKind, Read, Write}, process::Stdio, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, thread, }; @@ -182,7 +177,7 @@ impl From for Type { pub struct ByteStream { stream: ByteStreamSource, span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, known_size: Option, } @@ -192,13 +187,13 @@ impl ByteStream { pub fn new( stream: ByteStreamSource, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self { Self { stream, span, - ctrlc: interrupt, + signals, type_, known_size: None, } @@ -208,33 +203,33 @@ impl ByteStream { pub fn read( reader: impl Read + Send + 'static, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self { Self::new( ByteStreamSource::Read(Box::new(reader)), span, - interrupt, + signals, type_, ) } /// Create a [`ByteStream`] from a string. The type of the stream is always `String`. - pub fn read_string(string: String, span: Span, interrupt: Option>) -> Self { + pub fn read_string(string: String, span: Span, signals: Signals) -> Self { let len = string.len(); ByteStream::read( Cursor::new(string.into_bytes()), span, - interrupt, + signals, ByteStreamType::String, ) .with_known_size(Some(len as u64)) } /// Create a [`ByteStream`] from a byte vector. The type of the stream is always `Binary`. - pub fn read_binary(bytes: Vec, span: Span, interrupt: Option>) -> Self { + pub fn read_binary(bytes: Vec, span: Span, signals: Signals) -> Self { let len = bytes.len(); - ByteStream::read(Cursor::new(bytes), span, interrupt, ByteStreamType::Binary) + ByteStream::read(Cursor::new(bytes), span, signals, ByteStreamType::Binary) .with_known_size(Some(len as u64)) } @@ -242,11 +237,11 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether files will /// return text or binary. - pub fn file(file: File, span: Span, interrupt: Option>) -> Self { + pub fn file(file: File, span: Span, signals: Signals) -> Self { Self::new( ByteStreamSource::File(file), span, - interrupt, + signals, ByteStreamType::Unknown, ) } @@ -259,7 +254,7 @@ impl ByteStream { Self::new( ByteStreamSource::Child(Box::new(child)), span, - None, + Signals::empty(), ByteStreamType::Unknown, ) } @@ -271,14 +266,19 @@ impl ByteStream { pub fn stdin(span: Span) -> Result { let stdin = os_pipe::dup_stdin().err_span(span)?; let source = ByteStreamSource::File(convert_file(stdin)); - Ok(Self::new(source, span, None, ByteStreamType::Unknown)) + Ok(Self::new( + source, + span, + Signals::empty(), + ByteStreamType::Unknown, + )) } /// Create a [`ByteStream`] from a generator function that writes data to the given buffer /// when called, and returns `Ok(false)` on end of stream. pub fn from_fn( span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, generator: impl FnMut(&mut Vec) -> Result + Send + 'static, ) -> Self { @@ -288,7 +288,7 @@ impl ByteStream { generator, }, span, - interrupt, + signals, type_, ) } @@ -301,12 +301,7 @@ impl ByteStream { /// Create a new [`ByteStream`] from an [`Iterator`] of bytes slices. /// /// The returned [`ByteStream`] will have a [`ByteStreamSource`] of `Read`. - pub fn from_iter( - iter: I, - span: Span, - interrupt: Option>, - type_: ByteStreamType, - ) -> Self + pub fn from_iter(iter: I, span: Span, signals: Signals, type_: ByteStreamType) -> Self where I: IntoIterator, I::IntoIter: Send + 'static, @@ -314,7 +309,7 @@ impl ByteStream { { let iter = iter.into_iter(); let cursor = Some(Cursor::new(I::Item::default())); - Self::read(ReadIterator { iter, cursor }, span, interrupt, type_) + Self::read(ReadIterator { iter, cursor }, span, signals, type_) } /// Create a new [`ByteStream`] from an [`Iterator`] of [`Result`] bytes slices. @@ -323,7 +318,7 @@ impl ByteStream { pub fn from_result_iter( iter: I, span: Span, - interrupt: Option>, + signals: Signals, type_: ByteStreamType, ) -> Self where @@ -333,7 +328,7 @@ impl ByteStream { { let iter = iter.into_iter(); let cursor = Some(Cursor::new(T::default())); - Self::read(ReadResultIterator { iter, cursor }, span, interrupt, type_) + Self::read(ReadResultIterator { iter, cursor }, span, signals, type_) } /// Set the known size, in number of bytes, of the [`ByteStream`]. @@ -378,7 +373,7 @@ impl ByteStream { Some(Reader { reader: BufReader::new(reader), span: self.span, - ctrlc: self.ctrlc, + signals: self.signals, }) } @@ -394,7 +389,7 @@ impl ByteStream { Some(Lines { reader: BufReader::new(reader), span: self.span, - ctrlc: self.ctrlc, + signals: self.signals, }) } @@ -415,7 +410,7 @@ impl ByteStream { /// then the stream is considered empty and `None` will be returned. pub fn chunks(self) -> Option { let reader = self.stream.reader()?; - Some(Chunks::new(reader, self.span, self.ctrlc, self.type_)) + Some(Chunks::new(reader, self.span, self.signals, self.type_)) } /// Convert the [`ByteStream`] into its inner [`ByteStreamSource`]. @@ -552,7 +547,7 @@ impl ByteStream { pub fn drain(self) -> Result, ShellError> { match self.stream { ByteStreamSource::Read(read) => { - copy_with_interrupt(read, io::sink(), self.span, self.ctrlc.as_deref())?; + copy_with_signals(read, io::sink(), self.span, &self.signals)?; Ok(None) } ByteStreamSource::File(_) => Ok(None), @@ -578,14 +573,14 @@ impl ByteStream { /// then the [`ExitStatus`] of the [`ChildProcess`] is returned. pub fn write_to(self, dest: impl Write) -> Result, ShellError> { let span = self.span; - let ctrlc = self.ctrlc.as_deref(); + let signals = &self.signals; match self.stream { ByteStreamSource::Read(read) => { - copy_with_interrupt(read, dest, span, ctrlc)?; + copy_with_signals(read, dest, span, signals)?; Ok(None) } ByteStreamSource::File(file) => { - copy_with_interrupt(file, dest, span, ctrlc)?; + copy_with_signals(file, dest, span, signals)?; Ok(None) } ByteStreamSource::Child(mut child) => { @@ -597,10 +592,10 @@ impl ByteStream { if let Some(stdout) = child.stdout.take() { match stdout { ChildPipe::Pipe(pipe) => { - copy_with_interrupt(pipe, dest, span, ctrlc)?; + copy_with_signals(pipe, dest, span, signals)?; } ChildPipe::Tee(tee) => { - copy_with_interrupt(tee, dest, span, ctrlc)?; + copy_with_signals(tee, dest, span, signals)?; } } } @@ -615,21 +610,21 @@ impl ByteStream { stderr: &OutDest, ) -> Result, ShellError> { let span = self.span; - let ctrlc = self.ctrlc.as_deref(); + let signals = &self.signals; match self.stream { ByteStreamSource::Read(read) => { - write_to_out_dest(read, stdout, true, span, ctrlc)?; + write_to_out_dest(read, stdout, true, span, signals)?; Ok(None) } ByteStreamSource::File(file) => { match stdout { OutDest::Pipe | OutDest::Capture | OutDest::Null => {} OutDest::Inherit => { - copy_with_interrupt(file, io::stdout(), span, ctrlc)?; + copy_with_signals(file, io::stdout(), span, signals)?; } OutDest::File(f) => { - copy_with_interrupt(file, f.as_ref(), span, ctrlc)?; + copy_with_signals(file, f.as_ref(), span, signals)?; } } Ok(None) @@ -643,20 +638,20 @@ impl ByteStream { .name("stderr writer".into()) .spawn_scoped(s, || match err { ChildPipe::Pipe(pipe) => { - write_to_out_dest(pipe, stderr, false, span, ctrlc) + write_to_out_dest(pipe, stderr, false, span, signals) } ChildPipe::Tee(tee) => { - write_to_out_dest(tee, stderr, false, span, ctrlc) + write_to_out_dest(tee, stderr, false, span, signals) } }) .err_span(span); match out { ChildPipe::Pipe(pipe) => { - write_to_out_dest(pipe, stdout, true, span, ctrlc) + write_to_out_dest(pipe, stdout, true, span, signals) } ChildPipe::Tee(tee) => { - write_to_out_dest(tee, stdout, true, span, ctrlc) + write_to_out_dest(tee, stdout, true, span, signals) } }?; @@ -672,11 +667,11 @@ impl ByteStream { } (Some(out), None) => { // single output stream, we can consume directly - write_to_out_dest(out, stdout, true, span, ctrlc)?; + write_to_out_dest(out, stdout, true, span, signals)?; } (None, Some(err)) => { // single output stream, we can consume directly - write_to_out_dest(err, stderr, false, span, ctrlc)?; + write_to_out_dest(err, stderr, false, span, signals)?; } (None, None) => {} } @@ -749,7 +744,7 @@ where pub struct Reader { reader: BufReader, span: Span, - ctrlc: Option>, + signals: Signals, } impl Reader { @@ -760,14 +755,8 @@ impl Reader { impl Read for Reader { fn read(&mut self, buf: &mut [u8]) -> io::Result { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { - Err(ShellError::InterruptedByUser { - span: Some(self.span), - } - .into()) - } else { - self.reader.read(buf) - } + self.signals.check(self.span)?; + self.reader.read(buf) } } @@ -784,7 +773,7 @@ impl BufRead for Reader { pub struct Lines { reader: BufReader, span: Span, - ctrlc: Option>, + signals: Signals, } impl Lines { @@ -797,7 +786,7 @@ impl Iterator for Lines { type Item = Result; fn next(&mut self) -> Option { - if nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.signals.interrupted() { None } else { let mut buf = Vec::new(); @@ -826,23 +815,18 @@ pub struct Chunks { pos: u64, error: bool, span: Span, - ctrlc: Option>, + signals: Signals, type_: ByteStreamType, } impl Chunks { - fn new( - reader: SourceReader, - span: Span, - ctrlc: Option>, - type_: ByteStreamType, - ) -> Self { + fn new(reader: SourceReader, span: Span, signals: Signals, type_: ByteStreamType) -> Self { Self { reader: BufReader::new(reader), pos: 0, error: false, span, - ctrlc, + signals, type_, } } @@ -922,7 +906,7 @@ impl Iterator for Chunks { type Item = Result; fn next(&mut self) -> Option { - if self.error || nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if self.error || self.signals.interrupted() { None } else { match self.type_ { @@ -988,14 +972,14 @@ fn write_to_out_dest( stream: &OutDest, stdout: bool, span: Span, - ctrlc: Option<&AtomicBool>, + signals: &Signals, ) -> Result<(), ShellError> { match stream { OutDest::Pipe | OutDest::Capture => return Ok(()), - OutDest::Null => copy_with_interrupt(read, io::sink(), span, ctrlc), - OutDest::Inherit if stdout => copy_with_interrupt(read, io::stdout(), span, ctrlc), - OutDest::Inherit => copy_with_interrupt(read, io::stderr(), span, ctrlc), - OutDest::File(file) => copy_with_interrupt(read, file.as_ref(), span, ctrlc), + OutDest::Null => copy_with_signals(read, io::sink(), span, signals), + OutDest::Inherit if stdout => copy_with_signals(read, io::stdout(), span, signals), + OutDest::Inherit => copy_with_signals(read, io::stderr(), span, signals), + OutDest::File(file) => copy_with_signals(read, file.as_ref(), span, signals), }?; Ok(()) } @@ -1012,28 +996,13 @@ pub(crate) fn convert_file>(file: impl Into) - const DEFAULT_BUF_SIZE: usize = 8192; -pub fn copy_with_interrupt( +pub fn copy_with_signals( mut reader: impl Read, mut writer: impl Write, span: Span, - interrupt: Option<&AtomicBool>, + signals: &Signals, ) -> Result { - if let Some(interrupt) = interrupt { - // #[cfg(any(target_os = "linux", target_os = "android"))] - // { - // return crate::sys::kernel_copy::copy_spec(reader, writer); - // } - match generic_copy(&mut reader, &mut writer, span, interrupt) { - Ok(len) => { - writer.flush().err_span(span)?; - Ok(len) - } - Err(err) => { - let _ = writer.flush(); - Err(err) - } - } - } else { + if signals.is_empty() { match io::copy(&mut reader, &mut writer) { Ok(n) => { writer.flush().err_span(span)?; @@ -1044,6 +1013,21 @@ pub fn copy_with_interrupt( Err(err.into_spanned(span).into()) } } + } else { + // #[cfg(any(target_os = "linux", target_os = "android"))] + // { + // return crate::sys::kernel_copy::copy_spec(reader, writer); + // } + match generic_copy(&mut reader, &mut writer, span, signals) { + Ok(len) => { + writer.flush().err_span(span)?; + Ok(len) + } + Err(err) => { + let _ = writer.flush(); + Err(err) + } + } } } @@ -1052,14 +1036,12 @@ fn generic_copy( mut reader: impl Read, mut writer: impl Write, span: Span, - interrupt: &AtomicBool, + signals: &Signals, ) -> Result { let buf = &mut [0; DEFAULT_BUF_SIZE]; let mut len = 0; loop { - if interrupt.load(Ordering::Relaxed) { - return Err(ShellError::InterruptedByUser { span: Some(span) }); - } + signals.check(span)?; let n = match reader.read(buf) { Ok(0) => break, Ok(n) => n, @@ -1134,7 +1116,7 @@ mod tests { Chunks::new( SourceReader::Read(Box::new(reader)), Span::test_data(), - None, + Signals::empty(), type_, ) } diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 117c264219..997cc3f77b 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -1,8 +1,5 @@ -use crate::{Config, PipelineData, ShellError, Span, Value}; -use std::{ - fmt::Debug, - sync::{atomic::AtomicBool, Arc}, -}; +use crate::{Config, PipelineData, ShellError, Signals, Span, Value}; +use std::fmt::Debug; pub type ValueIterator = Box + Send + 'static>; @@ -21,10 +18,10 @@ impl ListStream { pub fn new( iter: impl Iterator + Send + 'static, span: Span, - interrupt: Option>, + signals: Signals, ) -> Self { Self { - stream: Box::new(Interrupt::new(iter, interrupt)), + stream: Box::new(InterruptIter::new(iter, signals)), span, } } @@ -69,10 +66,10 @@ impl ListStream { /// E.g., `take`, `filter`, `step_by`, and more. /// /// ``` - /// use nu_protocol::{ListStream, Span, Value}; + /// use nu_protocol::{ListStream, Signals, Span, Value}; /// /// let span = Span::unknown(); - /// let stream = ListStream::new(std::iter::repeat(Value::int(0, span)), span, None); + /// let stream = ListStream::new(std::iter::repeat(Value::int(0, span)), span, Signals::empty()); /// let new_stream = stream.modify(|iter| iter.take(100)); /// ``` pub fn modify(self, f: impl FnOnce(ValueIterator) -> I) -> Self @@ -128,22 +125,22 @@ impl Iterator for IntoIter { } } -struct Interrupt { +struct InterruptIter { iter: I, - interrupt: Option>, + signals: Signals, } -impl Interrupt { - fn new(iter: I, interrupt: Option>) -> Self { - Self { iter, interrupt } +impl InterruptIter { + fn new(iter: I, signals: Signals) -> Self { + Self { iter, signals } } } -impl Iterator for Interrupt { +impl Iterator for InterruptIter { type Item = ::Item; fn next(&mut self) -> Option { - if nu_utils::ctrl_c::was_pressed(&self.interrupt) { + if self.signals.interrupted() { None } else { self.iter.next() diff --git a/crates/nu-protocol/src/pipeline/mod.rs b/crates/nu-protocol/src/pipeline/mod.rs index a018a084ed..525806425d 100644 --- a/crates/nu-protocol/src/pipeline/mod.rs +++ b/crates/nu-protocol/src/pipeline/mod.rs @@ -3,9 +3,11 @@ pub mod list_stream; mod metadata; mod out_dest; mod pipeline_data; +mod signals; pub use byte_stream::*; pub use list_stream::*; pub use metadata::*; pub use out_dest::*; pub use pipeline_data::*; +pub use signals::*; diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index c0c0bd1c33..a546e90191 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -3,13 +3,10 @@ use crate::{ engine::{EngineState, Stack}, process::{ChildPipe, ChildProcess, ExitStatus}, ByteStream, ByteStreamType, Config, ErrSpan, ListStream, OutDest, PipelineMetadata, Range, - ShellError, Span, Type, Value, + ShellError, Signals, Span, Type, Value, }; use nu_utils::{stderr_write_all_and_flush, stdout_write_all_and_flush}; -use std::{ - io::{Cursor, Read, Write}, - sync::{atomic::AtomicBool, Arc}, -}; +use std::io::{Cursor, Read, Write}; const LINE_ENDING_PATTERN: &[char] = &['\r', '\n']; @@ -196,19 +193,23 @@ impl PipelineData { let val_span = value.span(); match value { Value::List { vals, .. } => PipelineIteratorInner::ListStream( - ListStream::new(vals.into_iter(), val_span, None).into_iter(), + ListStream::new(vals.into_iter(), val_span, Signals::empty()).into_iter(), ), Value::Binary { val, .. } => PipelineIteratorInner::ListStream( ListStream::new( val.into_iter().map(move |x| Value::int(x as i64, val_span)), val_span, - None, + Signals::empty(), ) .into_iter(), ), Value::Range { val, .. } => PipelineIteratorInner::ListStream( - ListStream::new(val.into_range_iter(val_span, None), val_span, None) - .into_iter(), + ListStream::new( + val.into_range_iter(val_span, Signals::empty()), + val_span, + Signals::empty(), + ) + .into_iter(), ), // Propagate errors by explicitly matching them before the final case. Value::Error { error, .. } => return Err(*error), @@ -301,11 +302,7 @@ impl PipelineData { } /// Simplified mapper to help with simple values also. For full iterator support use `.into_iter()` instead - pub fn map( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn map(self, mut f: F, signals: &Signals) -> Result where Self: Sized, F: FnMut(Value) -> Value + 'static + Send, @@ -314,13 +311,14 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().map(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .map(f) - .into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .map(f) + .into_pipeline_data(span, signals.clone()), value => match f(value) { Value::Error { error, .. } => return Err(*error), v => v.into_pipeline_data(), @@ -339,11 +337,7 @@ impl PipelineData { } /// Simplified flatmapper. For full iterator support use `.into_iter()` instead - pub fn flat_map( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn flat_map(self, mut f: F, signals: &Signals) -> Result where Self: Sized, U: IntoIterator + 'static, @@ -355,14 +349,17 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().flat_map(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .flat_map(f) - .into_pipeline_data(span, ctrlc), - value => f(value).into_iter().into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .flat_map(f) + .into_pipeline_data(span, signals.clone()), + value => f(value) + .into_iter() + .into_pipeline_data(span, signals.clone()), }; Ok(pipeline.set_metadata(metadata)) } @@ -380,18 +377,16 @@ impl PipelineData { } Err(err) => f(Value::binary(err.into_bytes(), span)), }; - Ok(iter - .into_iter() - .into_pipeline_data_with_metadata(span, ctrlc, metadata)) + Ok(iter.into_iter().into_pipeline_data_with_metadata( + span, + signals.clone(), + metadata, + )) } } } - pub fn filter( - self, - mut f: F, - ctrlc: Option>, - ) -> Result + pub fn filter(self, mut f: F, signals: &Signals) -> Result where Self: Sized, F: FnMut(&Value) -> bool + 'static + Send, @@ -401,13 +396,14 @@ impl PipelineData { PipelineData::Value(value, metadata) => { let span = value.span(); let pipeline = match value { - Value::List { vals, .. } => { - vals.into_iter().filter(f).into_pipeline_data(span, ctrlc) - } - Value::Range { val, .. } => val - .into_range_iter(span, ctrlc.clone()) + Value::List { vals, .. } => vals + .into_iter() .filter(f) - .into_pipeline_data(span, ctrlc), + .into_pipeline_data(span, signals.clone()), + Value::Range { val, .. } => val + .into_range_iter(span, Signals::empty()) + .filter(f) + .into_pipeline_data(span, signals.clone()), value => { if f(&value) { value.into_pipeline_data() @@ -538,7 +534,8 @@ impl PipelineData { } } } - let range_values: Vec = val.into_range_iter(span, None).collect(); + let range_values: Vec = + val.into_range_iter(span, Signals::empty()).collect(); Ok(PipelineData::Value(Value::list(range_values, span), None)) } x => Ok(PipelineData::Value(x, metadata)), @@ -638,10 +635,15 @@ impl IntoIterator for PipelineData { let span = value.span(); match value { Value::List { vals, .. } => PipelineIteratorInner::ListStream( - ListStream::new(vals.into_iter(), span, None).into_iter(), + ListStream::new(vals.into_iter(), span, Signals::empty()).into_iter(), ), Value::Range { val, .. } => PipelineIteratorInner::ListStream( - ListStream::new(val.into_range_iter(span, None), span, None).into_iter(), + ListStream::new( + val.into_range_iter(span, Signals::empty()), + span, + Signals::empty(), + ) + .into_iter(), ), x => PipelineIteratorInner::Value(x), } @@ -703,11 +705,11 @@ where } pub trait IntoInterruptiblePipelineData { - fn into_pipeline_data(self, span: Span, ctrlc: Option>) -> PipelineData; + fn into_pipeline_data(self, span: Span, signals: Signals) -> PipelineData; fn into_pipeline_data_with_metadata( self, span: Span, - ctrlc: Option>, + signals: Signals, metadata: impl Into>, ) -> PipelineData; } @@ -718,18 +720,18 @@ where I::IntoIter: Send + 'static, ::Item: Into, { - fn into_pipeline_data(self, span: Span, ctrlc: Option>) -> PipelineData { - ListStream::new(self.into_iter().map(Into::into), span, ctrlc).into() + fn into_pipeline_data(self, span: Span, signals: Signals) -> PipelineData { + ListStream::new(self.into_iter().map(Into::into), span, signals).into() } fn into_pipeline_data_with_metadata( self, span: Span, - ctrlc: Option>, + signals: Signals, metadata: impl Into>, ) -> PipelineData { PipelineData::ListStream( - ListStream::new(self.into_iter().map(Into::into), span, ctrlc), + ListStream::new(self.into_iter().map(Into::into), span, signals), metadata.into(), ) } diff --git a/crates/nu-protocol/src/pipeline/signals.rs b/crates/nu-protocol/src/pipeline/signals.rs new file mode 100644 index 0000000000..06ce583c82 --- /dev/null +++ b/crates/nu-protocol/src/pipeline/signals.rs @@ -0,0 +1,76 @@ +use crate::{ShellError, Span}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +/// Used to check for signals to suspend or terminate the execution of Nushell code. +/// +/// For now, this struct only supports interruption (ctrl+c or SIGINT). +#[derive(Debug, Clone)] +pub struct Signals { + signals: Option>, +} + +impl Signals { + /// A [`Signals`] that is not hooked up to any event/signals source. + /// + /// So, this [`Signals`] will never be interrupted. + pub const EMPTY: Self = Signals { signals: None }; + + /// Create a new [`Signals`] with `ctrlc` as the interrupt source. + /// + /// Once `ctrlc` is set to `true`, [`check`](Self::check) will error + /// and [`interrupted`](Self::interrupted) will return `true`. + pub fn new(ctrlc: Arc) -> Self { + Self { + signals: Some(ctrlc), + } + } + + /// Create a [`Signals`] that is not hooked up to any event/signals source. + /// + /// So, the returned [`Signals`] will never be interrupted. + /// + /// This should only be used in test code, or if the stream/iterator being created + /// already has an underlying [`Signals`]. + pub const fn empty() -> Self { + Self::EMPTY + } + + /// Returns an `Err` if an interrupt has been triggered. + /// + /// Otherwise, returns `Ok`. + #[inline] + pub fn check(&self, span: Span) -> Result<(), ShellError> { + #[inline] + #[cold] + fn interrupt_error(span: Span) -> Result<(), ShellError> { + Err(ShellError::Interrupted { span }) + } + + if self.interrupted() { + interrupt_error(span) + } else { + Ok(()) + } + } + + /// Returns whether an interrupt has been triggered. + #[inline] + pub fn interrupted(&self) -> bool { + self.signals + .as_deref() + .is_some_and(|b| b.load(Ordering::Relaxed)) + } + + pub(crate) fn is_empty(&self) -> bool { + self.signals.is_none() + } + + pub(crate) fn reset(&self) { + if let Some(signals) = &self.signals { + signals.store(false, Ordering::Relaxed); + } + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index df030ad3e9..fc618d6341 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -23,7 +23,7 @@ use crate::{ ast::{Bits, Boolean, CellPath, Comparison, Math, Operator, PathMember}, did_you_mean, engine::{Closure, EngineState}, - Config, ShellError, Span, Type, + Config, ShellError, Signals, Span, Type, }; use chrono::{DateTime, Datelike, FixedOffset, Locale, TimeZone}; use chrono_humanize::HumanTime; @@ -1017,8 +1017,9 @@ impl Value { } } Value::Range { ref val, .. } => { - if let Some(item) = - val.into_range_iter(current.span(), None).nth(*count) + if let Some(item) = val + .into_range_iter(current.span(), Signals::empty()) + .nth(*count) { current = item; } else if *optional { diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 5b1fa32ec7..6ec7ce16ad 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,25 +1,13 @@ //! A Range is an iterator over integers or floats. +use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; -use std::{ - cmp::Ordering, - fmt::Display, - sync::{atomic::AtomicBool, Arc}, -}; - -use crate::{ast::RangeInclusion, ShellError, Span, Value}; +use std::{cmp::Ordering, fmt::Display}; mod int_range { - use std::{ - cmp::Ordering, - fmt::Display, - ops::Bound, - sync::{atomic::AtomicBool, Arc}, - }; - + use crate::{ast::RangeInclusion, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; - - use crate::{ast::RangeInclusion, ShellError, Span, Value}; + use std::{cmp::Ordering, fmt::Display, ops::Bound}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct IntRange { @@ -123,12 +111,12 @@ mod int_range { } } - pub fn into_range_iter(self, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { current: Some(self.start), step: self.step, end: self.end, - ctrlc, + signals, } } } @@ -202,7 +190,7 @@ mod int_range { current: Option, step: i64, end: Bound, - ctrlc: Option>, + signals: Signals, } impl Iterator for Iter { @@ -218,7 +206,7 @@ mod int_range { (_, Bound::Unbounded) => true, // will stop once integer overflows }; - if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if not_end && !self.signals.interrupted() { self.current = current.checked_add(self.step); Some(current) } else { @@ -233,16 +221,9 @@ mod int_range { } mod float_range { - use std::{ - cmp::Ordering, - fmt::Display, - ops::Bound, - sync::{atomic::AtomicBool, Arc}, - }; - + use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Signals, Span, Value}; use serde::{Deserialize, Serialize}; - - use crate::{ast::RangeInclusion, IntRange, Range, ShellError, Span, Value}; + use std::{cmp::Ordering, fmt::Display, ops::Bound}; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct FloatRange { @@ -365,13 +346,13 @@ mod float_range { } } - pub fn into_range_iter(self, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, signals: Signals) -> Iter { Iter { start: self.start, step: self.step, end: self.end, iter: Some(0), - ctrlc, + signals, } } } @@ -477,7 +458,7 @@ mod float_range { step: f64, end: Bound, iter: Option, - ctrlc: Option>, + signals: Signals, } impl Iterator for Iter { @@ -495,7 +476,7 @@ mod float_range { (_, Bound::Unbounded) => current.is_finite(), }; - if not_end && !nu_utils::ctrl_c::was_pressed(&self.ctrlc) { + if not_end && !self.signals.interrupted() { self.iter = iter.checked_add(1); Some(current) } else { @@ -549,10 +530,10 @@ impl Range { } } - pub fn into_range_iter(self, span: Span, ctrlc: Option>) -> Iter { + pub fn into_range_iter(self, span: Span, signals: Signals) -> Iter { match self { - Range::IntRange(range) => Iter::IntIter(range.into_range_iter(ctrlc), span), - Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(ctrlc), span), + Range::IntRange(range) => Iter::IntIter(range.into_range_iter(signals), span), + Range::FloatRange(range) => Iter::FloatIter(range.into_range_iter(signals), span), } } } diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index 9472cc2846..668e5ea25c 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -102,9 +102,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -143,9 +141,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -230,9 +226,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -353,9 +347,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> StringResult { let mut data = Vec::with_capacity(record.len()); for (key, value) in record { - if nu_utils::ctrl_c::was_pressed(&cfg.opts.ctrlc) { - return Ok(None); - } + cfg.opts.signals.check(cfg.opts.span)?; let (value, is_expanded) = match expand_table_value(value, value_width, &cfg)? { Some(val) => val, diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index 4a6cd302e8..66048267d7 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -43,9 +43,7 @@ fn create_table(input: &[Value], opts: TableOpts<'_>) -> Result, fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut data = vec![Vec::with_capacity(2); record.len()]; for ((column, value), row) in record.iter().zip(data.iter_mut()) { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; let value = nu_value_to_string_colored(value, opts.config, opts.style_computer); @@ -123,9 +121,7 @@ fn to_table_with_header( } for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); @@ -158,9 +154,7 @@ fn to_table_with_no_header( table.set_index_style(get_index_style(opts.style_computer)); for (row, item) in input.iter().enumerate() { - if nu_utils::ctrl_c::was_pressed(&opts.ctrlc) { - return Ok(None); - } + opts.signals.check(opts.span)?; if let Value::Error { error, .. } = item { return Err(*error.clone()); diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index 10289c3b81..163e4b0c81 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -8,8 +8,7 @@ pub use general::JustTable; use crate::{common::INDEX_COLUMN_NAME, NuTable}; use nu_color_config::StyleComputer; -use nu_protocol::{Config, Span, TableIndexMode, TableMode}; -use std::sync::{atomic::AtomicBool, Arc}; +use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; pub struct TableOutput { pub table: NuTable, @@ -29,7 +28,7 @@ impl TableOutput { #[derive(Debug, Clone)] pub struct TableOpts<'a> { - ctrlc: Option>, + signals: &'a Signals, config: &'a Config, style_computer: &'a StyleComputer<'a>, span: Span, @@ -45,7 +44,7 @@ impl<'a> TableOpts<'a> { pub fn new( config: &'a Config, style_computer: &'a StyleComputer<'a>, - ctrlc: Option>, + signals: &'a Signals, span: Span, width: usize, indent: (usize, usize), @@ -54,7 +53,7 @@ impl<'a> TableOpts<'a> { index_remove: bool, ) -> Self { Self { - ctrlc, + signals, config, style_computer, span, diff --git a/crates/nu-utils/src/ctrl_c.rs b/crates/nu-utils/src/ctrl_c.rs deleted file mode 100644 index 72719139ad..0000000000 --- a/crates/nu-utils/src/ctrl_c.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; - -/// Returns true if Nu has received a SIGINT signal / ctrl+c event -pub fn was_pressed(ctrlc: &Option>) -> bool { - if let Some(ctrlc) = ctrlc { - ctrlc.load(Ordering::SeqCst) - } else { - false - } -} diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index f1a915290b..cb834e5e0f 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -1,5 +1,4 @@ mod casing; -pub mod ctrl_c; mod deansi; pub mod emoji; pub mod filesystem; diff --git a/crates/nu_plugin_example/src/commands/collect_bytes.rs b/crates/nu_plugin_example/src/commands/collect_bytes.rs index 398a1de4b1..e716493785 100644 --- a/crates/nu_plugin_example/src/commands/collect_bytes.rs +++ b/crates/nu_plugin_example/src/commands/collect_bytes.rs @@ -1,7 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - ByteStream, ByteStreamType, Category, Example, LabeledError, PipelineData, Signature, Type, - Value, + ByteStream, ByteStreamType, Category, Example, LabeledError, PipelineData, Signals, Signature, + Type, Value, }; use crate::ExamplePlugin; @@ -52,7 +52,7 @@ impl PluginCommand for CollectBytes { ByteStream::from_result_iter( input.into_iter().map(Value::coerce_into_binary), call.head, - None, + Signals::empty(), ByteStreamType::Unknown, ), None, diff --git a/crates/nu_plugin_example/src/commands/generate.rs b/crates/nu_plugin_example/src/commands/generate.rs index 9938518f4c..c039cc23d5 100644 --- a/crates/nu_plugin_example/src/commands/generate.rs +++ b/crates/nu_plugin_example/src/commands/generate.rs @@ -1,7 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signature, - SyntaxShape, Type, Value, + Category, Example, IntoInterruptiblePipelineData, LabeledError, PipelineData, Signals, + Signature, SyntaxShape, Type, Value, }; use crate::ExamplePlugin; @@ -85,7 +85,7 @@ impl PluginCommand for Generate { }) .map(|result| result.unwrap_or_else(|err| Value::error(err, head))) }) - .into_pipeline_data(head, None)) + .into_pipeline_data(head, Signals::empty())) } } diff --git a/crates/nu_plugin_example/src/commands/seq.rs b/crates/nu_plugin_example/src/commands/seq.rs index 5652b3ef9a..88254394c1 100644 --- a/crates/nu_plugin_example/src/commands/seq.rs +++ b/crates/nu_plugin_example/src/commands/seq.rs @@ -1,6 +1,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - Category, Example, LabeledError, ListStream, PipelineData, Signature, SyntaxShape, Type, Value, + Category, Example, LabeledError, ListStream, PipelineData, Signals, Signature, SyntaxShape, + Type, Value, }; use crate::ExamplePlugin; @@ -54,7 +55,7 @@ impl PluginCommand for Seq { let first: i64 = call.req(0)?; let last: i64 = call.req(1)?; let iter = (first..=last).map(move |number| Value::int(number, head)); - Ok(ListStream::new(iter, head, None).into()) + Ok(ListStream::new(iter, head, Signals::empty()).into()) } } diff --git a/src/main.rs b/src/main.rs index 6eb65276b9..2c0c4d5e4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,11 +32,7 @@ use nu_std::load_standard_library; use nu_utils::perf; use run::{run_commands, run_file, run_repl}; use signals::ctrlc_protection; -use std::{ - path::PathBuf, - str::FromStr, - sync::{atomic::AtomicBool, Arc}, -}; +use std::{path::PathBuf, str::FromStr, sync::Arc}; fn get_engine_state() -> EngineState { let engine_state = nu_cmd_lang::create_default_context(); @@ -74,9 +70,8 @@ fn main() -> Result<()> { report_error_new(&engine_state, &err); } - let ctrlc = Arc::new(AtomicBool::new(false)); // TODO: make this conditional in the future - ctrlc_protection(&mut engine_state, &ctrlc); + ctrlc_protection(&mut engine_state); // Begin: Default NU_LIB_DIRS, NU_PLUGIN_DIRS // Set default NU_LIB_DIRS and NU_PLUGIN_DIRS here before the env.nu is processed. If @@ -397,7 +392,7 @@ fn main() -> Result<()> { ); } - LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state, ctrlc)? + LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state)? } else if let Some(commands) = parsed_nu_cli_args.commands.clone() { run_commands( &mut engine_state, diff --git a/src/signals.rs b/src/signals.rs index 9247ccb095..bb3539b95e 100644 --- a/src/signals.rs +++ b/src/signals.rs @@ -1,17 +1,14 @@ -use nu_protocol::engine::EngineState; +use nu_protocol::{engine::EngineState, Signals}; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; -pub(crate) fn ctrlc_protection(engine_state: &mut EngineState, ctrlc: &Arc) { - let handler_ctrlc = ctrlc.clone(); - let engine_state_ctrlc = ctrlc.clone(); - +pub(crate) fn ctrlc_protection(engine_state: &mut EngineState) { + let interrupt = Arc::new(AtomicBool::new(false)); + engine_state.set_signals(Signals::new(interrupt.clone())); ctrlc::set_handler(move || { - handler_ctrlc.store(true, Ordering::SeqCst); + interrupt.store(true, Ordering::Relaxed); }) .expect("Error setting Ctrl-C handler"); - - engine_state.ctrlc = Some(engine_state_ctrlc); } From e98b2ceb8c668fed6073373a445456a29115a206 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 9 Jul 2024 09:25:23 +0000 Subject: [PATCH 18/63] Path migration 1 (#13309) # Description Part 1 of replacing `std::path` types with `nu_path` types added in #13115. --- crates/nu-cli/src/repl.rs | 1 + crates/nu-cmd-base/src/util.rs | 2 + .../nu-command/src/env/config/config_env.rs | 7 ++- crates/nu-command/src/env/config/config_nu.rs | 7 ++- crates/nu-command/src/filesystem/cd.rs | 6 +- crates/nu-command/src/path/type.rs | 8 +-- crates/nu-command/src/system/exec.rs | 2 +- crates/nu-command/src/system/run_external.rs | 4 +- crates/nu-command/src/viewers/griddle.rs | 6 +- crates/nu-engine/src/eval.rs | 7 ++- crates/nu-protocol/src/engine/engine_state.rs | 55 +++++++------------ 11 files changed, 51 insertions(+), 54 deletions(-) diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 5b0db96741..7099b70ba8 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -336,6 +336,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { .with_cwd(Some( engine_state .cwd(None) + .map(|cwd| cwd.into_std_path_buf()) .unwrap_or_default() .to_string_lossy() .to_string(), diff --git a/crates/nu-cmd-base/src/util.rs b/crates/nu-cmd-base/src/util.rs index 905c990a9e..e485243877 100644 --- a/crates/nu-cmd-base/src/util.rs +++ b/crates/nu-cmd-base/src/util.rs @@ -1,3 +1,4 @@ +use nu_path::AbsolutePathBuf; use nu_protocol::{ engine::{EngineState, Stack}, Range, ShellError, Span, Value, @@ -15,6 +16,7 @@ pub fn get_init_cwd() -> PathBuf { pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf { engine_state .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) .unwrap_or(crate::util::get_init_cwd()) } diff --git a/crates/nu-command/src/env/config/config_env.rs b/crates/nu-command/src/env/config/config_env.rs index 32bd7bba90..777b6f3aac 100644 --- a/crates/nu-command/src/env/config/config_env.rs +++ b/crates/nu-command/src/env/config/config_env.rs @@ -60,12 +60,13 @@ impl Command for ConfigEnv { let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = - crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand { + let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( + ShellError::ExternalCommand { label: format!("`{editor_name}` not found"), help: "Failed to find the editor executable".into(), span: call.head, - })?; + }, + )?; let Some(env_path) = engine_state.get_config_path("env-path") else { return Err(ShellError::GenericError { diff --git a/crates/nu-command/src/env/config/config_nu.rs b/crates/nu-command/src/env/config/config_nu.rs index 08695ca5bb..80f0bbc012 100644 --- a/crates/nu-command/src/env/config/config_nu.rs +++ b/crates/nu-command/src/env/config/config_nu.rs @@ -64,12 +64,13 @@ impl Command for ConfigNu { let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = - crate::which(&editor_name, &paths, &cwd).ok_or(ShellError::ExternalCommand { + let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( + ShellError::ExternalCommand { label: format!("`{editor_name}` not found"), help: "Failed to find the editor executable".into(), span: call.head, - })?; + }, + )?; let Some(config_path) = engine_state.get_config_path("config-path") else { return Err(ShellError::GenericError { diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 4e09d900d2..23faf6f67b 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,5 +1,6 @@ use nu_cmd_base::util::get_init_cwd; use nu_engine::command_prelude::*; +use nu_path::AbsolutePathBuf; use nu_utils::filesystem::{have_permission, PermissionResult}; #[derive(Clone)] @@ -43,7 +44,10 @@ impl Command for Cd { // If getting PWD failed, default to the initial directory. This way, the // user can use `cd` to recover PWD to a good state. - let cwd = engine_state.cwd(Some(stack)).unwrap_or(get_init_cwd()); + let cwd = engine_state + .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or(get_init_cwd()); let path_val = { if let Some(path) = path_val { diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index bf66c8cb3b..010fd295ad 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -1,13 +1,11 @@ use super::PathSubcommandArguments; use nu_engine::command_prelude::*; +use nu_path::AbsolutePathBuf; use nu_protocol::engine::StateWorkingSet; -use std::{ - io, - path::{Path, PathBuf}, -}; +use std::{io, path::Path}; struct Arguments { - pwd: PathBuf, + pwd: AbsolutePathBuf, } impl PathSubcommandArguments for Arguments {} diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index 35ab9428be..018017cfb2 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -39,7 +39,7 @@ On Windows based systems, Nushell will wait for the command to finish and then e let name: Spanned = call.req(engine_state, stack, 0)?; let executable = { let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = crate::which(&name.item, &paths, &cwd) else { + let Some(executable) = crate::which(&name.item, &paths, cwd.as_ref()) else { return Err(crate::command_not_found( &name.item, call.head, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index e475833747..06bc5a69ca 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -79,7 +79,7 @@ impl Command for External { // Determine the PATH to be used and then use `which` to find it - though this has no // effect if it's an absolute path already let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = which(expanded_name, &paths, &cwd) else { + let Some(executable) = which(expanded_name, &paths, cwd.as_ref()) else { return Err(command_not_found(&name_str, call.head, engine_state, stack)); }; executable @@ -228,7 +228,7 @@ pub fn eval_arguments_from_call( match arg { // Expand globs passed to run-external Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, &cwd, expr.span, engine_state.signals())? + expand_glob(&val, cwd.as_ref(), expr.span, engine_state.signals())? .into_iter() .map(|s| s.into_spanned(expr.span)), ), diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 8b39bf5649..cd73c1ca71 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -83,7 +83,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } else { Ok(PipelineData::empty()) @@ -101,7 +101,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } else { // dbg!(data); @@ -124,7 +124,7 @@ prints out the list properly."# separator_param, env_str, use_grid_icons, - &cwd, + cwd.as_ref(), )?) } x => { diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 1460dec464..b495589011 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ #[allow(deprecated)] use crate::{current_dir, get_config, get_full_help}; -use nu_path::expand_path_with; +use nu_path::{expand_path_with, AbsolutePathBuf}; use nu_protocol::{ ast::{ Assignment, Block, Call, Expr, Expression, ExternalArgument, PathMember, PipelineElement, @@ -679,7 +679,10 @@ impl Eval for EvalRuntime { } else if quoted { Ok(Value::string(path, span)) } else { - let cwd = engine_state.cwd(Some(stack)).unwrap_or_default(); + let cwd = engine_state + .cwd(Some(stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or_default(); let path = expand_path_with(path, cwd, true); Ok(Value::string(path.to_string_lossy(), span)) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index e393d78d41..d45fbafb88 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -12,6 +12,7 @@ use crate::{ }; use fancy_regex::Regex; use lru::LruCache; +use nu_path::AbsolutePathBuf; use std::{ collections::HashMap, num::NonZeroUsize, @@ -922,58 +923,44 @@ impl EngineState { /// /// If `stack` is supplied, also considers modifications to the working /// directory on the stack that have yet to be merged into the engine state. - pub fn cwd(&self, stack: Option<&Stack>) -> Result { + pub fn cwd(&self, stack: Option<&Stack>) -> Result { // Helper function to create a simple generic error. - fn error(msg: &str, cwd: impl AsRef) -> Result { - Err(ShellError::GenericError { + fn error(msg: &str, cwd: impl AsRef) -> ShellError { + ShellError::GenericError { error: msg.into(), msg: format!("$env.PWD = {}", cwd.as_ref().display()), span: None, help: Some("Use `cd` to reset $env.PWD into a good state".into()), inner: vec![], - }) - } - - // Helper function to check if a path is a root path. - fn is_root(path: &Path) -> bool { - path.parent().is_none() - } - - // Helper function to check if a path has trailing slashes. - fn has_trailing_slash(path: &Path) -> bool { - nu_path::components(path).last() - == Some(std::path::Component::Normal(std::ffi::OsStr::new(""))) + } } // Retrieve $env.PWD from the stack or the engine state. let pwd = if let Some(stack) = stack { stack.get_env_var(self, "PWD") } else { - self.get_env_var("PWD").map(ToOwned::to_owned) + self.get_env_var("PWD").cloned() }; - if let Some(pwd) = pwd { - if let Value::String { val, .. } = pwd { - let path = PathBuf::from(val); + let pwd = pwd.ok_or_else(|| error("$env.PWD not found", ""))?; - // Technically, a root path counts as "having trailing slashes", but - // for the purpose of PWD, a root path is acceptable. - if !is_root(&path) && has_trailing_slash(&path) { - error("$env.PWD contains trailing slashes", path) - } else if !path.is_absolute() { - error("$env.PWD is not an absolute path", path) - } else if !path.exists() { - error("$env.PWD points to a non-existent directory", path) - } else if !path.is_dir() { - error("$env.PWD points to a non-directory", path) - } else { - Ok(path) - } + if let Value::String { val, .. } = pwd { + let path = AbsolutePathBuf::try_from(val) + .map_err(|path| error("$env.PWD is not an absolute path", path))?; + + // Technically, a root path counts as "having trailing slashes", but + // for the purpose of PWD, a root path is acceptable. + if path.parent().is_some() && nu_path::has_trailing_slash(path.as_ref()) { + Err(error("$env.PWD contains trailing slashes", &path)) + } else if !path.exists() { + Err(error("$env.PWD points to a non-existent directory", &path)) + } else if !path.is_dir() { + Err(error("$env.PWD points to a non-directory", &path)) } else { - error("$env.PWD is not a string", format!("{pwd:?}")) + Ok(path) } } else { - error("$env.PWD not found", "") + Err(error("$env.PWD is not a string", format!("{pwd:?}"))) } } From 1964dacaefefdf99e6c2ea8e5f907e625f872727 Mon Sep 17 00:00:00 2001 From: Wind Date: Tue, 9 Jul 2024 20:11:25 +0800 Subject: [PATCH 19/63] Raise error when using `o>|` pipe (#13323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description From the feedbacks from @amtoine , it's good to make nushell shows error for `o>|` syntax. # User-Facing Changes ## Before ```nushell 'foo' o>| print 07/09/2024 06:44:23 AM Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #6:1:9] 1 │ 'foo' o>| print · ┬ · ╰── expected redirection target ``` ## After ```nushell 'foo' o>| print 07/09/2024 06:47:26 AM Error: nu::parser::parse_mismatch × Parse mismatch during operation. ╭─[entry #1:1:7] 1 │ 'foo' o>| print · ─┬─ · ╰── expected `|`. Redirection stdout to pipe is the same as piping directly. ╰──── ``` # Tests + Formatting Added one test --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-parser/src/lex.rs | 53 ++++++++++++++++------- tests/shell/pipeline/commands/internal.rs | 8 ++++ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 4639cae25d..9b52467ef4 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -580,7 +580,10 @@ fn lex_internal( // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { - let token = try_lex_special_piped_item(input, &mut curr_offset, span_offset); + let (token, err) = try_lex_special_piped_item(input, &mut curr_offset, span_offset); + if error.is_none() { + error = err; + } if let Some(token) = token { output.push(token); is_complete = false; @@ -614,40 +617,60 @@ fn try_lex_special_piped_item( input: &[u8], curr_offset: &mut usize, span_offset: usize, -) -> Option { +) -> (Option, Option) { let c = input[*curr_offset]; let e_pipe_len = 3; let eo_pipe_len = 5; + let o_pipe_len = 3; let offset = *curr_offset; if c == b'e' { // expect `e>|` if (offset + e_pipe_len <= input.len()) && (&input[offset..offset + e_pipe_len] == b"e>|") { *curr_offset += e_pipe_len; - return Some(Token::new( - TokenContents::ErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + e_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::ErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + e_pipe_len), + )), + None, + ); } if (offset + eo_pipe_len <= input.len()) && (&input[offset..offset + eo_pipe_len] == b"e+o>|") { *curr_offset += eo_pipe_len; - return Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::OutErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), + )), + None, + ); } } else if c == b'o' { + // indicates an error if user happened to type `o>|` + if offset + o_pipe_len <= input.len() && (&input[offset..offset + o_pipe_len] == b"o>|") { + return ( + None, + Some(ParseError::Expected( + "`|`. Redirecting stdout to a pipe is the same as normal piping.", + Span::new(span_offset + offset, span_offset + offset + o_pipe_len), + )), + ); + } // it can be the following case: `o+e>|` if (offset + eo_pipe_len <= input.len()) && (&input[offset..offset + eo_pipe_len] == b"o+e>|") { *curr_offset += eo_pipe_len; - return Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )); + return ( + Some(Token::new( + TokenContents::OutErrGreaterPipe, + Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), + )), + None, + ); } } - None + (None, None) } diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index 84f0bf146f..59c9f31fce 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -1161,3 +1161,11 @@ fn command_not_found_error_shows_not_found_2() { && actual.err.contains("Did you mean `for`?") ); } + +#[test] +fn error_on_out_greater_pipe() { + let actual = nu!(r#""foo" o>| print"#); + assert!(actual + .err + .contains("Redirecting stdout to a pipe is the same as normal piping")) +} From 4cdceca1f716d4c7d8a815365f6052ecbe3f7d19 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Tue, 9 Jul 2024 17:49:04 +0300 Subject: [PATCH 20/63] Fix kv table width issue with header_on_border configuration (#13325) GOOD CATCH............................................................. SORRY I've added a test to catch regression just in case. close #13319 cc: @fdncred --- crates/nu-command/tests/commands/table.rs | 6 ++++++ crates/nu-table/src/table.rs | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 9c4f34ebfd..16d5eb54ab 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2889,3 +2889,9 @@ fn table_list() { let actual = nu!("table --list --theme basic"); assert_eq!(actual.out, "╭────┬────────────────╮│ 0 │ basic ││ 1 │ compact ││ 2 │ compact_double ││ 3 │ default ││ 4 │ heavy ││ 5 │ light ││ 6 │ none ││ 7 │ reinforced ││ 8 │ rounded ││ 9 │ thin ││ 10 │ with_love ││ 11 │ psql ││ 12 │ markdown ││ 13 │ dots ││ 14 │ restructured ││ 15 │ ascii_rounded ││ 16 │ basic_compact │╰────┴────────────────╯"); } + +#[test] +fn table_kv_header_on_separator_trim_algorithm() { + let actual = nu!("$env.config.table.header_on_separator = true; {key1: '111111111111111111111111111111111111111111111111111111111111'} | table --width=60 --theme basic"); + assert_eq!(actual.out, "+------+---------------------------------------------------+| key1 | 1111111111111111111111111111111111111111111111111 || | 11111111111 |+------+---------------------------------------------------+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e1b1269874..e39284c2ea 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -355,11 +355,14 @@ impl TableOption, ColoredConfig> for let total_width = get_total_width2(&self.width, cfg); if total_width > self.max { + let has_header = self.cfg.with_header && rec.count_rows() > 1; + let trim_as_head = has_header && self.cfg.header_on_border; + TableTrim { max: self.max, strategy: self.cfg.trim, width: self.width, - do_as_head: self.cfg.header_on_border, + trim_as_head, } .change(rec, cfg, dim); } else if self.cfg.expand && self.max > total_width { @@ -375,7 +378,7 @@ struct TableTrim { width: Vec, strategy: TrimStrategy, max: usize, - do_as_head: bool, + trim_as_head: bool, } impl TableOption, ColoredConfig> for TableTrim { @@ -387,7 +390,7 @@ impl TableOption, ColoredConfig> for ) { // we already must have been estimated that it's safe to do. // and all dims will be suffitient - if self.do_as_head { + if self.trim_as_head { if recs.is_empty() { return; } @@ -544,7 +547,10 @@ fn maybe_truncate_columns( const TERMWIDTH_THRESHOLD: usize = 120; let preserve_content = termwidth > TERMWIDTH_THRESHOLD; - let truncate = if cfg.header_on_border { + let has_header = cfg.with_header && data.count_rows() > 1; + let is_header_on_border = has_header && cfg.header_on_border; + + let truncate = if is_header_on_border { truncate_columns_by_head } else if preserve_content { truncate_columns_by_columns From ff27d6a18eaa0d98db792abe048b1c5052e6c6dd Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:17:20 -0700 Subject: [PATCH 21/63] Implemented a command to expose polar's pivot functionality (#13282) # Description Implementing pivot support The example below is a port of the [python API example](https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.pivot.html) Screenshot 2024-07-01 at 14 29 27 # User-Facing Changes * Introduction of the `polars pivot` command --- crates/nu_plugin_polars/Cargo.toml | 2 +- .../src/dataframe/eager/mod.rs | 2 + .../src/dataframe/eager/pivot.rs | 265 ++++++++++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 crates/nu_plugin_polars/src/dataframe/eager/pivot.rs diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index 9589c89525..f9b4295cb9 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -33,7 +33,7 @@ serde = { version = "1.0", features = ["derive"] } sqlparser = { version = "0.47"} polars-io = { version = "0.41", features = ["avro"]} polars-arrow = { version = "0.41"} -polars-ops = { version = "0.41"} +polars-ops = { version = "0.41", features = ["pivot"]} polars-plan = { version = "0.41", features = ["regex"]} polars-utils = { version = "0.41"} typetag = "0.2" diff --git a/crates/nu_plugin_polars/src/dataframe/eager/mod.rs b/crates/nu_plugin_polars/src/dataframe/eager/mod.rs index 509c2b6dc4..6aa37d1ed1 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/mod.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/mod.rs @@ -10,6 +10,7 @@ mod first; mod get; mod last; mod open; +mod pivot; mod query_df; mod rename; mod sample; @@ -76,6 +77,7 @@ pub(crate) fn eager_commands() -> Vec &str { + "polars pivot" + } + + fn usage(&self) -> &str { + "Pivot a DataFrame from wide to long format." + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required_named( + "on", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names for pivoting", + Some('o'), + ) + .required_named( + "index", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names for indexes", + Some('i'), + ) + .required_named( + "values", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column names used as value columns", + Some('v'), + ) + .named( + "aggregate", + SyntaxShape::String, + "Aggregation to apply when pivoting. The following are supported: first, sum, min, max, mean, median, count, last", + Some('a'), + ) + .switch( + "sort", + "Sort columns", + Some('s'), + ) + .switch( + "streamable", + "Whether or not to use the polars streaming engine. Only valid for lazy dataframes", + Some('t'), + ) + .input_output_type( + Type::Custom("dataframe".into()), + Type::Custom("dataframe".into()), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[[name subject test_1 test_2]; [Cady maths 98 100] [Cady physics 99 100] [Karen maths 61 60] [Karen physics 58 60]] | polars into-df | polars pivot --on [subject] --index [name] --values [test_1]", + description: "Perform a pivot in order to show individuals test score by subject", + result: Some( + NuDataFrame::try_from_columns( + vec![ + Column::new( + "name".to_string(), + vec![Value::string("Cady", Span::test_data()), Value::string("Karen", Span::test_data())], + ), + Column::new( + "maths".to_string(), + vec![Value::int(98, Span::test_data()), Value::int(61, Span::test_data())], + ), + Column::new( + "physics".to_string(), + vec![Value::int(99, Span::test_data()), Value::int(58, Span::test_data())], + ), + ], + None, + ) + .expect("simple df for test should not fail") + .into_value(Span::unknown()) + ) + } + ] + } + + fn run( + &self, + plugin: &Self::Plugin, + engine: &EngineInterface, + call: &EvaluatedCall, + input: PipelineData, + ) -> Result { + match PolarsPluginObject::try_from_pipeline(plugin, input, call.head)? { + PolarsPluginObject::NuDataFrame(df) => command_eager(plugin, engine, call, df), + PolarsPluginObject::NuLazyFrame(lazy) => { + command_eager(plugin, engine, call, lazy.collect(call.head)?) + } + _ => Err(ShellError::GenericError { + error: "Must be a dataframe or lazy dataframe".into(), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + }), + } + .map_err(LabeledError::from) + } +} + +fn command_eager( + plugin: &PolarsPlugin, + engine: &EngineInterface, + call: &EvaluatedCall, + df: NuDataFrame, +) -> Result { + let on_col: Vec = call.get_flag("on")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let val_col: Vec = call.get_flag("values")?.expect("required value"); + + let (on_col_string, id_col_span) = convert_columns_string(on_col, call.head)?; + let (index_col_string, index_col_span) = convert_columns_string(index_col, call.head)?; + let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?; + + check_column_datatypes(df.as_ref(), &on_col_string, id_col_span)?; + check_column_datatypes(df.as_ref(), &index_col_string, index_col_span)?; + check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?; + + let aggregate: Option = call + .get_flag::("aggregate")? + .map(pivot_agg_for_str) + .transpose()?; + + let sort = call.has_flag("sort")?; + + let polars_df = df.to_polars(); + // todo add other args + let pivoted = pivot( + &polars_df, + &on_col_string, + Some(&index_col_string), + Some(&val_col_string), + sort, + aggregate, + None, + ) + .map_err(|e| ShellError::GenericError { + error: format!("Pivot error: {e}"), + msg: "".into(), + span: Some(call.head), + help: None, + inner: vec![], + })?; + + let res = NuDataFrame::new(false, pivoted); + res.to_pipeline_data(plugin, engine, call.head) +} + +fn check_column_datatypes>( + df: &polars::prelude::DataFrame, + cols: &[T], + col_span: Span, +) -> Result<(), ShellError> { + if cols.is_empty() { + return Err(ShellError::GenericError { + error: "Merge error".into(), + msg: "empty column list".into(), + span: Some(col_span), + help: None, + inner: vec![], + }); + } + + // Checking if they are same type + if cols.len() > 1 { + for w in cols.windows(2) { + let l_series = df + .column(w[0].as_ref()) + .map_err(|e| ShellError::GenericError { + error: "Error selecting columns".into(), + msg: e.to_string(), + span: Some(col_span), + help: None, + inner: vec![], + })?; + + let r_series = df + .column(w[1].as_ref()) + .map_err(|e| ShellError::GenericError { + error: "Error selecting columns".into(), + msg: e.to_string(), + span: Some(col_span), + help: None, + inner: vec![], + })?; + + if l_series.dtype() != r_series.dtype() { + return Err(ShellError::GenericError { + error: "Merge error".into(), + msg: "found different column types in list".into(), + span: Some(col_span), + help: Some(format!( + "datatypes {} and {} are incompatible", + l_series.dtype(), + r_series.dtype() + )), + inner: vec![], + }); + } + } + } + + Ok(()) +} + +fn pivot_agg_for_str(agg: impl AsRef) -> Result { + match agg.as_ref() { + "first" => Ok(PivotAgg::First), + "sum" => Ok(PivotAgg::Sum), + "min" => Ok(PivotAgg::Min), + "max" => Ok(PivotAgg::Max), + "mean" => Ok(PivotAgg::Mean), + "median" => Ok(PivotAgg::Median), + "count" => Ok(PivotAgg::Count), + "last" => Ok(PivotAgg::Last), + s => Err(ShellError::GenericError { + error: format!("{s} is not a valid aggregation"), + msg: "".into(), + span: None, + help: Some( + "Use one of the following: first, sum, min, max, mean, median, count, last".into(), + ), + inner: vec![], + }), + } +} + +#[cfg(test)] +mod test { + use crate::test::test_polars_plugin_command; + + use super::*; + + #[test] + fn test_examples() -> Result<(), ShellError> { + test_polars_plugin_command(&PivotDF) + } +} From ad8054ebed7db1bbfbfcf15a85b00f96fc0f8325 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:52:57 -0500 Subject: [PATCH 22/63] update table comments --- crates/nu-table/src/table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index e39284c2ea..d2cf8f4d63 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -966,10 +966,10 @@ impl TableOption, ColoredConfig> for .collect(); } None => { - // we don't have widths cached; which means that NO widtds adjustmens were done + // we don't have widths cached; which means that NO width adjustments were done // which means we are OK to leave columns as they are. // - // but we are actually always got to have widths at this point + // but we actually always have to have widths at this point } }; From 0178295363fb00a455a9ad403c2aca20093e4ee5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:53:46 -0500 Subject: [PATCH 23/63] Bump crate-ci/typos from 1.22.9 to 1.23.1 (#13328) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.9 to 1.23.1.
Release notes

Sourced from crate-ci/typos's releases.

v1.23.1

[1.23.1] - 2024-07-05

Fixes

v1.23.0

[1.23.0] - 2024-07-05

Fixes

  • Updated the dictionary with the June 2024 changes
Changelog

Sourced from crate-ci/typos's changelog.

[1.23.1] - 2024-07-05

Fixes

[1.23.0] - 2024-07-05

Fixes

  • Updated the dictionary with the June 2024 changes
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.22.9&new-version=1.23.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) Dependabot will merge this PR once CI passes on it, as requested by @fdncred. [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 95fc51b970..0f1e4cb5d9 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.22.9 + uses: crate-ci/typos@v1.23.1 From b68c7cf3fac9b5510c600e08ff70b6af0ec6adb3 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:36:38 -0700 Subject: [PATCH 24/63] Make `polars unpivot` consistent with `polars pivot` (#13335) # Description Makes `polars unpivot` use the same arguments as `polars pivot` and makes it consistent with the polars' rust api. Additionally, support for the polar's streaming engine has been exposed on eager dataframes. Previously, it would only work with lazy dataframes. # User-Facing Changes * `polars unpivot` argument `--columns`|`-c` has been renamed to `--index`|`-i` * `polars unpivot` argument `--values`|`-v` has been renamed to `--on`|`-o` * `polars unpivot` short argument for `--streamable` is now `-t` to make it consistent with `polars pivot`. It was made `-t` for `polars pivot` because `-s` is short for `--short` --- .../src/dataframe/eager/unpivot.rs | 74 ++++++++----------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs b/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs index c535b54c0e..dafdc65ab4 100644 --- a/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs +++ b/crates/nu_plugin_polars/src/dataframe/eager/unpivot.rs @@ -30,16 +30,16 @@ impl PluginCommand for UnpivotDF { fn signature(&self) -> Signature { Signature::build(self.name()) .required_named( - "columns", + "index", SyntaxShape::Table(vec![]), "column names for unpivoting", - Some('c'), + Some('i'), ) .required_named( - "values", + "on", SyntaxShape::Table(vec![]), "column names used as value columns", - Some('v'), + Some('o'), ) .named( "variable-name", @@ -60,7 +60,7 @@ impl PluginCommand for UnpivotDF { .switch( "streamable", "Whether or not to use the polars streaming engine. Only valid for lazy dataframes", - Some('s'), + Some('t'), ) .category(Category::Custom("dataframe".into())) } @@ -70,7 +70,7 @@ impl PluginCommand for UnpivotDF { Example { description: "unpivot on an eager dataframe", example: - "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-df | polars unpivot -c [b c] -v [a d]", + "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-df | polars unpivot -i [b c] -o [a d]", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -125,7 +125,7 @@ impl PluginCommand for UnpivotDF { Example { description: "unpivot on a lazy dataframe", example: - "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-lazy | polars unpivot -c [b c] -v [a d] | polars collect", + "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | polars into-lazy | polars unpivot -i [b c] -o [a d] | polars collect", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -208,21 +208,31 @@ fn command_eager( call: &EvaluatedCall, df: NuDataFrame, ) -> Result { - let id_col: Vec = call.get_flag("columns")?.expect("required value"); - let val_col: Vec = call.get_flag("values")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let on_col: Vec = call.get_flag("on")?.expect("required value"); let value_name: Option> = call.get_flag("value-name")?; let variable_name: Option> = call.get_flag("variable-name")?; - let (id_col_string, id_col_span) = convert_columns_string(id_col, call.head)?; - let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?; + let (index_col_string, index_col_span) = convert_columns_string(index_col, call.head)?; + let (on_col_string, on_col_span) = convert_columns_string(on_col, call.head)?; - check_column_datatypes(df.as_ref(), &id_col_string, id_col_span)?; - check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?; + check_column_datatypes(df.as_ref(), &index_col_string, index_col_span)?; + check_column_datatypes(df.as_ref(), &on_col_string, on_col_span)?; - let mut res = df + let streamable = call.has_flag("streamable")?; + + let args = UnpivotArgs { + on: on_col_string.iter().map(Into::into).collect(), + index: index_col_string.iter().map(Into::into).collect(), + variable_name: variable_name.map(|s| s.item.into()), + value_name: value_name.map(|s| s.item.into()), + streamable, + }; + + let res = df .as_ref() - .unpivot(&val_col_string, &id_col_string) + .unpivot2(args) .map_err(|e| ShellError::GenericError { error: "Error calculating unpivot".into(), msg: e.to_string(), @@ -231,28 +241,6 @@ fn command_eager( inner: vec![], })?; - if let Some(name) = &variable_name { - res.rename("variable", &name.item) - .map_err(|e| ShellError::GenericError { - error: "Error renaming column".into(), - msg: e.to_string(), - span: Some(name.span), - help: None, - inner: vec![], - })?; - } - - if let Some(name) = &value_name { - res.rename("value", &name.item) - .map_err(|e| ShellError::GenericError { - error: "Error renaming column".into(), - msg: e.to_string(), - span: Some(name.span), - help: None, - inner: vec![], - })?; - } - let res = NuDataFrame::new(false, res); res.to_pipeline_data(plugin, engine, call.head) } @@ -263,11 +251,11 @@ fn command_lazy( call: &EvaluatedCall, df: NuLazyFrame, ) -> Result { - let id_col: Vec = call.get_flag("columns")?.expect("required value"); - let val_col: Vec = call.get_flag("values")?.expect("required value"); + let index_col: Vec = call.get_flag("index")?.expect("required value"); + let on_col: Vec = call.get_flag("on")?.expect("required value"); - let (id_col_string, _id_col_span) = convert_columns_string(id_col, call.head)?; - let (val_col_string, _val_col_span) = convert_columns_string(val_col, call.head)?; + let (index_col_string, _index_col_span) = convert_columns_string(index_col, call.head)?; + let (on_col_string, _on_col_span) = convert_columns_string(on_col, call.head)?; let value_name: Option = call.get_flag("value-name")?; let variable_name: Option = call.get_flag("variable-name")?; @@ -275,8 +263,8 @@ fn command_lazy( let streamable = call.has_flag("streamable")?; let unpivot_args = UnpivotArgs { - on: val_col_string.iter().map(Into::into).collect(), - index: id_col_string.iter().map(Into::into).collect(), + on: on_col_string.iter().map(Into::into).collect(), + index: index_col_string.iter().map(Into::into).collect(), value_name: value_name.map(Into::into), variable_name: variable_name.map(Into::into), streamable, From 616e9faaf13ccd90fb898008b5921c235893357e Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Wed, 10 Jul 2024 19:05:24 -0400 Subject: [PATCH 25/63] Fix main binary being rebuilt when nothing has changed (#13337) # Description The build script is currently re-run on each `cargo build` even when it has not changed. The `rerun-if-changed` line points to `/build.rs`, but `build.rs` is actually located at `/scripts/build.rs`. This updates that path. # User-Facing Changes N/A # Tests + Formatting N/A --- scripts/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.rs b/scripts/build.rs index beabba80dc..e366b4998c 100644 --- a/scripts/build.rs +++ b/scripts/build.rs @@ -14,5 +14,5 @@ fn main() { // Tango uses dynamic linking, to allow us to dynamically change between two bench suit at runtime. // This is currently not supported on non nightly rust, on windows. println!("cargo:rustc-link-arg-benches=-rdynamic"); - println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=scripts/build.rs"); } From ea8c4e3af28d778d98fd570800c74a3e19cddf29 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 16:16:22 -0700 Subject: [PATCH 26/63] Make pipe redirections consistent, add `err>|` etc. forms (#13334) # Description Fixes the lexer to recognize `out>|`, `err>|`, `out+err>|`, etc. Previously only the short-style forms were recognized, which was inconsistent with normal file redirections. I also integrated it all more into the normal lex path by checking `|` in a special way, which should be more performant and consistent, and cleans up the code a bunch. Closes #13331. # User-Facing Changes - Adds `out>|` (error), `err>|`, `out+err>|`, `err+out>|` as recognized forms of the pipe redirection. # Tests + Formatting All passing. Added tests for the new forms. # After Submitting - [ ] release notes --- crates/nu-parser/src/lex.rs | 103 ++++++---------------- tests/shell/pipeline/commands/external.rs | 23 +++-- 2 files changed, 44 insertions(+), 82 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 9b52467ef4..3290a774f4 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -238,6 +238,10 @@ pub fn lex_item( Some(e), ); } + } else if c == b'|' && is_redirection(&input[token_start..*curr_offset]) { + // matches err>| etc. + *curr_offset += 1; + break; } else if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } @@ -301,6 +305,16 @@ pub fn lex_item( contents: TokenContents::OutGreaterGreaterThan, span, }, + b"out>|" | b"o>|" => { + err = Some(ParseError::Expected( + "`|`. Redirecting stdout to a pipe is the same as normal piping.", + span, + )); + Token { + contents: TokenContents::Item, + span, + } + } b"err>" | b"e>" => Token { contents: TokenContents::ErrGreaterThan, span, @@ -309,6 +323,10 @@ pub fn lex_item( contents: TokenContents::ErrGreaterGreaterThan, span, }, + b"err>|" | b"e>|" => Token { + contents: TokenContents::ErrGreaterPipe, + span, + }, b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token { contents: TokenContents::OutErrGreaterThan, span, @@ -317,6 +335,10 @@ pub fn lex_item( contents: TokenContents::OutErrGreaterGreaterThan, span, }, + b"out+err>|" | b"err+out>|" | b"o+e>|" | b"e+o>|" => Token { + contents: TokenContents::OutErrGreaterPipe, + span, + }, b"&&" => { err = Some(ParseError::ShellAndAnd(span)); Token { @@ -580,17 +602,6 @@ fn lex_internal( // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { - let (token, err) = try_lex_special_piped_item(input, &mut curr_offset, span_offset); - if error.is_none() { - error = err; - } - if let Some(token) = token { - output.push(token); - is_complete = false; - continue; - } - - // Otherwise, try to consume an unclassified token. let (token, err) = lex_item( input, &mut curr_offset, @@ -609,68 +620,10 @@ fn lex_internal( (output, error) } -/// trying to lex for the following item: -/// e>|, e+o>|, o+e>| -/// -/// It returns Some(token) if we find the item, or else return None. -fn try_lex_special_piped_item( - input: &[u8], - curr_offset: &mut usize, - span_offset: usize, -) -> (Option, Option) { - let c = input[*curr_offset]; - let e_pipe_len = 3; - let eo_pipe_len = 5; - let o_pipe_len = 3; - let offset = *curr_offset; - if c == b'e' { - // expect `e>|` - if (offset + e_pipe_len <= input.len()) && (&input[offset..offset + e_pipe_len] == b"e>|") { - *curr_offset += e_pipe_len; - return ( - Some(Token::new( - TokenContents::ErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + e_pipe_len), - )), - None, - ); - } - if (offset + eo_pipe_len <= input.len()) - && (&input[offset..offset + eo_pipe_len] == b"e+o>|") - { - *curr_offset += eo_pipe_len; - return ( - Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )), - None, - ); - } - } else if c == b'o' { - // indicates an error if user happened to type `o>|` - if offset + o_pipe_len <= input.len() && (&input[offset..offset + o_pipe_len] == b"o>|") { - return ( - None, - Some(ParseError::Expected( - "`|`. Redirecting stdout to a pipe is the same as normal piping.", - Span::new(span_offset + offset, span_offset + offset + o_pipe_len), - )), - ); - } - // it can be the following case: `o+e>|` - if (offset + eo_pipe_len <= input.len()) - && (&input[offset..offset + eo_pipe_len] == b"o+e>|") - { - *curr_offset += eo_pipe_len; - return ( - Some(Token::new( - TokenContents::OutErrGreaterPipe, - Span::new(span_offset + offset, span_offset + offset + eo_pipe_len), - )), - None, - ); - } - } - (None, None) +/// True if this the start of a redirection. Does not match `>>` or `>|` forms. +fn is_redirection(token: &[u8]) -> bool { + matches!( + token, + b"o>" | b"out>" | b"e>" | b"err>" | b"o+e>" | b"e+o>" | b"out+err>" | b"err+out>" + ) } diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index d138b65766..a6efe2b6c9 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -149,17 +149,26 @@ fn command_substitution_wont_output_extra_newline() { assert_eq!(actual.out, "bar"); } -#[test] -fn basic_err_pipe_works() { - let actual = - nu!(r#"with-env { FOO: "bar" } { nu --testbin echo_env_stderr FOO e>| str length }"#); +#[rstest::rstest] +#[case("err>|")] +#[case("e>|")] +fn basic_err_pipe_works(#[case] redirection: &str) { + let actual = nu!( + r#"with-env { FOO: "bar" } { nu --testbin echo_env_stderr FOO {redirection} str length }"# + .replace("{redirection}", redirection) + ); assert_eq!(actual.out, "3"); } -#[test] -fn basic_outerr_pipe_works() { +#[rstest::rstest] +#[case("out+err>|")] +#[case("err+out>|")] +#[case("o+e>|")] +#[case("e+o>|")] +fn basic_outerr_pipe_works(#[case] redirection: &str) { let actual = nu!( - r#"with-env { FOO: "bar" } { nu --testbin echo_env_mixed out-err FOO FOO o+e>| str length }"# + r#"with-env { FOO: "bar" } { nu --testbin echo_env_mixed out-err FOO FOO {redirection} str length }"# + .replace("{redirection}", redirection) ); assert_eq!(actual.out, "7"); } From d7392f1f3b9b8ffb58bc5f6f00d4de02b149a23d Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 17:33:59 -0700 Subject: [PATCH 27/63] Internal representation (IR) compiler and evaluator (#13330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds an internal representation language to Nushell, offering an alternative evaluator based on simple instructions, stream-containing registers, and indexed control flow. The number of registers required is determined statically at compile-time, and the fixed size required is allocated upon entering the block. Each instruction is associated with a span, which makes going backwards from IR instructions to source code very easy. Motivations for IR: 1. **Performance.** By simplifying the evaluation path and making it more cache-friendly and branch predictor-friendly, code that does a lot of computation in Nushell itself can be sped up a decent bit. Because the IR is fairly easy to reason about, we can also implement optimization passes in the future to eliminate and simplify code. 2. **Correctness.** The instructions mostly have very simple and easily-specified behavior, so hopefully engine changes are a little bit easier to reason about, and they can be specified in a more formal way at some point. I have made an effort to document each of the instructions in the docs for the enum itself in a reasonably specific way. Some of the errors that would have happened during evaluation before are now moved to the compilation step instead, because they don't make sense to check during evaluation. 3. **As an intermediate target.** This is a good step for us to bring the [`new-nu-parser`](https://github.com/nushell/new-nu-parser) in at some point, as code generated from new AST can be directly compared to code generated from old AST. If the IR code is functionally equivalent, it will behave the exact same way. 4. **Debugging.** With a little bit more work, we can probably give control over advancing the virtual machine that `IrBlock`s run on to some sort of external driver, making things like breakpoints and single stepping possible. Tools like `view ir` and [`explore ir`](https://github.com/devyn/nu_plugin_explore_ir) make it easier than before to see what exactly is going on with your Nushell code. The goal is to eventually replace the AST evaluator entirely, once we're sure it's working just as well. You can help dogfood this by running Nushell with `$env.NU_USE_IR` set to some value. The environment variable is checked when Nushell starts, so config runs with IR, or it can also be set on a line at the REPL to change it dynamically. It is also checked when running `do` in case within a script you want to just run a specific piece of code with or without IR. # Example ```nushell view ir { |data| mut sum = 0 for n in $data { $sum += $n } $sum } ``` ```gas # 3 registers, 19 instructions, 0 bytes of data 0: load-literal %0, int(0) 1: store-variable var 904, %0 # let 2: drain %0 3: drop %0 4: load-variable %1, var 903 5: iterate %0, %1, end 15 # for, label(1), from(14:) 6: store-variable var 905, %0 7: load-variable %0, var 904 8: load-variable %2, var 905 9: binary-op %0, Math(Plus), %2 10: span %0 11: store-variable var 904, %0 12: load-literal %0, nothing 13: drain %0 14: jump 5 15: drop %0 # label(0), from(5:) 16: drain %0 17: load-variable %0, var 904 18: return %0 ``` # Benchmarks All benchmarks run on a base model Mac Mini M1. ## Iterative Fibonacci sequence This is about as best case as possible, making use of the much faster control flow. Most code will not experience a speed improvement nearly this large. ```nushell def fib [n: int] { mut a = 0 mut b = 1 for _ in 2..=$n { let c = $a + $b $a = $b $b = $c } $b } use std bench bench { 0..50 | each { |n| fib $n } } ``` IR disabled: ``` ╭───────┬─────────────────╮ │ mean │ 1ms 924µs 665ns │ │ min │ 1ms 700µs 83ns │ │ max │ 3ms 450µs 125ns │ │ std │ 395µs 759ns │ │ times │ [list 50 items] │ ╰───────┴─────────────────╯ ``` IR enabled: ``` ╭───────┬─────────────────╮ │ mean │ 452µs 820ns │ │ min │ 427µs 417ns │ │ max │ 540µs 167ns │ │ std │ 17µs 158ns │ │ times │ [list 50 items] │ ╰───────┴─────────────────╯ ``` ![explore ir view](https://github.com/nushell/nushell/assets/10729/d7bccc03-5222-461c-9200-0dce71b83b83) ## [gradient_benchmark_no_check.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/gradient_benchmark_no_check.nu) IR disabled: ``` ╭───┬──────────────────╮ │ 0 │ 27ms 929µs 958ns │ │ 1 │ 21ms 153µs 459ns │ │ 2 │ 18ms 639µs 666ns │ │ 3 │ 19ms 554µs 583ns │ │ 4 │ 13ms 383µs 375ns │ │ 5 │ 11ms 328µs 208ns │ │ 6 │ 5ms 659µs 542ns │ ╰───┴──────────────────╯ ``` IR enabled: ``` ╭───┬──────────────────╮ │ 0 │ 22ms 662µs │ │ 1 │ 17ms 221µs 792ns │ │ 2 │ 14ms 786µs 708ns │ │ 3 │ 13ms 876µs 834ns │ │ 4 │ 13ms 52µs 875ns │ │ 5 │ 11ms 269µs 666ns │ │ 6 │ 6ms 942µs 500ns │ ╰───┴──────────────────╯ ``` ## [random-bytes.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/random-bytes.nu) I got pretty random results out of this benchmark so I decided not to include it. Not clear why. # User-Facing Changes - IR compilation errors may appear even if the user isn't evaluating with IR. - IR evaluation can be enabled by setting the `NU_USE_IR` environment variable to any value. - New command `view ir` pretty-prints the IR for a block, and `view ir --json` can be piped into an external tool like [`explore ir`](https://github.com/devyn/nu_plugin_explore_ir). # Tests + Formatting All tests are passing with `NU_USE_IR=1`, and I've added some more eval tests to compare the results for some very core operations. I will probably want to add some more so we don't have to always check `NU_USE_IR=1 toolkit test --workspace` on a regular basis. # After Submitting - [ ] release notes - [ ] further documentation of instructions? - [ ] post-release: publish `nu_plugin_explore_ir` --- Cargo.lock | 3 + Cargo.toml | 3 +- benches/benchmarks.rs | 4 + .../nu-cli/src/commands/keybindings_list.rs | 28 +- crates/nu-cli/src/eval_cmds.rs | 5 + crates/nu-cli/src/repl.rs | 3 + crates/nu-cli/src/util.rs | 5 + .../nu-cmd-lang/src/core_commands/const_.rs | 3 + crates/nu-cmd-lang/src/core_commands/do_.rs | 4 + crates/nu-cmd-lang/src/core_commands/for_.rs | 3 + crates/nu-cmd-lang/src/core_commands/if_.rs | 6 + crates/nu-cmd-lang/src/core_commands/let_.rs | 3 + crates/nu-cmd-lang/src/core_commands/loop_.rs | 3 + .../nu-cmd-lang/src/core_commands/match_.rs | 3 + crates/nu-cmd-lang/src/core_commands/mut_.rs | 3 + .../src/core_commands/overlay/use_.rs | 8 +- crates/nu-cmd-lang/src/core_commands/try_.rs | 3 + crates/nu-cmd-lang/src/core_commands/use_.rs | 7 +- .../nu-cmd-lang/src/core_commands/while_.rs | 3 + crates/nu-command/src/bytes/build_.rs | 6 +- crates/nu-command/src/debug/explain.rs | 4 +- crates/nu-command/src/debug/metadata.rs | 6 +- crates/nu-command/src/debug/mod.rs | 2 + crates/nu-command/src/debug/timeit.rs | 14 +- crates/nu-command/src/debug/view_ir.rs | 83 + crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/env/export_env.rs | 6 +- crates/nu-command/src/env/source_env.rs | 2 +- crates/nu-command/src/filesystem/du.rs | 2 +- crates/nu-command/src/filesystem/ls.rs | 2 +- crates/nu-command/src/filesystem/open.rs | 7 +- crates/nu-command/src/filesystem/save.rs | 64 +- crates/nu-command/src/filesystem/util.rs | 39 +- crates/nu-command/src/filters/transpose.rs | 10 +- crates/nu-command/src/filters/utils.rs | 3 +- crates/nu-command/src/generators/cal.rs | 11 +- crates/nu-command/src/math/utils.rs | 2 +- crates/nu-command/src/platform/ansi/ansi_.rs | 27 +- crates/nu-command/src/platform/is_terminal.rs | 2 +- crates/nu-command/src/platform/kill.rs | 17 +- .../src/strings/encode_decode/base64.rs | 4 +- crates/nu-command/src/strings/mod.rs | 3 +- crates/nu-command/src/system/nu_check.rs | 2 +- crates/nu-command/src/system/run_external.rs | 68 +- crates/nu-command/src/system/uname.rs | 9 +- crates/nu-command/src/viewers/table.rs | 4 +- crates/nu-engine/Cargo.toml | 3 +- crates/nu-engine/src/call_ext.rs | 219 ++- crates/nu-engine/src/command_prelude.rs | 4 +- crates/nu-engine/src/compile/builder.rs | 575 +++++++ crates/nu-engine/src/compile/call.rs | 270 +++ crates/nu-engine/src/compile/expression.rs | 535 ++++++ crates/nu-engine/src/compile/keyword.rs | 902 ++++++++++ crates/nu-engine/src/compile/mod.rs | 204 +++ crates/nu-engine/src/compile/operator.rs | 378 +++++ crates/nu-engine/src/compile/redirect.rs | 157 ++ crates/nu-engine/src/documentation.rs | 9 +- crates/nu-engine/src/env.rs | 21 +- crates/nu-engine/src/eval.rs | 16 +- crates/nu-engine/src/eval_helpers.rs | 16 +- crates/nu-engine/src/eval_ir.rs | 1462 +++++++++++++++++ crates/nu-engine/src/lib.rs | 4 + crates/nu-parser/src/known_external.rs | 204 ++- crates/nu-parser/src/parse_patterns.rs | 2 +- crates/nu-parser/src/parser.rs | 26 +- crates/nu-parser/tests/test_parser.rs | 9 +- crates/nu-plugin-engine/src/context.rs | 11 +- .../nu-plugin-protocol/src/evaluated_call.rs | 40 +- crates/nu-protocol/Cargo.toml | 3 +- crates/nu-protocol/src/alias.rs | 4 +- crates/nu-protocol/src/ast/block.rs | 11 +- crates/nu-protocol/src/ast/expr.rs | 14 +- crates/nu-protocol/src/ast/match_pattern.rs | 6 +- crates/nu-protocol/src/ast/pipeline.rs | 14 +- crates/nu-protocol/src/engine/argument.rs | 124 ++ crates/nu-protocol/src/engine/call.rs | 223 +++ crates/nu-protocol/src/engine/command.rs | 8 +- .../nu-protocol/src/engine/error_handler.rs | 55 + crates/nu-protocol/src/engine/mod.rs | 6 + crates/nu-protocol/src/engine/stack.rs | 22 +- .../src/engine/state_working_set.rs | 12 +- crates/nu-protocol/src/errors/cli_error.rs | 4 + .../nu-protocol/src/errors/compile_error.rs | 238 +++ crates/nu-protocol/src/errors/mod.rs | 2 + crates/nu-protocol/src/errors/shell_error.rs | 17 + crates/nu-protocol/src/eval_const.rs | 2 +- crates/nu-protocol/src/id.rs | 14 + crates/nu-protocol/src/ir/call.rs | 351 ++++ crates/nu-protocol/src/ir/display.rs | 452 +++++ crates/nu-protocol/src/ir/mod.rs | 419 +++++ crates/nu-protocol/src/lib.rs | 1 + .../nu-protocol/src/pipeline/byte_stream.rs | 6 + .../nu-protocol/src/pipeline/list_stream.rs | 11 + .../nu-protocol/src/pipeline/pipeline_data.rs | 29 +- crates/nu-protocol/src/signature.rs | 3 +- crates/nu-protocol/src/span.rs | 16 + crates/nu-test-support/src/macros.rs | 11 + src/run.rs | 12 + tests/eval/mod.rs | 457 +++++- 99 files changed, 7768 insertions(+), 346 deletions(-) create mode 100644 crates/nu-command/src/debug/view_ir.rs create mode 100644 crates/nu-engine/src/compile/builder.rs create mode 100644 crates/nu-engine/src/compile/call.rs create mode 100644 crates/nu-engine/src/compile/expression.rs create mode 100644 crates/nu-engine/src/compile/keyword.rs create mode 100644 crates/nu-engine/src/compile/mod.rs create mode 100644 crates/nu-engine/src/compile/operator.rs create mode 100644 crates/nu-engine/src/compile/redirect.rs create mode 100644 crates/nu-engine/src/eval_ir.rs create mode 100644 crates/nu-protocol/src/engine/argument.rs create mode 100644 crates/nu-protocol/src/engine/call.rs create mode 100644 crates/nu-protocol/src/engine/error_handler.rs create mode 100644 crates/nu-protocol/src/errors/compile_error.rs create mode 100644 crates/nu-protocol/src/ir/call.rs create mode 100644 crates/nu-protocol/src/ir/display.rs create mode 100644 crates/nu-protocol/src/ir/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d4f759aed0..7243a801fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2900,6 +2900,7 @@ dependencies = [ "openssl", "pretty_assertions", "reedline", + "regex", "rstest", "serde_json", "serial_test", @@ -3151,6 +3152,7 @@ dependencies = [ name = "nu-engine" version = "0.95.1" dependencies = [ + "log", "nu-glob", "nu-path", "nu-protocol", @@ -3339,6 +3341,7 @@ dependencies = [ "convert_case", "fancy-regex", "indexmap", + "log", "lru", "miette", "nix", diff --git a/Cargo.toml b/Cargo.toml index 5701fe3958..eb8a92c630 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -232,6 +232,7 @@ assert_cmd = "2.0" dirs-next = { workspace = true } tango-bench = "0.5" pretty_assertions = { workspace = true } +regex = { workspace = true } rstest = { workspace = true, default-features = false } serial_test = "3.1" tempfile = { workspace = true } @@ -310,4 +311,4 @@ reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # Run individual benchmarks like `cargo bench -- ` e.g. `cargo bench -- parse` [[bench]] name = "benchmarks" -harness = false \ No newline at end of file +harness = false diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index ea296f0d06..4efb666507 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -45,6 +45,10 @@ fn setup_stack_and_engine_from_command(command: &str) -> (Stack, EngineState) { }; let mut stack = Stack::new(); + + // Support running benchmarks with IR mode + stack.use_ir = std::env::var_os("NU_USE_IR").is_some(); + evaluate_commands( &commands, &mut engine, diff --git a/crates/nu-cli/src/commands/keybindings_list.rs b/crates/nu-cli/src/commands/keybindings_list.rs index f4450c0c23..350df7b820 100644 --- a/crates/nu-cli/src/commands/keybindings_list.rs +++ b/crates/nu-cli/src/commands/keybindings_list.rs @@ -49,22 +49,24 @@ impl Command for KeybindingsList { fn run( &self, - _engine_state: &EngineState, - _stack: &mut Stack, + engine_state: &EngineState, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { - let records = if call.named_len() == 0 { - let all_options = ["modifiers", "keycodes", "edits", "modes", "events"]; - all_options - .iter() - .flat_map(|argument| get_records(argument, call.head)) - .collect() - } else { - call.named_iter() - .flat_map(|(argument, _, _)| get_records(argument.item.as_str(), call.head)) - .collect() - }; + let all_options = ["modifiers", "keycodes", "edits", "modes", "events"]; + + let presence = all_options + .iter() + .map(|option| call.has_flag(engine_state, stack, option)) + .collect::, ShellError>>()?; + + let records = all_options + .iter() + .zip(presence) + .filter(|(_, present)| *present) + .flat_map(|(option, _)| get_records(option, call.head)) + .collect(); Ok(Value::list(records, call.head).into_pipeline_data()) } diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 13141f6174..ad3a15304d 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -70,6 +70,11 @@ pub fn evaluate_commands( std::process::exit(1); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + (output, working_set.render()) }; diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 7099b70ba8..07272701f3 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -268,6 +268,9 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { if let Err(err) = engine_state.merge_env(&mut stack, cwd) { report_error_new(engine_state, &err); } + // Check whether $env.NU_USE_IR is set, so that the user can change it in the REPL + // Temporary while IR eval is optional + stack.use_ir = stack.has_env_var(engine_state, "NU_USE_IR"); perf!("merge env", start_time, use_color); start_time = std::time::Instant::now(); diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index d3cf73056f..bcee53c9b0 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -262,6 +262,11 @@ fn evaluate_source( return Ok(Some(1)); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + (output, working_set.render()) }; diff --git a/crates/nu-cmd-lang/src/core_commands/const_.rs b/crates/nu-cmd-lang/src/core_commands/const_.rs index f780c5ada9..5b3d03443a 100644 --- a/crates/nu-cmd-lang/src/core_commands/const_.rs +++ b/crates/nu-cmd-lang/src/core_commands/const_.rs @@ -46,6 +46,9 @@ impl Command for Const { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = if let Some(id) = call.positional_nth(0).and_then(|pos| pos.as_var()) { id } else { diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index adf13cc0bb..bf29a2159a 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -81,6 +81,10 @@ impl Command for Do { bind_args_to(&mut callee_stack, &block.signature, rest, head)?; let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); + + // Applies to all block evaluation once set true + callee_stack.use_ir = caller_stack.has_env_var(engine_state, "NU_USE_IR"); + let result = eval_block_with_early_return(engine_state, &mut callee_stack, block, input); if has_env { diff --git a/crates/nu-cmd-lang/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs index 1e90e5f06d..36df743e5f 100644 --- a/crates/nu-cmd-lang/src/core_commands/for_.rs +++ b/crates/nu-cmd-lang/src/core_commands/for_.rs @@ -48,6 +48,9 @@ impl Command for For { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let var_id = call .positional_nth(0) diff --git a/crates/nu-cmd-lang/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs index 738d901759..8667843770 100644 --- a/crates/nu-cmd-lang/src/core_commands/if_.rs +++ b/crates/nu-cmd-lang/src/core_commands/if_.rs @@ -60,6 +60,9 @@ impl Command for If { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let cond = call.positional_nth(0).expect("checked through parser"); let then_block = call .positional_nth(1) @@ -99,6 +102,9 @@ impl Command for If { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let cond = call.positional_nth(0).expect("checked through parser"); let then_block = call .positional_nth(1) diff --git a/crates/nu-cmd-lang/src/core_commands/let_.rs b/crates/nu-cmd-lang/src/core_commands/let_.rs index f2da628c31..46324ef39e 100644 --- a/crates/nu-cmd-lang/src/core_commands/let_.rs +++ b/crates/nu-cmd-lang/src/core_commands/let_.rs @@ -46,6 +46,9 @@ impl Command for Let { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs index 86e18389de..f495c8d3ae 100644 --- a/crates/nu-cmd-lang/src/core_commands/loop_.rs +++ b/crates/nu-cmd-lang/src/core_commands/loop_.rs @@ -37,6 +37,9 @@ impl Command for Loop { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let block_id = call .positional_nth(0) diff --git a/crates/nu-cmd-lang/src/core_commands/match_.rs b/crates/nu-cmd-lang/src/core_commands/match_.rs index d28a59cbad..c3a3d61216 100644 --- a/crates/nu-cmd-lang/src/core_commands/match_.rs +++ b/crates/nu-cmd-lang/src/core_commands/match_.rs @@ -43,6 +43,9 @@ impl Command for Match { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let value: Value = call.req(engine_state, stack, 0)?; let matches = call .positional_nth(1) diff --git a/crates/nu-cmd-lang/src/core_commands/mut_.rs b/crates/nu-cmd-lang/src/core_commands/mut_.rs index 5db3c929af..b729590027 100644 --- a/crates/nu-cmd-lang/src/core_commands/mut_.rs +++ b/crates/nu-cmd-lang/src/core_commands/mut_.rs @@ -46,6 +46,9 @@ impl Command for Mut { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let var_id = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs index e8b51fb59b..d6d3ae745a 100644 --- a/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs @@ -65,9 +65,9 @@ impl Command for OverlayUse { name_arg.item = trim_quotes_str(&name_arg.item).to_string(); let maybe_origin_module_id = - if let Some(overlay_expr) = call.get_parser_info("overlay_expr") { + if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") { if let Expr::Overlay(module_id) = &overlay_expr.expr { - module_id + *module_id } else { return Err(ShellError::NushellFailedSpanned { msg: "Not an overlay".to_string(), @@ -110,7 +110,7 @@ impl Command for OverlayUse { // a) adding a new overlay // b) refreshing an active overlay (the origin module changed) - let module = engine_state.get_module(*module_id); + let module = engine_state.get_module(module_id); // Evaluate the export-env block (if any) and keep its environment if let Some(block_id) = module.env_block { @@ -118,7 +118,7 @@ impl Command for OverlayUse { &name_arg.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let block = engine_state.get_block(block_id); diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index f99825b88d..2309897a1b 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -47,6 +47,9 @@ impl Command for Try { call: &Call, input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let try_block = call .positional_nth(0) .expect("checked through parser") diff --git a/crates/nu-cmd-lang/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs index b0f3648304..7f544fa5d4 100644 --- a/crates/nu-cmd-lang/src/core_commands/use_.rs +++ b/crates/nu-cmd-lang/src/core_commands/use_.rs @@ -57,7 +57,7 @@ This command is a parser keyword. For details, check: let Some(Expression { expr: Expr::ImportPattern(import_pattern), .. - }) = call.get_parser_info("import_pattern") + }) = call.get_parser_info(caller_stack, "import_pattern") else { return Err(ShellError::GenericError { error: "Unexpected import".into(), @@ -68,6 +68,9 @@ This command is a parser keyword. For details, check: }); }; + // Necessary so that we can modify the stack. + let import_pattern = import_pattern.clone(); + if let Some(module_id) = import_pattern.head.id { // Add constants for var_id in &import_pattern.constants { @@ -99,7 +102,7 @@ This command is a parser keyword. For details, check: &module_arg_str, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )?; let maybe_parent = maybe_file_path .as_ref() diff --git a/crates/nu-cmd-lang/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs index 22bb4c5dbd..a67c47fcab 100644 --- a/crates/nu-cmd-lang/src/core_commands/while_.rs +++ b/crates/nu-cmd-lang/src/core_commands/while_.rs @@ -46,6 +46,9 @@ impl Command for While { call: &Call, _input: PipelineData, ) -> Result { + // This is compiled specially by the IR compiler. The code here is never used when + // running in IR mode. + let call = call.assert_ast_call()?; let head = call.head; let cond = call.positional_nth(0).expect("checked through parser"); let block_id = call diff --git a/crates/nu-command/src/bytes/build_.rs b/crates/nu-command/src/bytes/build_.rs index f6b1327621..9a3599a071 100644 --- a/crates/nu-command/src/bytes/build_.rs +++ b/crates/nu-command/src/bytes/build_.rs @@ -49,10 +49,8 @@ impl Command for BytesBuild { _input: PipelineData, ) -> Result { let mut output = vec![]; - for val in call.rest_iter_flattened(0, |expr| { - let eval_expression = get_eval_expression(engine_state); - eval_expression(engine_state, stack, expr) - })? { + let eval_expression = get_eval_expression(engine_state); + for val in call.rest_iter_flattened(engine_state, stack, eval_expression, 0)? { let val_span = val.span(); match val { Value::Binary { mut val, .. } => output.append(&mut val), diff --git a/crates/nu-command/src/debug/explain.rs b/crates/nu-command/src/debug/explain.rs index b451d6916a..710e37935a 100644 --- a/crates/nu-command/src/debug/explain.rs +++ b/crates/nu-command/src/debug/explain.rs @@ -1,6 +1,6 @@ use nu_engine::{command_prelude::*, get_eval_expression}; use nu_protocol::{ - ast::{Argument, Block, Expr, Expression}, + ast::{self, Argument, Block, Expr, Expression}, engine::Closure, }; @@ -106,7 +106,7 @@ pub fn get_pipeline_elements( fn get_arguments( engine_state: &EngineState, stack: &mut Stack, - call: &Call, + call: &ast::Call, eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, ) -> Vec { let mut arg_value = vec![]; diff --git a/crates/nu-command/src/debug/metadata.rs b/crates/nu-command/src/debug/metadata.rs index 543e598e28..245c150cea 100644 --- a/crates/nu-command/src/debug/metadata.rs +++ b/crates/nu-command/src/debug/metadata.rs @@ -28,6 +28,10 @@ impl Command for Metadata { .category(Category::Debug) } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -35,7 +39,7 @@ impl Command for Metadata { call: &Call, input: PipelineData, ) -> Result { - let arg = call.positional_nth(0); + let arg = call.positional_nth(stack, 0); let head = call.head; match arg { diff --git a/crates/nu-command/src/debug/mod.rs b/crates/nu-command/src/debug/mod.rs index f19ddab916..ec18c2be87 100644 --- a/crates/nu-command/src/debug/mod.rs +++ b/crates/nu-command/src/debug/mod.rs @@ -10,6 +10,7 @@ mod profile; mod timeit; mod view; mod view_files; +mod view_ir; mod view_source; mod view_span; @@ -25,5 +26,6 @@ pub use profile::DebugProfile; pub use timeit::TimeIt; pub use view::View; pub use view_files::ViewFiles; +pub use view_ir::ViewIr; pub use view_source::ViewSource; pub use view_span::ViewSpan; diff --git a/crates/nu-command/src/debug/timeit.rs b/crates/nu-command/src/debug/timeit.rs index a445679b81..7a48644a6d 100644 --- a/crates/nu-command/src/debug/timeit.rs +++ b/crates/nu-command/src/debug/timeit.rs @@ -32,6 +32,10 @@ impl Command for TimeIt { vec!["timing", "timer", "benchmark", "measure"] } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -39,13 +43,14 @@ impl Command for TimeIt { call: &Call, input: PipelineData, ) -> Result { - let command_to_run = call.positional_nth(0); + // reset outdest, so the command can write to stdout and stderr. + let stack = &mut stack.push_redirection(None, None); + + let command_to_run = call.positional_nth(stack, 0); // Get the start time after all other computation has been done. let start_time = Instant::now(); - // reset outdest, so the command can write to stdout and stderr. - let stack = &mut stack.push_redirection(None, None); if let Some(command_to_run) = command_to_run { if let Some(block_id) = command_to_run.as_block() { let eval_block = get_eval_block(engine_state); @@ -53,7 +58,8 @@ impl Command for TimeIt { eval_block(engine_state, stack, block, input)? } else { let eval_expression_with_input = get_eval_expression_with_input(engine_state); - eval_expression_with_input(engine_state, stack, command_to_run, input)?.0 + let expression = &command_to_run.clone(); + eval_expression_with_input(engine_state, stack, expression, input)?.0 } } else { PipelineData::empty() diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs new file mode 100644 index 0000000000..df4f6cad6b --- /dev/null +++ b/crates/nu-command/src/debug/view_ir.rs @@ -0,0 +1,83 @@ +use nu_engine::command_prelude::*; +use nu_protocol::engine::Closure; + +#[derive(Clone)] +pub struct ViewIr; + +impl Command for ViewIr { + fn name(&self) -> &str { + "view ir" + } + + fn signature(&self) -> Signature { + Signature::new(self.name()) + .required( + "closure", + SyntaxShape::Closure(None), + "The closure to see compiled code for.", + ) + .switch( + "json", + "Dump the raw block data as JSON (unstable).", + Some('j'), + ) + .input_output_type(Type::Nothing, Type::String) + } + + fn usage(&self) -> &str { + "View the compiled IR code for a block of code." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let closure: Closure = call.req(engine_state, stack, 0)?; + let json = call.has_flag(engine_state, stack, "json")?; + + let block = engine_state.get_block(closure.block_id); + let ir_block = block + .ir_block + .as_ref() + .ok_or_else(|| ShellError::GenericError { + error: "Can't view IR for this block".into(), + msg: "block is missing compiled representation".into(), + span: block.span, + help: Some("the IrBlock is probably missing due to a compilation error".into()), + inner: vec![], + })?; + + let formatted = if json { + let formatted_instructions = ir_block + .instructions + .iter() + .map(|instruction| { + instruction + .display(engine_state, &ir_block.data) + .to_string() + }) + .collect::>(); + + serde_json::to_string_pretty(&serde_json::json!({ + "block_id": closure.block_id, + "span": block.span, + "ir_block": ir_block, + "formatted_instructions": formatted_instructions, + })) + .map_err(|err| ShellError::GenericError { + error: "JSON serialization failed".into(), + msg: err.to_string(), + span: Some(call.head), + help: None, + inner: vec![], + })? + } else { + format!("{}", ir_block.display(engine_state)) + }; + + Ok(Value::string(formatted, call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 847d2349ed..b270a78dce 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -154,6 +154,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { TimeIt, View, ViewFiles, + ViewIr, ViewSource, ViewSpan, }; diff --git a/crates/nu-command/src/env/export_env.rs b/crates/nu-command/src/env/export_env.rs index 00f2c73ef4..7b583c9959 100644 --- a/crates/nu-command/src/env/export_env.rs +++ b/crates/nu-command/src/env/export_env.rs @@ -33,6 +33,10 @@ impl Command for ExportEnv { CommandType::Keyword } + fn requires_ast_for_arguments(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -41,7 +45,7 @@ impl Command for ExportEnv { input: PipelineData, ) -> Result { let block_id = call - .positional_nth(0) + .positional_nth(caller_stack, 0) .expect("checked through parser") .as_block() .expect("internal error: missing block"); diff --git a/crates/nu-command/src/env/source_env.rs b/crates/nu-command/src/env/source_env.rs index 0d8b118e8d..1813a92f1f 100644 --- a/crates/nu-command/src/env/source_env.rs +++ b/crates/nu-command/src/env/source_env.rs @@ -56,7 +56,7 @@ impl Command for SourceEnv { &source_filename.item, engine_state, caller_stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(caller_stack, call), )? { PathBuf::from(&path) } else { diff --git a/crates/nu-command/src/filesystem/du.rs b/crates/nu-command/src/filesystem/du.rs index 93f08f7785..d34892ace9 100644 --- a/crates/nu-command/src/filesystem/du.rs +++ b/crates/nu-command/src/filesystem/du.rs @@ -102,7 +102,7 @@ impl Command for Du { let current_dir = current_dir(engine_state, stack)?; let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; - let paths = if call.rest_iter(0).count() == 0 { + let paths = if !call.has_positional_args(stack, 0) { None } else { Some(paths) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index f465a93dc1..807e4f3409 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -108,7 +108,7 @@ impl Command for Ls { }; let pattern_arg = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; - let input_pattern_arg = if call.rest_iter(0).count() == 0 { + let input_pattern_arg = if !call.has_positional_args(stack, 0) { None } else { Some(pattern_arg) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index e654b27f05..0351d1d9b2 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -1,7 +1,7 @@ use super::util::get_rest_for_glob_pattern; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir, get_eval_block}; -use nu_protocol::{ByteStream, DataSource, NuGlob, PipelineMetadata}; +use nu_protocol::{ast, ByteStream, DataSource, NuGlob, PipelineMetadata}; use std::path::Path; #[cfg(feature = "sqlite")] @@ -56,7 +56,7 @@ impl Command for Open { let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; let eval_block = get_eval_block(engine_state); - if paths.is_empty() && call.rest_iter(0).next().is_none() { + if paths.is_empty() && !call.has_positional_args(stack, 0) { // try to use path from pipeline input if there were no positional or spread args let (filename, span) = match input { PipelineData::Value(val, ..) => { @@ -180,7 +180,8 @@ impl Command for Open { let block = engine_state.get_block(block_id); eval_block(engine_state, stack, block, stream) } else { - decl.run(engine_state, stack, &Call::new(call_span), stream) + let call = ast::Call::new(call_span); + decl.run(engine_state, stack, &(&call).into(), stream) }; output.push(command_output.map_err(|inner| { ShellError::GenericError{ diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 6ca5c09559..be5073ef20 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -4,10 +4,8 @@ use nu_engine::get_eval_block; use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; use nu_protocol::{ - ast::{Expr, Expression}, - byte_stream::copy_with_signals, - process::ChildPipe, - ByteStreamSource, DataSource, OutDest, PipelineMetadata, Signals, + ast, byte_stream::copy_with_signals, process::ChildPipe, ByteStreamSource, DataSource, OutDest, + PipelineMetadata, Signals, }; use std::{ fs::File, @@ -69,24 +67,6 @@ impl Command for Save { let append = call.has_flag(engine_state, stack, "append")?; let force = call.has_flag(engine_state, stack, "force")?; let progress = call.has_flag(engine_state, stack, "progress")?; - let out_append = if let Some(Expression { - expr: Expr::Bool(out_append), - .. - }) = call.get_parser_info("out-append") - { - *out_append - } else { - false - }; - let err_append = if let Some(Expression { - expr: Expr::Bool(err_append), - .. - }) = call.get_parser_info("err-append") - { - *err_append - } else { - false - }; let span = call.head; #[allow(deprecated)] @@ -109,14 +89,7 @@ impl Command for Save { PipelineData::ByteStream(stream, metadata) => { check_saving_to_source_file(metadata.as_ref(), &path, stderr_path.as_ref())?; - let (file, stderr_file) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (file, stderr_file) = get_files(&path, stderr_path.as_ref(), append, force)?; let size = stream.known_size(); let signals = engine_state.signals(); @@ -221,14 +194,7 @@ impl Command for Save { stderr_path.as_ref(), )?; - let (mut file, _) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; for val in ls { file.write_all(&value_to_bytes(val)?) .map_err(|err| ShellError::IOError { @@ -258,14 +224,7 @@ impl Command for Save { input_to_bytes(input, Path::new(&path.item), raw, engine_state, stack, span)?; // Only open file after successful conversion - let (mut file, _) = get_files( - &path, - stderr_path.as_ref(), - append, - out_append, - err_append, - force, - )?; + let (mut file, _) = get_files(&path, stderr_path.as_ref(), append, force)?; file.write_all(&bytes).map_err(|err| ShellError::IOError { msg: err.to_string(), @@ -397,7 +356,8 @@ fn convert_to_extension( let eval_block = get_eval_block(engine_state); eval_block(engine_state, stack, block, input) } else { - decl.run(engine_state, stack, &Call::new(span), input) + let call = ast::Call::new(span); + decl.run(engine_state, stack, &(&call).into(), input) } } else { Ok(input) @@ -473,19 +433,17 @@ fn get_files( path: &Spanned, stderr_path: Option<&Spanned>, append: bool, - out_append: bool, - err_append: bool, force: bool, ) -> Result<(File, Option), ShellError> { // First check both paths - let (path, path_span) = prepare_path(path, append || out_append, force)?; + let (path, path_span) = prepare_path(path, append, force)?; let stderr_path_and_span = stderr_path .as_ref() - .map(|stderr_path| prepare_path(stderr_path, append || err_append, force)) + .map(|stderr_path| prepare_path(stderr_path, append, force)) .transpose()?; // Only if both files can be used open and possibly truncate them - let file = open_file(path, path_span, append || out_append)?; + let file = open_file(path, path_span, append)?; let stderr_file = stderr_path_and_span .map(|(stderr_path, stderr_path_span)| { @@ -498,7 +456,7 @@ fn get_files( inner: vec![], }) } else { - open_file(stderr_path, stderr_path_span, append || err_append) + open_file(stderr_path, stderr_path_span, append) } }) .transpose()?; diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 1b755875bd..de32d204a0 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -1,6 +1,6 @@ use dialoguer::Input; use nu_engine::{command_prelude::*, get_eval_expression}; -use nu_protocol::{ast::Expr, FromValue, NuGlob}; +use nu_protocol::{FromValue, NuGlob}; use std::{ error::Error, path::{Path, PathBuf}, @@ -92,42 +92,19 @@ pub fn is_older(src: &Path, dst: &Path) -> Option { /// Get rest arguments from given `call`, starts with `starting_pos`. /// -/// It's similar to `call.rest`, except that it always returns NuGlob. And if input argument has -/// Type::Glob, the NuGlob is unquoted, which means it's required to expand. +/// It's similar to `call.rest`, except that it always returns NuGlob. pub fn get_rest_for_glob_pattern( engine_state: &EngineState, stack: &mut Stack, call: &Call, starting_pos: usize, ) -> Result>, ShellError> { - let mut output = vec![]; let eval_expression = get_eval_expression(engine_state); - for result in call.rest_iter_flattened(starting_pos, |expr| { - let result = eval_expression(engine_state, stack, expr); - match result { - Err(e) => Err(e), - Ok(result) => { - let span = result.span(); - // convert from string to quoted string if expr is a variable - // or string interpolation - match result { - Value::String { val, .. } - if matches!( - &expr.expr, - Expr::FullCellPath(_) | Expr::StringInterpolation(_) - ) => - { - // should not expand if given input type is not glob. - Ok(Value::glob(val, expr.ty != Type::Glob, span)) - } - other => Ok(other), - } - } - } - })? { - output.push(FromValue::from_value(result)?); - } - - Ok(output) + call.rest_iter_flattened(engine_state, stack, eval_expression, starting_pos)? + .into_iter() + // This used to be much more complex, but I think `FromValue` should be able to handle the + // nuance here. + .map(FromValue::from_value) + .collect() } diff --git a/crates/nu-command/src/filters/transpose.rs b/crates/nu-command/src/filters/transpose.rs index 95aa382e4f..4a14c1aea2 100644 --- a/crates/nu-command/src/filters/transpose.rs +++ b/crates/nu-command/src/filters/transpose.rs @@ -149,27 +149,27 @@ pub fn transpose( if !args.rest.is_empty() && args.header_row { return Err(ShellError::IncompatibleParametersSingle { msg: "Can not provide header names and use `--header-row`".into(), - span: call.get_named_arg("header-row").expect("has flag").span, + span: call.get_flag_span(stack, "header-row").expect("has flag"), }); } if !args.header_row && args.keep_all { return Err(ShellError::IncompatibleParametersSingle { msg: "Can only be used with `--header-row`(`-r`)".into(), - span: call.get_named_arg("keep-all").expect("has flag").span, + span: call.get_flag_span(stack, "keep-all").expect("has flag"), }); } if !args.header_row && args.keep_last { return Err(ShellError::IncompatibleParametersSingle { msg: "Can only be used with `--header-row`(`-r`)".into(), - span: call.get_named_arg("keep-last").expect("has flag").span, + span: call.get_flag_span(stack, "keep-last").expect("has flag"), }); } if args.keep_all && args.keep_last { return Err(ShellError::IncompatibleParameters { left_message: "can't use `--keep-last` at the same time".into(), - left_span: call.get_named_arg("keep-last").expect("has flag").span, + left_span: call.get_flag_span(stack, "keep-last").expect("has flag"), right_message: "because of `--keep-all`".into(), - right_span: call.get_named_arg("keep-all").expect("has flag").span, + right_span: call.get_flag_span(stack, "keep-all").expect("has flag"), }); } diff --git a/crates/nu-command/src/filters/utils.rs b/crates/nu-command/src/filters/utils.rs index 3ebd4bafbd..4c67667e8e 100644 --- a/crates/nu-command/src/filters/utils.rs +++ b/crates/nu-command/src/filters/utils.rs @@ -1,7 +1,6 @@ use nu_engine::{CallExt, ClosureEval}; use nu_protocol::{ - ast::Call, - engine::{Closure, EngineState, Stack}, + engine::{Call, Closure, EngineState, Stack}, IntoPipelineData, PipelineData, ShellError, Span, Value, }; diff --git a/crates/nu-command/src/generators/cal.rs b/crates/nu-command/src/generators/cal.rs index a257f3ab7c..018d9370dd 100644 --- a/crates/nu-command/src/generators/cal.rs +++ b/crates/nu-command/src/generators/cal.rs @@ -1,7 +1,7 @@ use chrono::{Datelike, Local, NaiveDate}; use nu_color_config::StyleComputer; use nu_engine::command_prelude::*; -use nu_protocol::ast::{Expr, Expression}; +use nu_protocol::ast::{self, Expr, Expression}; use std::collections::VecDeque; @@ -143,7 +143,7 @@ pub fn cal( style_computer, )?; - let mut table_no_index = Call::new(Span::unknown()); + let mut table_no_index = ast::Call::new(Span::unknown()); table_no_index.add_named(( Spanned { item: "index".to_string(), @@ -160,7 +160,12 @@ pub fn cal( let cal_table_output = Value::list(calendar_vec_deque.into_iter().collect(), tag).into_pipeline_data(); if !arguments.as_table { - crate::Table.run(engine_state, stack, &table_no_index, cal_table_output) + crate::Table.run( + engine_state, + stack, + &(&table_no_index).into(), + cal_table_output, + ) } else { Ok(cal_table_output) } diff --git a/crates/nu-command/src/math/utils.rs b/crates/nu-command/src/math/utils.rs index 765c1f42fb..62f96ea073 100644 --- a/crates/nu-command/src/math/utils.rs +++ b/crates/nu-command/src/math/utils.rs @@ -1,6 +1,6 @@ use core::slice; use indexmap::IndexMap; -use nu_protocol::{ast::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; +use nu_protocol::{engine::Call, IntoPipelineData, PipelineData, ShellError, Signals, Span, Value}; pub fn run_with_function( call: &Call, diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 29603be9e7..23161eb7bf 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -676,7 +676,7 @@ Operating system commands: } }; - let output = heavy_lifting(code, escape, osc, call)?; + let output = heavy_lifting(code, escape, osc, stack, call)?; Ok(Value::string(output, call.head).into_pipeline_data()) } @@ -713,26 +713,30 @@ Operating system commands: } }; - let output = heavy_lifting(code, escape, osc, call)?; + let output = heavy_lifting(code, escape, osc, &Stack::new(), call)?; Ok(Value::string(output, call.head).into_pipeline_data()) } } -fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result { +fn heavy_lifting( + code: Value, + escape: bool, + osc: bool, + stack: &Stack, + call: &Call, +) -> Result { let param_is_string = matches!(code, Value::String { .. }); if escape && osc { return Err(ShellError::IncompatibleParameters { left_message: "escape".into(), left_span: call - .get_named_arg("escape") - .expect("Unexpected missing argument") - .span, + .get_flag_span(stack, "escape") + .expect("Unexpected missing argument"), right_message: "osc".into(), right_span: call - .get_named_arg("osc") - .expect("Unexpected missing argument") - .span, + .get_flag_span(stack, "osc") + .expect("Unexpected missing argument"), }); } let code_string = if param_is_string { @@ -744,10 +748,7 @@ fn heavy_lifting(code: Value, escape: bool, osc: bool, call: &Call) -> Result = code_string.chars().collect(); if code_vec[0] == '\\' { - let span = match call.get_flag_expr("escape") { - Some(expr) => expr.span, - None => call.head, - }; + let span = call.get_flag_span(stack, "escape").unwrap_or(call.head); return Err(ShellError::TypeMismatch { err_message: "no need for escape characters".into(), diff --git a/crates/nu-command/src/platform/is_terminal.rs b/crates/nu-command/src/platform/is_terminal.rs index c67329e839..2195f3ff8a 100644 --- a/crates/nu-command/src/platform/is_terminal.rs +++ b/crates/nu-command/src/platform/is_terminal.rs @@ -58,7 +58,7 @@ impl Command for IsTerminal { _ => { return Err(ShellError::IncompatibleParametersSingle { msg: "Only one stream may be checked".into(), - span: Span::merge_many(call.arguments.iter().map(|arg| arg.span())), + span: call.arguments_span(), }); } }; diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs index 2e47ee8c78..1cf6f15f01 100644 --- a/crates/nu-command/src/platform/kill.rs +++ b/crates/nu-command/src/platform/kill.rs @@ -84,27 +84,26 @@ impl Command for Kill { { return Err(ShellError::IncompatibleParameters { left_message: "force".to_string(), - left_span: call - .get_named_arg("force") - .ok_or_else(|| ShellError::GenericError { + left_span: call.get_flag_span(stack, "force").ok_or_else(|| { + ShellError::GenericError { error: "Flag error".into(), msg: "flag force not found".into(), span: Some(call.head), help: None, inner: vec![], - })? - .span, + } + })?, right_message: "signal".to_string(), right_span: Span::merge( - call.get_named_arg("signal") - .ok_or_else(|| ShellError::GenericError { + call.get_flag_span(stack, "signal").ok_or_else(|| { + ShellError::GenericError { error: "Flag error".into(), msg: "flag signal not found".into(), span: Some(call.head), help: None, inner: vec![], - })? - .span, + } + })?, signal_span, ), }); diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index afc143983e..dd9289a141 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -8,8 +8,8 @@ use base64::{ }; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; use nu_protocol::{ - ast::{Call, CellPath}, - engine::EngineState, + ast::CellPath, + engine::{Call, EngineState}, PipelineData, ShellError, Span, Spanned, Value, }; diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index d1ebf540e5..8b5af2dec4 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -17,8 +17,7 @@ pub use str_::*; use nu_engine::CallExt; use nu_protocol::{ - ast::Call, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{Call, EngineState, Stack, StateWorkingSet}, ShellError, }; diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index f9e0879c00..334569c79e 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -87,7 +87,7 @@ impl Command for NuCheck { &path_str.item, engine_state, stack, - get_dirs_var_from_call(call), + get_dirs_var_from_call(stack, call), ) { Ok(path) => { if let Some(path) = path { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 06bc5a69ca..a5cf343970 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,9 +1,7 @@ use nu_cmd_base::hook::eval_hook; use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; use nu_path::{dots::expand_ndots, expand_tilde}; -use nu_protocol::{ - ast::Expression, did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals, -}; +use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals}; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; @@ -222,20 +220,21 @@ pub fn eval_arguments_from_call( call: &Call, ) -> Result>, ShellError> { let cwd = engine_state.cwd(Some(stack))?; - let mut args: Vec> = vec![]; - for (expr, spread) in call.rest_iter(1) { - for arg in eval_argument(engine_state, stack, expr, spread)? { - match arg { - // Expand globs passed to run-external - Value::Glob { val, no_expand, .. } if !no_expand => args.extend( - expand_glob(&val, cwd.as_ref(), expr.span, engine_state.signals())? - .into_iter() - .map(|s| s.into_spanned(expr.span)), - ), - other => { - args.push(OsString::from(coerce_into_string(other)?).into_spanned(expr.span)) - } - } + let eval_expression = get_eval_expression(engine_state); + let call_args = call.rest_iter_flattened(engine_state, stack, eval_expression, 1)?; + let mut args: Vec> = Vec::with_capacity(call_args.len()); + + for arg in call_args { + let span = arg.span(); + match arg { + // Expand globs passed to run-external + Value::Glob { val, no_expand, .. } if !no_expand => args.extend( + expand_glob(&val, cwd.as_std_path(), span, engine_state.signals())? + .into_iter() + .map(|s| s.into_spanned(span)), + ), + other => args + .push(OsString::from(coerce_into_string(engine_state, other)?).into_spanned(span)), } } Ok(args) @@ -243,42 +242,17 @@ pub fn eval_arguments_from_call( /// Custom `coerce_into_string()`, including globs, since those are often args to `run-external` /// as well -fn coerce_into_string(val: Value) -> Result { +fn coerce_into_string(engine_state: &EngineState, val: Value) -> Result { match val { + Value::List { .. } => Err(ShellError::CannotPassListToExternal { + arg: String::from_utf8_lossy(engine_state.get_span_contents(val.span())).into_owned(), + span: val.span(), + }), Value::Glob { val, .. } => Ok(val), _ => val.coerce_into_string(), } } -/// Evaluate an argument, returning more than one value if it was a list to be spread. -fn eval_argument( - engine_state: &EngineState, - stack: &mut Stack, - expr: &Expression, - spread: bool, -) -> Result, ShellError> { - let eval = get_eval_expression(engine_state); - match eval(engine_state, stack, expr)? { - Value::List { vals, .. } => { - if spread { - Ok(vals) - } else { - Err(ShellError::CannotPassListToExternal { - arg: String::from_utf8_lossy(engine_state.get_span_contents(expr.span)).into(), - span: expr.span, - }) - } - } - value => { - if spread { - Err(ShellError::CannotSpreadAsList { span: expr.span }) - } else { - Ok(vec![value]) - } - } - } -} - /// Performs glob expansion on `arg`. If the expansion found no matches or the pattern /// is not a valid glob, then this returns the original string as the expansion result. /// diff --git a/crates/nu-command/src/system/uname.rs b/crates/nu-command/src/system/uname.rs index e267fcaeb2..0bcb749f02 100644 --- a/crates/nu-command/src/system/uname.rs +++ b/crates/nu-command/src/system/uname.rs @@ -1,10 +1,5 @@ -use nu_protocol::record; -use nu_protocol::Value; -use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Type, -}; +use nu_engine::command_prelude::*; +use nu_protocol::{record, Value}; #[derive(Clone)] pub struct UName; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index e3738a3952..190c3659af 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -344,7 +344,7 @@ fn get_theme_flag( struct CmdInput<'a> { engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, data: PipelineData, } @@ -352,7 +352,7 @@ impl<'a> CmdInput<'a> { fn new( engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, data: PipelineData, ) -> Self { Self { diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 3e6c3f787b..c416a2f590 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -15,6 +15,7 @@ nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.95. nu-path = { path = "../nu-path", version = "0.95.1" } nu-glob = { path = "../nu-glob", version = "0.95.1" } nu-utils = { path = "../nu-utils", version = "0.95.1" } +log = { workspace = true } [features] -plugin = [] \ No newline at end of file +plugin = [] diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index daedb24a1f..d3f36215e6 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -1,10 +1,10 @@ use crate::eval_expression; use nu_protocol::{ - ast::Call, + ast, debugger::WithoutDebug, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{self, EngineState, Stack, StateWorkingSet}, eval_const::eval_constant, - FromValue, ShellError, Value, + ir, FromValue, ShellError, Span, Value, }; pub trait CallExt { @@ -23,6 +23,9 @@ pub trait CallExt { name: &str, ) -> Result, ShellError>; + /// Efficiently get the span of a flag argument + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option; + fn rest( &self, engine_state: &EngineState, @@ -56,9 +59,12 @@ pub trait CallExt { stack: &mut Stack, name: &str, ) -> Result; + + /// True if the command has any positional or rest arguments, excluding before the given index. + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool; } -impl CallExt for Call { +impl CallExt for ast::Call { fn has_flag( &self, engine_state: &EngineState, @@ -104,6 +110,10 @@ impl CallExt for Call { } } + fn get_flag_span(&self, _stack: &Stack, name: &str) -> Option { + self.get_named_arg(name).map(|arg| arg.span) + } + fn rest( &self, engine_state: &EngineState, @@ -189,4 +199,205 @@ impl CallExt for Call { }) } } + + fn has_positional_args(&self, _stack: &Stack, starting_pos: usize) -> bool { + self.rest_iter(starting_pos).next().is_some() + } +} + +impl CallExt for ir::Call { + fn has_flag( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + flag_name: &str, + ) -> Result { + Ok(self + .named_iter(stack) + .find(|(name, _)| name.item == flag_name) + .is_some_and(|(_, value)| { + // Handle --flag=false + !matches!(value, Some(Value::Bool { val: false, .. })) + })) + } + + fn get_flag( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result, ShellError> { + if let Some(val) = self.get_named_arg(stack, name) { + T::from_value(val.clone()).map(Some) + } else { + Ok(None) + } + } + + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option { + self.named_iter(stack) + .find_map(|(i_name, _)| (i_name.item == name).then_some(i_name.span)) + } + + fn rest( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + starting_pos: usize, + ) -> Result, ShellError> { + self.rest_iter_flattened(stack, starting_pos)? + .into_iter() + .map(T::from_value) + .collect() + } + + fn opt( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result, ShellError> { + self.positional_iter(stack) + .nth(pos) + .cloned() + .map(T::from_value) + .transpose() + } + + fn opt_const( + &self, + _working_set: &StateWorkingSet, + _pos: usize, + ) -> Result, ShellError> { + Err(ShellError::IrEvalError { + msg: "const evaluation is not yet implemented on ir::Call".into(), + span: Some(self.head), + }) + } + + fn req( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result { + if let Some(val) = self.opt(engine_state, stack, pos)? { + Ok(val) + } else if self.positional_len(stack) == 0 { + Err(ShellError::AccessEmptyContent { span: self.head }) + } else { + Err(ShellError::AccessBeyondEnd { + max_idx: self.positional_len(stack) - 1, + span: self.head, + }) + } + } + + fn req_parser_info( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result { + // FIXME: this depends on the AST evaluator. We can fix this by making the parser info an + // enum rather than using expressions. It's not clear that evaluation of this is ever really + // needed. + if let Some(expr) = self.get_parser_info(stack, name) { + let expr = expr.clone(); + let stack = &mut stack.use_call_arg_out_dest(); + let result = eval_expression::(engine_state, stack, &expr)?; + FromValue::from_value(result) + } else { + Err(ShellError::CantFindColumn { + col_name: name.into(), + span: None, + src_span: self.head, + }) + } + } + + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool { + self.rest_iter(stack, starting_pos).next().is_some() + } +} + +macro_rules! proxy { + ($self:ident . $method:ident ($($param:expr),*)) => (match &$self.inner { + engine::CallImpl::AstRef(call) => call.$method($($param),*), + engine::CallImpl::AstBox(call) => call.$method($($param),*), + engine::CallImpl::IrRef(call) => call.$method($($param),*), + engine::CallImpl::IrBox(call) => call.$method($($param),*), + }) +} + +impl CallExt for engine::Call<'_> { + fn has_flag( + &self, + engine_state: &EngineState, + stack: &mut Stack, + flag_name: &str, + ) -> Result { + proxy!(self.has_flag(engine_state, stack, flag_name)) + } + + fn get_flag( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result, ShellError> { + proxy!(self.get_flag(engine_state, stack, name)) + } + + fn get_flag_span(&self, stack: &Stack, name: &str) -> Option { + proxy!(self.get_flag_span(stack, name)) + } + + fn rest( + &self, + engine_state: &EngineState, + stack: &mut Stack, + starting_pos: usize, + ) -> Result, ShellError> { + proxy!(self.rest(engine_state, stack, starting_pos)) + } + + fn opt( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result, ShellError> { + proxy!(self.opt(engine_state, stack, pos)) + } + + fn opt_const( + &self, + working_set: &StateWorkingSet, + pos: usize, + ) -> Result, ShellError> { + proxy!(self.opt_const(working_set, pos)) + } + + fn req( + &self, + engine_state: &EngineState, + stack: &mut Stack, + pos: usize, + ) -> Result { + proxy!(self.req(engine_state, stack, pos)) + } + + fn req_parser_info( + &self, + engine_state: &EngineState, + stack: &mut Stack, + name: &str, + ) -> Result { + proxy!(self.req_parser_info(engine_state, stack, name)) + } + + fn has_positional_args(&self, stack: &Stack, starting_pos: usize) -> bool { + proxy!(self.has_positional_args(stack, starting_pos)) + } } diff --git a/crates/nu-engine/src/command_prelude.rs b/crates/nu-engine/src/command_prelude.rs index 5c21af27e0..e6ddb5fb91 100644 --- a/crates/nu-engine/src/command_prelude.rs +++ b/crates/nu-engine/src/command_prelude.rs @@ -1,7 +1,7 @@ pub use crate::CallExt; pub use nu_protocol::{ - ast::{Call, CellPath}, - engine::{Command, EngineState, Stack, StateWorkingSet}, + ast::CellPath, + engine::{Call, Command, EngineState, Stack, StateWorkingSet}, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, diff --git a/crates/nu-engine/src/compile/builder.rs b/crates/nu-engine/src/compile/builder.rs new file mode 100644 index 0000000000..d77075cc3f --- /dev/null +++ b/crates/nu-engine/src/compile/builder.rs @@ -0,0 +1,575 @@ +use nu_protocol::{ + ir::{DataSlice, Instruction, IrAstRef, IrBlock, Literal}, + CompileError, IntoSpanned, RegId, Span, Spanned, +}; + +/// A label identifier. Only exists while building code. Replaced with the actual target. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct LabelId(pub usize); + +/// Builds [`IrBlock`]s progressively by consuming instructions and handles register allocation. +#[derive(Debug)] +pub(crate) struct BlockBuilder { + pub(crate) block_span: Option, + pub(crate) instructions: Vec, + pub(crate) spans: Vec, + /// The actual instruction index that a label refers to. While building IR, branch targets are + /// specified as indices into this array rather than the true instruction index. This makes it + /// easier to make modifications to code, as just this array needs to be changed, and it's also + /// less error prone as during `finish()` we check to make sure all of the used labels have had + /// an index actually set. + pub(crate) labels: Vec>, + pub(crate) data: Vec, + pub(crate) ast: Vec>, + pub(crate) comments: Vec, + pub(crate) register_allocation_state: Vec, + pub(crate) file_count: u32, + pub(crate) loop_stack: Vec, +} + +impl BlockBuilder { + /// Starts a new block, with the first register (`%0`) allocated as input. + pub(crate) fn new(block_span: Option) -> Self { + BlockBuilder { + block_span, + instructions: vec![], + spans: vec![], + labels: vec![], + data: vec![], + ast: vec![], + comments: vec![], + register_allocation_state: vec![true], + file_count: 0, + loop_stack: vec![], + } + } + + /// Get the next unused register for code generation. + pub(crate) fn next_register(&mut self) -> Result { + if let Some(index) = self + .register_allocation_state + .iter_mut() + .position(|is_allocated| { + if !*is_allocated { + *is_allocated = true; + true + } else { + false + } + }) + { + Ok(RegId(index as u32)) + } else if self.register_allocation_state.len() < (u32::MAX as usize - 2) { + let reg_id = RegId(self.register_allocation_state.len() as u32); + self.register_allocation_state.push(true); + Ok(reg_id) + } else { + Err(CompileError::RegisterOverflow { + block_span: self.block_span, + }) + } + } + + /// Check if a register is initialized with a value. + pub(crate) fn is_allocated(&self, reg_id: RegId) -> bool { + self.register_allocation_state + .get(reg_id.0 as usize) + .is_some_and(|state| *state) + } + + /// Mark a register as initialized. + pub(crate) fn mark_register(&mut self, reg_id: RegId) -> Result<(), CompileError> { + if let Some(is_allocated) = self.register_allocation_state.get_mut(reg_id.0 as usize) { + *is_allocated = true; + Ok(()) + } else { + Err(CompileError::RegisterOverflow { + block_span: self.block_span, + }) + } + } + + /// Mark a register as empty, so that it can be used again by something else. + #[track_caller] + pub(crate) fn free_register(&mut self, reg_id: RegId) -> Result<(), CompileError> { + let index = reg_id.0 as usize; + + if self + .register_allocation_state + .get(index) + .is_some_and(|is_allocated| *is_allocated) + { + self.register_allocation_state[index] = false; + Ok(()) + } else { + log::warn!("register {reg_id} uninitialized, builder = {self:#?}"); + Err(CompileError::RegisterUninitialized { + reg_id, + caller: std::panic::Location::caller().to_string(), + }) + } + } + + /// Define a label, which can be used by branch instructions. The target can optionally be + /// specified now. + pub(crate) fn label(&mut self, target_index: Option) -> LabelId { + let label_id = self.labels.len(); + self.labels.push(target_index); + LabelId(label_id) + } + + /// Change the target of a label. + pub(crate) fn set_label( + &mut self, + label_id: LabelId, + target_index: usize, + ) -> Result<(), CompileError> { + *self + .labels + .get_mut(label_id.0) + .ok_or(CompileError::UndefinedLabel { + label_id: label_id.0, + span: None, + })? = Some(target_index); + Ok(()) + } + + /// Insert an instruction into the block, automatically marking any registers populated by + /// the instruction, and freeing any registers consumed by the instruction. + #[track_caller] + pub(crate) fn push(&mut self, instruction: Spanned) -> Result<(), CompileError> { + // Free read registers, and mark write registers. + // + // If a register is both read and written, it should be on both sides, so that we can verify + // that the register was in the right state beforehand. + let mut allocate = |read: &[RegId], write: &[RegId]| -> Result<(), CompileError> { + for reg in read { + self.free_register(*reg)?; + } + for reg in write { + self.mark_register(*reg)?; + } + Ok(()) + }; + + let allocate_result = match &instruction.item { + Instruction::Unreachable => Ok(()), + Instruction::LoadLiteral { dst, lit } => { + allocate(&[], &[*dst]).and( + // Free any registers on the literal + match lit { + Literal::Range { + start, + step, + end, + inclusion: _, + } => allocate(&[*start, *step, *end], &[]), + Literal::Bool(_) + | Literal::Int(_) + | Literal::Float(_) + | Literal::Filesize(_) + | Literal::Duration(_) + | Literal::Binary(_) + | Literal::Block(_) + | Literal::Closure(_) + | Literal::RowCondition(_) + | Literal::List { capacity: _ } + | Literal::Record { capacity: _ } + | Literal::Filepath { + val: _, + no_expand: _, + } + | Literal::Directory { + val: _, + no_expand: _, + } + | Literal::GlobPattern { + val: _, + no_expand: _, + } + | Literal::String(_) + | Literal::RawString(_) + | Literal::CellPath(_) + | Literal::Date(_) + | Literal::Nothing => Ok(()), + }, + ) + } + Instruction::LoadValue { dst, val: _ } => allocate(&[], &[*dst]), + Instruction::Move { dst, src } => allocate(&[*src], &[*dst]), + Instruction::Clone { dst, src } => allocate(&[*src], &[*dst, *src]), + Instruction::Collect { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::Span { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::Drop { src } => allocate(&[*src], &[]), + Instruction::Drain { src } => allocate(&[*src], &[]), + Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]), + Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]), + Instruction::LoadEnv { dst, key: _ } => allocate(&[], &[*dst]), + Instruction::LoadEnvOpt { dst, key: _ } => allocate(&[], &[*dst]), + Instruction::StoreEnv { key: _, src } => allocate(&[*src], &[]), + Instruction::PushPositional { src } => allocate(&[*src], &[]), + Instruction::AppendRest { src } => allocate(&[*src], &[]), + Instruction::PushFlag { name: _ } => Ok(()), + Instruction::PushShortFlag { short: _ } => Ok(()), + Instruction::PushNamed { name: _, src } => allocate(&[*src], &[]), + Instruction::PushShortNamed { short: _, src } => allocate(&[*src], &[]), + Instruction::PushParserInfo { name: _, info: _ } => Ok(()), + Instruction::RedirectOut { mode: _ } => Ok(()), + Instruction::RedirectErr { mode: _ } => Ok(()), + Instruction::CheckErrRedirected { src } => allocate(&[*src], &[*src]), + Instruction::OpenFile { + file_num: _, + path, + append: _, + } => allocate(&[*path], &[]), + Instruction::WriteFile { file_num: _, src } => allocate(&[*src], &[]), + Instruction::CloseFile { file_num: _ } => Ok(()), + Instruction::Call { + decl_id: _, + src_dst, + } => allocate(&[*src_dst], &[*src_dst]), + Instruction::StringAppend { src_dst, val } => allocate(&[*src_dst, *val], &[*src_dst]), + Instruction::GlobFrom { + src_dst, + no_expand: _, + } => allocate(&[*src_dst], &[*src_dst]), + Instruction::ListPush { src_dst, item } => allocate(&[*src_dst, *item], &[*src_dst]), + Instruction::ListSpread { src_dst, items } => { + allocate(&[*src_dst, *items], &[*src_dst]) + } + Instruction::RecordInsert { src_dst, key, val } => { + allocate(&[*src_dst, *key, *val], &[*src_dst]) + } + Instruction::RecordSpread { src_dst, items } => { + allocate(&[*src_dst, *items], &[*src_dst]) + } + Instruction::Not { src_dst } => allocate(&[*src_dst], &[*src_dst]), + Instruction::BinaryOp { + lhs_dst, + op: _, + rhs, + } => allocate(&[*lhs_dst, *rhs], &[*lhs_dst]), + Instruction::FollowCellPath { src_dst, path } => { + allocate(&[*src_dst, *path], &[*src_dst]) + } + Instruction::CloneCellPath { dst, src, path } => { + allocate(&[*src, *path], &[*src, *dst]) + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => allocate(&[*src_dst, *path, *new_value], &[*src_dst]), + Instruction::Jump { index: _ } => Ok(()), + Instruction::BranchIf { cond, index: _ } => allocate(&[*cond], &[]), + Instruction::BranchIfEmpty { src, index: _ } => allocate(&[*src], &[*src]), + Instruction::Match { + pattern: _, + src, + index: _, + } => allocate(&[*src], &[*src]), + Instruction::CheckMatchGuard { src } => allocate(&[*src], &[*src]), + Instruction::Iterate { + dst, + stream, + end_index: _, + } => allocate(&[*stream], &[*dst, *stream]), + Instruction::OnError { index: _ } => Ok(()), + Instruction::OnErrorInto { index: _, dst } => allocate(&[], &[*dst]), + Instruction::PopErrorHandler => Ok(()), + Instruction::CheckExternalFailed { dst, src } => allocate(&[*src], &[*dst, *src]), + Instruction::ReturnEarly { src } => allocate(&[*src], &[]), + Instruction::Return { src } => allocate(&[*src], &[]), + }; + + // Add more context to the error + match allocate_result { + Ok(()) => (), + Err(CompileError::RegisterUninitialized { reg_id, caller }) => { + return Err(CompileError::RegisterUninitializedWhilePushingInstruction { + reg_id, + caller, + instruction: format!("{:?}", instruction.item), + span: instruction.span, + }); + } + Err(err) => return Err(err), + } + + self.instructions.push(instruction.item); + self.spans.push(instruction.span); + self.ast.push(None); + self.comments.push(String::new()); + Ok(()) + } + + /// Set the AST of the last instruction. Separate method because it's rarely used. + pub(crate) fn set_last_ast(&mut self, ast_ref: Option) { + *self.ast.last_mut().expect("no last instruction") = ast_ref; + } + + /// Add a comment to the last instruction. + pub(crate) fn add_comment(&mut self, comment: impl std::fmt::Display) { + add_comment( + self.comments.last_mut().expect("no last instruction"), + comment, + ) + } + + /// Load a register with a literal. + pub(crate) fn load_literal( + &mut self, + reg_id: RegId, + literal: Spanned, + ) -> Result<(), CompileError> { + self.push( + Instruction::LoadLiteral { + dst: reg_id, + lit: literal.item, + } + .into_spanned(literal.span), + )?; + Ok(()) + } + + /// Allocate a new register and load a literal into it. + pub(crate) fn literal(&mut self, literal: Spanned) -> Result { + let reg_id = self.next_register()?; + self.load_literal(reg_id, literal)?; + Ok(reg_id) + } + + /// Deallocate a register and set it to `Empty`, if it is allocated + pub(crate) fn drop_reg(&mut self, reg_id: RegId) -> Result<(), CompileError> { + if self.is_allocated(reg_id) { + self.push(Instruction::Drop { src: reg_id }.into_spanned(Span::unknown()))?; + } + Ok(()) + } + + /// Set a register to `Empty`, but mark it as in-use, e.g. for input + pub(crate) fn load_empty(&mut self, reg_id: RegId) -> Result<(), CompileError> { + self.drop_reg(reg_id)?; + self.mark_register(reg_id) + } + + /// Drain the stream in a register (fully consuming it) + pub(crate) fn drain(&mut self, src: RegId, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Drain { src }.into_spanned(span)) + } + + /// Add data to the `data` array and return a [`DataSlice`] referencing it. + pub(crate) fn data(&mut self, data: impl AsRef<[u8]>) -> Result { + let data = data.as_ref(); + let start = self.data.len(); + if data.is_empty() { + Ok(DataSlice::empty()) + } else if start + data.len() < u32::MAX as usize { + let slice = DataSlice { + start: start as u32, + len: data.len() as u32, + }; + self.data.extend_from_slice(data); + Ok(slice) + } else { + Err(CompileError::DataOverflow { + block_span: self.block_span, + }) + } + } + + /// Clone a register with a `clone` instruction. + pub(crate) fn clone_reg(&mut self, src: RegId, span: Span) -> Result { + let dst = self.next_register()?; + self.push(Instruction::Clone { dst, src }.into_spanned(span))?; + Ok(dst) + } + + /// Add a `branch-if` instruction + pub(crate) fn branch_if( + &mut self, + cond: RegId, + label_id: LabelId, + span: Span, + ) -> Result<(), CompileError> { + self.push( + Instruction::BranchIf { + cond, + index: label_id.0, + } + .into_spanned(span), + ) + } + + /// Add a `branch-if-empty` instruction + pub(crate) fn branch_if_empty( + &mut self, + src: RegId, + label_id: LabelId, + span: Span, + ) -> Result<(), CompileError> { + self.push( + Instruction::BranchIfEmpty { + src, + index: label_id.0, + } + .into_spanned(span), + ) + } + + /// Add a `jump` instruction + pub(crate) fn jump(&mut self, label_id: LabelId, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Jump { index: label_id.0 }.into_spanned(span)) + } + + /// The index that the next instruction [`.push()`]ed will have. + pub(crate) fn here(&self) -> usize { + self.instructions.len() + } + + /// Allocate a new file number, for redirection. + pub(crate) fn next_file_num(&mut self) -> Result { + let next = self.file_count; + self.file_count = self + .file_count + .checked_add(1) + .ok_or(CompileError::FileOverflow { + block_span: self.block_span, + })?; + Ok(next) + } + + /// Push a new loop state onto the builder. Creates new labels that must be set. + pub(crate) fn begin_loop(&mut self) -> Loop { + let loop_ = Loop { + break_label: self.label(None), + continue_label: self.label(None), + }; + self.loop_stack.push(loop_); + loop_ + } + + /// True if we are currently in a loop. + pub(crate) fn is_in_loop(&self) -> bool { + !self.loop_stack.is_empty() + } + + /// Add a loop breaking jump instruction. + pub(crate) fn push_break(&mut self, span: Span) -> Result<(), CompileError> { + let loop_ = self + .loop_stack + .last() + .ok_or_else(|| CompileError::NotInALoop { + msg: "`break` called from outside of a loop".into(), + span: Some(span), + })?; + self.jump(loop_.break_label, span) + } + + /// Add a loop continuing jump instruction. + pub(crate) fn push_continue(&mut self, span: Span) -> Result<(), CompileError> { + let loop_ = self + .loop_stack + .last() + .ok_or_else(|| CompileError::NotInALoop { + msg: "`continue` called from outside of a loop".into(), + span: Some(span), + })?; + self.jump(loop_.continue_label, span) + } + + /// Pop the loop state. Checks that the loop being ended is the same one that was expected. + pub(crate) fn end_loop(&mut self, loop_: Loop) -> Result<(), CompileError> { + let ended_loop = self + .loop_stack + .pop() + .ok_or_else(|| CompileError::NotInALoop { + msg: "end_loop() called outside of a loop".into(), + span: None, + })?; + + if ended_loop == loop_ { + Ok(()) + } else { + Err(CompileError::IncoherentLoopState { + block_span: self.block_span, + }) + } + } + + /// Mark an unreachable code path. Produces an error at runtime if executed. + pub(crate) fn unreachable(&mut self, span: Span) -> Result<(), CompileError> { + self.push(Instruction::Unreachable.into_spanned(span)) + } + + /// Consume the builder and produce the final [`IrBlock`]. + pub(crate) fn finish(mut self) -> Result { + // Add comments to label targets + for (index, label_target) in self.labels.iter().enumerate() { + if let Some(label_target) = label_target { + add_comment( + &mut self.comments[*label_target], + format_args!("label({index})"), + ); + } + } + + // Populate the actual target indices of labels into the instructions + for ((index, instruction), span) in + self.instructions.iter_mut().enumerate().zip(&self.spans) + { + if let Some(label_id) = instruction.branch_target() { + let target_index = self.labels.get(label_id).cloned().flatten().ok_or( + CompileError::UndefinedLabel { + label_id, + span: Some(*span), + }, + )?; + // Add a comment to the target index that we come from here + add_comment( + &mut self.comments[target_index], + format_args!("from({index}:)"), + ); + instruction.set_branch_target(target_index).map_err(|_| { + CompileError::SetBranchTargetOfNonBranchInstruction { + instruction: format!("{:?}", instruction), + span: *span, + } + })?; + } + } + + Ok(IrBlock { + instructions: self.instructions, + spans: self.spans, + data: self.data.into(), + ast: self.ast, + comments: self.comments.into_iter().map(|s| s.into()).collect(), + register_count: self + .register_allocation_state + .len() + .try_into() + .expect("register count overflowed in finish() despite previous checks"), + file_count: self.file_count, + }) + } +} + +/// Keeps track of the `break` and `continue` target labels for a loop. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Loop { + pub(crate) break_label: LabelId, + pub(crate) continue_label: LabelId, +} + +/// Add a new comment to an existing one +fn add_comment(comment: &mut String, new_comment: impl std::fmt::Display) { + use std::fmt::Write; + write!( + comment, + "{}{}", + if comment.is_empty() { "" } else { ", " }, + new_comment + ) + .expect("formatting failed"); +} diff --git a/crates/nu-engine/src/compile/call.rs b/crates/nu-engine/src/compile/call.rs new file mode 100644 index 0000000000..d9f1b8e581 --- /dev/null +++ b/crates/nu-engine/src/compile/call.rs @@ -0,0 +1,270 @@ +use std::sync::Arc; + +use nu_protocol::{ + ast::{Argument, Call, Expression, ExternalArgument}, + engine::StateWorkingSet, + ir::{Instruction, IrAstRef, Literal}, + IntoSpanned, RegId, Span, Spanned, +}; + +use super::{compile_expression, keyword::*, BlockBuilder, CompileError, RedirectModes}; + +pub(crate) fn compile_call( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + let decl = working_set.get_decl(call.decl_id); + + // Check if this call has --help - if so, just redirect to `help` + if call.named_iter().any(|(name, _, _)| name.item == "help") { + return compile_help( + working_set, + builder, + decl.name().into_spanned(call.head), + io_reg, + ); + } + + // Try to figure out if this is a keyword call like `if`, and handle those specially + if decl.is_keyword() { + match decl.name() { + "if" => { + return compile_if(working_set, builder, call, redirect_modes, io_reg); + } + "match" => { + return compile_match(working_set, builder, call, redirect_modes, io_reg); + } + "const" => { + // This differs from the behavior of the const command, which adds the const value + // to the stack. Since `load-variable` also checks `engine_state` for the variable + // and will get a const value though, is it really necessary to do that? + return builder.load_empty(io_reg); + } + "alias" => { + // Alias does nothing + return builder.load_empty(io_reg); + } + "let" | "mut" => { + return compile_let(working_set, builder, call, redirect_modes, io_reg); + } + "try" => { + return compile_try(working_set, builder, call, redirect_modes, io_reg); + } + "loop" => { + return compile_loop(working_set, builder, call, redirect_modes, io_reg); + } + "while" => { + return compile_while(working_set, builder, call, redirect_modes, io_reg); + } + "for" => { + return compile_for(working_set, builder, call, redirect_modes, io_reg); + } + "break" => { + return compile_break(working_set, builder, call, redirect_modes, io_reg); + } + "continue" => { + return compile_continue(working_set, builder, call, redirect_modes, io_reg); + } + "return" => { + return compile_return(working_set, builder, call, redirect_modes, io_reg); + } + _ => (), + } + } + + // Keep AST if the decl needs it. + let requires_ast = decl.requires_ast_for_arguments(); + + // It's important that we evaluate the args first before trying to set up the argument + // state for the call. + // + // We could technically compile anything that isn't another call safely without worrying about + // the argument state, but we'd have to check all of that first and it just isn't really worth + // it. + enum CompiledArg<'a> { + Positional(RegId, Span, Option), + Named( + &'a str, + Option<&'a str>, + Option, + Span, + Option, + ), + Spread(RegId, Span, Option), + } + + let mut compiled_args = vec![]; + + for arg in &call.arguments { + let arg_reg = arg + .expr() + .map(|expr| { + let arg_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(arg.span()), + None, + arg_reg, + )?; + + Ok(arg_reg) + }) + .transpose()?; + + let ast_ref = arg + .expr() + .filter(|_| requires_ast) + .map(|expr| IrAstRef(Arc::new(expr.clone()))); + + match arg { + Argument::Positional(_) | Argument::Unknown(_) => { + compiled_args.push(CompiledArg::Positional( + arg_reg.expect("expr() None in non-Named"), + arg.span(), + ast_ref, + )) + } + Argument::Named((name, short, _)) => compiled_args.push(CompiledArg::Named( + &name.item, + short.as_ref().map(|spanned| spanned.item.as_str()), + arg_reg, + arg.span(), + ast_ref, + )), + Argument::Spread(_) => compiled_args.push(CompiledArg::Spread( + arg_reg.expect("expr() None in non-Named"), + arg.span(), + ast_ref, + )), + } + } + + // Now that the args are all compiled, set up the call state (argument stack and redirections) + for arg in compiled_args { + match arg { + CompiledArg::Positional(reg, span, ast_ref) => { + builder.push(Instruction::PushPositional { src: reg }.into_spanned(span))?; + builder.set_last_ast(ast_ref); + } + CompiledArg::Named(name, short, Some(reg), span, ast_ref) => { + if !name.is_empty() { + let name = builder.data(name)?; + builder.push(Instruction::PushNamed { name, src: reg }.into_spanned(span))?; + } else { + let short = builder.data(short.unwrap_or(""))?; + builder + .push(Instruction::PushShortNamed { short, src: reg }.into_spanned(span))?; + } + builder.set_last_ast(ast_ref); + } + CompiledArg::Named(name, short, None, span, ast_ref) => { + if !name.is_empty() { + let name = builder.data(name)?; + builder.push(Instruction::PushFlag { name }.into_spanned(span))?; + } else { + let short = builder.data(short.unwrap_or(""))?; + builder.push(Instruction::PushShortFlag { short }.into_spanned(span))?; + } + builder.set_last_ast(ast_ref); + } + CompiledArg::Spread(reg, span, ast_ref) => { + builder.push(Instruction::AppendRest { src: reg }.into_spanned(span))?; + builder.set_last_ast(ast_ref); + } + } + } + + // Add any parser info from the call + for (name, info) in &call.parser_info { + let name = builder.data(name)?; + let info = Box::new(info.clone()); + builder.push(Instruction::PushParserInfo { name, info }.into_spanned(call.head))?; + } + + if let Some(mode) = redirect_modes.out { + builder.push(mode.map(|mode| Instruction::RedirectOut { mode }))?; + } + + if let Some(mode) = redirect_modes.err { + builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?; + } + + // The state is set up, so we can do the call into io_reg + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + + Ok(()) +} + +pub(crate) fn compile_help( + working_set: &StateWorkingSet<'_>, + builder: &mut BlockBuilder, + decl_name: Spanned<&str>, + io_reg: RegId, +) -> Result<(), CompileError> { + let help_command_id = + working_set + .find_decl(b"help") + .ok_or_else(|| CompileError::MissingRequiredDeclaration { + decl_name: "help".into(), + span: decl_name.span, + })?; + + let name_data = builder.data(decl_name.item)?; + let name_literal = builder.literal(decl_name.map(|_| Literal::String(name_data)))?; + + builder.push(Instruction::PushPositional { src: name_literal }.into_spanned(decl_name.span))?; + + builder.push( + Instruction::Call { + decl_id: help_command_id, + src_dst: io_reg, + } + .into_spanned(decl_name.span), + )?; + + Ok(()) +} + +pub(crate) fn compile_external_call( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + head: &Expression, + args: &[ExternalArgument], + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pass everything to run-external + let run_external_id = working_set + .find_decl(b"run-external") + .ok_or(CompileError::RunExternalNotFound { span: head.span })?; + + let mut call = Call::new(head.span); + call.decl_id = run_external_id; + + call.arguments.push(Argument::Positional(head.clone())); + + for arg in args { + match arg { + ExternalArgument::Regular(expr) => { + call.arguments.push(Argument::Positional(expr.clone())); + } + ExternalArgument::Spread(expr) => { + call.arguments.push(Argument::Spread(expr.clone())); + } + } + } + + compile_call(working_set, builder, &call, redirect_modes, io_reg) +} diff --git a/crates/nu-engine/src/compile/expression.rs b/crates/nu-engine/src/compile/expression.rs new file mode 100644 index 0000000000..38ee58ea26 --- /dev/null +++ b/crates/nu-engine/src/compile/expression.rs @@ -0,0 +1,535 @@ +use super::{ + compile_binary_op, compile_block, compile_call, compile_external_call, compile_load_env, + BlockBuilder, CompileError, RedirectModes, +}; + +use nu_protocol::{ + ast::{CellPath, Expr, Expression, ListItem, RecordItem, ValueWithUnit}, + engine::StateWorkingSet, + ir::{DataSlice, Instruction, Literal}, + IntoSpanned, RegId, Span, Value, ENV_VARIABLE_ID, +}; + +pub(crate) fn compile_expression( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + expr: &Expression, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let drop_input = |builder: &mut BlockBuilder| { + if let Some(in_reg) = in_reg { + if in_reg != out_reg { + builder.drop_reg(in_reg)?; + } + } + Ok(()) + }; + + let lit = |builder: &mut BlockBuilder, literal: Literal| { + drop_input(builder)?; + + builder + .push( + Instruction::LoadLiteral { + dst: out_reg, + lit: literal, + } + .into_spanned(expr.span), + ) + .map(|_| ()) + }; + + let ignore = |builder: &mut BlockBuilder| { + drop_input(builder)?; + builder.load_empty(out_reg) + }; + + let unexpected = |expr_name: &str| CompileError::UnexpectedExpression { + expr_name: expr_name.into(), + span: expr.span, + }; + + let move_in_reg_to_out_reg = |builder: &mut BlockBuilder| { + // Ensure that out_reg contains the input value, because a call only uses one register + if let Some(in_reg) = in_reg { + if in_reg != out_reg { + // Have to move in_reg to out_reg so it can be used + builder.push( + Instruction::Move { + dst: out_reg, + src: in_reg, + } + .into_spanned(expr.span), + )?; + } + } else { + // Will have to initialize out_reg with Empty first + builder.load_empty(out_reg)?; + } + Ok(()) + }; + + match &expr.expr { + Expr::Bool(b) => lit(builder, Literal::Bool(*b)), + Expr::Int(i) => lit(builder, Literal::Int(*i)), + Expr::Float(f) => lit(builder, Literal::Float(*f)), + Expr::Binary(bin) => { + let data_slice = builder.data(bin)?; + lit(builder, Literal::Binary(data_slice)) + } + Expr::Range(range) => { + // Compile the subexpressions of the range + let compile_part = |builder: &mut BlockBuilder, + part_expr: Option<&Expression>| + -> Result { + let reg = builder.next_register()?; + if let Some(part_expr) = part_expr { + compile_expression( + working_set, + builder, + part_expr, + RedirectModes::capture_out(part_expr.span), + None, + reg, + )?; + } else { + builder.load_literal(reg, Literal::Nothing.into_spanned(expr.span))?; + } + Ok(reg) + }; + + drop_input(builder)?; + + let start = compile_part(builder, range.from.as_ref())?; + let step = compile_part(builder, range.next.as_ref())?; + let end = compile_part(builder, range.to.as_ref())?; + + // Assemble the range + builder.load_literal( + out_reg, + Literal::Range { + start, + step, + end, + inclusion: range.operator.inclusion, + } + .into_spanned(expr.span), + ) + } + Expr::Var(var_id) => { + drop_input(builder)?; + builder.push( + Instruction::LoadVariable { + dst: out_reg, + var_id: *var_id, + } + .into_spanned(expr.span), + )?; + Ok(()) + } + Expr::VarDecl(_) => Err(unexpected("VarDecl")), + Expr::Call(call) => { + move_in_reg_to_out_reg(builder)?; + + compile_call(working_set, builder, call, redirect_modes, out_reg) + } + Expr::ExternalCall(head, args) => { + move_in_reg_to_out_reg(builder)?; + + compile_external_call(working_set, builder, head, args, redirect_modes, out_reg) + } + Expr::Operator(_) => Err(unexpected("Operator")), + Expr::RowCondition(block_id) => lit(builder, Literal::RowCondition(*block_id)), + Expr::UnaryNot(subexpr) => { + drop_input(builder)?; + compile_expression( + working_set, + builder, + subexpr, + RedirectModes::capture_out(subexpr.span), + None, + out_reg, + )?; + builder.push(Instruction::Not { src_dst: out_reg }.into_spanned(expr.span))?; + Ok(()) + } + Expr::BinaryOp(lhs, op, rhs) => { + if let Expr::Operator(ref operator) = op.expr { + drop_input(builder)?; + compile_binary_op( + working_set, + builder, + lhs, + operator.clone().into_spanned(op.span), + rhs, + expr.span, + out_reg, + ) + } else { + Err(CompileError::UnsupportedOperatorExpression { span: op.span }) + } + } + Expr::Subexpression(block_id) => { + let block = working_set.get_block(*block_id); + compile_block(working_set, builder, block, redirect_modes, in_reg, out_reg) + } + Expr::Block(block_id) => lit(builder, Literal::Block(*block_id)), + Expr::Closure(block_id) => lit(builder, Literal::Closure(*block_id)), + Expr::MatchBlock(_) => Err(unexpected("MatchBlock")), // only for `match` keyword + Expr::List(items) => { + // Guess capacity based on items (does not consider spread as more than 1) + lit( + builder, + Literal::List { + capacity: items.len(), + }, + )?; + for item in items { + // Compile the expression of the item / spread + let reg = builder.next_register()?; + let expr = match item { + ListItem::Item(expr) | ListItem::Spread(_, expr) => expr, + }; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + reg, + )?; + + match item { + ListItem::Item(_) => { + // Add each item using list-push + builder.push( + Instruction::ListPush { + src_dst: out_reg, + item: reg, + } + .into_spanned(expr.span), + )?; + } + ListItem::Spread(spread_span, _) => { + // Spread the list using list-spread + builder.push( + Instruction::ListSpread { + src_dst: out_reg, + items: reg, + } + .into_spanned(*spread_span), + )?; + } + } + } + Ok(()) + } + Expr::Table(table) => { + lit( + builder, + Literal::List { + capacity: table.rows.len(), + }, + )?; + + // Evaluate the columns + let column_registers = table + .columns + .iter() + .map(|column| { + let reg = builder.next_register()?; + compile_expression( + working_set, + builder, + column, + RedirectModes::capture_out(column.span), + None, + reg, + )?; + Ok(reg) + }) + .collect::, CompileError>>()?; + + // Build records for each row + for row in table.rows.iter() { + let row_reg = builder.next_register()?; + builder.load_literal( + row_reg, + Literal::Record { + capacity: table.columns.len(), + } + .into_spanned(expr.span), + )?; + for (column_reg, item) in column_registers.iter().zip(row.iter()) { + let column_reg = builder.clone_reg(*column_reg, item.span)?; + let item_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + item, + RedirectModes::capture_out(item.span), + None, + item_reg, + )?; + builder.push( + Instruction::RecordInsert { + src_dst: row_reg, + key: column_reg, + val: item_reg, + } + .into_spanned(item.span), + )?; + } + builder.push( + Instruction::ListPush { + src_dst: out_reg, + item: row_reg, + } + .into_spanned(expr.span), + )?; + } + + // Free the column registers, since they aren't needed anymore + for reg in column_registers { + builder.drop_reg(reg)?; + } + + Ok(()) + } + Expr::Record(items) => { + lit( + builder, + Literal::Record { + capacity: items.len(), + }, + )?; + + for item in items { + match item { + RecordItem::Pair(key, val) => { + // Add each item using record-insert + let key_reg = builder.next_register()?; + let val_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + key, + RedirectModes::capture_out(key.span), + None, + key_reg, + )?; + compile_expression( + working_set, + builder, + val, + RedirectModes::capture_out(val.span), + None, + val_reg, + )?; + builder.push( + Instruction::RecordInsert { + src_dst: out_reg, + key: key_reg, + val: val_reg, + } + .into_spanned(expr.span), + )?; + } + RecordItem::Spread(spread_span, expr) => { + // Spread the expression using record-spread + let reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + reg, + )?; + builder.push( + Instruction::RecordSpread { + src_dst: out_reg, + items: reg, + } + .into_spanned(*spread_span), + )?; + } + } + } + Ok(()) + } + Expr::Keyword(kw) => { + // keyword: just pass through expr, since commands that use it and are not being + // specially handled already are often just positional anyway + compile_expression( + working_set, + builder, + &kw.expr, + redirect_modes, + in_reg, + out_reg, + ) + } + Expr::ValueWithUnit(value_with_unit) => { + lit(builder, literal_from_value_with_unit(value_with_unit)?) + } + Expr::DateTime(dt) => lit(builder, Literal::Date(Box::new(*dt))), + Expr::Filepath(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::Filepath { + val, + no_expand: *no_expand, + }, + ) + } + Expr::Directory(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::Directory { + val, + no_expand: *no_expand, + }, + ) + } + Expr::GlobPattern(path, no_expand) => { + let val = builder.data(path)?; + lit( + builder, + Literal::GlobPattern { + val, + no_expand: *no_expand, + }, + ) + } + Expr::String(s) => { + let data_slice = builder.data(s)?; + lit(builder, Literal::String(data_slice)) + } + Expr::RawString(rs) => { + let data_slice = builder.data(rs)?; + lit(builder, Literal::RawString(data_slice)) + } + Expr::CellPath(path) => lit(builder, Literal::CellPath(Box::new(path.clone()))), + Expr::FullCellPath(full_cell_path) => { + if matches!(full_cell_path.head.expr, Expr::Var(ENV_VARIABLE_ID)) { + compile_load_env(builder, expr.span, &full_cell_path.tail, out_reg) + } else { + compile_expression( + working_set, + builder, + &full_cell_path.head, + RedirectModes::capture_out(expr.span), + in_reg, + out_reg, + )?; + // Only do the follow if this is actually needed + if !full_cell_path.tail.is_empty() { + let cell_path_reg = builder.literal( + Literal::CellPath(Box::new(CellPath { + members: full_cell_path.tail.clone(), + })) + .into_spanned(expr.span), + )?; + builder.push( + Instruction::FollowCellPath { + src_dst: out_reg, + path: cell_path_reg, + } + .into_spanned(expr.span), + )?; + } + Ok(()) + } + } + Expr::ImportPattern(_) => Err(unexpected("ImportPattern")), + Expr::Overlay(_) => Err(unexpected("Overlay")), + Expr::Signature(_) => ignore(builder), // no effect + Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => { + let mut exprs_iter = exprs.iter().peekable(); + + if exprs_iter + .peek() + .is_some_and(|e| matches!(e.expr, Expr::String(..) | Expr::RawString(..))) + { + // If the first expression is a string or raw string literal, just take it and build + // from that + compile_expression( + working_set, + builder, + exprs_iter.next().expect("peek() was Some"), + RedirectModes::capture_out(expr.span), + None, + out_reg, + )?; + } else { + // Start with an empty string + lit(builder, Literal::String(DataSlice::empty()))?; + } + + // Compile each expression and append to out_reg + for expr in exprs_iter { + let scratch_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(expr.span), + None, + scratch_reg, + )?; + builder.push( + Instruction::StringAppend { + src_dst: out_reg, + val: scratch_reg, + } + .into_spanned(expr.span), + )?; + } + + // If it's a glob interpolation, change it to a glob + if let Expr::GlobInterpolation(_, no_expand) = expr.expr { + builder.push( + Instruction::GlobFrom { + src_dst: out_reg, + no_expand, + } + .into_spanned(expr.span), + )?; + } + + Ok(()) + } + Expr::Nothing => lit(builder, Literal::Nothing), + Expr::Garbage => Err(CompileError::Garbage { span: expr.span }), + } +} + +fn literal_from_value_with_unit(value_with_unit: &ValueWithUnit) -> Result { + let Expr::Int(int_value) = value_with_unit.expr.expr else { + return Err(CompileError::UnexpectedExpression { + expr_name: format!("{:?}", value_with_unit.expr), + span: value_with_unit.expr.span, + }); + }; + + match value_with_unit + .unit + .item + .build_value(int_value, Span::unknown()) + .map_err(|err| CompileError::InvalidLiteral { + msg: err.to_string(), + span: value_with_unit.expr.span, + })? { + Value::Filesize { val, .. } => Ok(Literal::Filesize(val)), + Value::Duration { val, .. } => Ok(Literal::Duration(val)), + other => Err(CompileError::InvalidLiteral { + msg: format!("bad value returned by Unit::build_value(): {other:?}"), + span: value_with_unit.unit.span, + }), + } +} diff --git a/crates/nu-engine/src/compile/keyword.rs b/crates/nu-engine/src/compile/keyword.rs new file mode 100644 index 0000000000..12f4a54c10 --- /dev/null +++ b/crates/nu-engine/src/compile/keyword.rs @@ -0,0 +1,902 @@ +use nu_protocol::{ + ast::{Block, Call, Expr, Expression}, + engine::StateWorkingSet, + ir::Instruction, + IntoSpanned, RegId, Type, VarId, +}; + +use super::{compile_block, compile_expression, BlockBuilder, CompileError, RedirectModes}; + +/// Compile a call to `if` as a branch-if +pub(crate) fn compile_if( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- + // not %io_reg + // branch-if %io_reg, FALSE + // TRUE: ...... + // jump END + // FALSE: ...... OR drop %io_reg + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "if".into(), + span: call.head, + }; + + let condition = call.positional_nth(0).ok_or_else(invalid)?; + let true_block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let else_arg = call.positional_nth(2); + + let true_block_id = true_block_arg.as_block().ok_or_else(invalid)?; + let true_block = working_set.get_block(true_block_id); + + let true_label = builder.label(None); + let false_label = builder.label(None); + let end_label = builder.label(None); + + let not_condition_reg = { + // Compile the condition first + let condition_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + condition, + RedirectModes::capture_out(condition.span), + None, + condition_reg, + )?; + + // Negate the condition - we basically only want to jump if the condition is false + builder.push( + Instruction::Not { + src_dst: condition_reg, + } + .into_spanned(call.head), + )?; + + condition_reg + }; + + // Set up a branch if the condition is false. + builder.branch_if(not_condition_reg, false_label, call.head)?; + builder.add_comment("if false"); + + // Compile the true case + builder.set_label(true_label, builder.here())?; + compile_block( + working_set, + builder, + true_block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + + // Add a jump over the false case + builder.jump(end_label, else_arg.map(|e| e.span).unwrap_or(call.head))?; + builder.add_comment("end if"); + + // On the else side now, assert that io_reg is still valid + builder.set_label(false_label, builder.here())?; + builder.mark_register(io_reg)?; + + if let Some(else_arg) = else_arg { + let Expression { + expr: Expr::Keyword(else_keyword), + .. + } = else_arg + else { + return Err(invalid()); + }; + + if else_keyword.keyword.as_ref() != b"else" { + return Err(invalid()); + } + + let else_expr = &else_keyword.expr; + + match &else_expr.expr { + Expr::Block(block_id) => { + let false_block = working_set.get_block(*block_id); + compile_block( + working_set, + builder, + false_block, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + _ => { + // The else case supports bare expressions too, not only blocks + compile_expression( + working_set, + builder, + else_expr, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + } + } else { + // We don't have an else expression/block, so just set io_reg = Empty + builder.load_empty(io_reg)?; + } + + // Set the end label + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `match` +pub(crate) fn compile_match( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %match_reg <- + // collect %match_reg + // match (pat1), %match_reg, PAT1 + // MATCH2: match (pat2), %match_reg, PAT2 + // FAIL: drop %io_reg + // drop %match_reg + // jump END + // PAT1: %guard_reg <- + // check-match-guard %guard_reg + // not %guard_reg + // branch-if %guard_reg, MATCH2 + // drop %match_reg + // <...expr...> + // jump END + // PAT2: drop %match_reg + // <...expr...> + // jump END + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "match".into(), + span: call.head, + }; + + let match_expr = call.positional_nth(0).ok_or_else(invalid)?; + + let match_block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let match_block = match_block_arg.as_match_block().ok_or_else(invalid)?; + + let match_reg = builder.next_register()?; + + // Evaluate the match expression (patterns will be checked against this). + compile_expression( + working_set, + builder, + match_expr, + RedirectModes::capture_out(match_expr.span), + None, + match_reg, + )?; + + // Important to collect it first + builder.push(Instruction::Collect { src_dst: match_reg }.into_spanned(match_expr.span))?; + + // Generate the `match` instructions. Guards are not used at this stage. + let mut match_labels = Vec::with_capacity(match_block.len()); + let mut next_labels = Vec::with_capacity(match_block.len()); + let end_label = builder.label(None); + + for (pattern, _) in match_block { + let match_label = builder.label(None); + match_labels.push(match_label); + builder.push( + Instruction::Match { + pattern: Box::new(pattern.pattern.clone()), + src: match_reg, + index: match_label.0, + } + .into_spanned(pattern.span), + )?; + // Also add a label for the next match instruction or failure case + next_labels.push(builder.label(Some(builder.here()))); + } + + // Match fall-through to jump to the end, if no match + builder.load_empty(io_reg)?; + builder.drop_reg(match_reg)?; + builder.jump(end_label, call.head)?; + + // Generate each of the match expressions. Handle guards here, if present. + for (index, (pattern, expr)) in match_block.iter().enumerate() { + let match_label = match_labels[index]; + let next_label = next_labels[index]; + + // `io_reg` and `match_reg` are still valid at each of these branch targets + builder.mark_register(io_reg)?; + builder.mark_register(match_reg)?; + + // Set the original match instruction target here + builder.set_label(match_label, builder.here())?; + + // Handle guard, if present + if let Some(guard) = &pattern.guard { + let guard_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + guard, + RedirectModes::capture_out(guard.span), + None, + guard_reg, + )?; + builder + .push(Instruction::CheckMatchGuard { src: guard_reg }.into_spanned(guard.span))?; + builder.push(Instruction::Not { src_dst: guard_reg }.into_spanned(guard.span))?; + // Branch to the next match instruction if the branch fails to match + builder.branch_if( + guard_reg, + next_label, + // Span the branch with the next pattern, or the head if this is the end + match_block + .get(index + 1) + .map(|b| b.0.span) + .unwrap_or(call.head), + )?; + builder.add_comment("if match guard false"); + } + + // match_reg no longer needed, successful match + builder.drop_reg(match_reg)?; + + // Execute match right hand side expression + if let Some(block_id) = expr.as_block() { + let block = working_set.get_block(block_id); + compile_block( + working_set, + builder, + block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + } else { + compile_expression( + working_set, + builder, + expr, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + } + + // Jump to the end after the match logic is done + builder.jump(end_label, call.head)?; + builder.add_comment("end match"); + } + + // Set the end destination + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `let` or `mut` (just do store-variable) +pub(crate) fn compile_let( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- ...... <- %io_reg + // store-variable $var, %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "let".into(), + span: call.head, + }; + + let var_decl_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_arg = call.positional_nth(1).ok_or_else(invalid)?; + + let var_id = var_decl_arg.as_var().ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let variable = working_set.get_variable(var_id); + + compile_block( + working_set, + builder, + block, + RedirectModes::capture_out(call.head), + Some(io_reg), + io_reg, + )?; + + // If the variable is a glob type variable, we should cast it with GlobFrom + if variable.ty == Type::Glob { + builder.push( + Instruction::GlobFrom { + src_dst: io_reg, + no_expand: true, + } + .into_spanned(call.head), + )?; + } + + builder.push( + Instruction::StoreVariable { + var_id, + src: io_reg, + } + .into_spanned(call.head), + )?; + builder.add_comment("let"); + + // Don't forget to set io_reg to Empty afterward, as that's the result of an assignment + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `try`, setting an error handler over the evaluated block +pub(crate) fn compile_try( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode (literal block): + // + // on-error-into ERR, %io_reg // or without + // %io_reg <- <...block...> <- %io_reg + // check-external-failed %failed_reg, %io_reg + // branch-if %failed_reg, FAIL + // pop-error-handler + // jump END + // FAIL: drain %io_reg + // unreachable + // ERR: clone %err_reg, %io_reg + // store-variable $err_var, %err_reg // or without + // %io_reg <- <...catch block...> <- %io_reg // set to empty if no catch block + // END: + // + // with expression that can't be inlined: + // + // %closure_reg <- + // on-error-into ERR, %io_reg + // %io_reg <- <...block...> <- %io_reg + // check-external-failed %failed_reg, %io_reg + // branch-if %failed_reg, FAIL + // pop-error-handler + // jump END + // FAIL: drain %io_reg + // unreachable + // ERR: clone %err_reg, %io_reg + // push-positional %closure_reg + // push-positional %err_reg + // call "do", %io_reg + // END: + let invalid = || CompileError::InvalidKeywordCall { + keyword: "try".into(), + span: call.head, + }; + + let block_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let catch_expr = match call.positional_nth(1) { + Some(kw_expr) => Some(kw_expr.as_keyword().ok_or_else(invalid)?), + None => None, + }; + let catch_span = catch_expr.map(|e| e.span).unwrap_or(call.head); + + let err_label = builder.label(None); + let failed_label = builder.label(None); + let end_label = builder.label(None); + + // We have two ways of executing `catch`: if it was provided as a literal, we can inline it. + // Otherwise, we have to evaluate the expression and keep it as a register, and then call `do`. + enum CatchType<'a> { + Block { + block: &'a Block, + var_id: Option, + }, + Closure { + closure_reg: RegId, + }, + } + + let catch_type = catch_expr + .map(|catch_expr| match catch_expr.as_block() { + Some(block_id) => { + let block = working_set.get_block(block_id); + let var_id = block.signature.get_positional(0).and_then(|v| v.var_id); + Ok(CatchType::Block { block, var_id }) + } + None => { + // We have to compile the catch_expr and use it as a closure + let closure_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + catch_expr, + RedirectModes::capture_out(catch_expr.span), + None, + closure_reg, + )?; + Ok(CatchType::Closure { closure_reg }) + } + }) + .transpose()?; + + // Put the error handler instruction. If we have a catch expression then we should capture the + // error. + if catch_type.is_some() { + builder.push( + Instruction::OnErrorInto { + index: err_label.0, + dst: io_reg, + } + .into_spanned(call.head), + )? + } else { + // Otherwise, we don't need the error value. + builder.push(Instruction::OnError { index: err_label.0 }.into_spanned(call.head))? + }; + + builder.add_comment("try"); + + // Compile the block + compile_block( + working_set, + builder, + block, + redirect_modes.clone(), + Some(io_reg), + io_reg, + )?; + + // Check for external command exit code failure, and also redirect that to the catch handler + let failed_reg = builder.next_register()?; + builder.push( + Instruction::CheckExternalFailed { + dst: failed_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + builder.branch_if(failed_reg, failed_label, catch_span)?; + + // Successful case: pop the error handler + builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?; + + // Jump over the failure case + builder.jump(end_label, catch_span)?; + + // Set up an error handler preamble for failed external. + // Draining the %io_reg results in the error handler being called with Empty, and sets + // $env.LAST_EXIT_CODE + builder.set_label(failed_label, builder.here())?; + builder.drain(io_reg, catch_span)?; + builder.add_comment("branches to err"); + builder.unreachable(catch_span)?; + + // This is the real error handler + builder.set_label(err_label, builder.here())?; + + // Mark out register as likely not clean - state in error handler is not well defined + builder.mark_register(io_reg)?; + + // Now compile whatever is necessary for the error handler + match catch_type { + Some(CatchType::Block { block, var_id }) => { + // Error will be in io_reg + builder.mark_register(io_reg)?; + if let Some(var_id) = var_id { + // Take a copy of the error as $err, since it will also be input + let err_reg = builder.next_register()?; + builder.push( + Instruction::Clone { + dst: err_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + builder.push( + Instruction::StoreVariable { + var_id, + src: err_reg, + } + .into_spanned(catch_span), + )?; + } + // Compile the block, now that the variable is set + compile_block( + working_set, + builder, + block, + redirect_modes, + Some(io_reg), + io_reg, + )?; + } + Some(CatchType::Closure { closure_reg }) => { + // We should call `do`. Error will be in io_reg + let do_decl_id = working_set.find_decl(b"do").ok_or_else(|| { + CompileError::MissingRequiredDeclaration { + decl_name: "do".into(), + span: call.head, + } + })?; + + // Take a copy of io_reg, because we pass it both as an argument and input + builder.mark_register(io_reg)?; + let err_reg = builder.next_register()?; + builder.push( + Instruction::Clone { + dst: err_reg, + src: io_reg, + } + .into_spanned(catch_span), + )?; + + // Push the closure and the error + builder + .push(Instruction::PushPositional { src: closure_reg }.into_spanned(catch_span))?; + builder.push(Instruction::PushPositional { src: err_reg }.into_spanned(catch_span))?; + + // Call `$err | do $closure $err` + builder.push( + Instruction::Call { + decl_id: do_decl_id, + src_dst: io_reg, + } + .into_spanned(catch_span), + )?; + } + None => { + // Just set out to empty. + builder.load_empty(io_reg)?; + } + } + + // This is the end - if we succeeded, should jump here + builder.set_label(end_label, builder.here())?; + + Ok(()) +} + +/// Compile a call to `loop` (via `jump`) +pub(crate) fn compile_loop( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // drop %io_reg + // LOOP: %io_reg <- ...... + // drain %io_reg + // jump %LOOP + // END: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "loop".into(), + span: call.head, + }; + + let block_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let loop_ = builder.begin_loop(); + builder.load_empty(io_reg)?; + + builder.set_label(loop_.continue_label, builder.here())?; + + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the output, just like for a semicolon + builder.drain(io_reg, call.head)?; + + builder.jump(loop_.continue_label, call.head)?; + builder.add_comment("loop"); + + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // State of %io_reg is not necessarily well defined here due to control flow, so make sure it's + // empty. + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `while`, via branch instructions +pub(crate) fn compile_while( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // LOOP: %io_reg <- + // branch-if %io_reg, TRUE + // jump FALSE + // TRUE: %io_reg <- ...... + // drain %io_reg + // jump LOOP + // FALSE: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "while".into(), + span: call.head, + }; + + let cond_arg = call.positional_nth(0).ok_or_else(invalid)?; + let block_arg = call.positional_nth(1).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + let loop_ = builder.begin_loop(); + builder.set_label(loop_.continue_label, builder.here())?; + + let true_label = builder.label(None); + + compile_expression( + working_set, + builder, + cond_arg, + RedirectModes::capture_out(call.head), + None, + io_reg, + )?; + + builder.branch_if(io_reg, true_label, call.head)?; + builder.add_comment("while"); + builder.jump(loop_.break_label, call.head)?; + builder.add_comment("end while"); + + builder.set_label(true_label, builder.here())?; + + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the result, just like for a semicolon + builder.drain(io_reg, call.head)?; + + builder.jump(loop_.continue_label, call.head)?; + builder.add_comment("while"); + + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // State of %io_reg is not necessarily well defined here due to control flow, so make sure it's + // empty. + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `for` (via `iterate`) +pub(crate) fn compile_for( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %stream_reg <- + // LOOP: iterate %io_reg, %stream_reg, END + // store-variable $var, %io_reg + // %io_reg <- <...block...> + // drain %io_reg + // jump LOOP + // END: drop %io_reg + let invalid = || CompileError::InvalidKeywordCall { + keyword: "for".into(), + span: call.head, + }; + + if call.get_named_arg("numbered").is_some() { + // This is deprecated and we don't support it. + return Err(invalid()); + } + + let var_decl_arg = call.positional_nth(0).ok_or_else(invalid)?; + let var_id = var_decl_arg.as_var().ok_or_else(invalid)?; + + let in_arg = call.positional_nth(1).ok_or_else(invalid)?; + let in_expr = in_arg.as_keyword().ok_or_else(invalid)?; + + let block_arg = call.positional_nth(2).ok_or_else(invalid)?; + let block_id = block_arg.as_block().ok_or_else(invalid)?; + let block = working_set.get_block(block_id); + + // Ensure io_reg is marked so we don't use it + builder.mark_register(io_reg)?; + + let stream_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + in_expr, + RedirectModes::capture_out(in_expr.span), + None, + stream_reg, + )?; + + // Set up loop state + let loop_ = builder.begin_loop(); + builder.set_label(loop_.continue_label, builder.here())?; + + // This gets a value from the stream each time it's executed + // io_reg basically will act as our scratch register here + builder.push( + Instruction::Iterate { + dst: io_reg, + stream: stream_reg, + end_index: loop_.break_label.0, + } + .into_spanned(call.head), + )?; + builder.add_comment("for"); + + // Put the received value in the variable + builder.push( + Instruction::StoreVariable { + var_id, + src: io_reg, + } + .into_spanned(var_decl_arg.span), + )?; + + // Do the body of the block + compile_block( + working_set, + builder, + block, + RedirectModes::default(), + None, + io_reg, + )?; + + // Drain the output, just like for a semicolon + builder.drain(io_reg, call.head)?; + + // Loop back to iterate to get the next value + builder.jump(loop_.continue_label, call.head)?; + + // Set the end of the loop + builder.set_label(loop_.break_label, builder.here())?; + builder.end_loop(loop_)?; + + // We don't need stream_reg anymore, after the loop + // io_reg may or may not be empty, so be sure it is + builder.free_register(stream_reg)?; + builder.mark_register(io_reg)?; + builder.load_empty(io_reg)?; + + Ok(()) +} + +/// Compile a call to `break`. +pub(crate) fn compile_break( + _working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + if builder.is_in_loop() { + builder.load_empty(io_reg)?; + builder.push_break(call.head)?; + builder.add_comment("break"); + } else { + // Fall back to calling the command if we can't find the loop target statically + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + } + Ok(()) +} + +/// Compile a call to `continue`. +pub(crate) fn compile_continue( + _working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + if builder.is_in_loop() { + builder.load_empty(io_reg)?; + builder.push_continue(call.head)?; + builder.add_comment("continue"); + } else { + // Fall back to calling the command if we can't find the loop target statically + builder.push( + Instruction::Call { + decl_id: call.decl_id, + src_dst: io_reg, + } + .into_spanned(call.head), + )?; + } + Ok(()) +} + +/// Compile a call to `return` as a `return-early` instruction. +/// +/// This is not strictly necessary, but it is more efficient. +pub(crate) fn compile_return( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + call: &Call, + _redirect_modes: RedirectModes, + io_reg: RegId, +) -> Result<(), CompileError> { + // Pseudocode: + // + // %io_reg <- + // return-early %io_reg + if let Some(arg_expr) = call.positional_nth(0) { + compile_expression( + working_set, + builder, + arg_expr, + RedirectModes::capture_out(arg_expr.span), + None, + io_reg, + )?; + } else { + builder.load_empty(io_reg)?; + } + + // TODO: It would be nice if this could be `return` instead, but there is a little bit of + // behaviour remaining that still depends on `ShellError::Return` + builder.push(Instruction::ReturnEarly { src: io_reg }.into_spanned(call.head))?; + + // io_reg is supposed to remain allocated + builder.load_empty(io_reg)?; + + Ok(()) +} diff --git a/crates/nu-engine/src/compile/mod.rs b/crates/nu-engine/src/compile/mod.rs new file mode 100644 index 0000000000..8f6ae22682 --- /dev/null +++ b/crates/nu-engine/src/compile/mod.rs @@ -0,0 +1,204 @@ +use nu_protocol::{ + ast::{Block, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget}, + engine::StateWorkingSet, + ir::{Instruction, IrBlock, RedirectMode}, + CompileError, IntoSpanned, RegId, Span, +}; + +mod builder; +mod call; +mod expression; +mod keyword; +mod operator; +mod redirect; + +use builder::BlockBuilder; +use call::*; +use expression::compile_expression; +use operator::*; +use redirect::*; + +const BLOCK_INPUT: RegId = RegId(0); + +/// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions +/// for evaluation. +pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result { + let mut builder = BlockBuilder::new(block.span); + + let span = block.span.unwrap_or(Span::unknown()); + + compile_block( + working_set, + &mut builder, + block, + RedirectModes::caller(span), + Some(BLOCK_INPUT), + BLOCK_INPUT, + )?; + + // A complete block has to end with a `return` + builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?; + + builder.finish() +} + +/// Compiles a [`Block`] in-place into an IR block. This can be used in a nested manner, for example +/// by [`compile_if()`], where the instructions for the blocks for the if/else are inlined into the +/// top-level IR block. +fn compile_block( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + block: &Block, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let span = block.span.unwrap_or(Span::unknown()); + let mut redirect_modes = Some(redirect_modes); + if !block.pipelines.is_empty() { + let last_index = block.pipelines.len() - 1; + for (index, pipeline) in block.pipelines.iter().enumerate() { + compile_pipeline( + working_set, + builder, + pipeline, + span, + // the redirect mode only applies to the last pipeline. + if index == last_index { + redirect_modes + .take() + .expect("should only take redirect_modes once") + } else { + RedirectModes::default() + }, + // input is only passed to the first pipeline. + if index == 0 { in_reg } else { None }, + out_reg, + )?; + + if index != last_index { + // Explicitly drain the out reg after each non-final pipeline, because that's how + // the semicolon functions. + if builder.is_allocated(out_reg) { + builder.push(Instruction::Drain { src: out_reg }.into_spanned(span))?; + } + builder.load_empty(out_reg)?; + } + } + Ok(()) + } else if in_reg.is_none() { + builder.load_empty(out_reg) + } else { + Ok(()) + } +} + +fn compile_pipeline( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + pipeline: &Pipeline, + fallback_span: Span, + redirect_modes: RedirectModes, + in_reg: Option, + out_reg: RegId, +) -> Result<(), CompileError> { + let mut iter = pipeline.elements.iter().peekable(); + let mut in_reg = in_reg; + let mut redirect_modes = Some(redirect_modes); + while let Some(element) = iter.next() { + let span = element.pipe.unwrap_or(fallback_span); + + // We have to get the redirection mode from either the explicit redirection in the pipeline + // element, or from the next expression if it's specified there. If this is the last + // element, then it's from whatever is passed in as the mode to use. + + let next_redirect_modes = if let Some(next_element) = iter.peek() { + let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?; + + // If there's a next element with no inherent redirection we always pipe out *unless* + // this is a single redirection of stderr to pipe (e>|) + if modes.out.is_none() + && !matches!( + element.redirection, + Some(PipelineRedirection::Single { + source: RedirectionSource::Stderr, + target: RedirectionTarget::Pipe { .. } + }) + ) + { + let pipe_span = next_element.pipe.unwrap_or(next_element.expr.span); + modes.out = Some(RedirectMode::Pipe.into_spanned(pipe_span)); + } + + modes + } else { + redirect_modes + .take() + .expect("should only take redirect_modes once") + }; + + let spec_redirect_modes = match &element.redirection { + Some(PipelineRedirection::Single { source, target }) => { + let mode = redirection_target_to_mode(working_set, builder, target)?; + match source { + RedirectionSource::Stdout => RedirectModes { + out: Some(mode), + err: None, + }, + RedirectionSource::Stderr => RedirectModes { + out: None, + err: Some(mode), + }, + RedirectionSource::StdoutAndStderr => RedirectModes { + out: Some(mode), + err: Some(mode), + }, + } + } + Some(PipelineRedirection::Separate { out, err }) => { + // In this case, out and err must not both be Pipe + assert!( + !matches!( + (out, err), + ( + RedirectionTarget::Pipe { .. }, + RedirectionTarget::Pipe { .. } + ) + ), + "for Separate redirection, out and err targets must not both be Pipe" + ); + let out = redirection_target_to_mode(working_set, builder, out)?; + let err = redirection_target_to_mode(working_set, builder, err)?; + RedirectModes { + out: Some(out), + err: Some(err), + } + } + None => RedirectModes { + out: None, + err: None, + }, + }; + + let redirect_modes = RedirectModes { + out: spec_redirect_modes.out.or(next_redirect_modes.out), + err: spec_redirect_modes.err.or(next_redirect_modes.err), + }; + + compile_expression( + working_set, + builder, + &element.expr, + redirect_modes.clone(), + in_reg, + out_reg, + )?; + + // Clean up the redirection + finish_redirection(builder, redirect_modes, out_reg)?; + + // The next pipeline element takes input from this output + in_reg = Some(out_reg); + } + Ok(()) +} diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs new file mode 100644 index 0000000000..a1ed3f66df --- /dev/null +++ b/crates/nu-engine/src/compile/operator.rs @@ -0,0 +1,378 @@ +use nu_protocol::{ + ast::{Assignment, Boolean, CellPath, Expr, Expression, Math, Operator, PathMember}, + engine::StateWorkingSet, + ir::{Instruction, Literal}, + IntoSpanned, RegId, Span, Spanned, ENV_VARIABLE_ID, +}; +use nu_utils::IgnoreCaseExt; + +use super::{compile_expression, BlockBuilder, CompileError, RedirectModes}; + +pub(crate) fn compile_binary_op( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + lhs: &Expression, + op: Spanned, + rhs: &Expression, + span: Span, + out_reg: RegId, +) -> Result<(), CompileError> { + if let Operator::Assignment(assign_op) = op.item { + if let Some(decomposed_op) = decompose_assignment(assign_op) { + // Compiling an assignment that uses a binary op with the existing value + compile_binary_op( + working_set, + builder, + lhs, + decomposed_op.into_spanned(op.span), + rhs, + span, + out_reg, + )?; + } else { + // Compiling a plain assignment, where the current left-hand side value doesn't matter + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + out_reg, + )?; + } + + compile_assignment(working_set, builder, lhs, op.span, out_reg)?; + + // Load out_reg with Nothing, as that's the result of an assignment + builder.load_literal(out_reg, Literal::Nothing.into_spanned(op.span)) + } else { + // Not an assignment: just do the binary op + let lhs_reg = out_reg; + + compile_expression( + working_set, + builder, + lhs, + RedirectModes::capture_out(lhs.span), + None, + lhs_reg, + )?; + + match op.item { + // `and` / `or` are short-circuiting, and we can get by with one register and a branch + Operator::Boolean(Boolean::And) => { + let true_label = builder.label(None); + builder.branch_if(lhs_reg, true_label, op.span)?; + + // If the branch was not taken it's false, so short circuit to load false + let false_label = builder.label(None); + builder.jump(false_label, op.span)?; + + builder.set_label(true_label, builder.here())?; + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + lhs_reg, + )?; + + let end_label = builder.label(None); + builder.jump(end_label, op.span)?; + + // Consumed by `branch-if`, so we have to set it false again + builder.set_label(false_label, builder.here())?; + builder.load_literal(lhs_reg, Literal::Bool(false).into_spanned(lhs.span))?; + + builder.set_label(end_label, builder.here())?; + } + Operator::Boolean(Boolean::Or) => { + let true_label = builder.label(None); + builder.branch_if(lhs_reg, true_label, op.span)?; + + // If the branch was not taken it's false, so do the right-side expression + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + lhs_reg, + )?; + + let end_label = builder.label(None); + builder.jump(end_label, op.span)?; + + // Consumed by `branch-if`, so we have to set it true again + builder.set_label(true_label, builder.here())?; + builder.load_literal(lhs_reg, Literal::Bool(true).into_spanned(lhs.span))?; + + builder.set_label(end_label, builder.here())?; + } + _ => { + // Any other operator, via `binary-op` + let rhs_reg = builder.next_register()?; + + compile_expression( + working_set, + builder, + rhs, + RedirectModes::capture_out(rhs.span), + None, + rhs_reg, + )?; + + builder.push( + Instruction::BinaryOp { + lhs_dst: lhs_reg, + op: op.item, + rhs: rhs_reg, + } + .into_spanned(op.span), + )?; + } + } + + if lhs_reg != out_reg { + builder.push( + Instruction::Move { + dst: out_reg, + src: lhs_reg, + } + .into_spanned(op.span), + )?; + } + + builder.push(Instruction::Span { src_dst: out_reg }.into_spanned(span))?; + + Ok(()) + } +} + +/// The equivalent plain operator to use for an assignment, if any +pub(crate) fn decompose_assignment(assignment: Assignment) -> Option { + match assignment { + Assignment::Assign => None, + Assignment::PlusAssign => Some(Operator::Math(Math::Plus)), + Assignment::AppendAssign => Some(Operator::Math(Math::Append)), + Assignment::MinusAssign => Some(Operator::Math(Math::Minus)), + Assignment::MultiplyAssign => Some(Operator::Math(Math::Multiply)), + Assignment::DivideAssign => Some(Operator::Math(Math::Divide)), + } +} + +/// Compile assignment of the value in a register to a left-hand expression +pub(crate) fn compile_assignment( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + lhs: &Expression, + assignment_span: Span, + rhs_reg: RegId, +) -> Result<(), CompileError> { + match lhs.expr { + Expr::Var(var_id) => { + // Double check that the variable is supposed to be mutable + if !working_set.get_variable(var_id).mutable { + return Err(CompileError::AssignmentRequiresMutableVar { span: lhs.span }); + } + + builder.push( + Instruction::StoreVariable { + var_id, + src: rhs_reg, + } + .into_spanned(assignment_span), + )?; + Ok(()) + } + Expr::FullCellPath(ref path) => match (&path.head, &path.tail) { + ( + Expression { + expr: Expr::Var(var_id), + .. + }, + _, + ) if *var_id == ENV_VARIABLE_ID => { + // This will be an assignment to an environment variable. + let Some(PathMember::String { val: key, .. }) = path.tail.first() else { + return Err(CompileError::CannotReplaceEnv { span: lhs.span }); + }; + + // Some env vars can't be set by Nushell code. + const AUTOMATIC_NAMES: &[&str] = &["PWD", "FILE_PWD", "CURRENT_FILE"]; + if AUTOMATIC_NAMES.iter().any(|name| key.eq_ignore_case(name)) { + return Err(CompileError::AutomaticEnvVarSetManually { + envvar_name: "PWD".into(), + span: lhs.span, + }); + } + + let key_data = builder.data(key)?; + + let val_reg = if path.tail.len() > 1 { + // Get the current value of the head and first tail of the path, from env + let head_reg = builder.next_register()?; + + // We could use compile_load_env, but this shares the key data... + // Always use optional, because it doesn't matter if it's already there + builder.push( + Instruction::LoadEnvOpt { + dst: head_reg, + key: key_data, + } + .into_spanned(lhs.span), + )?; + + // Default to empty record so we can do further upserts + let default_label = builder.label(None); + let upsert_label = builder.label(None); + builder.branch_if_empty(head_reg, default_label, assignment_span)?; + builder.jump(upsert_label, assignment_span)?; + + builder.set_label(default_label, builder.here())?; + builder.load_literal( + head_reg, + Literal::Record { capacity: 0 }.into_spanned(lhs.span), + )?; + + // Do the upsert on the current value to incorporate rhs + builder.set_label(upsert_label, builder.here())?; + compile_upsert_cell_path( + builder, + (&path.tail[1..]).into_spanned(lhs.span), + head_reg, + rhs_reg, + assignment_span, + )?; + + head_reg + } else { + // Path has only one tail, so we don't need the current value to do an upsert, + // just set it directly to rhs + rhs_reg + }; + + // Finally, store the modified env variable + builder.push( + Instruction::StoreEnv { + key: key_data, + src: val_reg, + } + .into_spanned(assignment_span), + )?; + Ok(()) + } + (_, tail) if tail.is_empty() => { + // If the path tail is empty, we can really just treat this as if it were an + // assignment to the head + compile_assignment(working_set, builder, &path.head, assignment_span, rhs_reg) + } + _ => { + // Just a normal assignment to some path + let head_reg = builder.next_register()?; + + // Compile getting current value of the head expression + compile_expression( + working_set, + builder, + &path.head, + RedirectModes::capture_out(path.head.span), + None, + head_reg, + )?; + + // Upsert the tail of the path into the old value of the head expression + compile_upsert_cell_path( + builder, + path.tail.as_slice().into_spanned(lhs.span), + head_reg, + rhs_reg, + assignment_span, + )?; + + // Now compile the assignment of the updated value to the head + compile_assignment(working_set, builder, &path.head, assignment_span, head_reg) + } + }, + Expr::Garbage => Err(CompileError::Garbage { span: lhs.span }), + _ => Err(CompileError::AssignmentRequiresVar { span: lhs.span }), + } +} + +/// Compile an upsert-cell-path instruction, with known literal members +pub(crate) fn compile_upsert_cell_path( + builder: &mut BlockBuilder, + members: Spanned<&[PathMember]>, + src_dst: RegId, + new_value: RegId, + span: Span, +) -> Result<(), CompileError> { + let path_reg = builder.literal( + Literal::CellPath( + CellPath { + members: members.item.to_vec(), + } + .into(), + ) + .into_spanned(members.span), + )?; + builder.push( + Instruction::UpsertCellPath { + src_dst, + path: path_reg, + new_value, + } + .into_spanned(span), + )?; + Ok(()) +} + +/// Compile the correct sequence to get an environment variable + follow a path on it +pub(crate) fn compile_load_env( + builder: &mut BlockBuilder, + span: Span, + path: &[PathMember], + out_reg: RegId, +) -> Result<(), CompileError> { + if path.is_empty() { + builder.push( + Instruction::LoadVariable { + dst: out_reg, + var_id: ENV_VARIABLE_ID, + } + .into_spanned(span), + )?; + } else { + let (key, optional) = match &path[0] { + PathMember::String { val, optional, .. } => (builder.data(val)?, *optional), + PathMember::Int { span, .. } => { + return Err(CompileError::AccessEnvByInt { span: *span }) + } + }; + let tail = &path[1..]; + + if optional { + builder.push(Instruction::LoadEnvOpt { dst: out_reg, key }.into_spanned(span))?; + } else { + builder.push(Instruction::LoadEnv { dst: out_reg, key }.into_spanned(span))?; + } + + if !tail.is_empty() { + let path = builder.literal( + Literal::CellPath(Box::new(CellPath { + members: tail.to_vec(), + })) + .into_spanned(span), + )?; + builder.push( + Instruction::FollowCellPath { + src_dst: out_reg, + path, + } + .into_spanned(span), + )?; + } + } + Ok(()) +} diff --git a/crates/nu-engine/src/compile/redirect.rs b/crates/nu-engine/src/compile/redirect.rs new file mode 100644 index 0000000000..15af1a9f8c --- /dev/null +++ b/crates/nu-engine/src/compile/redirect.rs @@ -0,0 +1,157 @@ +use nu_protocol::{ + ast::{Expression, RedirectionTarget}, + engine::StateWorkingSet, + ir::{Instruction, RedirectMode}, + IntoSpanned, OutDest, RegId, Span, Spanned, +}; + +use super::{compile_expression, BlockBuilder, CompileError}; + +#[derive(Default, Clone)] +pub(crate) struct RedirectModes { + pub(crate) out: Option>, + pub(crate) err: Option>, +} + +impl RedirectModes { + pub(crate) fn capture_out(span: Span) -> Self { + RedirectModes { + out: Some(RedirectMode::Capture.into_spanned(span)), + err: None, + } + } + + pub(crate) fn caller(span: Span) -> RedirectModes { + RedirectModes { + out: Some(RedirectMode::Caller.into_spanned(span)), + err: Some(RedirectMode::Caller.into_spanned(span)), + } + } +} + +pub(crate) fn redirection_target_to_mode( + working_set: &StateWorkingSet, + builder: &mut BlockBuilder, + target: &RedirectionTarget, +) -> Result, CompileError> { + Ok(match target { + RedirectionTarget::File { + expr, + append, + span: redir_span, + } => { + let file_num = builder.next_file_num()?; + let path_reg = builder.next_register()?; + compile_expression( + working_set, + builder, + expr, + RedirectModes::capture_out(*redir_span), + None, + path_reg, + )?; + builder.push( + Instruction::OpenFile { + file_num, + path: path_reg, + append: *append, + } + .into_spanned(*redir_span), + )?; + RedirectMode::File { file_num }.into_spanned(*redir_span) + } + RedirectionTarget::Pipe { span } => RedirectMode::Pipe.into_spanned(*span), + }) +} + +pub(crate) fn redirect_modes_of_expression( + working_set: &StateWorkingSet, + expression: &Expression, + redir_span: Span, +) -> Result { + let (out, err) = expression.expr.pipe_redirection(working_set); + Ok(RedirectModes { + out: out + .map(|r| r.into_spanned(redir_span)) + .map(out_dest_to_redirect_mode) + .transpose()?, + err: err + .map(|r| r.into_spanned(redir_span)) + .map(out_dest_to_redirect_mode) + .transpose()?, + }) +} + +/// Finish the redirection for an expression, writing to and closing files as necessary +pub(crate) fn finish_redirection( + builder: &mut BlockBuilder, + modes: RedirectModes, + out_reg: RegId, +) -> Result<(), CompileError> { + if let Some(Spanned { + item: RedirectMode::File { file_num }, + span, + }) = modes.out + { + // If out is a file and err is a pipe, we must not consume the expression result - + // that is actually the err, in that case. + if !matches!( + modes.err, + Some(Spanned { + item: RedirectMode::Pipe { .. }, + .. + }) + ) { + builder.push( + Instruction::WriteFile { + file_num, + src: out_reg, + } + .into_spanned(span), + )?; + builder.load_empty(out_reg)?; + } + builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?; + } + + match modes.err { + Some(Spanned { + item: RedirectMode::File { file_num }, + span, + }) => { + // Close the file, unless it's the same as out (in which case it was already closed) + if !modes.out.is_some_and(|out_mode| match out_mode.item { + RedirectMode::File { + file_num: out_file_num, + } => file_num == out_file_num, + _ => false, + }) { + builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?; + } + } + Some(Spanned { + item: RedirectMode::Pipe, + span, + }) => { + builder.push(Instruction::CheckErrRedirected { src: out_reg }.into_spanned(span))?; + } + _ => (), + } + + Ok(()) +} + +pub(crate) fn out_dest_to_redirect_mode( + out_dest: Spanned, +) -> Result, CompileError> { + let span = out_dest.span; + out_dest + .map(|out_dest| match out_dest { + OutDest::Pipe => Ok(RedirectMode::Pipe), + OutDest::Capture => Ok(RedirectMode::Capture), + OutDest::Null => Ok(RedirectMode::Null), + OutDest::Inherit => Ok(RedirectMode::Inherit), + OutDest::File(_) => Err(CompileError::InvalidRedirectMode { span }), + }) + .transpose() +} diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index a7d4950036..7840d03c47 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -45,10 +45,12 @@ fn nu_highlight_string(code_string: &str, engine_state: &EngineState, stack: &mu if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); + let call = Call::new(Span::unknown()); + if let Ok(output) = decl.run( engine_state, stack, - &Call::new(Span::unknown()), + &(&call).into(), Value::string(code_string, Span::unknown()).into_pipeline_data(), ) { let result = output.into_value(Span::unknown()); @@ -269,11 +271,12 @@ fn get_documentation( let _ = write!(long_desc, "\n > {}\n", example.example); } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) { let decl = engine_state.get_decl(highlighter); + let call = Call::new(Span::unknown()); match decl.run( engine_state, stack, - &Call::new(Span::unknown()), + &(&call).into(), Value::string(example.example, Span::unknown()).into_pipeline_data(), ) { Ok(output) => { @@ -326,7 +329,7 @@ fn get_documentation( .run( engine_state, stack, - &table_call, + &(&table_call).into(), PipelineData::Value(result.clone(), None), ) .ok() diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 048d9bfb99..ab3a4bc50c 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,8 +1,8 @@ use crate::ClosureEvalOnce; use nu_path::canonicalize_with; use nu_protocol::{ - ast::{Call, Expr}, - engine::{EngineState, Stack, StateWorkingSet}, + ast::Expr, + engine::{Call, EngineState, Stack, StateWorkingSet}, Config, ShellError, Span, Value, VarId, }; use std::{ @@ -244,14 +244,15 @@ pub fn path_str( } pub const DIR_VAR_PARSER_INFO: &str = "dirs_var"; -pub fn get_dirs_var_from_call(call: &Call) -> Option { - call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| { - if let Expr::Var(id) = x.expr { - Some(id) - } else { - None - } - }) +pub fn get_dirs_var_from_call(stack: &Stack, call: &Call) -> Option { + call.get_parser_info(stack, DIR_VAR_PARSER_INFO) + .and_then(|x| { + if let Expr::Var(id) = x.expr { + Some(id) + } else { + None + } + }) } /// This helper function is used to find files during eval diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b495589011..6e171eb46c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,3 +1,4 @@ +use crate::eval_ir_block; #[allow(deprecated)] use crate::{current_dir, get_config, get_full_help}; use nu_path::{expand_path_with, AbsolutePathBuf}; @@ -7,7 +8,7 @@ use nu_protocol::{ PipelineRedirection, RedirectionSource, RedirectionTarget, }, debugger::DebugContext, - engine::{Closure, EngineState, Redirection, Stack}, + engine::{Closure, EngineState, Redirection, Stack, StateWorkingSet}, eval_base::Eval, ByteStreamSource, Config, FromValue, IntoPipelineData, OutDest, PipelineData, ShellError, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, @@ -174,7 +175,7 @@ pub fn eval_call( // We pass caller_stack here with the knowledge that internal commands // are going to be specifically looking for global state in the stack // rather than any local state. - decl.run(engine_state, caller_stack, call, input) + decl.run(engine_state, caller_stack, &call.into(), input) } } @@ -223,7 +224,7 @@ fn eval_external( } } - command.run(engine_state, stack, &call, input) + command.run(engine_state, stack, &(&call).into(), input) } pub fn eval_expression( @@ -507,6 +508,11 @@ pub fn eval_block( block: &Block, mut input: PipelineData, ) -> Result { + // Remove once IR is the default. + if stack.use_ir { + return eval_ir_block::(engine_state, stack, block, input); + } + D::enter_block(engine_state, block); let num_pipelines = block.len(); @@ -521,7 +527,7 @@ pub fn eval_block( for (i, element) in elements.iter().enumerate() { let next = elements.get(i + 1).unwrap_or(last); - let (next_out, next_err) = next.pipe_redirection(engine_state); + let (next_out, next_err) = next.pipe_redirection(&StateWorkingSet::new(engine_state)); let (stdout, stderr) = eval_element_redirection::( engine_state, stack, @@ -903,7 +909,7 @@ impl Eval for EvalRuntime { /// /// An automatic environment variable cannot be assigned to by user code. /// Current there are three of them: $env.PWD, $env.FILE_PWD, $env.CURRENT_FILE -fn is_automatic_env_var(var: &str) -> bool { +pub(crate) fn is_automatic_env_var(var: &str) -> bool { let names = ["PWD", "FILE_PWD", "CURRENT_FILE"]; names.iter().any(|&name| { if cfg!(windows) { diff --git a/crates/nu-engine/src/eval_helpers.rs b/crates/nu-engine/src/eval_helpers.rs index 66bda3e0eb..65ebc6b61d 100644 --- a/crates/nu-engine/src/eval_helpers.rs +++ b/crates/nu-engine/src/eval_helpers.rs @@ -1,6 +1,6 @@ use crate::{ eval_block, eval_block_with_early_return, eval_expression, eval_expression_with_input, - eval_subexpression, + eval_ir_block, eval_subexpression, }; use nu_protocol::{ ast::{Block, Expression}, @@ -13,6 +13,10 @@ use nu_protocol::{ pub type EvalBlockFn = fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; +/// Type of eval_ir_block() function +pub type EvalIrBlockFn = + fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; + /// Type of eval_block_with_early_return() function pub type EvalBlockWithEarlyReturnFn = fn(&EngineState, &mut Stack, &Block, PipelineData) -> Result; @@ -42,6 +46,16 @@ pub fn get_eval_block(engine_state: &EngineState) -> EvalBlockFn { } } +/// Helper function to fetch `eval_ir_block()` with the correct type parameter based on whether +/// engine_state is configured with or without a debugger. +pub fn get_eval_ir_block(engine_state: &EngineState) -> EvalIrBlockFn { + if engine_state.is_debugging() { + eval_ir_block:: + } else { + eval_ir_block:: + } +} + /// Helper function to fetch `eval_block_with_early_return()` with the correct type parameter based /// on whether engine_state is configured with or without a debugger. pub fn get_eval_block_with_early_return(engine_state: &EngineState) -> EvalBlockWithEarlyReturnFn { diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs new file mode 100644 index 0000000000..a505c9be34 --- /dev/null +++ b/crates/nu-engine/src/eval_ir.rs @@ -0,0 +1,1462 @@ +use std::{borrow::Cow, fs::File, sync::Arc}; + +use nu_path::{expand_path_with, AbsolutePathBuf}; +use nu_protocol::{ + ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator}, + debugger::DebugContext, + engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack}, + ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, + record, ByteStreamSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, + OutDest, PipelineData, PositionalArg, Range, Record, RegId, ShellError, Signals, Signature, + Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, +}; +use nu_utils::IgnoreCaseExt; + +use crate::{eval::is_automatic_env_var, eval_block_with_early_return}; + +/// Evaluate the compiled representation of a [`Block`]. +pub fn eval_ir_block( + engine_state: &EngineState, + stack: &mut Stack, + block: &Block, + input: PipelineData, +) -> Result { + // Rust does not check recursion limits outside of const evaluation. + // But nu programs run in the same process as the shell. + // To prevent a stack overflow in user code from crashing the shell, + // we limit the recursion depth of function calls. + let maximum_call_stack_depth: u64 = engine_state.config.recursion_limit as u64; + if stack.recursion_count > maximum_call_stack_depth { + return Err(ShellError::RecursionLimitReached { + recursion_limit: maximum_call_stack_depth, + span: block.span, + }); + } + + if let Some(ir_block) = &block.ir_block { + D::enter_block(engine_state, block); + + let args_base = stack.arguments.get_base(); + let error_handler_base = stack.error_handlers.get_base(); + + // Allocate and initialize registers. I've found that it's not really worth trying to avoid + // the heap allocation here by reusing buffers - our allocator is fast enough + let mut registers = Vec::with_capacity(ir_block.register_count as usize); + for _ in 0..ir_block.register_count { + registers.push(PipelineData::Empty); + } + + // Initialize file storage. + let mut files = vec![None; ir_block.file_count as usize]; + + let result = eval_ir_block_impl::( + &mut EvalContext { + engine_state, + stack, + data: &ir_block.data, + block_span: &block.span, + args_base, + error_handler_base, + redirect_out: None, + redirect_err: None, + matches: vec![], + registers: &mut registers[..], + files: &mut files[..], + }, + ir_block, + input, + ); + + stack.error_handlers.leave_frame(error_handler_base); + stack.arguments.leave_frame(args_base); + + D::leave_block(engine_state, block); + + result + } else { + // FIXME blocks having IR should not be optional + Err(ShellError::GenericError { + error: "Can't evaluate block in IR mode".into(), + msg: "block is missing compiled representation".into(), + span: block.span, + help: Some("the IrBlock is probably missing due to a compilation error".into()), + inner: vec![], + }) + } +} + +/// All of the pointers necessary for evaluation +struct EvalContext<'a> { + engine_state: &'a EngineState, + stack: &'a mut Stack, + data: &'a Arc<[u8]>, + /// The span of the block + block_span: &'a Option, + /// Base index on the argument stack to reset to after a call + args_base: usize, + /// Base index on the error handler stack to reset to after a call + error_handler_base: usize, + /// State set by redirect-out + redirect_out: Option, + /// State set by redirect-err + redirect_err: Option, + /// Scratch space to use for `match` + matches: Vec<(VarId, Value)>, + /// Intermediate pipeline data storage used by instructions, indexed by RegId + registers: &'a mut [PipelineData], + /// Holds open files used by redirections + files: &'a mut [Option>], +} + +impl<'a> EvalContext<'a> { + /// Replace the contents of a register with a new value + #[inline] + fn put_reg(&mut self, reg_id: RegId, new_value: PipelineData) { + // log::trace!("{reg_id} <- {new_value:?}"); + self.registers[reg_id.0 as usize] = new_value; + } + + /// Borrow the contents of a register. + #[inline] + fn borrow_reg(&self, reg_id: RegId) -> &PipelineData { + &self.registers[reg_id.0 as usize] + } + + /// Replace the contents of a register with `Empty` and then return the value that it contained + #[inline] + fn take_reg(&mut self, reg_id: RegId) -> PipelineData { + // log::trace!("<- {reg_id}"); + std::mem::replace(&mut self.registers[reg_id.0 as usize], PipelineData::Empty) + } + + /// Clone data from a register. Must be collected first. + fn clone_reg(&mut self, reg_id: RegId, error_span: Span) -> Result { + match &self.registers[reg_id.0 as usize] { + PipelineData::Empty => Ok(PipelineData::Empty), + PipelineData::Value(val, meta) => Ok(PipelineData::Value(val.clone(), meta.clone())), + _ => Err(ShellError::IrEvalError { + msg: "Must collect to value before using instruction that clones from a register" + .into(), + span: Some(error_span), + }), + } + } + + /// Clone a value from a register. Must be collected first. + fn clone_reg_value(&mut self, reg_id: RegId, fallback_span: Span) -> Result { + match self.clone_reg(reg_id, fallback_span)? { + PipelineData::Empty => Ok(Value::nothing(fallback_span)), + PipelineData::Value(val, _) => Ok(val), + _ => unreachable!("clone_reg should never return stream data"), + } + } + + /// Take and implicitly collect a register to a value + fn collect_reg(&mut self, reg_id: RegId, fallback_span: Span) -> Result { + let data = self.take_reg(reg_id); + let span = data.span().unwrap_or(fallback_span); + data.into_value(span) + } + + /// Get a string from data or produce evaluation error if it's invalid UTF-8 + fn get_str(&self, slice: DataSlice, error_span: Span) -> Result<&'a str, ShellError> { + std::str::from_utf8(&self.data[slice]).map_err(|_| ShellError::IrEvalError { + msg: format!("data slice does not refer to valid UTF-8: {slice:?}"), + span: Some(error_span), + }) + } +} + +/// Eval an IR block on the provided slice of registers. +fn eval_ir_block_impl( + ctx: &mut EvalContext<'_>, + ir_block: &IrBlock, + input: PipelineData, +) -> Result { + if !ctx.registers.is_empty() { + ctx.registers[0] = input; + } + + // Program counter, starts at zero. + let mut pc = 0; + + while pc < ir_block.instructions.len() { + let instruction = &ir_block.instructions[pc]; + let span = &ir_block.spans[pc]; + let ast = &ir_block.ast[pc]; + log::trace!( + "{pc:-4}: {}", + instruction.display(ctx.engine_state, ctx.data) + ); + match eval_instruction::(ctx, instruction, span, ast) { + Ok(InstructionResult::Continue) => { + pc += 1; + } + Ok(InstructionResult::Branch(next_pc)) => { + pc = next_pc; + } + Ok(InstructionResult::Return(reg_id)) => { + return Ok(ctx.take_reg(reg_id)); + } + Ok(InstructionResult::ExitCode(exit_code)) => { + if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { + // If an error handler is set, branch there + prepare_error_handler(ctx, error_handler, None); + pc = error_handler.handler_index; + } else { + // If not, exit the block with the exit code + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_code, + )); + } + } + Err( + err @ (ShellError::Return { .. } + | ShellError::Continue { .. } + | ShellError::Break { .. }), + ) => { + // These block control related errors should be passed through + return Err(err); + } + Err(err) => { + if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { + // If an error handler is set, branch there + prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span))); + pc = error_handler.handler_index; + } else { + // If not, exit the block with the error + return Err(err); + } + } + } + } + + // Fell out of the loop, without encountering a Return. + Err(ShellError::IrEvalError { + msg: format!( + "Program counter out of range (pc={pc}, len={len})", + len = ir_block.instructions.len(), + ), + span: *ctx.block_span, + }) +} + +/// Prepare the context for an error handler +fn prepare_error_handler( + ctx: &mut EvalContext<'_>, + error_handler: ErrorHandler, + error: Option>, +) { + if let Some(reg_id) = error_handler.error_register { + if let Some(error) = error { + // Create the error value and put it in the register + let value = Value::record( + record! { + "msg" => Value::string(format!("{}", error.item), error.span), + "debug" => Value::string(format!("{:?}", error.item), error.span), + "raw" => Value::error(error.item, error.span), + }, + error.span, + ); + ctx.put_reg(reg_id, PipelineData::Value(value, None)); + } else { + // Set the register to empty + ctx.put_reg(reg_id, PipelineData::Empty); + } + } +} + +/// The result of performing an instruction. Describes what should happen next +#[derive(Debug)] +enum InstructionResult { + Continue, + Branch(usize), + Return(RegId), + ExitCode(i32), +} + +/// Perform an instruction +fn eval_instruction( + ctx: &mut EvalContext<'_>, + instruction: &Instruction, + span: &Span, + ast: &Option, +) -> Result { + use self::InstructionResult::*; + + // See the docs for `Instruction` for more information on what these instructions are supposed + // to do. + match instruction { + Instruction::Unreachable => Err(ShellError::IrEvalError { + msg: "Reached unreachable code".into(), + span: Some(*span), + }), + Instruction::LoadLiteral { dst, lit } => load_literal(ctx, *dst, lit, *span), + Instruction::LoadValue { dst, val } => { + ctx.put_reg(*dst, Value::clone(val).into_pipeline_data()); + Ok(Continue) + } + Instruction::Move { dst, src } => { + let val = ctx.take_reg(*src); + ctx.put_reg(*dst, val); + Ok(Continue) + } + Instruction::Clone { dst, src } => { + let data = ctx.clone_reg(*src, *span)?; + ctx.put_reg(*dst, data); + Ok(Continue) + } + Instruction::Collect { src_dst } => { + let data = ctx.take_reg(*src_dst); + let value = collect(data, *span)?; + ctx.put_reg(*src_dst, value); + Ok(Continue) + } + Instruction::Span { src_dst } => { + let data = ctx.take_reg(*src_dst); + let spanned = data.with_span(*span); + ctx.put_reg(*src_dst, spanned); + Ok(Continue) + } + Instruction::Drop { src } => { + ctx.take_reg(*src); + Ok(Continue) + } + Instruction::Drain { src } => { + let data = ctx.take_reg(*src); + drain(ctx, data) + } + Instruction::LoadVariable { dst, var_id } => { + let value = get_var(ctx, *var_id, *span)?; + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } + Instruction::StoreVariable { var_id, src } => { + let value = ctx.collect_reg(*src, *span)?; + ctx.stack.add_var(*var_id, value); + Ok(Continue) + } + Instruction::LoadEnv { dst, key } => { + let key = ctx.get_str(*key, *span)?; + if let Some(value) = get_env_var_case_insensitive(ctx, key) { + let new_value = value.clone().into_pipeline_data(); + ctx.put_reg(*dst, new_value); + Ok(Continue) + } else { + // FIXME: using the same span twice, shouldn't this really be + // EnvVarNotFoundAtRuntime? There are tests that depend on CantFindColumn though... + Err(ShellError::CantFindColumn { + col_name: key.into(), + span: Some(*span), + src_span: *span, + }) + } + } + Instruction::LoadEnvOpt { dst, key } => { + let key = ctx.get_str(*key, *span)?; + let value = get_env_var_case_insensitive(ctx, key) + .cloned() + .unwrap_or(Value::nothing(*span)); + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } + Instruction::StoreEnv { key, src } => { + let key = ctx.get_str(*key, *span)?; + let value = ctx.collect_reg(*src, *span)?; + + let key = get_env_var_name_case_insensitive(ctx, key); + + if !is_automatic_env_var(&key) { + ctx.stack.add_env_var(key.into_owned(), value); + Ok(Continue) + } else { + Err(ShellError::AutomaticEnvVarSetManually { + envvar_name: key.into(), + span: *span, + }) + } + } + Instruction::PushPositional { src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + ctx.stack.arguments.push(Argument::Positional { + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::AppendRest { src } => { + let vals = ctx.collect_reg(*src, *span)?.with_span(*span); + ctx.stack.arguments.push(Argument::Spread { + span: *span, + vals, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushFlag { name } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Flag { + data, + name: *name, + short: DataSlice::empty(), + span: *span, + }); + Ok(Continue) + } + Instruction::PushShortFlag { short } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Flag { + data, + name: DataSlice::empty(), + short: *short, + span: *span, + }); + Ok(Continue) + } + Instruction::PushNamed { name, src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Named { + data, + name: *name, + short: DataSlice::empty(), + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushShortNamed { short, src } => { + let val = ctx.collect_reg(*src, *span)?.with_span(*span); + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::Named { + data, + name: DataSlice::empty(), + short: *short, + span: *span, + val, + ast: ast.clone().map(|ast_ref| ast_ref.0), + }); + Ok(Continue) + } + Instruction::PushParserInfo { name, info } => { + let data = ctx.data.clone(); + ctx.stack.arguments.push(Argument::ParserInfo { + data, + name: *name, + info: info.clone(), + }); + Ok(Continue) + } + Instruction::RedirectOut { mode } => { + ctx.redirect_out = eval_redirection(ctx, mode, *span, RedirectionStream::Out)?; + Ok(Continue) + } + Instruction::RedirectErr { mode } => { + ctx.redirect_err = eval_redirection(ctx, mode, *span, RedirectionStream::Err)?; + Ok(Continue) + } + Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) { + PipelineData::ByteStream(stream, _) + if matches!(stream.source(), ByteStreamSource::Child(_)) => + { + Ok(Continue) + } + _ => Err(ShellError::GenericError { + error: "Can't redirect stderr of internal command output".into(), + msg: "piping stderr only works on external commands".into(), + span: Some(*span), + help: None, + inner: vec![], + }), + }, + Instruction::OpenFile { + file_num, + path, + append, + } => { + let path = ctx.collect_reg(*path, *span)?; + let file = open_file(ctx, &path, *append)?; + ctx.files[*file_num as usize] = Some(file); + Ok(Continue) + } + Instruction::WriteFile { file_num, src } => { + let src = ctx.take_reg(*src); + let file = ctx + .files + .get(*file_num as usize) + .cloned() + .flatten() + .ok_or_else(|| ShellError::IrEvalError { + msg: format!("Tried to write to file #{file_num}, but it is not open"), + span: Some(*span), + })?; + let result = { + let mut stack = ctx + .stack + .push_redirection(Some(Redirection::File(file)), None); + src.write_to_out_dests(ctx.engine_state, &mut stack)? + }; + // Abort execution if there's an exit code from a failed external + drain(ctx, result) + } + Instruction::CloseFile { file_num } => { + if ctx.files[*file_num as usize].take().is_some() { + Ok(Continue) + } else { + Err(ShellError::IrEvalError { + msg: format!("Tried to close file #{file_num}, but it is not open"), + span: Some(*span), + }) + } + } + Instruction::Call { decl_id, src_dst } => { + let input = ctx.take_reg(*src_dst); + let result = eval_call::(ctx, *decl_id, *span, input)?; + ctx.put_reg(*src_dst, result); + Ok(Continue) + } + Instruction::StringAppend { src_dst, val } => { + let string_value = ctx.collect_reg(*src_dst, *span)?; + let operand_value = ctx.collect_reg(*val, *span)?; + let string_span = string_value.span(); + + let mut string = string_value.into_string()?; + let operand = if let Value::String { val, .. } = operand_value { + // Small optimization, so we don't have to copy the string *again* + val + } else { + operand_value.to_expanded_string(", ", ctx.engine_state.get_config()) + }; + string.push_str(&operand); + + let new_string_value = Value::string(string, string_span); + ctx.put_reg(*src_dst, new_string_value.into_pipeline_data()); + Ok(Continue) + } + Instruction::GlobFrom { src_dst, no_expand } => { + let string_value = ctx.collect_reg(*src_dst, *span)?; + let glob_value = if matches!(string_value, Value::Glob { .. }) { + // It already is a glob, so don't touch it. + string_value + } else { + // Treat it as a string, then cast + let string = string_value.into_string()?; + Value::glob(string, *no_expand, *span) + }; + ctx.put_reg(*src_dst, glob_value.into_pipeline_data()); + Ok(Continue) + } + Instruction::ListPush { src_dst, item } => { + let list_value = ctx.collect_reg(*src_dst, *span)?; + let item = ctx.collect_reg(*item, *span)?; + let list_span = list_value.span(); + let mut list = list_value.into_list()?; + list.push(item); + ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data()); + Ok(Continue) + } + Instruction::ListSpread { src_dst, items } => { + let list_value = ctx.collect_reg(*src_dst, *span)?; + let items = ctx.collect_reg(*items, *span)?; + let list_span = list_value.span(); + let items_span = items.span(); + let mut list = list_value.into_list()?; + list.extend( + items + .into_list() + .map_err(|_| ShellError::CannotSpreadAsList { span: items_span })?, + ); + ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data()); + Ok(Continue) + } + Instruction::RecordInsert { src_dst, key, val } => { + let record_value = ctx.collect_reg(*src_dst, *span)?; + let key = ctx.collect_reg(*key, *span)?; + let val = ctx.collect_reg(*val, *span)?; + let record_span = record_value.span(); + let mut record = record_value.into_record()?; + + let key = key.coerce_into_string()?; + if let Some(old_value) = record.insert(&key, val) { + return Err(ShellError::ColumnDefinedTwice { + col_name: key, + second_use: *span, + first_use: old_value.span(), + }); + } + + ctx.put_reg( + *src_dst, + Value::record(record, record_span).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::RecordSpread { src_dst, items } => { + let record_value = ctx.collect_reg(*src_dst, *span)?; + let items = ctx.collect_reg(*items, *span)?; + let record_span = record_value.span(); + let items_span = items.span(); + let mut record = record_value.into_record()?; + // Not using .extend() here because it doesn't handle duplicates + for (key, val) in items + .into_record() + .map_err(|_| ShellError::CannotSpreadAsRecord { span: items_span })? + { + if let Some(first_value) = record.insert(&key, val) { + return Err(ShellError::ColumnDefinedTwice { + col_name: key, + second_use: *span, + first_use: first_value.span(), + }); + } + } + ctx.put_reg( + *src_dst, + Value::record(record, record_span).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::Not { src_dst } => { + let bool = ctx.collect_reg(*src_dst, *span)?; + let negated = !bool.as_bool()?; + ctx.put_reg( + *src_dst, + Value::bool(negated, bool.span()).into_pipeline_data(), + ); + Ok(Continue) + } + Instruction::BinaryOp { lhs_dst, op, rhs } => binary_op(ctx, *lhs_dst, op, *rhs, *span), + Instruction::FollowCellPath { src_dst, path } => { + let data = ctx.take_reg(*src_dst); + let path = ctx.take_reg(*path); + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + let value = data.follow_cell_path(&path.members, *span, true)?; + ctx.put_reg(*src_dst, value.into_pipeline_data()); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::CloneCellPath { dst, src, path } => { + let value = ctx.clone_reg_value(*src, *span)?; + let path = ctx.take_reg(*path); + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + // TODO: make follow_cell_path() not have to take ownership, probably using Cow + let value = value.follow_cell_path(&path.members, true)?; + ctx.put_reg(*dst, value.into_pipeline_data()); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => { + let data = ctx.take_reg(*src_dst); + let metadata = data.metadata(); + // Change the span because we're modifying it + let mut value = data.into_value(*span)?; + let path = ctx.take_reg(*path); + let new_value = ctx.collect_reg(*new_value, *span)?; + if let PipelineData::Value(Value::CellPath { val: path, .. }, _) = path { + value.upsert_data_at_cell_path(&path.members, new_value)?; + ctx.put_reg(*src_dst, value.into_pipeline_data_with_metadata(metadata)); + Ok(Continue) + } else if let PipelineData::Value(Value::Error { error, .. }, _) = path { + Err(*error) + } else { + Err(ShellError::TypeMismatch { + err_message: "expected cell path".into(), + span: path.span().unwrap_or(*span), + }) + } + } + Instruction::Jump { index } => Ok(Branch(*index)), + Instruction::BranchIf { cond, index } => { + let data = ctx.take_reg(*cond); + let data_span = data.span(); + let val = match data { + PipelineData::Value(Value::Bool { val, .. }, _) => val, + PipelineData::Value(Value::Error { error, .. }, _) => { + return Err(*error); + } + _ => { + return Err(ShellError::TypeMismatch { + err_message: "expected bool".into(), + span: data_span.unwrap_or(*span), + }); + } + }; + if val { + Ok(Branch(*index)) + } else { + Ok(Continue) + } + } + Instruction::BranchIfEmpty { src, index } => { + let is_empty = matches!( + ctx.borrow_reg(*src), + PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, _) + ); + + if is_empty { + Ok(Branch(*index)) + } else { + Ok(Continue) + } + } + Instruction::Match { + pattern, + src, + index, + } => { + let value = ctx.clone_reg_value(*src, *span)?; + ctx.matches.clear(); + if pattern.match_value(&value, &mut ctx.matches) { + // Match succeeded: set variables and branch + for (var_id, match_value) in ctx.matches.drain(..) { + ctx.stack.add_var(var_id, match_value); + } + Ok(Branch(*index)) + } else { + // Failed to match, put back original value + ctx.matches.clear(); + Ok(Continue) + } + } + Instruction::CheckMatchGuard { src } => { + if matches!( + ctx.borrow_reg(*src), + PipelineData::Value(Value::Bool { .. }, _) + ) { + Ok(Continue) + } else { + Err(ShellError::MatchGuardNotBool { span: *span }) + } + } + Instruction::Iterate { + dst, + stream, + end_index, + } => eval_iterate(ctx, *dst, *stream, *end_index), + Instruction::OnError { index } => { + ctx.stack.error_handlers.push(ErrorHandler { + handler_index: *index, + error_register: None, + }); + Ok(Continue) + } + Instruction::OnErrorInto { index, dst } => { + ctx.stack.error_handlers.push(ErrorHandler { + handler_index: *index, + error_register: Some(*dst), + }); + Ok(Continue) + } + Instruction::PopErrorHandler => { + ctx.stack.error_handlers.pop(ctx.error_handler_base); + Ok(Continue) + } + Instruction::CheckExternalFailed { dst, src } => { + let data = ctx.take_reg(*src); + let (data, failed) = data.check_external_failed()?; + ctx.put_reg(*src, data); + ctx.put_reg(*dst, Value::bool(failed, *span).into_pipeline_data()); + Ok(Continue) + } + Instruction::ReturnEarly { src } => { + let val = ctx.collect_reg(*src, *span)?; + Err(ShellError::Return { + span: *span, + value: Box::new(val), + }) + } + Instruction::Return { src } => Ok(Return(*src)), + } +} + +/// Load a literal value into a register +fn load_literal( + ctx: &mut EvalContext<'_>, + dst: RegId, + lit: &Literal, + span: Span, +) -> Result { + let value = literal_value(ctx, lit, span)?; + ctx.put_reg(dst, PipelineData::Value(value, None)); + Ok(InstructionResult::Continue) +} + +fn literal_value( + ctx: &mut EvalContext<'_>, + lit: &Literal, + span: Span, +) -> Result { + Ok(match lit { + Literal::Bool(b) => Value::bool(*b, span), + Literal::Int(i) => Value::int(*i, span), + Literal::Float(f) => Value::float(*f, span), + Literal::Filesize(q) => Value::filesize(*q, span), + Literal::Duration(q) => Value::duration(*q, span), + Literal::Binary(bin) => Value::binary(&ctx.data[*bin], span), + Literal::Block(block_id) | Literal::RowCondition(block_id) | Literal::Closure(block_id) => { + let block = ctx.engine_state.get_block(*block_id); + let captures = block + .captures + .iter() + .map(|var_id| get_var(ctx, *var_id, span).map(|val| (*var_id, val))) + .collect::, ShellError>>()?; + Value::closure( + Closure { + block_id: *block_id, + captures, + }, + span, + ) + } + Literal::Range { + start, + step, + end, + inclusion, + } => { + let start = ctx.collect_reg(*start, span)?; + let step = ctx.collect_reg(*step, span)?; + let end = ctx.collect_reg(*end, span)?; + let range = Range::new(start, step, end, *inclusion, span)?; + Value::range(range, span) + } + Literal::List { capacity } => Value::list(Vec::with_capacity(*capacity), span), + Literal::Record { capacity } => Value::record(Record::with_capacity(*capacity), span), + Literal::Filepath { + val: path, + no_expand, + } => { + let path = ctx.get_str(*path, span)?; + if *no_expand { + Value::string(path, span) + } else { + let cwd = ctx.engine_state.cwd(Some(ctx.stack))?; + let path = expand_path_with(path, cwd, true); + + Value::string(path.to_string_lossy(), span) + } + } + Literal::Directory { + val: path, + no_expand, + } => { + let path = ctx.get_str(*path, span)?; + if path == "-" { + Value::string("-", span) + } else if *no_expand { + Value::string(path, span) + } else { + let cwd = ctx + .engine_state + .cwd(Some(ctx.stack)) + .map(AbsolutePathBuf::into_std_path_buf) + .unwrap_or_default(); + let path = expand_path_with(path, cwd, true); + + Value::string(path.to_string_lossy(), span) + } + } + Literal::GlobPattern { val, no_expand } => { + Value::glob(ctx.get_str(*val, span)?, *no_expand, span) + } + Literal::String(s) => Value::string(ctx.get_str(*s, span)?, span), + Literal::RawString(s) => Value::string(ctx.get_str(*s, span)?, span), + Literal::CellPath(path) => Value::cell_path(CellPath::clone(path), span), + Literal::Date(dt) => Value::date(**dt, span), + Literal::Nothing => Value::nothing(span), + }) +} + +fn binary_op( + ctx: &mut EvalContext<'_>, + lhs_dst: RegId, + op: &Operator, + rhs: RegId, + span: Span, +) -> Result { + let lhs_val = ctx.collect_reg(lhs_dst, span)?; + let rhs_val = ctx.collect_reg(rhs, span)?; + + // Handle binary op errors early + if let Value::Error { error, .. } = lhs_val { + return Err(*error); + } + if let Value::Error { error, .. } = rhs_val { + return Err(*error); + } + + // We only have access to one span here, but the generated code usually adds a `span` + // instruction to set the output span to the right span. + let op_span = span; + + let result = match op { + Operator::Comparison(cmp) => match cmp { + Comparison::Equal => lhs_val.eq(op_span, &rhs_val, span)?, + Comparison::NotEqual => lhs_val.ne(op_span, &rhs_val, span)?, + Comparison::LessThan => lhs_val.lt(op_span, &rhs_val, span)?, + Comparison::GreaterThan => lhs_val.gt(op_span, &rhs_val, span)?, + Comparison::LessThanOrEqual => lhs_val.lte(op_span, &rhs_val, span)?, + Comparison::GreaterThanOrEqual => lhs_val.gte(op_span, &rhs_val, span)?, + Comparison::RegexMatch => { + lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, false, span)? + } + Comparison::NotRegexMatch => { + lhs_val.regex_match(ctx.engine_state, op_span, &rhs_val, true, span)? + } + Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?, + Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?, + Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?, + Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?, + }, + Operator::Math(mat) => match mat { + Math::Plus => lhs_val.add(op_span, &rhs_val, span)?, + Math::Append => lhs_val.append(op_span, &rhs_val, span)?, + Math::Minus => lhs_val.sub(op_span, &rhs_val, span)?, + Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?, + Math::Divide => lhs_val.div(op_span, &rhs_val, span)?, + Math::Modulo => lhs_val.modulo(op_span, &rhs_val, span)?, + Math::FloorDivision => lhs_val.floor_div(op_span, &rhs_val, span)?, + Math::Pow => lhs_val.pow(op_span, &rhs_val, span)?, + }, + Operator::Boolean(bl) => match bl { + Boolean::And => lhs_val.and(op_span, &rhs_val, span)?, + Boolean::Or => lhs_val.or(op_span, &rhs_val, span)?, + Boolean::Xor => lhs_val.xor(op_span, &rhs_val, span)?, + }, + Operator::Bits(bit) => match bit { + Bits::BitOr => lhs_val.bit_or(op_span, &rhs_val, span)?, + Bits::BitXor => lhs_val.bit_xor(op_span, &rhs_val, span)?, + Bits::BitAnd => lhs_val.bit_and(op_span, &rhs_val, span)?, + Bits::ShiftLeft => lhs_val.bit_shl(op_span, &rhs_val, span)?, + Bits::ShiftRight => lhs_val.bit_shr(op_span, &rhs_val, span)?, + }, + Operator::Assignment(_asg) => { + return Err(ShellError::IrEvalError { + msg: "can't eval assignment with the `binary-op` instruction".into(), + span: Some(span), + }) + } + }; + + ctx.put_reg(lhs_dst, PipelineData::Value(result, None)); + + Ok(InstructionResult::Continue) +} + +/// Evaluate a call +fn eval_call( + ctx: &mut EvalContext<'_>, + decl_id: DeclId, + head: Span, + input: PipelineData, +) -> Result { + let EvalContext { + engine_state, + stack: caller_stack, + args_base, + redirect_out, + redirect_err, + .. + } = ctx; + + let args_len = caller_stack.arguments.get_len(*args_base); + let decl = engine_state.get_decl(decl_id); + + // Set up redirect modes + let mut caller_stack = caller_stack.push_redirection(redirect_out.take(), redirect_err.take()); + + let result; + + if let Some(block_id) = decl.block_id() { + // If the decl is a custom command + let block = engine_state.get_block(block_id); + + // Set up a callee stack with the captures and move arguments from the stack into variables + let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures); + + gather_arguments( + engine_state, + block, + &mut caller_stack, + &mut callee_stack, + *args_base, + args_len, + head, + )?; + + // Add one to the recursion count, so we don't recurse too deep. Stack overflows are not + // recoverable in Rust. + callee_stack.recursion_count += 1; + + result = eval_block_with_early_return::(engine_state, &mut callee_stack, block, input); + + // Move environment variables back into the caller stack scope if requested to do so + if block.redirect_env { + redirect_env(engine_state, &mut caller_stack, &callee_stack); + } + } else { + // FIXME: precalculate this and save it somewhere + let span = Span::merge_many( + std::iter::once(head).chain( + caller_stack + .arguments + .get_args(*args_base, args_len) + .iter() + .flat_map(|arg| arg.span()), + ), + ); + + let call = Call { + decl_id, + head, + span, + args_base: *args_base, + args_len, + }; + + // Run the call + result = decl.run(engine_state, &mut caller_stack, &(&call).into(), input); + }; + + drop(caller_stack); + + // Important that this runs, to reset state post-call: + ctx.stack.arguments.leave_frame(ctx.args_base); + ctx.redirect_out = None; + ctx.redirect_err = None; + + result +} + +fn find_named_var_id( + sig: &Signature, + name: &[u8], + short: &[u8], + span: Span, +) -> Result { + sig.named + .iter() + .find(|n| { + if !n.long.is_empty() { + n.long.as_bytes() == name + } else { + // It's possible to only have a short name and no long name + n.short + .is_some_and(|s| s.encode_utf8(&mut [0; 4]).as_bytes() == short) + } + }) + .ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block does not have an argument named `{}`", + String::from_utf8_lossy(name) + ), + span: Some(span), + }) + .and_then(|flag| expect_named_var_id(flag, span)) +} + +fn expect_named_var_id(arg: &Flag, span: Span) -> Result { + arg.var_id.ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block signature is missing var id for named arg `{}`", + arg.long + ), + span: Some(span), + }) +} + +fn expect_positional_var_id(arg: &PositionalArg, span: Span) -> Result { + arg.var_id.ok_or_else(|| ShellError::IrEvalError { + msg: format!( + "block signature is missing var id for positional arg `{}`", + arg.name + ), + span: Some(span), + }) +} + +/// Move arguments from the stack into variables for a custom command +fn gather_arguments( + engine_state: &EngineState, + block: &Block, + caller_stack: &mut Stack, + callee_stack: &mut Stack, + args_base: usize, + args_len: usize, + call_head: Span, +) -> Result<(), ShellError> { + let mut positional_iter = block + .signature + .required_positional + .iter() + .map(|p| (p, true)) + .chain( + block + .signature + .optional_positional + .iter() + .map(|p| (p, false)), + ); + + // Arguments that didn't get consumed by required/optional + let mut rest = vec![]; + + // If we encounter a spread, all further positionals should go to rest + let mut always_spread = false; + + for arg in caller_stack.arguments.drain_args(args_base, args_len) { + match arg { + Argument::Positional { span, val, .. } => { + // Don't check next positional arg if we encountered a spread previously + let next = (!always_spread).then(|| positional_iter.next()).flatten(); + if let Some((positional_arg, required)) = next { + let var_id = expect_positional_var_id(positional_arg, span)?; + if required { + // By checking the type of the bound variable rather than converting the + // SyntaxShape here, we might be able to save some allocations and effort + let variable = engine_state.get_var(var_id); + check_type(&val, &variable.ty)?; + } + callee_stack.add_var(var_id, val); + } else { + rest.push(val); + } + } + Argument::Spread { vals, .. } => { + if let Value::List { vals, .. } = vals { + rest.extend(vals); + // All further positional args should go to spread + always_spread = true; + } else if let Value::Error { error, .. } = vals { + return Err(*error); + } else { + return Err(ShellError::CannotSpreadAsList { span: vals.span() }); + } + } + Argument::Flag { + data, + name, + short, + span, + } => { + let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?; + callee_stack.add_var(var_id, Value::bool(true, span)) + } + Argument::Named { + data, + name, + short, + span, + val, + .. + } => { + let var_id = find_named_var_id(&block.signature, &data[name], &data[short], span)?; + callee_stack.add_var(var_id, val) + } + Argument::ParserInfo { .. } => (), + } + } + + // Add the collected rest of the arguments if a spread argument exists + if let Some(rest_arg) = &block.signature.rest_positional { + let rest_span = rest.first().map(|v| v.span()).unwrap_or(call_head); + let var_id = expect_positional_var_id(rest_arg, rest_span)?; + callee_stack.add_var(var_id, Value::list(rest, rest_span)); + } + + // Check for arguments that haven't yet been set and set them to their defaults + for (positional_arg, _) in positional_iter { + let var_id = expect_positional_var_id(positional_arg, call_head)?; + callee_stack.add_var( + var_id, + positional_arg + .default_value + .clone() + .unwrap_or(Value::nothing(call_head)), + ); + } + + for named_arg in &block.signature.named { + if let Some(var_id) = named_arg.var_id { + // For named arguments, we do this check by looking to see if the variable was set yet on + // the stack. This assumes that the stack's variables was previously empty, but that's a + // fair assumption for a brand new callee stack. + if !callee_stack.vars.iter().any(|(id, _)| *id == var_id) { + let val = if named_arg.arg.is_none() { + Value::bool(false, call_head) + } else if let Some(value) = &named_arg.default_value { + value.clone() + } else { + Value::nothing(call_head) + }; + callee_stack.add_var(var_id, val); + } + } + } + + Ok(()) +} + +/// Type check helper. Produces `CantConvert` error if `val` is not compatible with `ty`. +fn check_type(val: &Value, ty: &Type) -> Result<(), ShellError> { + if match val { + // An empty list is compatible with any list or table type + Value::List { vals, .. } if vals.is_empty() => { + matches!(ty, Type::Any | Type::List(_) | Type::Table(_)) + } + // FIXME: the allocation that might be required here is not great, it would be nice to be + // able to just directly check whether a value is compatible with a type + _ => val.get_type().is_subtype(ty), + } { + Ok(()) + } else { + Err(ShellError::CantConvert { + to_type: ty.to_string(), + from_type: val.get_type().to_string(), + span: val.span(), + help: None, + }) + } +} + +/// Get variable from [`Stack`] or [`EngineState`] +fn get_var(ctx: &EvalContext<'_>, var_id: VarId, span: Span) -> Result { + match var_id { + // $env + ENV_VARIABLE_ID => { + let env_vars = ctx.stack.get_env_vars(ctx.engine_state); + let env_columns = env_vars.keys(); + let env_values = env_vars.values(); + + let mut pairs = env_columns + .map(|x| x.to_string()) + .zip(env_values.cloned()) + .collect::>(); + + pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + Ok(Value::record(pairs.into_iter().collect(), span)) + } + _ => ctx.stack.get_var(var_id, span).or_else(|err| { + // $nu is handled by getting constant + if let Some(const_val) = ctx.engine_state.get_constant(var_id).cloned() { + Ok(const_val.with_span(span)) + } else { + Err(err) + } + }), + } +} + +/// Get an environment variable, case-insensitively +fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> Option<&'a Value> { + // Read scopes in order + ctx.stack + .env_vars + .iter() + .rev() + .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .flat_map(|overlays| { + // Read overlays in order + ctx.stack + .active_overlays + .iter() + .rev() + .filter_map(|name| overlays.get(name)) + }) + .find_map(|map| { + // Use the hashmap first to try to be faster? + map.get(key).or_else(|| { + // Check to see if it exists at all in the map + map.iter() + .find_map(|(k, v)| k.eq_ignore_case(key).then_some(v)) + }) + }) +} + +/// Get the existing name of an environment variable, case-insensitively. This is used to implement +/// case preservation of environment variables, so that changing an environment variable that +/// already exists always uses the same case. +fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str) -> Cow<'a, str> { + // Read scopes in order + ctx.stack + .env_vars + .iter() + .rev() + .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .flat_map(|overlays| { + // Read overlays in order + ctx.stack + .active_overlays + .iter() + .rev() + .filter_map(|name| overlays.get(name)) + }) + .find_map(|map| { + // Use the hashmap first to try to be faster? + if map.contains_key(key) { + Some(Cow::Borrowed(key)) + } else { + map.keys().find(|k| k.eq_ignore_case(key)).map(|k| { + // it exists, but with a different case + Cow::Owned(k.to_owned()) + }) + } + }) + // didn't exist. + .unwrap_or(Cow::Borrowed(key)) +} + +/// Helper to collect values into [`PipelineData`], preserving original span and metadata +fn collect(data: PipelineData, fallback_span: Span) -> Result { + let span = data.span().unwrap_or(fallback_span); + let metadata = data.metadata(); + let value = data.into_value(span)?; + Ok(PipelineData::Value(value, metadata)) +} + +/// Helper for drain behavior. Returns `Ok(ExitCode)` on failed external. +fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result { + use self::InstructionResult::*; + let span = data.span().unwrap_or(Span::unknown()); + if let Some(exit_status) = data.drain()? { + ctx.stack.add_env_var( + "LAST_EXIT_CODE".into(), + Value::int(exit_status.code() as i64, span), + ); + if exit_status.code() == 0 { + Ok(Continue) + } else { + Ok(ExitCode(exit_status.code())) + } + } else { + Ok(Continue) + } +} + +enum RedirectionStream { + Out, + Err, +} + +/// Open a file for redirection +fn open_file(ctx: &EvalContext<'_>, path: &Value, append: bool) -> Result, ShellError> { + let path_expanded = + expand_path_with(path.as_str()?, ctx.engine_state.cwd(Some(ctx.stack))?, true); + let mut options = File::options(); + if append { + options.append(true); + } else { + options.write(true).truncate(true); + } + let file = options + .create(true) + .open(path_expanded) + .err_span(path.span())?; + Ok(Arc::new(file)) +} + +/// Set up a [`Redirection`] from a [`RedirectMode`] +fn eval_redirection( + ctx: &mut EvalContext<'_>, + mode: &RedirectMode, + span: Span, + which: RedirectionStream, +) -> Result, ShellError> { + match mode { + RedirectMode::Pipe => Ok(Some(Redirection::Pipe(OutDest::Pipe))), + RedirectMode::Capture => Ok(Some(Redirection::Pipe(OutDest::Capture))), + RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))), + RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))), + RedirectMode::File { file_num } => { + let file = ctx + .files + .get(*file_num as usize) + .cloned() + .flatten() + .ok_or_else(|| ShellError::IrEvalError { + msg: format!("Tried to redirect to file #{file_num}, but it is not open"), + span: Some(span), + })?; + Ok(Some(Redirection::File(file))) + } + RedirectMode::Caller => Ok(match which { + RedirectionStream::Out => ctx.stack.pipe_stdout().cloned().map(Redirection::Pipe), + RedirectionStream::Err => ctx.stack.pipe_stderr().cloned().map(Redirection::Pipe), + }), + } +} + +/// Do an `iterate` instruction. This can be called repeatedly to get more values from an iterable +fn eval_iterate( + ctx: &mut EvalContext<'_>, + dst: RegId, + stream: RegId, + end_index: usize, +) -> Result { + let mut data = ctx.take_reg(stream); + if let PipelineData::ListStream(list_stream, _) = &mut data { + // Modify the stream, taking one value off, and branching if it's empty + if let Some(val) = list_stream.next_value() { + ctx.put_reg(dst, val.into_pipeline_data()); + ctx.put_reg(stream, data); // put the stream back so it can be iterated on again + Ok(InstructionResult::Continue) + } else { + ctx.put_reg(dst, PipelineData::Empty); + Ok(InstructionResult::Branch(end_index)) + } + } else { + // Convert the PipelineData to an iterator, and wrap it in a ListStream so it can be + // iterated on + let metadata = data.metadata(); + let span = data.span().unwrap_or(Span::unknown()); + ctx.put_reg( + stream, + PipelineData::ListStream( + ListStream::new(data.into_iter(), span, Signals::EMPTY), + metadata, + ), + ); + eval_iterate(ctx, dst, stream, end_index) + } +} + +/// Redirect environment from the callee stack to the caller stack +fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) { + // TODO: make this more efficient + // Grab all environment variables from the callee + let caller_env_vars = caller_stack.get_env_var_names(engine_state); + + // remove env vars that are present in the caller but not in the callee + // (the callee hid them) + for var in caller_env_vars.iter() { + if !callee_stack.has_env_var(engine_state, var) { + caller_stack.remove_env_var(engine_state, var); + } + } + + // add new env vars from callee to caller + for (var, value) in callee_stack.get_stack_env_vars() { + caller_stack.add_env_var(var, value); + } +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index e3c8f8eede..7ed246e975 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -2,16 +2,19 @@ mod call_ext; mod closure_eval; pub mod column; pub mod command_prelude; +mod compile; pub mod documentation; pub mod env; mod eval; mod eval_helpers; +mod eval_ir; mod glob_from; pub mod scope; pub use call_ext::CallExt; pub use closure_eval::*; pub use column::get_columns; +pub use compile::compile; pub use documentation::get_full_help; pub use env::*; pub use eval::{ @@ -19,4 +22,5 @@ pub use eval::{ eval_expression_with_input, eval_subexpression, eval_variable, redirect_env, }; pub use eval_helpers::*; +pub use eval_ir::eval_ir_block; pub use glob_from::glob_from; diff --git a/crates/nu-parser/src/known_external.rs b/crates/nu-parser/src/known_external.rs index 453112aa32..a41cf3a4e8 100644 --- a/crates/nu-parser/src/known_external.rs +++ b/crates/nu-parser/src/known_external.rs @@ -1,7 +1,8 @@ use nu_engine::command_prelude::*; use nu_protocol::{ - ast::{Argument, Expr, Expression}, - engine::{CommandType, UNKNOWN_SPAN_ID}, + ast::{self, Expr, Expression}, + engine::{self, CallImpl, CommandType, UNKNOWN_SPAN_ID}, + ir::{self, DataSlice}, }; #[derive(Clone)] @@ -43,8 +44,6 @@ impl Command for KnownExternal { let command = engine_state.get_decl(decl_id); - let mut extern_call = Call::new(head_span); - let extern_name = if let Some(name_bytes) = engine_state.find_decl_name(call.decl_id, &[]) { String::from_utf8_lossy(name_bytes) } else { @@ -56,59 +55,166 @@ impl Command for KnownExternal { }; let extern_name: Vec<_> = extern_name.split(' ').collect(); - let call_head_id = engine_state - .find_span_id(call.head) - .unwrap_or(UNKNOWN_SPAN_ID); - let arg_extern_name = Expression::new_existing( - Expr::String(extern_name[0].to_string()), + match &call.inner { + CallImpl::AstRef(call) => { + let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::AstBox(call) => { + let extern_call = ast_call_to_extern_call(engine_state, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::IrRef(call) => { + let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + CallImpl::IrBox(call) => { + let extern_call = ir_call_to_extern_call(stack, call, &extern_name)?; + command.run(engine_state, stack, &(&extern_call).into(), input) + } + } + } +} + +/// Transform the args from an `ast::Call` onto a `run-external` call +fn ast_call_to_extern_call( + engine_state: &EngineState, + call: &ast::Call, + extern_name: &[&str], +) -> Result { + let head_span = call.head; + + let mut extern_call = ast::Call::new(head_span); + + let call_head_id = engine_state + .find_span_id(call.head) + .unwrap_or(UNKNOWN_SPAN_ID); + + let arg_extern_name = Expression::new_existing( + Expr::String(extern_name[0].to_string()), + call.head, + call_head_id, + Type::String, + ); + + extern_call.add_positional(arg_extern_name); + + for subcommand in extern_name.iter().skip(1) { + extern_call.add_positional(Expression::new_existing( + Expr::String(subcommand.to_string()), call.head, call_head_id, Type::String, - ); + )); + } - extern_call.add_positional(arg_extern_name); - - for subcommand in extern_name.into_iter().skip(1) { - extern_call.add_positional(Expression::new_existing( - Expr::String(subcommand.to_string()), - call.head, - call_head_id, - Type::String, - )); - } - - for arg in &call.arguments { - match arg { - Argument::Positional(positional) => extern_call.add_positional(positional.clone()), - Argument::Named(named) => { - let named_span_id = engine_state - .find_span_id(named.0.span) - .unwrap_or(UNKNOWN_SPAN_ID); - if let Some(short) = &named.1 { - extern_call.add_positional(Expression::new_existing( - Expr::String(format!("-{}", short.item)), - named.0.span, - named_span_id, - Type::String, - )); - } else { - extern_call.add_positional(Expression::new_existing( - Expr::String(format!("--{}", named.0.item)), - named.0.span, - named_span_id, - Type::String, - )); - } - if let Some(arg) = &named.2 { - extern_call.add_positional(arg.clone()); - } + for arg in &call.arguments { + match arg { + ast::Argument::Positional(positional) => extern_call.add_positional(positional.clone()), + ast::Argument::Named(named) => { + let named_span_id = engine_state + .find_span_id(named.0.span) + .unwrap_or(UNKNOWN_SPAN_ID); + if let Some(short) = &named.1 { + extern_call.add_positional(Expression::new_existing( + Expr::String(format!("-{}", short.item)), + named.0.span, + named_span_id, + Type::String, + )); + } else { + extern_call.add_positional(Expression::new_existing( + Expr::String(format!("--{}", named.0.item)), + named.0.span, + named_span_id, + Type::String, + )); } - Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()), - Argument::Spread(args) => extern_call.add_spread(args.clone()), + if let Some(arg) = &named.2 { + extern_call.add_positional(arg.clone()); + } + } + ast::Argument::Unknown(unknown) => extern_call.add_unknown(unknown.clone()), + ast::Argument::Spread(args) => extern_call.add_spread(args.clone()), + } + } + + Ok(extern_call) +} + +/// Transform the args from an `ir::Call` onto a `run-external` call +fn ir_call_to_extern_call( + stack: &mut Stack, + call: &ir::Call, + extern_name: &[&str], +) -> Result { + let mut extern_call = ir::Call::build(call.decl_id, call.head); + + // Add the command and subcommands + for name in extern_name { + extern_call.add_positional(stack, call.head, Value::string(*name, call.head)); + } + + // Add the arguments, reformatting named arguments into string positionals + for index in 0..call.args_len { + match &call.arguments(stack)[index] { + engine::Argument::Flag { + data, + name, + short, + span, + } => { + let name_arg = engine::Argument::Positional { + span: *span, + val: Value::string(known_external_option_name(data, *name, *short), *span), + ast: None, + }; + extern_call.add_argument(stack, name_arg); + } + engine::Argument::Named { + data, + name, + short, + span, + val, + .. + } => { + let name_arg = engine::Argument::Positional { + span: *span, + val: Value::string(known_external_option_name(data, *name, *short), *span), + ast: None, + }; + let val_arg = engine::Argument::Positional { + span: *span, + val: val.clone(), + ast: None, + }; + extern_call.add_argument(stack, name_arg); + extern_call.add_argument(stack, val_arg); + } + a @ (engine::Argument::Positional { .. } + | engine::Argument::Spread { .. } + | engine::Argument::ParserInfo { .. }) => { + let argument = a.clone(); + extern_call.add_argument(stack, argument); } } + } - command.run(engine_state, stack, &extern_call, input) + Ok(extern_call.finish()) +} + +fn known_external_option_name(data: &[u8], name: DataSlice, short: DataSlice) -> String { + if !data[name].is_empty() { + format!( + "--{}", + std::str::from_utf8(&data[name]).expect("invalid utf-8 in flag name") + ) + } else { + format!( + "-{}", + std::str::from_utf8(&data[short]).expect("invalid utf-8 in flag short name") + ) } } diff --git a/crates/nu-parser/src/parse_patterns.rs b/crates/nu-parser/src/parse_patterns.rs index 73668b7d04..dc4a64ce37 100644 --- a/crates/nu-parser/src/parse_patterns.rs +++ b/crates/nu-parser/src/parse_patterns.rs @@ -39,7 +39,7 @@ pub fn parse_pattern(working_set: &mut StateWorkingSet, span: Span) -> MatchPatt let value = parse_value(working_set, span, &SyntaxShape::Any); MatchPattern { - pattern: Pattern::Value(value), + pattern: Pattern::Value(Box::new(value)), guard: None, span, } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 51935214f5..fc2131aad7 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3302,6 +3302,8 @@ pub fn parse_row_condition(working_set: &mut StateWorkingSet, spans: &[Span]) -> default_value: None, }); + compile_block(working_set, &mut block); + working_set.add_block(Arc::new(block)) } }; @@ -4445,7 +4447,7 @@ pub fn parse_match_block_expression(working_set: &mut StateWorkingSet, span: Spa &SyntaxShape::MathExpression, ); - pattern.guard = Some(guard); + pattern.guard = Some(Box::new(guard)); position += if found { start + 1 } else { start }; connector = working_set.get_span_contents(output[position].span); } @@ -5298,6 +5300,8 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex let ty = output.ty.clone(); block.pipelines = vec![Pipeline::from_vec(vec![output])]; + compile_block(working_set, &mut block); + let block_id = working_set.add_block(Arc::new(block)); let mut env_vars = vec![]; @@ -5853,9 +5857,25 @@ pub fn parse_block( working_set.parse_errors.extend_from_slice(&errors); } + // Do not try to compile blocks that are subexpressions, or when we've already had a parse + // failure as that definitely will fail to compile + if !is_subexpression && working_set.parse_errors.is_empty() { + compile_block(working_set, &mut block); + } + block } +/// Compile an IR block for the `Block`, adding a compile error on failure +fn compile_block(working_set: &mut StateWorkingSet<'_>, block: &mut Block) { + match nu_engine::compile(working_set, block) { + Ok(ir_block) => { + block.ir_block = Some(ir_block); + } + Err(err) => working_set.compile_errors.push(err), + } +} + pub fn discover_captures_in_closure( working_set: &StateWorkingSet, block: &Block, @@ -6298,12 +6318,14 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) default_value: None, }); - let block = Block { + let mut block = Block { pipelines: vec![Pipeline::from_vec(vec![expr.clone()])], signature: Box::new(signature), ..Default::default() }; + compile_block(working_set, &mut block); + let block_id = working_set.add_block(Arc::new(block)); output.push(Argument::Positional(Expression::new( diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 0784fe69d4..7762863ba1 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,7 +1,7 @@ use nu_parser::*; use nu_protocol::{ - ast::{Argument, Call, Expr, Expression, ExternalArgument, PathMember, Range}, - engine::{Command, EngineState, Stack, StateWorkingSet}, + ast::{Argument, Expr, Expression, ExternalArgument, PathMember, Range}, + engine::{Call, Command, EngineState, Stack, StateWorkingSet}, ParseError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, }; use rstest::rstest; @@ -1759,10 +1759,7 @@ mod range { #[cfg(test)] mod input_types { use super::*; - use nu_protocol::{ - ast::{Argument, Call}, - Category, PipelineData, ShellError, Type, - }; + use nu_protocol::{ast::Argument, engine::Call, Category, PipelineData, ShellError, Type}; #[derive(Clone)] pub struct LsTest; diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index b026d21b23..0df533a11e 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -1,8 +1,7 @@ use crate::util::MutableCow; use nu_engine::{get_eval_block_with_early_return, get_full_help, ClosureEvalOnce}; use nu_protocol::{ - ast::Call, - engine::{Closure, EngineState, Redirection, Stack}, + engine::{Call, Closure, EngineState, Redirection, Stack}, Config, IntoSpanned, OutDest, PipelineData, PluginIdentity, ShellError, Signals, Span, Spanned, Value, }; @@ -54,7 +53,7 @@ pub struct PluginExecutionCommandContext<'a> { identity: Arc, engine_state: Cow<'a, EngineState>, stack: MutableCow<'a, Stack>, - call: Cow<'a, Call>, + call: Call<'a>, } impl<'a> PluginExecutionCommandContext<'a> { @@ -62,13 +61,13 @@ impl<'a> PluginExecutionCommandContext<'a> { identity: Arc, engine_state: &'a EngineState, stack: &'a mut Stack, - call: &'a Call, + call: &'a Call<'a>, ) -> PluginExecutionCommandContext<'a> { PluginExecutionCommandContext { identity, engine_state: Cow::Borrowed(engine_state), stack: MutableCow::Borrowed(stack), - call: Cow::Borrowed(call), + call: call.clone(), } } } @@ -217,7 +216,7 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { identity: self.identity.clone(), engine_state: Cow::Owned(self.engine_state.clone().into_owned()), stack: self.stack.owned(), - call: Cow::Owned(self.call.clone().into_owned()), + call: self.call.to_owned(), }) } } diff --git a/crates/nu-plugin-protocol/src/evaluated_call.rs b/crates/nu-plugin-protocol/src/evaluated_call.rs index 19f9049340..58f8987865 100644 --- a/crates/nu-plugin-protocol/src/evaluated_call.rs +++ b/crates/nu-plugin-protocol/src/evaluated_call.rs @@ -1,7 +1,7 @@ use nu_protocol::{ - ast::{Call, Expression}, - engine::{EngineState, Stack}, - FromValue, ShellError, Span, Spanned, Value, + ast::{self, Expression}, + engine::{Call, CallImpl, EngineState, Stack}, + ir, FromValue, ShellError, Span, Spanned, Value, }; use serde::{Deserialize, Serialize}; @@ -33,6 +33,24 @@ impl EvaluatedCall { engine_state: &EngineState, stack: &mut Stack, eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, + ) -> Result { + match &call.inner { + CallImpl::AstRef(call) => { + Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn) + } + CallImpl::AstBox(call) => { + Self::try_from_ast_call(call, engine_state, stack, eval_expression_fn) + } + CallImpl::IrRef(call) => Self::try_from_ir_call(call, stack), + CallImpl::IrBox(call) => Self::try_from_ir_call(call, stack), + } + } + + fn try_from_ast_call( + call: &ast::Call, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression_fn: fn(&EngineState, &mut Stack, &Expression) -> Result, ) -> Result { let positional = call.rest_iter_flattened(0, |expr| eval_expression_fn(engine_state, stack, expr))?; @@ -54,6 +72,22 @@ impl EvaluatedCall { }) } + fn try_from_ir_call(call: &ir::Call, stack: &Stack) -> Result { + let positional = call.rest_iter_flattened(stack, 0)?; + + let mut named = Vec::with_capacity(call.named_len(stack)); + named.extend( + call.named_iter(stack) + .map(|(name, value)| (name.map(|s| s.to_owned()), value.cloned())), + ); + + Ok(Self { + head: call.head, + positional, + named, + }) + } + /// Check if a flag (named parameter that does not take a value) is set /// Returns Ok(true) if flag is set or passed true value /// Returns Ok(false) if flag is not set or passed false value diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index ee0f5a8221..eaa861a073 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -33,6 +33,7 @@ serde = { workspace = true, default-features = false } thiserror = "1.0" typetag = "0.2" os_pipe = { workspace = true, features = ["io_safety"] } +log = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true, default-features = false, features = ["signal"] } @@ -54,4 +55,4 @@ tempfile = { workspace = true } os_pipe = { workspace = true } [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/crates/nu-protocol/src/alias.rs b/crates/nu-protocol/src/alias.rs index 24448225d4..8f5ea43934 100644 --- a/crates/nu-protocol/src/alias.rs +++ b/crates/nu-protocol/src/alias.rs @@ -1,6 +1,6 @@ use crate::{ - ast::{Call, Expression}, - engine::{Command, CommandType, EngineState, Stack}, + ast::Expression, + engine::{Call, Command, CommandType, EngineState, Stack}, PipelineData, ShellError, Signature, }; diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index 6e3449af26..8f62ff99ba 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,5 +1,5 @@ use super::Pipeline; -use crate::{engine::EngineState, OutDest, Signature, Span, Type, VarId}; +use crate::{engine::StateWorkingSet, ir::IrBlock, OutDest, Signature, Span, Type, VarId}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -8,6 +8,8 @@ pub struct Block { pub pipelines: Vec, pub captures: Vec, pub redirect_env: bool, + /// The block compiled to IR instructions. Not available for subexpressions. + pub ir_block: Option, pub span: Option, // None option encodes no span to avoid using test_span() } @@ -22,10 +24,10 @@ impl Block { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { if let Some(first) = self.pipelines.first() { - first.pipe_redirection(engine_state) + first.pipe_redirection(working_set) } else { (None, None) } @@ -45,6 +47,7 @@ impl Block { pipelines: vec![], captures: vec![], redirect_env: false, + ir_block: None, span: None, } } @@ -55,6 +58,7 @@ impl Block { pipelines: Vec::with_capacity(capacity), captures: vec![], redirect_env: false, + ir_block: None, span: None, } } @@ -86,6 +90,7 @@ where pipelines: pipelines.collect(), captures: vec![], redirect_env: false, + ir_block: None, span: None, } } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 0e561e5c8f..43548d39e4 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -5,7 +5,9 @@ use super::{ Call, CellPath, Expression, ExternalArgument, FullCellPath, Keyword, MatchPattern, Operator, Range, Table, ValueWithUnit, }; -use crate::{ast::ImportPattern, engine::EngineState, BlockId, OutDest, Signature, Span, VarId}; +use crate::{ + ast::ImportPattern, engine::StateWorkingSet, BlockId, OutDest, Signature, Span, VarId, +}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Expr { @@ -60,17 +62,17 @@ const _: () = assert!(std::mem::size_of::() <= 40); impl Expr { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { // Usages of `$in` will be wrapped by a `collect` call by the parser, // so we do not have to worry about that when considering // which of the expressions below may consume pipeline output. match self { - Expr::Call(call) => engine_state.get_decl(call.decl_id).pipe_redirection(), - Expr::Subexpression(block_id) | Expr::Block(block_id) => engine_state + Expr::Call(call) => working_set.get_decl(call.decl_id).pipe_redirection(), + Expr::Subexpression(block_id) | Expr::Block(block_id) => working_set .get_block(*block_id) - .pipe_redirection(engine_state), - Expr::FullCellPath(cell_path) => cell_path.head.expr.pipe_redirection(engine_state), + .pipe_redirection(working_set), + Expr::FullCellPath(cell_path) => cell_path.head.expr.pipe_redirection(working_set), Expr::Bool(_) | Expr::Int(_) | Expr::Float(_) diff --git a/crates/nu-protocol/src/ast/match_pattern.rs b/crates/nu-protocol/src/ast/match_pattern.rs index b8f87c3f63..1aafe84701 100644 --- a/crates/nu-protocol/src/ast/match_pattern.rs +++ b/crates/nu-protocol/src/ast/match_pattern.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MatchPattern { pub pattern: Pattern, - pub guard: Option, + pub guard: Option>, pub span: Span, } @@ -19,7 +19,9 @@ impl MatchPattern { pub enum Pattern { Record(Vec<(String, MatchPattern)>), List(Vec), - Value(Expression), + // TODO: it would be nice if this didn't depend on AST + // maybe const evaluation can get us to a Value instead? + Value(Box), Variable(VarId), Or(Vec), Rest(VarId), // the ..$foo pattern diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index f03c016daf..3f2a216485 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -1,8 +1,4 @@ -use crate::{ - ast::Expression, - engine::{EngineState, StateWorkingSet}, - OutDest, Span, -}; +use crate::{ast::Expression, engine::StateWorkingSet, OutDest, Span}; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -120,9 +116,9 @@ impl PipelineElement { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { - self.expr.expr.pipe_redirection(engine_state) + self.expr.expr.pipe_redirection(working_set) } } @@ -166,10 +162,10 @@ impl Pipeline { pub fn pipe_redirection( &self, - engine_state: &EngineState, + working_set: &StateWorkingSet, ) -> (Option, Option) { if let Some(first) = self.elements.first() { - first.pipe_redirection(engine_state) + first.pipe_redirection(working_set) } else { (None, None) } diff --git a/crates/nu-protocol/src/engine/argument.rs b/crates/nu-protocol/src/engine/argument.rs new file mode 100644 index 0000000000..043654b761 --- /dev/null +++ b/crates/nu-protocol/src/engine/argument.rs @@ -0,0 +1,124 @@ +use std::sync::Arc; + +use crate::{ast::Expression, ir::DataSlice, Span, Value}; + +/// Represents a fully evaluated argument to a call. +#[derive(Debug, Clone)] +pub enum Argument { + /// A positional argument + Positional { + span: Span, + val: Value, + ast: Option>, + }, + /// A spread argument, e.g. `...$args` + Spread { + span: Span, + vals: Value, + ast: Option>, + }, + /// A named argument with no value, e.g. `--flag` + Flag { + data: Arc<[u8]>, + name: DataSlice, + short: DataSlice, + span: Span, + }, + /// A named argument with a value, e.g. `--flag value` or `--flag=` + Named { + data: Arc<[u8]>, + name: DataSlice, + short: DataSlice, + span: Span, + val: Value, + ast: Option>, + }, + /// Information generated by the parser for use by certain keyword commands + ParserInfo { + data: Arc<[u8]>, + name: DataSlice, + // TODO: rather than `Expression`, this would probably be best served by a specific enum + // type for this purpose. + info: Box, + }, +} + +impl Argument { + /// The span encompassing the argument's usage within the call, distinct from the span of the + /// actual value of the argument. + pub fn span(&self) -> Option { + match self { + Argument::Positional { span, .. } => Some(*span), + Argument::Spread { span, .. } => Some(*span), + Argument::Flag { span, .. } => Some(*span), + Argument::Named { span, .. } => Some(*span), + // Because `ParserInfo` is generated, its span shouldn't be used + Argument::ParserInfo { .. } => None, + } + } + + /// The original AST [`Expression`] for the argument's value. This is not usually available; + /// declarations have to opt-in if they require this. + pub fn ast_expression(&self) -> Option<&Arc> { + match self { + Argument::Positional { ast, .. } => ast.as_ref(), + Argument::Spread { ast, .. } => ast.as_ref(), + Argument::Flag { .. } => None, + Argument::Named { ast, .. } => ast.as_ref(), + Argument::ParserInfo { .. } => None, + } + } +} + +/// Stores the argument context for calls in IR evaluation. +#[derive(Debug, Clone)] +pub struct ArgumentStack { + arguments: Vec, +} + +impl ArgumentStack { + /// Create a new, empty argument stack. + pub const fn new() -> Self { + ArgumentStack { arguments: vec![] } + } + + /// Returns the index of the end of the argument stack. Call and save this before adding + /// arguments. + pub fn get_base(&self) -> usize { + self.arguments.len() + } + + /// Calculates the number of arguments past the given [previously retrieved](.get_base) base + /// pointer. + pub fn get_len(&self, base: usize) -> usize { + self.arguments.len().checked_sub(base).unwrap_or_else(|| { + panic!( + "base ({}) is beyond the end of the arguments stack ({})", + base, + self.arguments.len() + ); + }) + } + + /// Push an argument onto the end of the argument stack. + pub fn push(&mut self, argument: Argument) { + self.arguments.push(argument); + } + + /// Clear all of the arguments after the given base index, to prepare for the next frame. + pub fn leave_frame(&mut self, base: usize) { + self.arguments.truncate(base); + } + + /// Get arguments for the frame based on the given [`base`](`.get_base()`) and + /// [`len`](`.get_len()`) parameters. + pub fn get_args(&self, base: usize, len: usize) -> &[Argument] { + &self.arguments[base..(base + len)] + } + + /// Move arguments for the frame based on the given [`base`](`.get_base()`) and + /// [`len`](`.get_len()`) parameters. + pub fn drain_args(&mut self, base: usize, len: usize) -> impl Iterator + '_ { + self.arguments.drain(base..(base + len)) + } +} diff --git a/crates/nu-protocol/src/engine/call.rs b/crates/nu-protocol/src/engine/call.rs new file mode 100644 index 0000000000..741e2bd87a --- /dev/null +++ b/crates/nu-protocol/src/engine/call.rs @@ -0,0 +1,223 @@ +use crate::{ + ast::{self, Expression}, + ir, DeclId, FromValue, ShellError, Span, Value, +}; + +use super::{EngineState, Stack, StateWorkingSet}; + +/// This is a HACK to help [`Command`](super::Command) support both the old AST evaluator and the +/// new IR evaluator at the same time. It should be removed once we are satisfied with the new +/// evaluator. +#[derive(Debug, Clone)] +pub struct Call<'a> { + pub head: Span, + pub decl_id: DeclId, + pub inner: CallImpl<'a>, +} + +#[derive(Debug, Clone)] +pub enum CallImpl<'a> { + AstRef(&'a ast::Call), + AstBox(Box), + IrRef(&'a ir::Call), + IrBox(Box), +} + +impl Call<'_> { + /// Returns a new AST call with the given span. This is often used by commands that need an + /// empty call to pass to a command. It's not easily possible to add anything to this. + pub fn new(span: Span) -> Self { + // this is using the boxed variant, which isn't so efficient... but this is only temporary + // anyway. + Call { + head: span, + decl_id: 0, + inner: CallImpl::AstBox(Box::new(ast::Call::new(span))), + } + } + + /// Convert the `Call` from any lifetime into `'static`, by cloning the data within onto the + /// heap. + pub fn to_owned(&self) -> Call<'static> { + Call { + head: self.head, + decl_id: self.decl_id, + inner: self.inner.to_owned(), + } + } + + /// Assert that the call is `ast::Call`, and fail with an error if it isn't. + /// + /// Provided as a stop-gap for commands that can't work with `ir::Call`, or just haven't been + /// implemented yet. Eventually these issues should be resolved and then this can be removed. + pub fn assert_ast_call(&self) -> Result<&ast::Call, ShellError> { + match &self.inner { + CallImpl::AstRef(call) => Ok(call), + CallImpl::AstBox(call) => Ok(call), + _ => Err(ShellError::NushellFailedSpanned { + msg: "Can't be used in IR context".into(), + label: "this command is not yet supported by IR evaluation".into(), + span: self.head, + }), + } + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn has_flag_const( + &self, + working_set: &StateWorkingSet, + flag_name: &str, + ) -> Result { + self.assert_ast_call()? + .has_flag_const(working_set, flag_name) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn get_flag_const( + &self, + working_set: &StateWorkingSet, + name: &str, + ) -> Result, ShellError> { + self.assert_ast_call()?.get_flag_const(working_set, name) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn req_const( + &self, + working_set: &StateWorkingSet, + pos: usize, + ) -> Result { + self.assert_ast_call()?.req_const(working_set, pos) + } + + /// FIXME: implementation asserts `ast::Call` and proxies to that + pub fn rest_const( + &self, + working_set: &StateWorkingSet, + starting_pos: usize, + ) -> Result, ShellError> { + self.assert_ast_call()? + .rest_const(working_set, starting_pos) + } + + /// Returns a span covering the call's arguments. + pub fn arguments_span(&self) -> Span { + match &self.inner { + CallImpl::AstRef(call) => call.arguments_span(), + CallImpl::AstBox(call) => call.arguments_span(), + CallImpl::IrRef(call) => call.arguments_span(), + CallImpl::IrBox(call) => call.arguments_span(), + } + } + + /// Returns a span covering the whole call. + pub fn span(&self) -> Span { + match &self.inner { + CallImpl::AstRef(call) => call.span(), + CallImpl::AstBox(call) => call.span(), + CallImpl::IrRef(call) => call.span(), + CallImpl::IrBox(call) => call.span(), + } + } + + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&'a self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + match &self.inner { + CallImpl::AstRef(call) => call.get_parser_info(name), + CallImpl::AstBox(call) => call.get_parser_info(name), + CallImpl::IrRef(call) => call.get_parser_info(stack, name), + CallImpl::IrBox(call) => call.get_parser_info(stack, name), + } + } + + /// Evaluator-agnostic implementation of `rest_iter_flattened()`. Evaluates or gets all of the + /// positional and spread arguments, flattens spreads, and then returns one list of values. + pub fn rest_iter_flattened( + &self, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression: fn( + &EngineState, + &mut Stack, + &ast::Expression, + ) -> Result, + starting_pos: usize, + ) -> Result, ShellError> { + fn by_ast( + call: &ast::Call, + engine_state: &EngineState, + stack: &mut Stack, + eval_expression: fn( + &EngineState, + &mut Stack, + &ast::Expression, + ) -> Result, + starting_pos: usize, + ) -> Result, ShellError> { + call.rest_iter_flattened(starting_pos, |expr| { + eval_expression(engine_state, stack, expr) + }) + } + + fn by_ir( + call: &ir::Call, + stack: &Stack, + starting_pos: usize, + ) -> Result, ShellError> { + call.rest_iter_flattened(stack, starting_pos) + } + + match &self.inner { + CallImpl::AstRef(call) => { + by_ast(call, engine_state, stack, eval_expression, starting_pos) + } + CallImpl::AstBox(call) => { + by_ast(call, engine_state, stack, eval_expression, starting_pos) + } + CallImpl::IrRef(call) => by_ir(call, stack, starting_pos), + CallImpl::IrBox(call) => by_ir(call, stack, starting_pos), + } + } + + /// Get the original AST expression for a positional argument. Does not usually work for IR + /// unless the decl specified `requires_ast_for_arguments()` + pub fn positional_nth<'a>(&'a self, stack: &'a Stack, index: usize) -> Option<&'a Expression> { + match &self.inner { + CallImpl::AstRef(call) => call.positional_nth(index), + CallImpl::AstBox(call) => call.positional_nth(index), + CallImpl::IrRef(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()), + CallImpl::IrBox(call) => call.positional_ast(stack, index).map(|arc| arc.as_ref()), + } + } +} + +impl CallImpl<'_> { + pub fn to_owned(&self) -> CallImpl<'static> { + match self { + CallImpl::AstRef(call) => CallImpl::AstBox(Box::new((*call).clone())), + CallImpl::AstBox(call) => CallImpl::AstBox(call.clone()), + CallImpl::IrRef(call) => CallImpl::IrBox(Box::new((*call).clone())), + CallImpl::IrBox(call) => CallImpl::IrBox(call.clone()), + } + } +} + +impl<'a> From<&'a ast::Call> for Call<'a> { + fn from(call: &'a ast::Call) -> Self { + Call { + head: call.head, + decl_id: call.decl_id, + inner: CallImpl::AstRef(call), + } + } +} + +impl<'a> From<&'a ir::Call> for Call<'a> { + fn from(call: &'a ir::Call) -> Self { + Call { + head: call.head, + decl_id: call.decl_id, + inner: CallImpl::IrRef(call), + } + } +} diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 043d2a66c7..48cdc4440d 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,5 +1,5 @@ use super::{EngineState, Stack, StateWorkingSet}; -use crate::{ast::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature}; +use crate::{engine::Call, Alias, BlockId, Example, OutDest, PipelineData, ShellError, Signature}; use std::fmt::Display; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -124,6 +124,12 @@ pub trait Command: Send + Sync + CommandClone { fn pipe_redirection(&self) -> (Option, Option) { (None, None) } + + /// Return true if the AST nodes for the arguments are required for IR evaluation. This is + /// currently inefficient so is not generally done. + fn requires_ast_for_arguments(&self) -> bool { + false + } } pub trait CommandClone { diff --git a/crates/nu-protocol/src/engine/error_handler.rs b/crates/nu-protocol/src/engine/error_handler.rs new file mode 100644 index 0000000000..076678be20 --- /dev/null +++ b/crates/nu-protocol/src/engine/error_handler.rs @@ -0,0 +1,55 @@ +use crate::RegId; + +/// Describes an error handler stored during IR evaluation. +#[derive(Debug, Clone, Copy)] +pub struct ErrorHandler { + /// Instruction index within the block that will handle the error + pub handler_index: usize, + /// Register to put the error information into, when an error occurs + pub error_register: Option, +} + +/// Keeps track of error handlers pushed during evaluation of an IR block. +#[derive(Debug, Clone)] +pub struct ErrorHandlerStack { + handlers: Vec, +} + +impl ErrorHandlerStack { + pub const fn new() -> ErrorHandlerStack { + ErrorHandlerStack { handlers: vec![] } + } + + /// Get the current base of the stack, which establishes a frame. + pub fn get_base(&self) -> usize { + self.handlers.len() + } + + /// Push a new error handler onto the stack. + pub fn push(&mut self, handler: ErrorHandler) { + self.handlers.push(handler); + } + + /// Try to pop an error handler from the stack. Won't go below `base`, to avoid retrieving a + /// handler belonging to a parent frame. + pub fn pop(&mut self, base: usize) -> Option { + if self.handlers.len() > base { + self.handlers.pop() + } else { + None + } + } + + /// Reset the stack to the state it was in at the beginning of the frame, in preparation to + /// return control to the parent frame. + pub fn leave_frame(&mut self, base: usize) { + if self.handlers.len() >= base { + self.handlers.truncate(base); + } else { + panic!( + "ErrorHandlerStack bug: tried to leave frame at {base}, but current base is {}", + self.get_base() + ) + } + } +} diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index c6e71afb37..1b1762fe3c 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,8 +1,11 @@ +mod argument; mod cached_file; +mod call; mod call_info; mod capture_block; mod command; mod engine_state; +mod error_handler; mod overlay; mod pattern_match; mod stack; @@ -14,10 +17,13 @@ mod variable; pub use cached_file::CachedFile; +pub use argument::*; +pub use call::*; pub use call_info::*; pub use capture_block::*; pub use command::*; pub use engine_state::*; +pub use error_handler::*; pub use overlay::*; pub use pattern_match::*; pub use stack::*; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 19726db9c0..b289c1ae8b 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,7 +1,7 @@ use crate::{ engine::{ - EngineState, Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest, - DEFAULT_OVERLAY_NAME, + ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard, + StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME, }, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, }; @@ -41,6 +41,12 @@ pub struct Stack { pub env_hidden: HashMap>, /// List of active overlays pub active_overlays: Vec, + /// Argument stack for IR evaluation + pub arguments: ArgumentStack, + /// Error handler stack for IR evaluation + pub error_handlers: ErrorHandlerStack, + /// Set true to always use IR mode + pub use_ir: bool, pub recursion_count: u64, pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) @@ -68,6 +74,9 @@ impl Stack { env_vars: Vec::new(), env_hidden: HashMap::new(), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: false, recursion_count: 0, parent_stack: None, parent_deletions: vec![], @@ -85,6 +94,9 @@ impl Stack { env_vars: parent.env_vars.clone(), env_hidden: parent.env_hidden.clone(), active_overlays: parent.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: parent.use_ir, recursion_count: parent.recursion_count, vars: vec![], parent_deletions: vec![], @@ -254,6 +266,9 @@ impl Stack { env_vars, env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: self.use_ir, recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], @@ -284,6 +299,9 @@ impl Stack { env_vars, env_hidden: self.env_hidden.clone(), active_overlays: self.active_overlays.clone(), + arguments: ArgumentStack::new(), + error_handlers: ErrorHandlerStack::new(), + use_ir: self.use_ir, recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index af950b8321..8c3968a824 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -4,8 +4,8 @@ use crate::{ usage::build_usage, CachedFile, Command, CommandType, EngineState, OverlayFrame, StateDelta, Variable, VirtualPath, Visibility, }, - BlockId, Category, Config, DeclId, FileId, GetSpan, Module, ModuleId, ParseError, ParseWarning, - Span, SpanId, Type, Value, VarId, VirtualPathId, + BlockId, Category, CompileError, Config, DeclId, FileId, GetSpan, Module, ModuleId, ParseError, + ParseWarning, Span, SpanId, Type, Value, VarId, VirtualPathId, }; use core::panic; use std::{ @@ -31,6 +31,7 @@ pub struct StateWorkingSet<'a> { pub search_predecls: bool, pub parse_errors: Vec, pub parse_warnings: Vec, + pub compile_errors: Vec, } impl<'a> StateWorkingSet<'a> { @@ -50,6 +51,7 @@ impl<'a> StateWorkingSet<'a> { search_predecls: true, parse_errors: vec![], parse_warnings: vec![], + compile_errors: vec![], } } @@ -260,6 +262,12 @@ impl<'a> StateWorkingSet<'a> { } pub fn add_block(&mut self, block: Arc) -> BlockId { + log::trace!( + "block id={} added, has IR = {:?}", + self.num_blocks(), + block.ir_block.is_some() + ); + self.delta.blocks.push(block); self.num_blocks() - 1 diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index 003564f933..181839b948 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -107,4 +107,8 @@ impl<'src> miette::Diagnostic for CliError<'src> { fn related<'a>(&'a self) -> Option + 'a>> { self.0.related() } + + fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> { + self.0.diagnostic_source() + } } diff --git a/crates/nu-protocol/src/errors/compile_error.rs b/crates/nu-protocol/src/errors/compile_error.rs new file mode 100644 index 0000000000..cc805a73ed --- /dev/null +++ b/crates/nu-protocol/src/errors/compile_error.rs @@ -0,0 +1,238 @@ +use crate::{RegId, Span}; +use miette::Diagnostic; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// An internal compiler error, generally means a Nushell bug rather than an issue with user error +/// since parsing and typechecking has already passed. +#[derive(Debug, Clone, Error, Diagnostic, PartialEq, Serialize, Deserialize)] +pub enum CompileError { + #[error("Register overflow.")] + #[diagnostic(code(nu::compile::register_overflow))] + RegisterOverflow { + #[label("the code being compiled is probably too large")] + block_span: Option, + }, + + #[error("Register {reg_id} was uninitialized when used, possibly reused.")] + #[diagnostic( + code(nu::compile::register_uninitialized), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new\nfrom: {caller}"), + )] + RegisterUninitialized { reg_id: RegId, caller: String }, + + #[error("Register {reg_id} was uninitialized when used, possibly reused.")] + #[diagnostic( + code(nu::compile::register_uninitialized), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new\nfrom: {caller}"), + )] + RegisterUninitializedWhilePushingInstruction { + reg_id: RegId, + caller: String, + instruction: String, + #[label("while adding this instruction: {instruction}")] + span: Span, + }, + + #[error("Block contains too much string data: maximum 4 GiB exceeded.")] + #[diagnostic( + code(nu::compile::data_overflow), + help("try loading the string data from a file instead") + )] + DataOverflow { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Block contains too many files.")] + #[diagnostic( + code(nu::compile::register_overflow), + help("try using fewer file redirections") + )] + FileOverflow { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Invalid redirect mode: File should not be specified by commands.")] + #[diagnostic( + code(nu::compile::invalid_redirect_mode), + help("this is a command bug. Please report it at https://github.com/nushell/nushell/issues/new") + )] + InvalidRedirectMode { + #[label("while compiling this expression")] + span: Span, + }, + + #[error("Encountered garbage, likely due to parse error.")] + #[diagnostic(code(nu::compile::garbage))] + Garbage { + #[label("garbage found here")] + span: Span, + }, + + #[error("Unsupported operator expression.")] + #[diagnostic(code(nu::compile::unsupported_operator_expression))] + UnsupportedOperatorExpression { + #[label("this expression is in operator position but is not an operator")] + span: Span, + }, + + #[error("Attempted access of $env by integer path.")] + #[diagnostic(code(nu::compile::access_env_by_int))] + AccessEnvByInt { + #[label("$env keys should be strings")] + span: Span, + }, + + #[error("Encountered invalid `{keyword}` keyword call.")] + #[diagnostic(code(nu::compile::invalid_keyword_call))] + InvalidKeywordCall { + keyword: String, + #[label("this call is not properly formed")] + span: Span, + }, + + #[error("Attempted to set branch target of non-branch instruction.")] + #[diagnostic( + code(nu::compile::set_branch_target_of_non_branch_instruction), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + SetBranchTargetOfNonBranchInstruction { + instruction: String, + #[label("tried to modify: {instruction}")] + span: Span, + }, + + /// You're trying to run an unsupported external command. + /// + /// ## Resolution + /// + /// Make sure there's an appropriate `run-external` declaration for this external command. + #[error("External calls are not supported.")] + #[diagnostic( + code(nu::compile::run_external_not_found), + help("`run-external` was not found in scope") + )] + RunExternalNotFound { + #[label("can't be run in this context")] + span: Span, + }, + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a variable or variable cell path. + #[error("Assignment operations require a variable.")] + #[diagnostic( + code(nu::compile::assignment_requires_variable), + help("try assigning to a variable or a cell path of a variable") + )] + AssignmentRequiresVar { + #[label("needs to be a variable")] + span: Span, + }, + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a mutable variable or cell path. + #[error("Assignment to an immutable variable.")] + #[diagnostic( + code(nu::compile::assignment_requires_mutable_variable), + help("declare the variable with `mut`, or shadow it again with `let`") + )] + AssignmentRequiresMutableVar { + #[label("needs to be a mutable variable")] + span: Span, + }, + + /// This environment variable cannot be set manually. + /// + /// ## Resolution + /// + /// This environment variable is set automatically by Nushell and cannot not be set manually. + #[error("{envvar_name} cannot be set manually.")] + #[diagnostic( + code(nu::compile::automatic_env_var_set_manually), + help( + r#"The environment variable '{envvar_name}' is set automatically by Nushell and cannot be set manually."# + ) + )] + AutomaticEnvVarSetManually { + envvar_name: String, + #[label("cannot set '{envvar_name}' manually")] + span: Span, + }, + + /// It is not possible to replace the entire environment at once + /// + /// ## Resolution + /// + /// Setting the entire environment is not allowed. Change environment variables individually + /// instead. + #[error("Cannot replace environment.")] + #[diagnostic( + code(nu::compile::cannot_replace_env), + help("Assigning a value to '$env' is not allowed.") + )] + CannotReplaceEnv { + #[label("setting '$env' not allowed")] + span: Span, + }, + + #[error("Unexpected expression.")] + #[diagnostic(code(nu::compile::unexpected_expression))] + UnexpectedExpression { + expr_name: String, + #[label("{expr_name} is not allowed in this context")] + span: Span, + }, + + #[error("Missing required declaration: `{decl_name}`")] + #[diagnostic(code(nu::compile::missing_required_declaration))] + MissingRequiredDeclaration { + decl_name: String, + #[label("`{decl_name}` must be in scope to compile this expression")] + span: Span, + }, + + #[error("Invalid literal")] + #[diagnostic(code(nu::compile::invalid_literal))] + InvalidLiteral { + msg: String, + #[label("{msg}")] + span: Span, + }, + + #[error("{msg}")] + #[diagnostic(code(nu::compile::not_in_a_loop))] + NotInALoop { + msg: String, + #[label("can't be used outside of a loop")] + span: Option, + }, + + #[error("Incoherent loop state: the loop that ended was not the one we were expecting.")] + #[diagnostic( + code(nu::compile::incoherent_loop_state), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + IncoherentLoopState { + #[label("while compiling this block")] + block_span: Option, + }, + + #[error("Undefined label `{label_id}`.")] + #[diagnostic( + code(nu::compile::undefined_label), + help("this is a compiler bug. Please report it at https://github.com/nushell/nushell/issues/new"), + )] + UndefinedLabel { + label_id: usize, + #[label("label was used while compiling this code")] + span: Option, + }, +} diff --git a/crates/nu-protocol/src/errors/mod.rs b/crates/nu-protocol/src/errors/mod.rs index 23006ab684..3f895cd65f 100644 --- a/crates/nu-protocol/src/errors/mod.rs +++ b/crates/nu-protocol/src/errors/mod.rs @@ -1,10 +1,12 @@ pub mod cli_error; +mod compile_error; mod labeled_error; mod parse_error; mod parse_warning; mod shell_error; pub use cli_error::{format_error, report_error, report_error_new}; +pub use compile_error::CompileError; pub use labeled_error::{ErrorLabel, LabeledError}; pub use parse_error::{DidYouMean, ParseError}; pub use parse_warning::ParseWarning; diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index be24fd093e..ab01ccfa54 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1376,6 +1376,23 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"# help("Set XDG_CONFIG_HOME to an absolute path, or set it to an empty string to ignore it") )] InvalidXdgConfig { xdg: String, default: String }, + + /// An unexpected error occurred during IR evaluation. + /// + /// ## Resolution + /// + /// This is most likely a correctness issue with the IR compiler or evaluator. Please file a + /// bug with the minimum code needed to reproduce the issue, if possible. + #[error("IR evaluation error: {msg}")] + #[diagnostic( + code(nu::shell::ir_eval_error), + help("this is a bug, please report it at https://github.com/nushell/nushell/issues/new along with the code you were running if able") + )] + IrEvalError { + msg: String, + #[label = "while running this code"] + span: Option, + }, } // TODO: Implement as From trait diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 87913e4ee3..5393e35e59 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -307,7 +307,7 @@ fn eval_const_call( return Err(ShellError::NotAConstHelp { span: call.head }); } - decl.run_const(working_set, call, input) + decl.run_const(working_set, &call.into(), input) } pub fn eval_const_subexpression( diff --git a/crates/nu-protocol/src/id.rs b/crates/nu-protocol/src/id.rs index 73c4f52e70..829ee8f36d 100644 --- a/crates/nu-protocol/src/id.rs +++ b/crates/nu-protocol/src/id.rs @@ -7,5 +7,19 @@ pub type ModuleId = usize; pub type OverlayId = usize; pub type FileId = usize; pub type VirtualPathId = usize; + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct SpanId(pub usize); // more robust ID style used in the new parser + +/// An ID for an [IR](crate::ir) register. `%n` is a common shorthand for `RegId(n)`. +/// +/// Note: `%0` is allocated with the block input at the beginning of a compiled block. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[repr(transparent)] +pub struct RegId(pub u32); + +impl std::fmt::Display for RegId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "%{}", self.0) + } +} diff --git a/crates/nu-protocol/src/ir/call.rs b/crates/nu-protocol/src/ir/call.rs new file mode 100644 index 0000000000..3d16f82eb6 --- /dev/null +++ b/crates/nu-protocol/src/ir/call.rs @@ -0,0 +1,351 @@ +use std::sync::Arc; + +use crate::{ + ast::Expression, + engine::{self, Argument, Stack}, + DeclId, ShellError, Span, Spanned, Value, +}; + +use super::DataSlice; + +/// Contains the information for a call being made to a declared command. +#[derive(Debug, Clone)] +pub struct Call { + /// The declaration ID of the command to be invoked. + pub decl_id: DeclId, + /// The span encompassing the command name, before the arguments. + pub head: Span, + /// The span encompassing the command name and all arguments. + pub span: Span, + /// The base index of the arguments for this call within the + /// [argument stack](crate::engine::ArgumentStack). + pub args_base: usize, + /// The number of [`Argument`]s for the call. Note that this just counts the number of + /// `Argument` entries on the stack, and has nothing to do with the actual number of positional + /// or spread arguments. + pub args_len: usize, +} + +impl Call { + /// Build a new call with arguments. + pub fn build(decl_id: DeclId, head: Span) -> CallBuilder { + CallBuilder { + inner: Call { + decl_id, + head, + span: head, + args_base: 0, + args_len: 0, + }, + } + } + + /// Get the arguments for this call from the arguments stack. + pub fn arguments<'a>(&self, stack: &'a Stack) -> &'a [Argument] { + stack.arguments.get_args(self.args_base, self.args_len) + } + + /// The span encompassing the arguments + /// + /// If there are no arguments the span covers where the first argument would exist + /// + /// If there are one or more arguments the span encompasses the start of the first argument to + /// end of the last argument + pub fn arguments_span(&self) -> Span { + let past = self.head.past(); + Span::new(past.start, self.span.end) + } + + /// The number of named arguments, with or without values. + pub fn named_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Named { .. } | Argument::Flag { .. })) + .count() + } + + /// Iterate through named arguments, with or without values. + pub fn named_iter<'a>( + &'a self, + stack: &'a Stack, + ) -> impl Iterator, Option<&'a Value>)> + 'a { + self.arguments(stack).iter().filter_map( + |arg: &Argument| -> Option<(Spanned<&str>, Option<&Value>)> { + match arg { + Argument::Flag { + data, name, span, .. + } => Some(( + Spanned { + item: std::str::from_utf8(&data[*name]).expect("invalid arg name"), + span: *span, + }, + None, + )), + Argument::Named { + data, + name, + span, + val, + .. + } => Some(( + Spanned { + item: std::str::from_utf8(&data[*name]).expect("invalid arg name"), + span: *span, + }, + Some(val), + )), + _ => None, + } + }, + ) + } + + /// Get a named argument's value by name. Returns [`None`] for named arguments with no value as + /// well. + pub fn get_named_arg<'a>(&self, stack: &'a Stack, flag_name: &str) -> Option<&'a Value> { + // Optimized to avoid str::from_utf8() + self.arguments(stack) + .iter() + .find_map(|arg: &Argument| -> Option> { + match arg { + Argument::Flag { data, name, .. } if &data[*name] == flag_name.as_bytes() => { + Some(None) + } + Argument::Named { + data, name, val, .. + } if &data[*name] == flag_name.as_bytes() => Some(Some(val)), + _ => None, + } + }) + .flatten() + } + + /// The number of positional arguments, excluding spread arguments. + pub fn positional_len(&self, stack: &Stack) -> usize { + self.arguments(stack) + .iter() + .filter(|arg| matches!(arg, Argument::Positional { .. })) + .count() + } + + /// Iterate through positional arguments. Does not include spread arguments. + pub fn positional_iter<'a>(&self, stack: &'a Stack) -> impl Iterator { + self.arguments(stack).iter().filter_map(|arg| match arg { + Argument::Positional { val, .. } => Some(val), + _ => None, + }) + } + + /// Get a positional argument by index. Does not include spread arguments. + pub fn positional_nth<'a>(&self, stack: &'a Stack, index: usize) -> Option<&'a Value> { + self.positional_iter(stack).nth(index) + } + + /// Get the AST node for a positional argument by index. Not usually available unless the decl + /// required it. + pub fn positional_ast<'a>( + &self, + stack: &'a Stack, + index: usize, + ) -> Option<&'a Arc> { + self.arguments(stack) + .iter() + .filter_map(|arg| match arg { + Argument::Positional { ast, .. } => Some(ast), + _ => None, + }) + .nth(index) + .and_then(|option| option.as_ref()) + } + + /// Returns every argument to the rest parameter, as well as whether each argument + /// is spread or a normal positional argument (true for spread, false for normal) + pub fn rest_iter<'a>( + &self, + stack: &'a Stack, + start: usize, + ) -> impl Iterator + 'a { + self.arguments(stack) + .iter() + .filter_map(|arg| match arg { + Argument::Positional { val, .. } => Some((val, false)), + Argument::Spread { vals, .. } => Some((vals, true)), + _ => None, + }) + .skip(start) + } + + /// Returns all of the positional arguments including and after `start`, with spread arguments + /// flattened into a single `Vec`. + pub fn rest_iter_flattened( + &self, + stack: &Stack, + start: usize, + ) -> Result, ShellError> { + let mut acc = vec![]; + for (rest_val, spread) in self.rest_iter(stack, start) { + if spread { + match rest_val { + Value::List { vals, .. } => acc.extend(vals.iter().cloned()), + Value::Error { error, .. } => return Err(ShellError::clone(error)), + _ => { + return Err(ShellError::CannotSpreadAsList { + span: rest_val.span(), + }) + } + } + } else { + acc.push(rest_val.clone()); + } + } + Ok(acc) + } + + /// Get a parser info argument by name. + pub fn get_parser_info<'a>(&self, stack: &'a Stack, name: &str) -> Option<&'a Expression> { + self.arguments(stack) + .iter() + .find_map(|argument| match argument { + Argument::ParserInfo { + data, + name: name_slice, + info: expr, + } if &data[*name_slice] == name.as_bytes() => Some(expr.as_ref()), + _ => None, + }) + } + + /// Returns a span encompassing the entire call. + pub fn span(&self) -> Span { + self.span + } + + /// Resets the [`Stack`] to its state before the call was made. + pub fn leave(&self, stack: &mut Stack) { + stack.arguments.leave_frame(self.args_base); + } +} + +/// Utility struct for building a [`Call`] with arguments on the [`Stack`]. +pub struct CallBuilder { + inner: Call, +} + +impl CallBuilder { + /// Add an argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_argument(&mut self, stack: &mut Stack, argument: Argument) -> &mut Self { + if self.inner.args_len == 0 { + self.inner.args_base = stack.arguments.get_base(); + } + self.inner.args_len += 1; + if let Some(span) = argument.span() { + self.inner.span = self.inner.span.append(span); + } + stack.arguments.push(argument); + self + } + + /// Add a positional argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_positional(&mut self, stack: &mut Stack, span: Span, val: Value) -> &mut Self { + self.add_argument( + stack, + Argument::Positional { + span, + val, + ast: None, + }, + ) + } + + /// Add a spread argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_spread(&mut self, stack: &mut Stack, span: Span, vals: Value) -> &mut Self { + self.add_argument( + stack, + Argument::Spread { + span, + vals, + ast: None, + }, + ) + } + + /// Add a flag (no-value named) argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_flag( + &mut self, + stack: &mut Stack, + name: impl AsRef, + short: impl AsRef, + span: Span, + ) -> &mut Self { + let (data, name, short) = data_from_name_and_short(name.as_ref(), short.as_ref()); + self.add_argument( + stack, + Argument::Flag { + data, + name, + short, + span, + }, + ) + } + + /// Add a named argument to the [`Stack`] and reference it from the [`Call`]. + pub fn add_named( + &mut self, + stack: &mut Stack, + name: impl AsRef, + short: impl AsRef, + span: Span, + val: Value, + ) -> &mut Self { + let (data, name, short) = data_from_name_and_short(name.as_ref(), short.as_ref()); + self.add_argument( + stack, + Argument::Named { + data, + name, + short, + span, + val, + ast: None, + }, + ) + } + + /// Produce the finished [`Call`] from the builder. + /// + /// The call should be entered / run before any other calls are constructed, because the + /// argument stack will be reset when they exit. + pub fn finish(&self) -> Call { + self.inner.clone() + } + + /// Run a closure with the [`Call`] as an [`engine::Call`] reference, and then clean up the + /// arguments that were added to the [`Stack`] after. + /// + /// For convenience. Calls [`Call::leave`] after the closure ends. + pub fn with( + self, + stack: &mut Stack, + f: impl FnOnce(&mut Stack, &engine::Call<'_>) -> T, + ) -> T { + let call = engine::Call::from(&self.inner); + let result = f(stack, &call); + self.inner.leave(stack); + result + } +} + +fn data_from_name_and_short(name: &str, short: &str) -> (Arc<[u8]>, DataSlice, DataSlice) { + let data: Vec = name.bytes().chain(short.bytes()).collect(); + let data: Arc<[u8]> = data.into(); + let name = DataSlice { + start: 0, + len: name.len().try_into().expect("flag name too big"), + }; + let short = DataSlice { + start: name.start.checked_add(name.len).expect("flag name too big"), + len: short.len().try_into().expect("flag short name too big"), + }; + (data, name, short) +} diff --git a/crates/nu-protocol/src/ir/display.rs b/crates/nu-protocol/src/ir/display.rs new file mode 100644 index 0000000000..c28323cca4 --- /dev/null +++ b/crates/nu-protocol/src/ir/display.rs @@ -0,0 +1,452 @@ +use std::fmt; + +use crate::{ast::Pattern, engine::EngineState, DeclId, VarId}; + +use super::{DataSlice, Instruction, IrBlock, Literal, RedirectMode}; + +pub struct FmtIrBlock<'a> { + pub(super) engine_state: &'a EngineState, + pub(super) ir_block: &'a IrBlock, +} + +impl<'a> fmt::Display for FmtIrBlock<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let plural = |count| if count == 1 { "" } else { "s" }; + writeln!( + f, + "# {} register{}, {} instruction{}, {} byte{} of data", + self.ir_block.register_count, + plural(self.ir_block.register_count as usize), + self.ir_block.instructions.len(), + plural(self.ir_block.instructions.len()), + self.ir_block.data.len(), + plural(self.ir_block.data.len()), + )?; + if self.ir_block.file_count > 0 { + writeln!( + f, + "# {} file{} used for redirection", + self.ir_block.file_count, + plural(self.ir_block.file_count as usize) + )?; + } + for (index, instruction) in self.ir_block.instructions.iter().enumerate() { + let formatted = format!( + "{:-4}: {}", + index, + FmtInstruction { + engine_state: self.engine_state, + instruction, + data: &self.ir_block.data, + } + ); + let comment = &self.ir_block.comments[index]; + if comment.is_empty() { + writeln!(f, "{formatted}")?; + } else { + writeln!(f, "{formatted:40} # {comment}")?; + } + } + Ok(()) + } +} + +pub struct FmtInstruction<'a> { + pub(super) engine_state: &'a EngineState, + pub(super) instruction: &'a Instruction, + pub(super) data: &'a [u8], +} + +impl<'a> fmt::Display for FmtInstruction<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + const WIDTH: usize = 22; + + match self.instruction { + Instruction::Unreachable => { + write!(f, "{:WIDTH$}", "unreachable") + } + Instruction::LoadLiteral { dst, lit } => { + let lit = FmtLiteral { + literal: lit, + data: self.data, + }; + write!(f, "{:WIDTH$} {dst}, {lit}", "load-literal") + } + Instruction::LoadValue { dst, val } => { + let val = val.to_debug_string(); + write!(f, "{:WIDTH$} {dst}, {val}", "load-value") + } + Instruction::Move { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "move") + } + Instruction::Clone { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "clone") + } + Instruction::Collect { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "collect") + } + Instruction::Span { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "span") + } + Instruction::Drop { src } => { + write!(f, "{:WIDTH$} {src}", "drop") + } + Instruction::Drain { src } => { + write!(f, "{:WIDTH$} {src}", "drain") + } + Instruction::LoadVariable { dst, var_id } => { + let var = FmtVar::new(self.engine_state, *var_id); + write!(f, "{:WIDTH$} {dst}, {var}", "load-variable") + } + Instruction::StoreVariable { var_id, src } => { + let var = FmtVar::new(self.engine_state, *var_id); + write!(f, "{:WIDTH$} {var}, {src}", "store-variable") + } + Instruction::LoadEnv { dst, key } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {dst}, {key}", "load-env") + } + Instruction::LoadEnvOpt { dst, key } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {dst}, {key}", "load-env-opt") + } + Instruction::StoreEnv { key, src } => { + let key = FmtData(self.data, *key); + write!(f, "{:WIDTH$} {key}, {src}", "store-env") + } + Instruction::PushPositional { src } => { + write!(f, "{:WIDTH$} {src}", "push-positional") + } + Instruction::AppendRest { src } => { + write!(f, "{:WIDTH$} {src}", "append-rest") + } + Instruction::PushFlag { name } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}", "push-flag") + } + Instruction::PushShortFlag { short } => { + let short = FmtData(self.data, *short); + write!(f, "{:WIDTH$} {short}", "push-short-flag") + } + Instruction::PushNamed { name, src } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}, {src}", "push-named") + } + Instruction::PushShortNamed { short, src } => { + let short = FmtData(self.data, *short); + write!(f, "{:WIDTH$} {short}, {src}", "push-short-named") + } + Instruction::PushParserInfo { name, info } => { + let name = FmtData(self.data, *name); + write!(f, "{:WIDTH$} {name}, {info:?}", "push-parser-info") + } + Instruction::RedirectOut { mode } => { + write!(f, "{:WIDTH$} {mode}", "redirect-out") + } + Instruction::RedirectErr { mode } => { + write!(f, "{:WIDTH$} {mode}", "redirect-err") + } + Instruction::CheckErrRedirected { src } => { + write!(f, "{:WIDTH$} {src}", "check-err-redirected") + } + Instruction::OpenFile { + file_num, + path, + append, + } => { + write!( + f, + "{:WIDTH$} file({file_num}), {path}, append = {append:?}", + "open-file" + ) + } + Instruction::WriteFile { file_num, src } => { + write!(f, "{:WIDTH$} file({file_num}), {src}", "write-file") + } + Instruction::CloseFile { file_num } => { + write!(f, "{:WIDTH$} file({file_num})", "close-file") + } + Instruction::Call { decl_id, src_dst } => { + let decl = FmtDecl::new(self.engine_state, *decl_id); + write!(f, "{:WIDTH$} {decl}, {src_dst}", "call") + } + Instruction::StringAppend { src_dst, val } => { + write!(f, "{:WIDTH$} {src_dst}, {val}", "string-append") + } + Instruction::GlobFrom { src_dst, no_expand } => { + let no_expand = if *no_expand { "no-expand" } else { "expand" }; + write!(f, "{:WIDTH$} {src_dst}, {no_expand}", "glob-from",) + } + Instruction::ListPush { src_dst, item } => { + write!(f, "{:WIDTH$} {src_dst}, {item}", "list-push") + } + Instruction::ListSpread { src_dst, items } => { + write!(f, "{:WIDTH$} {src_dst}, {items}", "list-spread") + } + Instruction::RecordInsert { src_dst, key, val } => { + write!(f, "{:WIDTH$} {src_dst}, {key}, {val}", "record-insert") + } + Instruction::RecordSpread { src_dst, items } => { + write!(f, "{:WIDTH$} {src_dst}, {items}", "record-spread") + } + Instruction::Not { src_dst } => { + write!(f, "{:WIDTH$} {src_dst}", "not") + } + Instruction::BinaryOp { lhs_dst, op, rhs } => { + write!(f, "{:WIDTH$} {lhs_dst}, {op:?}, {rhs}", "binary-op") + } + Instruction::FollowCellPath { src_dst, path } => { + write!(f, "{:WIDTH$} {src_dst}, {path}", "follow-cell-path") + } + Instruction::CloneCellPath { dst, src, path } => { + write!(f, "{:WIDTH$} {dst}, {src}, {path}", "clone-cell-path") + } + Instruction::UpsertCellPath { + src_dst, + path, + new_value, + } => { + write!( + f, + "{:WIDTH$} {src_dst}, {path}, {new_value}", + "upsert-cell-path" + ) + } + Instruction::Jump { index } => { + write!(f, "{:WIDTH$} {index}", "jump") + } + Instruction::BranchIf { cond, index } => { + write!(f, "{:WIDTH$} {cond}, {index}", "branch-if") + } + Instruction::BranchIfEmpty { src, index } => { + write!(f, "{:WIDTH$} {src}, {index}", "branch-if-empty") + } + Instruction::Match { + pattern, + src, + index, + } => { + let pattern = FmtPattern { + engine_state: self.engine_state, + pattern, + }; + write!(f, "{:WIDTH$} ({pattern}), {src}, {index}", "match") + } + Instruction::CheckMatchGuard { src } => { + write!(f, "{:WIDTH$} {src}", "check-match-guard") + } + Instruction::Iterate { + dst, + stream, + end_index, + } => { + write!(f, "{:WIDTH$} {dst}, {stream}, end {end_index}", "iterate") + } + Instruction::OnError { index } => { + write!(f, "{:WIDTH$} {index}", "on-error") + } + Instruction::OnErrorInto { index, dst } => { + write!(f, "{:WIDTH$} {index}, {dst}", "on-error-into") + } + Instruction::PopErrorHandler => { + write!(f, "{:WIDTH$}", "pop-error-handler") + } + Instruction::CheckExternalFailed { dst, src } => { + write!(f, "{:WIDTH$} {dst}, {src}", "check-external-failed") + } + Instruction::ReturnEarly { src } => { + write!(f, "{:WIDTH$} {src}", "return-early") + } + Instruction::Return { src } => { + write!(f, "{:WIDTH$} {src}", "return") + } + } + } +} + +struct FmtDecl<'a>(DeclId, &'a str); + +impl<'a> FmtDecl<'a> { + fn new(engine_state: &'a EngineState, decl_id: DeclId) -> Self { + FmtDecl(decl_id, engine_state.get_decl(decl_id).name()) + } +} + +impl fmt::Display for FmtDecl<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "decl {} {:?}", self.0, self.1) + } +} + +struct FmtVar<'a>(DeclId, Option<&'a str>); + +impl<'a> FmtVar<'a> { + fn new(engine_state: &'a EngineState, var_id: VarId) -> Self { + // Search for the name of the variable + let name: Option<&str> = engine_state + .active_overlays(&[]) + .flat_map(|overlay| overlay.vars.iter()) + .find(|(_, v)| **v == var_id) + .map(|(k, _)| std::str::from_utf8(k).unwrap_or("")); + FmtVar(var_id, name) + } +} + +impl fmt::Display for FmtVar<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(name) = self.1 { + write!(f, "var {} {:?}", self.0, name) + } else { + write!(f, "var {}", self.0) + } + } +} + +impl fmt::Display for RedirectMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RedirectMode::Pipe => write!(f, "pipe"), + RedirectMode::Capture => write!(f, "capture"), + RedirectMode::Null => write!(f, "null"), + RedirectMode::Inherit => write!(f, "inherit"), + RedirectMode::File { file_num } => write!(f, "file({file_num})"), + RedirectMode::Caller => write!(f, "caller"), + } + } +} + +struct FmtData<'a>(&'a [u8], DataSlice); + +impl<'a> fmt::Display for FmtData<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Ok(s) = std::str::from_utf8(&self.0[self.1]) { + // Write as string + write!(f, "{s:?}") + } else { + // Write as byte array + write!(f, "0x{:x?}", self.0) + } + } +} + +struct FmtLiteral<'a> { + literal: &'a Literal, + data: &'a [u8], +} + +impl<'a> fmt::Display for FmtLiteral<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.literal { + Literal::Bool(b) => write!(f, "bool({b:?})"), + Literal::Int(i) => write!(f, "int({i:?})"), + Literal::Float(fl) => write!(f, "float({fl:?})"), + Literal::Filesize(q) => write!(f, "filesize({q}b)"), + Literal::Duration(q) => write!(f, "duration({q}ns)"), + Literal::Binary(b) => write!(f, "binary({})", FmtData(self.data, *b)), + Literal::Block(id) => write!(f, "block({id})"), + Literal::Closure(id) => write!(f, "closure({id})"), + Literal::RowCondition(id) => write!(f, "row_condition({id})"), + Literal::Range { + start, + step, + end, + inclusion, + } => write!(f, "range({start}, {step}, {end}, {inclusion:?})"), + Literal::List { capacity } => write!(f, "list(capacity = {capacity})"), + Literal::Record { capacity } => write!(f, "record(capacity = {capacity})"), + Literal::Filepath { val, no_expand } => write!( + f, + "filepath({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::Directory { val, no_expand } => write!( + f, + "directory({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::GlobPattern { val, no_expand } => write!( + f, + "glob-pattern({}, no_expand = {no_expand:?})", + FmtData(self.data, *val) + ), + Literal::String(s) => write!(f, "string({})", FmtData(self.data, *s)), + Literal::RawString(rs) => write!(f, "raw-string({})", FmtData(self.data, *rs)), + Literal::CellPath(p) => write!(f, "cell-path({p})"), + Literal::Date(dt) => write!(f, "date({dt})"), + Literal::Nothing => write!(f, "nothing"), + } + } +} + +struct FmtPattern<'a> { + engine_state: &'a EngineState, + pattern: &'a Pattern, +} + +impl<'a> fmt::Display for FmtPattern<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.pattern { + Pattern::Record(bindings) => { + f.write_str("{")?; + for (name, pattern) in bindings { + write!( + f, + "{}: {}", + name, + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern, + } + )?; + } + f.write_str("}") + } + Pattern::List(bindings) => { + f.write_str("[")?; + for pattern in bindings { + write!( + f, + "{}", + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern + } + )?; + } + f.write_str("]") + } + Pattern::Value(expr) => { + let string = + String::from_utf8_lossy(self.engine_state.get_span_contents(expr.span)); + f.write_str(&string) + } + Pattern::Variable(var_id) => { + let variable = FmtVar::new(self.engine_state, *var_id); + write!(f, "{}", variable) + } + Pattern::Or(patterns) => { + for (index, pattern) in patterns.iter().enumerate() { + if index > 0 { + f.write_str(" | ")?; + } + write!( + f, + "{}", + FmtPattern { + engine_state: self.engine_state, + pattern: &pattern.pattern + } + )?; + } + Ok(()) + } + Pattern::Rest(var_id) => { + let variable = FmtVar::new(self.engine_state, *var_id); + write!(f, "..{}", variable) + } + Pattern::IgnoreRest => f.write_str(".."), + Pattern::IgnoreValue => f.write_str("_"), + Pattern::Garbage => f.write_str(""), + } + } +} diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs new file mode 100644 index 0000000000..28677b743c --- /dev/null +++ b/crates/nu-protocol/src/ir/mod.rs @@ -0,0 +1,419 @@ +use std::{fmt, sync::Arc}; + +use crate::{ + ast::{CellPath, Expression, Operator, Pattern, RangeInclusion}, + engine::EngineState, + BlockId, DeclId, RegId, Span, Value, VarId, +}; + +use chrono::{DateTime, FixedOffset}; +use serde::{Deserialize, Serialize}; + +mod call; +mod display; + +pub use call::*; +pub use display::{FmtInstruction, FmtIrBlock}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct IrBlock { + pub instructions: Vec, + pub spans: Vec, + #[serde(with = "serde_arc_u8_array")] + pub data: Arc<[u8]>, + pub ast: Vec>, + /// Additional information that can be added to help with debugging + pub comments: Vec>, + pub register_count: u32, + pub file_count: u32, +} + +impl fmt::Debug for IrBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // the ast field is too verbose and doesn't add much + f.debug_struct("IrBlock") + .field("instructions", &self.instructions) + .field("spans", &self.spans) + .field("data", &self.data) + .field("comments", &self.comments) + .field("register_count", &self.register_count) + .field("file_count", &self.register_count) + .finish_non_exhaustive() + } +} + +impl IrBlock { + /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed + /// listing of the instructions contained within this [`IrBlock`]. + pub fn display<'a>(&'a self, engine_state: &'a EngineState) -> FmtIrBlock<'a> { + FmtIrBlock { + engine_state, + ir_block: self, + } + } +} + +/// A slice into the `data` array of a block. This is a compact and cache-friendly way to store +/// string data that a block uses. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct DataSlice { + pub start: u32, + pub len: u32, +} + +impl DataSlice { + /// A data slice that contains no data. This slice is always valid. + pub const fn empty() -> DataSlice { + DataSlice { start: 0, len: 0 } + } +} + +impl std::ops::Index for [u8] { + type Output = [u8]; + + fn index(&self, index: DataSlice) -> &Self::Output { + &self[index.start as usize..(index.start as usize + index.len as usize)] + } +} + +/// A possible reference into the abstract syntax tree for an instruction. This is not present for +/// most instructions and is just added when needed. +#[derive(Debug, Clone)] +pub struct IrAstRef(pub Arc); + +impl Serialize for IrAstRef { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.as_ref().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for IrAstRef { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Expression::deserialize(deserializer).map(|expr| IrAstRef(Arc::new(expr))) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Instruction { + /// Unreachable code path (error) + Unreachable, + /// Load a literal value into the `dst` register + LoadLiteral { dst: RegId, lit: Literal }, + /// Load a clone of a boxed value into the `dst` register (e.g. from const evaluation) + LoadValue { dst: RegId, val: Box }, + /// Move a register. Value is taken from `src` (used by this instruction). + Move { dst: RegId, src: RegId }, + /// Copy a register (must be a collected value). Value is still in `src` after this instruction. + Clone { dst: RegId, src: RegId }, + /// Collect a stream in a register to a value + Collect { src_dst: RegId }, + /// Change the span of the contents of a register to the span of this instruction. + Span { src_dst: RegId }, + /// Drop the value/stream in a register, without draining + Drop { src: RegId }, + /// Drain the value/stream in a register and discard (e.g. semicolon). + /// + /// If passed a stream from an external command, sets $env.LAST_EXIT_CODE to the resulting exit + /// code, and invokes any available error handler with Empty, or if not available, returns an + /// exit-code-only stream, leaving the block. + Drain { src: RegId }, + /// Load the value of a variable into the `dst` register + LoadVariable { dst: RegId, var_id: VarId }, + /// Store the value of a variable from the `src` register + StoreVariable { var_id: VarId, src: RegId }, + /// Load the value of an environment variable into the `dst` register + LoadEnv { dst: RegId, key: DataSlice }, + /// Load the value of an environment variable into the `dst` register, or `Nothing` if it + /// doesn't exist + LoadEnvOpt { dst: RegId, key: DataSlice }, + /// Store the value of an environment variable from the `src` register + StoreEnv { key: DataSlice, src: RegId }, + /// Add a positional arg to the next (internal) call. + PushPositional { src: RegId }, + /// Add a list of args to the next (internal) call (spread/rest). + AppendRest { src: RegId }, + /// Add a named arg with no value to the next (internal) call. + PushFlag { name: DataSlice }, + /// Add a short named arg with no value to the next (internal) call. + PushShortFlag { short: DataSlice }, + /// Add a named arg with a value to the next (internal) call. + PushNamed { name: DataSlice, src: RegId }, + /// Add a short named arg with a value to the next (internal) call. + PushShortNamed { short: DataSlice, src: RegId }, + /// Add parser info to the next (internal) call. + PushParserInfo { + name: DataSlice, + info: Box, + }, + /// Set the redirection for stdout for the next call (only). + /// + /// The register for a file redirection is not consumed. + RedirectOut { mode: RedirectMode }, + /// Set the redirection for stderr for the next call (only). + /// + /// The register for a file redirection is not consumed. + RedirectErr { mode: RedirectMode }, + /// Throw an error if stderr wasn't redirected in the given stream. `src` is preserved. + CheckErrRedirected { src: RegId }, + /// Open a file for redirection, pushing it onto the file stack. + OpenFile { + file_num: u32, + path: RegId, + append: bool, + }, + /// Write data from the register to a file. This is done to finish a file redirection, in case + /// an internal command or expression was evaluated rather than an external one. + WriteFile { file_num: u32, src: RegId }, + /// Pop a file used for redirection from the file stack. + CloseFile { file_num: u32 }, + /// Make a call. The input is taken from `src_dst`, and the output is placed in `src_dst`, + /// overwriting it. The argument stack is used implicitly and cleared when the call ends. + Call { decl_id: DeclId, src_dst: RegId }, + /// Append a value onto the end of a string. Uses `to_expanded_string(", ", ...)` on the value. + /// Used for string interpolation literals. Not the same thing as the `++` operator. + StringAppend { src_dst: RegId, val: RegId }, + /// Convert a string into a glob. Used for glob interpolation and setting glob variables. If the + /// value is already a glob, it won't be modified (`no_expand` will have no effect). + GlobFrom { src_dst: RegId, no_expand: bool }, + /// Push a value onto the end of a list. Used to construct list literals. + ListPush { src_dst: RegId, item: RegId }, + /// Spread a value onto the end of a list. Used to construct list literals. + ListSpread { src_dst: RegId, items: RegId }, + /// Insert a key-value pair into a record. Used to construct record literals. Raises an error if + /// the key already existed in the record. + RecordInsert { + src_dst: RegId, + key: RegId, + val: RegId, + }, + /// Spread a record onto a record. Used to construct record literals. Any existing value for the + /// key is overwritten. + RecordSpread { src_dst: RegId, items: RegId }, + /// Negate a boolean. + Not { src_dst: RegId }, + /// Do a binary operation on `lhs_dst` (left) and `rhs` (right) and write the result to + /// `lhs_dst`. + BinaryOp { + lhs_dst: RegId, + op: Operator, + rhs: RegId, + }, + /// Follow a cell path on the value in `src_dst`, storing the result back to `src_dst` + FollowCellPath { src_dst: RegId, path: RegId }, + /// Clone the value at a cell path in `src`, storing the result to `dst`. The original value + /// remains in `src`. Must be a collected value. + CloneCellPath { dst: RegId, src: RegId, path: RegId }, + /// Update/insert a cell path to `new_value` on the value in `src_dst`, storing the modified + /// value back to `src_dst` + UpsertCellPath { + src_dst: RegId, + path: RegId, + new_value: RegId, + }, + /// Jump to an offset in this block + Jump { index: usize }, + /// Branch to an offset in this block if the value of the `cond` register is a true boolean, + /// otherwise continue execution + BranchIf { cond: RegId, index: usize }, + /// Branch to an offset in this block if the value of the `src` register is Empty or Nothing, + /// otherwise continue execution. The original value in `src` is preserved. + BranchIfEmpty { src: RegId, index: usize }, + /// Match a pattern on `src`. If the pattern matches, branch to `index` after having set any + /// variables captured by the pattern. If the pattern doesn't match, continue execution. The + /// original value is preserved in `src` through this instruction. + Match { + pattern: Box, + src: RegId, + index: usize, + }, + /// Check that a match guard is a boolean, throwing + /// [`MatchGuardNotBool`](crate::ShellError::MatchGuardNotBool) if it isn't. Preserves `src`. + CheckMatchGuard { src: RegId }, + /// Iterate on register `stream`, putting the next value in `dst` if present, or jumping to + /// `end_index` if the iterator is finished + Iterate { + dst: RegId, + stream: RegId, + end_index: usize, + }, + /// Push an error handler, without capturing the error value + OnError { index: usize }, + /// Push an error handler, capturing the error value into `dst`. If the error handler is not + /// called, the register should be freed manually. + OnErrorInto { index: usize, dst: RegId }, + /// Pop an error handler. This is not necessary when control flow is directed to the error + /// handler due to an error. + PopErrorHandler, + /// Check if an external command failed. Boolean value into `dst`. `src` is preserved, but it + /// does require waiting for the command to exit. + CheckExternalFailed { dst: RegId, src: RegId }, + /// Return early from the block, raising a `ShellError::Return` instead. + /// + /// Collecting the value is unavoidable. + ReturnEarly { src: RegId }, + /// Return from the block with the value in the register + Return { src: RegId }, +} + +impl Instruction { + /// Returns a value that can be formatted with [`Display`](std::fmt::Display) to show a detailed + /// listing of the instruction. + pub fn display<'a>( + &'a self, + engine_state: &'a EngineState, + data: &'a [u8], + ) -> FmtInstruction<'a> { + FmtInstruction { + engine_state, + instruction: self, + data, + } + } + + /// Returns the branch target index of the instruction if this is a branching instruction. + pub fn branch_target(&self) -> Option { + match self { + Instruction::Jump { index } => Some(*index), + Instruction::BranchIf { cond: _, index } => Some(*index), + Instruction::BranchIfEmpty { src: _, index } => Some(*index), + Instruction::Match { + pattern: _, + src: _, + index, + } => Some(*index), + + Instruction::Iterate { + dst: _, + stream: _, + end_index, + } => Some(*end_index), + Instruction::OnError { index } => Some(*index), + Instruction::OnErrorInto { index, dst: _ } => Some(*index), + _ => None, + } + } + + /// Sets the branch target of the instruction if this is a branching instruction. + /// + /// Returns `Err(target_index)` if it isn't a branching instruction. + pub fn set_branch_target(&mut self, target_index: usize) -> Result<(), usize> { + match self { + Instruction::Jump { index } => *index = target_index, + Instruction::BranchIf { cond: _, index } => *index = target_index, + Instruction::BranchIfEmpty { src: _, index } => *index = target_index, + Instruction::Match { + pattern: _, + src: _, + index, + } => *index = target_index, + + Instruction::Iterate { + dst: _, + stream: _, + end_index, + } => *end_index = target_index, + Instruction::OnError { index } => *index = target_index, + Instruction::OnErrorInto { index, dst: _ } => *index = target_index, + _ => return Err(target_index), + } + Ok(()) + } +} + +// This is to document/enforce the size of `Instruction` in bytes. +// We should try to avoid increasing the size of `Instruction`, +// and PRs that do so will have to change the number below so that it's noted in review. +const _: () = assert!(std::mem::size_of::() <= 24); + +/// A literal value that can be embedded in an instruction. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Literal { + Bool(bool), + Int(i64), + Float(f64), + Filesize(i64), + Duration(i64), + Binary(DataSlice), + Block(BlockId), + Closure(BlockId), + RowCondition(BlockId), + Range { + start: RegId, + step: RegId, + end: RegId, + inclusion: RangeInclusion, + }, + List { + capacity: usize, + }, + Record { + capacity: usize, + }, + Filepath { + val: DataSlice, + no_expand: bool, + }, + Directory { + val: DataSlice, + no_expand: bool, + }, + GlobPattern { + val: DataSlice, + no_expand: bool, + }, + String(DataSlice), + RawString(DataSlice), + CellPath(Box), + Date(Box>), + Nothing, +} + +/// A redirection mode for the next call. See [`OutDest`](crate::OutDest). +/// +/// This is generated by: +/// +/// 1. Explicit redirection in a [`PipelineElement`](crate::ast::PipelineElement), or +/// 2. The [`pipe_redirection()`](crate::engine::Command::pipe_redirection) of the command being +/// piped into. +/// +/// Not setting it uses the default, determined by [`Stack`](crate::engine::Stack). +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum RedirectMode { + Pipe, + Capture, + Null, + Inherit, + /// Use the given numbered file. + File { + file_num: u32, + }, + /// Use the redirection mode requested by the caller, for a pre-return call. + Caller, +} + +/// Just a hack to allow `Arc<[u8]>` to be serialized and deserialized +mod serde_arc_u8_array { + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + + pub fn serialize(data: &Arc<[u8]>, ser: S) -> Result + where + S: serde::Serializer, + { + data.as_ref().serialize(ser) + } + + pub fn deserialize<'de, D>(de: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let data: Vec = Deserialize::deserialize(de)?; + Ok(data.into()) + } +} diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 9c176953d5..cae5d3fd0a 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -9,6 +9,7 @@ pub mod eval_base; pub mod eval_const; mod example; mod id; +pub mod ir; mod lev_distance; mod module; pub mod parser_path; diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index 6226f1d8db..cd62b70801 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -352,6 +352,12 @@ impl ByteStream { self.span } + /// Changes the [`Span`] associated with the [`ByteStream`]. + pub fn with_span(mut self, span: Span) -> Self { + self.span = span; + self + } + /// Returns the [`ByteStreamType`] associated with the [`ByteStream`]. pub fn type_(&self) -> ByteStreamType { self.type_ diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 997cc3f77b..104bab6bcc 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -31,11 +31,22 @@ impl ListStream { self.span } + /// Changes the [`Span`] associated with this [`ListStream`]. + pub fn with_span(mut self, span: Span) -> Self { + self.span = span; + self + } + /// Convert a [`ListStream`] into its inner [`Value`] `Iterator`. pub fn into_inner(self) -> ValueIterator { self.stream } + /// Take a single value from the inner `Iterator`, modifying the stream. + pub fn next_value(&mut self) -> Option { + self.stream.next() + } + /// Converts each value in a [`ListStream`] into a string and then joins the strings together /// using the given separator. pub fn into_string(self, separator: &str, config: &Config) -> String { diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index a546e90191..a89337a6c2 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -96,6 +96,24 @@ impl PipelineData { } } + /// Change the span of the [`PipelineData`]. + /// + /// Returns `Value(Nothing)` with the given span if it was [`PipelineData::Empty`]. + pub fn with_span(self, span: Span) -> Self { + match self { + PipelineData::Empty => PipelineData::Value(Value::nothing(span), None), + PipelineData::Value(value, metadata) => { + PipelineData::Value(value.with_span(span), metadata) + } + PipelineData::ListStream(stream, metadata) => { + PipelineData::ListStream(stream.with_span(span), metadata) + } + PipelineData::ByteStream(stream, metadata) => { + PipelineData::ByteStream(stream.with_span(span), metadata) + } + } + } + /// Get a type that is representative of the `PipelineData`. /// /// The type returned here makes no effort to collect a stream, so it may be a different type @@ -129,7 +147,8 @@ impl PipelineData { /// without consuming input and without writing anything. /// /// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed - /// and `PipelineData::Empty` will be returned. + /// and `PipelineData::Empty` will be returned, unless the data is from an external stream, + /// in which case an external stream containing only that exit code will be returned. pub fn write_to_out_dests( self, engine_state: &EngineState, @@ -137,7 +156,11 @@ impl PipelineData { ) -> Result { match (self, stack.stdout()) { (PipelineData::ByteStream(stream, ..), stdout) => { - stream.write_to_out_dests(stdout, stack.stderr())?; + if let Some(exit_status) = stream.write_to_out_dests(stdout, stack.stderr())? { + return Ok(PipelineData::new_external_stream_with_only_exit_code( + exit_status.code(), + )); + } } (data, OutDest::Pipe | OutDest::Capture) => return Ok(data), (PipelineData::Empty, ..) => {} @@ -570,7 +593,7 @@ impl PipelineData { self.write_all_and_flush(engine_state, no_newline, to_stderr) } else { let call = Call::new(Span::new(0, 0)); - let table = command.run(engine_state, stack, &call, self)?; + let table = command.run(engine_state, stack, &(&call).into(), self)?; table.write_all_and_flush(engine_state, no_newline, to_stderr) } } else { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 70e94b35f1..5928ce0c0c 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,6 +1,5 @@ use crate::{ - ast::Call, - engine::{Command, CommandType, EngineState, Stack}, + engine::{Call, Command, CommandType, EngineState, Stack}, BlockId, PipelineData, ShellError, SyntaxShape, Type, Value, VarId, }; use serde::{Deserialize, Serialize}; diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 0d280eaa9d..f5bcebc543 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -53,6 +53,22 @@ impl Spanned { } } +impl Spanned> { + /// Move the `Result` to the outside, resulting in a spanned `Ok` or unspanned `Err`. + pub fn transpose(self) -> Result, E> { + match self { + Spanned { + item: Ok(item), + span, + } => Ok(Spanned { item, span }), + Spanned { + item: Err(err), + span: _, + } => Err(err), + } + } +} + /// Helper trait to create [`Spanned`] more ergonomically. pub trait IntoSpanned: Sized { /// Wrap items together with a span into [`Spanned`]. diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index e83b4354da..958a2453f5 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -247,6 +247,7 @@ pub struct NuOpts { pub locale: Option, pub envs: Option>, pub collapse_output: Option, + pub use_ir: Option, } pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> Outcome { @@ -296,6 +297,15 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O .stdout(Stdio::piped()) .stderr(Stdio::piped()); + // Explicitly set NU_USE_IR + if let Some(use_ir) = opts.use_ir { + if use_ir { + command.env("NU_USE_IR", "1"); + } else { + command.env_remove("NU_USE_IR"); + } + } + // Uncomment to debug the command being run: // println!("=== command\n{command:?}\n"); @@ -373,6 +383,7 @@ where if !executable_path.exists() { executable_path = crate::fs::installed_nu_path(); } + let process = match setup_command(&executable_path, &target_cwd) .envs(envs) .arg("--commands") diff --git a/src/run.rs b/src/run.rs index 6bb02451b9..10a5043b25 100644 --- a/src/run.rs +++ b/src/run.rs @@ -26,6 +26,10 @@ pub(crate) fn run_commands( let mut stack = Stack::new(); let start_time = std::time::Instant::now(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + // if the --no-config-file(-n) option is NOT passed, load the plugin file, // load the default env file or custom (depending on parsed_nu_cli_args.env_file), // and maybe a custom config file (depending on parsed_nu_cli_args.config_file) @@ -109,6 +113,10 @@ pub(crate) fn run_file( trace!("run_file"); let mut stack = Stack::new(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + // if the --no-config-file(-n) option is NOT passed, load the plugin file, // load the default env file or custom (depending on parsed_nu_cli_args.env_file), // and maybe a custom config file (depending on parsed_nu_cli_args.config_file) @@ -184,6 +192,10 @@ pub(crate) fn run_repl( let mut stack = Stack::new(); let start_time = std::time::Instant::now(); + if stack.has_env_var(engine_state, "NU_USE_IR") { + stack.use_ir = true; + } + if parsed_nu_cli_args.no_config_file.is_none() { setup_config( engine_state, diff --git a/tests/eval/mod.rs b/tests/eval/mod.rs index f3af92376a..e9afd401e9 100644 --- a/tests/eval/mod.rs +++ b/tests/eval/mod.rs @@ -1,7 +1,8 @@ -use nu_test_support::nu; +use nu_test_support::{nu, playground::Playground}; +use regex::Regex; #[test] -fn source_file_relative_to_file() { +fn record_with_redefined_key() { let actual = nu!("{x: 1, x: 2}"); assert!(actual.err.contains("redefined")); @@ -16,3 +17,455 @@ fn run_file_parse_error() { assert!(actual.err.contains("unknown type")); } + +enum ExpectedOut<'a> { + /// Equals a string exactly + Eq(&'a str), + /// Matches a regex + Matches(&'a str), + /// Produces an error (match regex) + Error(&'a str), + /// Drops a file that contains these contents + FileEq(&'a str, &'a str), +} +use self::ExpectedOut::*; + +fn test_eval(source: &str, expected_out: ExpectedOut) { + Playground::setup("test_eval_ast", |ast_dirs, _playground| { + Playground::setup("test_eval_ir", |ir_dirs, _playground| { + let actual_ast = nu!( + cwd: ast_dirs.test(), + use_ir: false, + source, + ); + let actual_ir = nu!( + cwd: ir_dirs.test(), + use_ir: true, + source, + ); + + match expected_out { + Eq(eq) => { + assert_eq!(actual_ast.out, eq); + assert_eq!(actual_ir.out, eq); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + Matches(regex) => { + let compiled_regex = Regex::new(regex).expect("regex failed to compile"); + assert!( + compiled_regex.is_match(&actual_ast.out), + "AST eval out does not match: {}\n{}", + regex, + actual_ast.out + ); + assert!( + compiled_regex.is_match(&actual_ir.out), + "IR eval out does not match: {}\n{}", + regex, + actual_ir.out, + ); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + Error(regex) => { + let compiled_regex = Regex::new(regex).expect("regex failed to compile"); + assert!( + compiled_regex.is_match(&actual_ast.err), + "AST eval err does not match: {}", + regex + ); + assert!( + compiled_regex.is_match(&actual_ir.err), + "IR eval err does not match: {}", + regex + ); + assert!(!actual_ast.status.success()); + assert!(!actual_ir.status.success()); + } + FileEq(path, contents) => { + let ast_contents = std::fs::read_to_string(ast_dirs.test().join(path)) + .expect("failed to read AST file"); + let ir_contents = std::fs::read_to_string(ir_dirs.test().join(path)) + .expect("failed to read IR file"); + assert_eq!(ast_contents.trim(), contents); + assert_eq!(ir_contents.trim(), contents); + assert!(actual_ast.status.success()); + assert!(actual_ir.status.success()); + } + } + assert_eq!(actual_ast.out, actual_ir.out); + }) + }); +} + +#[test] +fn literal_bool() { + test_eval("true", Eq("true")) +} + +#[test] +fn literal_int() { + test_eval("1", Eq("1")) +} + +#[test] +fn literal_float() { + test_eval("1.5", Eq("1.5")) +} + +#[test] +fn literal_filesize() { + test_eval("30MiB", Eq("30.0 MiB")) +} + +#[test] +fn literal_duration() { + test_eval("30ms", Eq("30ms")) +} + +#[test] +fn literal_binary() { + test_eval("0x[1f 2f f0]", Matches("Length.*1f.*2f.*f0")) +} + +#[test] +fn literal_closure() { + test_eval("{||}", Matches(" hello.txt", + FileEq("hello.txt", "hello"), + ) +} + +#[test] +fn let_variable() { + test_eval("let foo = 'test'; print $foo", Eq("test")) +} + +#[test] +fn let_variable_mutate_error() { + test_eval( + "let foo = 'test'; $foo = 'bar'; print $foo", + Error("immutable"), + ) +} + +#[test] +fn constant() { + test_eval("const foo = 1 + 2; print $foo", Eq("3")) +} + +#[test] +fn constant_assign_error() { + test_eval( + "const foo = 1 + 2; $foo = 4; print $foo", + Error("immutable"), + ) +} + +#[test] +fn mut_variable() { + test_eval("mut foo = 'test'; $foo = 'bar'; print $foo", Eq("bar")) +} + +#[test] +fn mut_variable_append_assign() { + test_eval( + "mut foo = 'test'; $foo ++= 'bar'; print $foo", + Eq("testbar"), + ) +} + +#[test] +fn bind_in_variable_to_input() { + test_eval("3 | (4 + $in)", Eq("7")) +} + +#[test] +fn if_true() { + test_eval("if true { 'foo' }", Eq("foo")) +} + +#[test] +fn if_false() { + test_eval("if false { 'foo' } | describe", Eq("nothing")) +} + +#[test] +fn if_else_true() { + test_eval("if 5 > 3 { 'foo' } else { 'bar' }", Eq("foo")) +} + +#[test] +fn if_else_false() { + test_eval("if 5 < 3 { 'foo' } else { 'bar' }", Eq("bar")) +} + +#[test] +fn match_empty_fallthrough() { + test_eval("match 42 { }; 'pass'", Eq("pass")) +} + +#[test] +fn match_value() { + test_eval("match 1 { 1 => 'pass', 2 => 'fail' }", Eq("pass")) +} + +#[test] +fn match_value_default() { + test_eval( + "match 3 { 1 => 'fail1', 2 => 'fail2', _ => 'pass' }", + Eq("pass"), + ) +} + +#[test] +fn match_value_fallthrough() { + test_eval("match 3 { 1 => 'fail1', 2 => 'fail2' }", Eq("")) +} + +#[test] +fn match_variable() { + test_eval( + "match 'pass' { $s => { print $s }, _ => { print 'fail' } }", + Eq("pass"), + ) +} + +#[test] +fn match_variable_in_list() { + test_eval("match [fail pass] { [$f, $p] => { print $p } }", Eq("pass")) +} + +#[test] +fn match_passthrough_input() { + test_eval( + "'yes' | match [pass fail] { [$p, ..] => (collect { |y| $y ++ $p }) }", + Eq("yespass"), + ) +} + +#[test] +fn while_mutate_var() { + test_eval("mut x = 2; while $x > 0 { print $x; $x -= 1 }", Eq("21")) +} + +#[test] +fn for_list() { + test_eval("for v in [1 2 3] { print ($v * 2) }", Eq(r"246")) +} + +#[test] +fn for_seq() { + test_eval("for v in (seq 1 4) { print ($v * 2) }", Eq("2468")) +} + +#[test] +fn early_return() { + test_eval("do { return 'foo'; 'bar' }", Eq("foo")) +} + +#[test] +fn early_return_from_if() { + test_eval("do { if true { return 'pass' }; 'fail' }", Eq("pass")) +} + +#[test] +fn early_return_from_loop() { + test_eval("do { loop { return 'pass' } }", Eq("pass")) +} + +#[test] +fn early_return_from_while() { + test_eval( + "do { let x = true; while $x { return 'pass' } }", + Eq("pass"), + ) +} + +#[test] +fn early_return_from_for() { + test_eval("do { for x in [pass fail] { return $x } }", Eq("pass")) +} + +#[test] +fn try_no_catch() { + test_eval("try { error make { msg: foo } }; 'pass'", Eq("pass")) +} + +#[test] +fn try_catch_no_var() { + test_eval( + "try { error make { msg: foo } } catch { 'pass' }", + Eq("pass"), + ) +} + +#[test] +fn try_catch_var() { + test_eval( + "try { error make { msg: foo } } catch { |err| $err.msg }", + Eq("foo"), + ) +} + +#[test] +fn try_catch_with_non_literal_closure_no_var() { + test_eval( + r#" + let error_handler = { || "pass" } + try { error make { msg: foobar } } catch $error_handler + "#, + Eq("pass"), + ) +} + +#[test] +fn try_catch_with_non_literal_closure() { + test_eval( + r#" + let error_handler = { |err| $err.msg } + try { error make { msg: foobar } } catch $error_handler + "#, + Eq("foobar"), + ) +} + +#[test] +fn row_condition() { + test_eval( + "[[a b]; [1 2] [3 4]] | where a < 3 | to nuon", + Eq("[[a, b]; [1, 2]]"), + ) +} + +#[test] +fn custom_command() { + test_eval( + r#" + def cmd [a: int, b: string = 'fail', ...c: string, --x: int] { $"($a)($b)($c)($x)" } + cmd 42 pass foo --x 30 + "#, + Eq("42pass[foo]30"), + ) +} From 1a5bf2447a9a46f83b8cc86c5cac0ea1bc6f1122 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 17:34:50 -0700 Subject: [PATCH 28/63] Use Arc for environment variables on the stack (#13333) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is another easy performance lift that just changes `env_vars` and `env_hidden` on `Stack` to use `Arc`. I noticed that these were being cloned on essentially every closure invocation during captures gathering, so we're paying the cost for all of that even when we don't change anything. On top of that, for `env_vars`, there's actually an entirely fresh `HashMap` created for each child scope, so it's highly unlikely that we'll modify the parent ones. Uses `Arc::make_mut` instead to take care of things when we need to mutate something, and most of the time nothing has to be cloned at all. # Benchmarks The benefits are greater the more calls there are to env-cloning functions like `captures_to_stack()`. Calling custom commands in a loop is basically best case for a performance improvement. Plain `each` with a literal block isn't so badly affected because the stack is set up once. ## random_bytes.nu ```nushell use std bench do { const SCRIPT = ../nu_scripts/benchmarks/random-bytes.nu let before_change = bench { nu $SCRIPT } let after_change = bench { target/release/nu $SCRIPT } { before: ($before_change | reject times), after: ($after_change | reject times) } } ``` ``` ╭────────┬──────────────────────────────╮ │ │ ╭──────┬───────────────────╮ │ │ before │ │ mean │ 603ms 759µs 727ns │ │ │ │ │ min │ 593ms 298µs 167ns │ │ │ │ │ max │ 648ms 612µs 291ns │ │ │ │ │ std │ 9ms 335µs 251ns │ │ │ │ ╰──────┴───────────────────╯ │ │ │ ╭──────┬───────────────────╮ │ │ after │ │ mean │ 518ms 400µs 557ns │ │ │ │ │ min │ 507ms 762µs 583ns │ │ │ │ │ max │ 566ms 695µs 166ns │ │ │ │ │ std │ 9ms 554µs 767ns │ │ │ │ ╰──────┴───────────────────╯ │ ╰────────┴──────────────────────────────╯ ``` ## gradient_benchmark_no_check.nu ```nushell use std bench do { const SCRIPT = ../nu_scripts/benchmarks/gradient_benchmark_no_check.nu let before_change = bench { nu $SCRIPT } let after_change = bench { target/release/nu $SCRIPT } { before: ($before_change | reject times), after: ($after_change | reject times) } } ``` ``` ╭────────┬──────────────────────────────╮ │ │ ╭──────┬───────────────────╮ │ │ before │ │ mean │ 146ms 543µs 380ns │ │ │ │ │ min │ 142ms 416µs 166ns │ │ │ │ │ max │ 189ms 595µs │ │ │ │ │ std │ 7ms 140µs 342ns │ │ │ │ ╰──────┴───────────────────╯ │ │ │ ╭──────┬───────────────────╮ │ │ after │ │ mean │ 134ms 211µs 678ns │ │ │ │ │ min │ 132ms 433µs 125ns │ │ │ │ │ max │ 135ms 722µs 583ns │ │ │ │ │ std │ 793µs 134ns │ │ │ │ ╰──────┴───────────────────╯ │ ╰────────┴──────────────────────────────╯ ``` # User-Facing Changes Better performance, particularly for custom commands, especially if there are a lot of environment variables. Nothing else. # Tests + Formatting All passing. --- crates/nu-engine/src/closure_eval.rs | 4 +-- crates/nu-protocol/src/engine/engine_state.rs | 2 +- crates/nu-protocol/src/engine/stack.rs | 30 ++++++++++--------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/nu-engine/src/closure_eval.rs b/crates/nu-engine/src/closure_eval.rs index f4bc40658b..b271d90cbe 100644 --- a/crates/nu-engine/src/closure_eval.rs +++ b/crates/nu-engine/src/closure_eval.rs @@ -62,8 +62,8 @@ pub struct ClosureEval { stack: Stack, block: Arc, arg_index: usize, - env_vars: Vec, - env_hidden: HashMap>, + env_vars: Vec>, + env_hidden: Arc>>, eval: EvalBlockWithEarlyReturnFn, } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index d45fbafb88..f48fde663b 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -304,7 +304,7 @@ impl EngineState { let mut config_updated = false; for mut scope in stack.env_vars.drain(..) { - for (overlay_name, mut env) in scope.drain() { + for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() { if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) { // Updating existing overlay for (k, v) in env.drain() { diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index b289c1ae8b..9315744dfb 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -36,9 +36,9 @@ pub struct Stack { /// Variables pub vars: Vec<(VarId, Value)>, /// Environment variables arranged as a stack to be able to recover values from parent scopes - pub env_vars: Vec, + pub env_vars: Vec>, /// Tells which environment variables from engine state are hidden, per overlay. - pub env_hidden: HashMap>, + pub env_hidden: Arc>>, /// List of active overlays pub active_overlays: Vec, /// Argument stack for IR evaluation @@ -72,7 +72,7 @@ impl Stack { Self { vars: Vec::new(), env_vars: Vec::new(), - env_hidden: HashMap::new(), + env_hidden: Arc::new(HashMap::new()), active_overlays: vec![DEFAULT_OVERLAY_NAME.to_string()], arguments: ArgumentStack::new(), error_handlers: ErrorHandlerStack::new(), @@ -131,8 +131,8 @@ impl Stack { pub fn with_env( &mut self, - env_vars: &[EnvVars], - env_hidden: &HashMap>, + env_vars: &[Arc], + env_hidden: &Arc>>, ) { // Do not clone the environment if it hasn't changed if self.env_vars.iter().any(|scope| !scope.is_empty()) { @@ -219,23 +219,24 @@ impl Stack { pub fn add_env_var(&mut self, var: String, value: Value) { if let Some(last_overlay) = self.active_overlays.last() { - if let Some(env_hidden) = self.env_hidden.get_mut(last_overlay) { + if let Some(env_hidden) = Arc::make_mut(&mut 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() { + let scope = Arc::make_mut(scope); if let Some(env_vars) = scope.get_mut(last_overlay) { env_vars.insert(var, value); } else { scope.insert(last_overlay.into(), [(var, value)].into_iter().collect()); } } else { - self.env_vars.push( + self.env_vars.push(Arc::new( [(last_overlay.into(), [(var, value)].into_iter().collect())] .into_iter() .collect(), - ); + )); } } else { // TODO: Remove panic @@ -257,9 +258,8 @@ impl Stack { } pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack { - // FIXME: this is probably slow let mut env_vars = self.env_vars.clone(); - env_vars.push(HashMap::new()); + env_vars.push(Arc::new(HashMap::new())); Stack { vars: captures, @@ -292,7 +292,7 @@ impl Stack { } let mut env_vars = self.env_vars.clone(); - env_vars.push(HashMap::new()); + env_vars.push(Arc::new(HashMap::new())); Stack { vars, @@ -462,6 +462,7 @@ impl Stack { pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> bool { for scope in self.env_vars.iter_mut().rev() { + let scope = Arc::make_mut(scope); for active_overlay in self.active_overlays.iter().rev() { if let Some(env_vars) = scope.get_mut(active_overlay) { if env_vars.remove(name).is_some() { @@ -474,10 +475,11 @@ impl Stack { for active_overlay in self.active_overlays.iter().rev() { if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { if env_vars.get(name).is_some() { - if let Some(env_hidden) = self.env_hidden.get_mut(active_overlay) { - env_hidden.insert(name.into()); + let env_hidden = Arc::make_mut(&mut self.env_hidden); + if let Some(env_hidden_in_overlay) = env_hidden.get_mut(active_overlay) { + env_hidden_in_overlay.insert(name.into()); } else { - self.env_hidden + env_hidden .insert(active_overlay.into(), [name.into()].into_iter().collect()); } From ac561b1b0e65e9fd6ee6e682a08abb919983b0da Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 10 Jul 2024 20:19:06 -0500 Subject: [PATCH 29/63] quick fix up for ir pr as_refs (#13340) # Description Was having an issue compiling main after the IR pr. Talked to devyn and he led me to change a couple things real quick and we're compiling once again. --- crates/nu-engine/src/eval_ir.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index a505c9be34..e2ec7ccdac 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -1275,7 +1275,7 @@ fn get_env_var_case_insensitive<'a>(ctx: &'a mut EvalContext<'_>, key: &str) -> .env_vars .iter() .rev() - .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .chain(std::iter::once(&ctx.engine_state.env_vars)) .flat_map(|overlays| { // Read overlays in order ctx.stack @@ -1303,7 +1303,7 @@ fn get_env_var_name_case_insensitive<'a>(ctx: &mut EvalContext<'_>, key: &'a str .env_vars .iter() .rev() - .chain(std::iter::once(ctx.engine_state.env_vars.as_ref())) + .chain(std::iter::once(&ctx.engine_state.env_vars)) .flat_map(|overlays| { // Read overlays in order ctx.stack From f87cf895c2062cb5d71e97cd3221d101c825c8af Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 19:13:35 -0700 Subject: [PATCH 30/63] Set the capacity of the Vec used in `gather_captures()` to the number of captures expected (#13339) # Description Just more efficient allocation during `Stack::gather_captures()` so that we don't have to grow the `Vec` needlessly. # User-Facing Changes Slightly better performance. --- crates/nu-protocol/src/engine/stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 9315744dfb..e963227ca8 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -277,7 +277,7 @@ impl Stack { } pub fn gather_captures(&self, engine_state: &EngineState, captures: &[VarId]) -> Stack { - let mut vars = vec![]; + let mut vars = Vec::with_capacity(captures.len()); let fake_span = Span::new(0, 0); From 801cfae279cb384ecef137caab0b77d899a26b44 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 10 Jul 2024 19:14:05 -0700 Subject: [PATCH 31/63] Avoid clone in `Signature::get_positional()` (#13338) # Description `Signature::get_positional()` was returning an owned `PositionalArg`, which contains a bunch of strings. `ClosureEval` uses this in `try_add_arg`, making all of that unnecessary cloning a little bit hot. # User-Facing Changes Slightly better performance --- crates/nu-protocol/src/signature.rs | 7 +++---- crates/nu-protocol/tests/test_signature.rs | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 5928ce0c0c..3241b0df22 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -485,15 +485,14 @@ impl Signature { (name, s) } - pub fn get_positional(&self, position: usize) -> Option { + pub fn get_positional(&self, position: usize) -> Option<&PositionalArg> { if position < self.required_positional.len() { - self.required_positional.get(position).cloned() + self.required_positional.get(position) } else if position < (self.required_positional.len() + self.optional_positional.len()) { self.optional_positional .get(position - self.required_positional.len()) - .cloned() } else { - self.rest_positional.clone() + self.rest_positional.as_ref() } } diff --git a/crates/nu-protocol/tests/test_signature.rs b/crates/nu-protocol/tests/test_signature.rs index e22029bab9..8faf772c38 100644 --- a/crates/nu-protocol/tests/test_signature.rs +++ b/crates/nu-protocol/tests/test_signature.rs @@ -39,7 +39,7 @@ fn test_signature_chained() { assert_eq!( signature.get_positional(0), - Some(PositionalArg { + Some(&PositionalArg { name: "required".to_string(), desc: "required description".to_string(), shape: SyntaxShape::String, @@ -49,7 +49,7 @@ fn test_signature_chained() { ); assert_eq!( signature.get_positional(1), - Some(PositionalArg { + Some(&PositionalArg { name: "optional".to_string(), desc: "optional description".to_string(), shape: SyntaxShape::String, @@ -59,7 +59,7 @@ fn test_signature_chained() { ); assert_eq!( signature.get_positional(2), - Some(PositionalArg { + Some(&PositionalArg { name: "rest".to_string(), desc: "rest description".to_string(), shape: SyntaxShape::String, From d97512df8ed4277ade59dd49fc94ee32b8c5cf62 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 04:00:59 -0700 Subject: [PATCH 32/63] Fix the signature of `view ir` (#13342) # Description Fix `view ir` to use `Signature::build()` rather than `new()`, which is required for `--help` to work. Also add `Category::Debug`, as that's most appropriate. --- crates/nu-command/src/debug/view_ir.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs index df4f6cad6b..2bbe0db020 100644 --- a/crates/nu-command/src/debug/view_ir.rs +++ b/crates/nu-command/src/debug/view_ir.rs @@ -10,7 +10,7 @@ impl Command for ViewIr { } fn signature(&self) -> Signature { - Signature::new(self.name()) + Signature::build(self.name()) .required( "closure", SyntaxShape::Closure(None), @@ -22,6 +22,7 @@ impl Command for ViewIr { Some('j'), ) .input_output_type(Type::Nothing, Type::String) + .category(Category::Debug) } fn usage(&self) -> &str { From 9de7f931c006498817d8cb67cd21d5acfe7fabed Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 04:05:06 -0700 Subject: [PATCH 33/63] Add more argument types to `view ir` (#13343) # Description Add a few more options to `view ir` for finding blocks, which I found myself wanting while trying to trace through the generated code. If we end up adding support for plugins to call commands that are in scope by name, this will also make it possible for `nu_plugin_explore_ir` to just step through IR automatically (by passing the block/decl ids) without exposing too many internals. With that I could potentially add keys that allow you to step in to closures or decls with the press of a button, just by calling `view ir --json` appropriately. # User-Facing Changes - `view ir` can now take names of custom commands that are in scope. - integer arguments are treated as block IDs, which sometimes show up in IR (closure, block, row condition literals). - `--decl-id` provided to treat the argument as a decl ID instead, which is also sometimes necessary to access something that isn't in scope. --- crates/nu-command/src/debug/view_ir.rs | 101 +++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/debug/view_ir.rs b/crates/nu-command/src/debug/view_ir.rs index 2bbe0db020..72136079fd 100644 --- a/crates/nu-command/src/debug/view_ir.rs +++ b/crates/nu-command/src/debug/view_ir.rs @@ -1,5 +1,4 @@ use nu_engine::command_prelude::*; -use nu_protocol::engine::Closure; #[derive(Clone)] pub struct ViewIr; @@ -12,15 +11,20 @@ impl Command for ViewIr { fn signature(&self) -> Signature { Signature::build(self.name()) .required( - "closure", - SyntaxShape::Closure(None), - "The closure to see compiled code for.", + "target", + SyntaxShape::Any, + "The name or block to view compiled code for.", ) .switch( "json", "Dump the raw block data as JSON (unstable).", Some('j'), ) + .switch( + "decl-id", + "Integer is a declaration ID rather than a block ID.", + Some('d'), + ) .input_output_type(Type::Nothing, Type::String) .category(Category::Debug) } @@ -29,6 +33,20 @@ impl Command for ViewIr { "View the compiled IR code for a block of code." } + fn extra_usage(&self) -> &str { + " +The target can be a closure, the name of a custom command, or an internal block +ID. Closure literals within IR dumps often reference the block by ID (e.g. +`closure(3231)`), so this provides an easy way to read the IR of any embedded +closures. + +The --decl-id option is provided to use a declaration ID instead, which can be +found on `call` instructions. This is sometimes better than using the name, as +the declaration may not be in scope. +" + .trim() + } + fn run( &self, engine_state: &EngineState, @@ -36,10 +54,79 @@ impl Command for ViewIr { call: &Call, _input: PipelineData, ) -> Result { - let closure: Closure = call.req(engine_state, stack, 0)?; + let target: Value = call.req(engine_state, stack, 0)?; let json = call.has_flag(engine_state, stack, "json")?; + let is_decl_id = call.has_flag(engine_state, stack, "decl-id")?; + + let block_id = match target { + Value::Closure { ref val, .. } => val.block_id, + // Decl by name + Value::String { ref val, .. } => { + if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) { + let decl = engine_state.get_decl(decl_id); + decl.block_id().ok_or_else(|| ShellError::GenericError { + error: format!("Can't view IR for `{val}`"), + msg: "not a custom command".into(), + span: Some(target.span()), + help: Some("internal commands don't have Nushell source code".into()), + inner: vec![], + })? + } else { + return Err(ShellError::GenericError { + error: format!("Can't view IR for `{val}`"), + msg: "can't find a command with this name".into(), + span: Some(target.span()), + help: None, + inner: vec![], + }); + } + } + // Decl by ID - IR dump always shows name of decl, but sometimes it isn't in scope + Value::Int { val, .. } if is_decl_id => { + let decl_id = val + .try_into() + .ok() + .filter(|id| *id < engine_state.num_decls()) + .ok_or_else(|| ShellError::IncorrectValue { + msg: "not a valid decl id".into(), + val_span: target.span(), + call_span: call.head, + })?; + let decl = engine_state.get_decl(decl_id); + decl.block_id().ok_or_else(|| ShellError::GenericError { + error: format!("Can't view IR for `{}`", decl.name()), + msg: "not a custom command".into(), + span: Some(target.span()), + help: Some("internal commands don't have Nushell source code".into()), + inner: vec![], + })? + } + // Block by ID - often shows up in IR + Value::Int { val, .. } => val.try_into().map_err(|_| ShellError::IncorrectValue { + msg: "not a valid block id".into(), + val_span: target.span(), + call_span: call.head, + })?, + // Pass through errors + Value::Error { error, .. } => return Err(*error), + _ => { + return Err(ShellError::TypeMismatch { + err_message: "expected closure, string, or int".into(), + span: call.head, + }) + } + }; + + let Some(block) = engine_state.try_get_block(block_id) else { + return Err(ShellError::GenericError { + error: format!("Unknown block ID: {block_id}"), + msg: "ensure the block ID is correct and try again".into(), + span: Some(target.span()), + help: None, + inner: vec![], + }); + }; - let block = engine_state.get_block(closure.block_id); let ir_block = block .ir_block .as_ref() @@ -63,7 +150,7 @@ impl Command for ViewIr { .collect::>(); serde_json::to_string_pretty(&serde_json::json!({ - "block_id": closure.block_id, + "block_id": block_id, "span": block.span, "ir_block": ir_block, "formatted_instructions": formatted_instructions, From 076a29ae19b8b982061c27bdd93782b898b33898 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 13:30:12 +0200 Subject: [PATCH 34/63] Document public types in `nu-protocol` (#12906) - **Doc-comment public `nu-protocol` modules** - **Doccomment argument/signature/call stuff** - **Doccomment cell path types** - **Doccomment expression stuff** - **Doccomment import patterns** - **Doccomment pattern matching AST nodes** --- crates/nu-protocol/src/alias.rs | 8 ++++- crates/nu-protocol/src/ast/call.rs | 34 +++++++++++++++++-- crates/nu-protocol/src/ast/cell_path.rs | 19 +++++++++++ crates/nu-protocol/src/ast/expr.rs | 3 ++ crates/nu-protocol/src/ast/expression.rs | 1 + crates/nu-protocol/src/ast/import_pattern.rs | 6 ++++ crates/nu-protocol/src/ast/match_pattern.rs | 17 ++++++++-- crates/nu-protocol/src/ast/mod.rs | 3 +- crates/nu-protocol/src/config/mod.rs | 1 + crates/nu-protocol/src/debugger/mod.rs | 1 + crates/nu-protocol/src/engine/mod.rs | 1 + crates/nu-protocol/src/errors/cli_error.rs | 3 ++ crates/nu-protocol/src/eval_base.rs | 1 + crates/nu-protocol/src/eval_const.rs | 4 +++ .../nu-protocol/src/pipeline/byte_stream.rs | 1 + .../nu-protocol/src/pipeline/list_stream.rs | 4 +++ crates/nu-protocol/src/process/mod.rs | 1 + crates/nu-protocol/src/signature.rs | 4 +++ crates/nu-protocol/src/span.rs | 1 + crates/nu-protocol/src/value/record.rs | 1 + 20 files changed, 107 insertions(+), 7 deletions(-) diff --git a/crates/nu-protocol/src/alias.rs b/crates/nu-protocol/src/alias.rs index 8f5ea43934..701543c57b 100644 --- a/crates/nu-protocol/src/alias.rs +++ b/crates/nu-protocol/src/alias.rs @@ -4,10 +4,16 @@ use crate::{ PipelineData, ShellError, Signature, }; +/// Command wrapper of an alias. +/// +/// Our current aliases are implemented as wrapping commands +/// This has some limitations compared to text-substitution macro aliases but can reliably use more +/// of our machinery #[derive(Clone)] pub struct Alias { pub name: String, - pub command: Option>, // None if external call + /// Wrapped inner [`Command`]. `None` if alias of external call + pub command: Option>, pub wrapped_call: Expression, pub usage: String, pub extra_usage: String, diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 6e8cc64a05..d26d0af300 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -7,12 +7,30 @@ use crate::{ ShellError, Span, Spanned, Value, }; +/// Parsed command arguments +/// +/// Primarily for internal commands #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Argument { + /// A positional argument (that is not [`Argument::Spread`]) + /// + /// ```nushell + /// my_cmd positional + /// ``` Positional(Expression), + /// A named/flag argument that can optionally receive a [`Value`] as an [`Expression`] + /// + /// The optional second `Spanned` refers to the short-flag version if used + /// ```nushell + /// my_cmd --flag + /// my_cmd -f + /// my_cmd --flag-with-value + /// ``` Named((Spanned, Option>, Option)), - Unknown(Expression), // unknown argument used in "fall-through" signatures - Spread(Expression), // a list spread to fill in rest arguments + /// unknown argument used in "fall-through" signatures + Unknown(Expression), + /// a list spread to fill in rest arguments + Spread(Expression), } impl Argument { @@ -47,12 +65,24 @@ impl Argument { } } +/// Argument passed to an external command +/// +/// Here the parsing rules slightly differ to directly pass strings to the external process #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ExternalArgument { + /// Expression that needs to be evaluated to turn into an external process argument Regular(Expression), + /// Occurrence of a `...` spread operator that needs to be expanded Spread(Expression), } +/// Parsed call of a `Command` +/// +/// As we also implement some internal keywords in terms of the `Command` trait, this type stores the passed arguments as [`Expression`]. +/// Some of its methods lazily evaluate those to [`Value`] while others return the underlying +/// [`Expression`]. +/// +/// For further utilities check the `nu_engine::CallExt` trait that extends [`Call`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Call { /// identifier of the declaration to call diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 3de37f3992..0880537557 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -3,16 +3,23 @@ use crate::Span; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display}; +/// One level of access of a [`CellPath`] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PathMember { + /// Accessing a member by string (i.e. columns of a table or [`Record`](crate::Record)) String { val: String, span: Span, + /// If marked as optional don't throw an error if not found but perform default handling + /// (e.g. return `Value::Nothing`) optional: bool, }, + /// Accessing a member by index (i.e. row of a table or item in a list) Int { val: usize, span: Span, + /// If marked as optional don't throw an error if not found but perform default handling + /// (e.g. return `Value::Nothing`) optional: bool, }, } @@ -143,6 +150,18 @@ impl PartialOrd for PathMember { } } +/// Represents the potentially nested access to fields/cells of a container type +/// +/// In our current implementation for table access the order of row/column is commutative. +/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could +/// otherwise be ambiguous +/// +/// ```nushell +/// col1.0 +/// 0.col1 +/// col2 +/// 42 +/// ``` #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] pub struct CellPath { pub members: Vec, diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 43548d39e4..fadbffc51f 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -9,6 +9,7 @@ use crate::{ ast::ImportPattern, engine::StateWorkingSet, BlockId, OutDest, Signature, Span, VarId, }; +/// An [`Expression`] AST node #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Expr { Bool(bool), @@ -127,6 +128,7 @@ impl Expr { } } +/// Expressions permitted inside a record expression/literal #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum RecordItem { /// A key: val mapping @@ -135,6 +137,7 @@ pub enum RecordItem { Spread(Span, Expression), } +/// Expressions permitted inside a list expression/literal #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ListItem { /// A normal expression diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 906fce3fe1..dfd4187a90 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -6,6 +6,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::sync::Arc; +/// Wrapper around [`Expr`] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Expression { pub expr: Expr, diff --git a/crates/nu-protocol/src/ast/import_pattern.rs b/crates/nu-protocol/src/ast/import_pattern.rs index 893dd9897b..ad08bfb2d1 100644 --- a/crates/nu-protocol/src/ast/import_pattern.rs +++ b/crates/nu-protocol/src/ast/import_pattern.rs @@ -3,10 +3,14 @@ use serde::{Deserialize, Serialize}; use crate::{ModuleId, Span, VarId}; use std::collections::HashSet; +/// possible patterns after the first module level in an [`ImportPattern`]. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum ImportPatternMember { + /// Wildcard import of items Glob { span: Span }, + /// single specific module or item Name { name: Vec, span: Span }, + /// list of items List { names: Vec<(Vec, Span)> }, } @@ -31,6 +35,7 @@ impl ImportPatternMember { } } +/// The first item of a `use` statement needs to specify an explicit module #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ImportPatternHead { pub name: Vec, @@ -38,6 +43,7 @@ pub struct ImportPatternHead { pub span: Span, } +/// The pattern specifying modules in a `use` statement #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ImportPattern { pub head: ImportPatternHead, diff --git a/crates/nu-protocol/src/ast/match_pattern.rs b/crates/nu-protocol/src/ast/match_pattern.rs index 1aafe84701..ca639270aa 100644 --- a/crates/nu-protocol/src/ast/match_pattern.rs +++ b/crates/nu-protocol/src/ast/match_pattern.rs @@ -2,6 +2,7 @@ use super::Expression; use crate::{Span, VarId}; use serde::{Deserialize, Serialize}; +/// AST Node for match arm with optional match guard #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MatchPattern { pub pattern: Pattern, @@ -15,18 +16,28 @@ impl MatchPattern { } } +/// AST Node for pattern matching rules #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Pattern { + /// Destructuring of records Record(Vec<(String, MatchPattern)>), + /// List destructuring List(Vec), + /// Matching against a literal // TODO: it would be nice if this didn't depend on AST // maybe const evaluation can get us to a Value instead? Value(Box), + /// binding to a variable Variable(VarId), + /// the `pattern1 \ pattern2` or-pattern Or(Vec), - Rest(VarId), // the ..$foo pattern - IgnoreRest, // the .. pattern - IgnoreValue, // the _ pattern + /// the `..$foo` pattern + Rest(VarId), + /// the `..` pattern + IgnoreRest, + /// the `_` pattern + IgnoreValue, + /// Failed parsing of a pattern Garbage, } diff --git a/crates/nu-protocol/src/ast/mod.rs b/crates/nu-protocol/src/ast/mod.rs index 7c627997fe..cb09168805 100644 --- a/crates/nu-protocol/src/ast/mod.rs +++ b/crates/nu-protocol/src/ast/mod.rs @@ -1,3 +1,4 @@ +//! Types representing parsed Nushell code (the Abstract Syntax Tree) mod block; mod call; mod cell_path; @@ -9,7 +10,7 @@ mod match_pattern; mod operator; mod pipeline; mod range; -pub mod table; +mod table; mod unit; mod value_with_unit; diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index e804e3f024..eda3d9a15e 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -1,3 +1,4 @@ +//! Module containing the internal representation of user configuration use self::completer::*; use self::helper::*; use self::hooks::*; diff --git a/crates/nu-protocol/src/debugger/mod.rs b/crates/nu-protocol/src/debugger/mod.rs index 03208fb3c7..86768a0a34 100644 --- a/crates/nu-protocol/src/debugger/mod.rs +++ b/crates/nu-protocol/src/debugger/mod.rs @@ -1,3 +1,4 @@ +//! Module containing the trait to instrument the engine for debugging and profiling pub mod debugger_trait; pub mod profiler; diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 1b1762fe3c..39bb5a7ae3 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,3 +1,4 @@ +//! Representation of the engine state and many of the details that implement the scoping mod argument; mod cached_file; mod call; diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index 181839b948..c12c601c4d 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -1,3 +1,6 @@ +//! This module manages the step of turning error types into printed error messages +//! +//! Relies on the `miette` crate for pretty layout use crate::{ engine::{EngineState, StateWorkingSet}, ErrorStyle, diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 7e1e2b0a7c..9c5878ee87 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -1,3 +1,4 @@ +//! Foundational [`Eval`] trait allowing dispatch between const-eval and regular evaluation use crate::{ ast::{ eval_operator, Assignment, Bits, Boolean, Call, Comparison, Expr, Expression, diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 5393e35e59..da7570b43c 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -1,3 +1,7 @@ +//! Implementation of const-evaluation +//! +//! This enables you to assign `const`-constants and execute parse-time code dependent on this. +//! e.g. `source $my_const` use crate::{ ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument}, debugger::{DebugContext, WithoutDebug}, diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index cd62b70801..69391b39a9 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,3 +1,4 @@ +//! Module managing the streaming of raw bytes between pipeline elements use crate::{ process::{ChildPipe, ChildProcess, ExitStatus}, ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value, diff --git a/crates/nu-protocol/src/pipeline/list_stream.rs b/crates/nu-protocol/src/pipeline/list_stream.rs index 104bab6bcc..55ae4bfee0 100644 --- a/crates/nu-protocol/src/pipeline/list_stream.rs +++ b/crates/nu-protocol/src/pipeline/list_stream.rs @@ -1,3 +1,7 @@ +//! Module managing the streaming of individual [`Value`]s as a [`ListStream`] between pipeline +//! elements +//! +//! For more general infos regarding our pipelining model refer to [`PipelineData`] use crate::{Config, PipelineData, ShellError, Signals, Span, Value}; use std::fmt::Debug; diff --git a/crates/nu-protocol/src/process/mod.rs b/crates/nu-protocol/src/process/mod.rs index 2fcf65f56e..6e10fdd620 100644 --- a/crates/nu-protocol/src/process/mod.rs +++ b/crates/nu-protocol/src/process/mod.rs @@ -1,3 +1,4 @@ +//! Handling of external subprocesses mod child; mod exit_status; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 3241b0df22..f8b83063d7 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -5,6 +5,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::fmt::Write; +/// The signature definition of a named flag that either accepts a value or acts as a toggle flag #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Flag { pub long: String, @@ -18,6 +19,7 @@ pub struct Flag { pub default_value: Option, } +/// The signature definition for a positional argument #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct PositionalArg { pub name: String, @@ -29,6 +31,7 @@ pub struct PositionalArg { pub default_value: Option, } +/// Command categories #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Category { Bits, @@ -102,6 +105,7 @@ impl std::fmt::Display for Category { } } +/// Signature information of a [`Command`] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Signature { pub name: String, diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index f5bcebc543..cb6d96421c 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -1,3 +1,4 @@ +//! [`Span`] to point to sections of source code and the [`Spanned`] wrapper type use crate::SpanId; use miette::SourceSpan; use serde::{Deserialize, Serialize}; diff --git a/crates/nu-protocol/src/value/record.rs b/crates/nu-protocol/src/value/record.rs index 8b61e61f7f..f4e963737f 100644 --- a/crates/nu-protocol/src/value/record.rs +++ b/crates/nu-protocol/src/value/record.rs @@ -1,3 +1,4 @@ +//! Our insertion ordered map-type [`Record`] use std::{iter::FusedIterator, ops::RangeBounds}; use crate::{ShellError, Span, Value}; From deaa711ca62150351ec89d47d373a6cc6e0a36f7 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 14:20:28 +0200 Subject: [PATCH 35/63] Bump yanked `libc` version (#13344) Fixes #13244 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7243a801fc..3820ff26d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2342,9 +2342,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" From f65bc97a545264e0fc0ca4ccb953a73a1cc1b07a Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 06:09:33 -0700 Subject: [PATCH 36/63] Update config directly at assignment (#13332) # Description Allows `Stack` to have a modified local `Config`, which is updated immediately when `$env.config` is assigned to. This means that even within a script, commands that come after `$env.config` changes will always see those changes in `Stack::get_config()`. Also fixed a lot of cases where `engine_state.get_config()` was used even when `Stack` was available. Closes #13324. # User-Facing Changes - Config changes apply immediately after the assignment is executed, rather than whenever config is read by a command that needs it. - Potentially slower performance when executing a lot of lines that change `$env.config` one after another. Recommended to get `$env.config` into a `mut` variable first and do modifications, then assign it back. - Much faster performance when executing a script that made modifications to `$env.config`, as the changes are only parsed once. # Tests + Formatting All passing. # After Submitting - [ ] release notes --- Cargo.lock | 2 + crates/nu-cli/src/eval_cmds.rs | 5 +- crates/nu-cli/src/menus/help_completions.rs | 32 ++++--- crates/nu-cli/src/nu_highlight.rs | 9 +- crates/nu-cli/src/reedline_config.rs | 54 ++++++----- crates/nu-cli/src/repl.rs | 3 +- crates/nu-cli/src/syntax_highlight.rs | 13 ++- .../nu-cmd-extra/src/extra/formats/to/html.rs | 2 +- .../src/extra/strings/format/command.rs | 36 ++++---- crates/nu-color-config/src/style_computer.rs | 4 +- .../nu-command/src/conversions/into/string.rs | 6 +- crates/nu-command/src/debug/debug_.rs | 2 +- crates/nu-command/src/filesystem/rm.rs | 2 +- crates/nu-command/src/filters/find.rs | 6 +- crates/nu-command/src/formats/to/md.rs | 4 +- crates/nu-command/src/formats/to/text.rs | 6 +- crates/nu-command/src/help/help_aliases.rs | 2 +- crates/nu-command/src/help/help_modules.rs | 2 +- crates/nu-command/src/network/url/parse.rs | 17 ++-- crates/nu-command/src/platform/ansi/ansi_.rs | 2 +- crates/nu-command/src/platform/ansi/strip.rs | 11 +-- crates/nu-command/src/platform/input/list.rs | 5 +- crates/nu-command/src/stor/insert.rs | 2 +- .../nu-command/src/strings/detect_columns.rs | 14 ++- crates/nu-command/src/system/nu_check.rs | 4 +- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 44 +++++++-- crates/nu-engine/src/documentation.rs | 92 ++++++++++++------- crates/nu-engine/src/env.rs | 14 +-- crates/nu-engine/src/eval.rs | 18 +++- crates/nu-explore/src/commands/expand.rs | 4 +- crates/nu-explore/src/explore.rs | 6 +- crates/nu-plugin-engine/Cargo.toml | 3 +- crates/nu-plugin-engine/src/context.rs | 8 +- crates/nu-plugin-engine/src/declaration.rs | 2 +- crates/nu-plugin-engine/src/interface/mod.rs | 3 +- crates/nu-plugin-protocol/src/lib.rs | 3 +- crates/nu-plugin/Cargo.toml | 3 +- crates/nu-plugin/src/plugin/interface/mod.rs | 5 +- crates/nu-plugin/src/plugin/mod.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 38 +++----- crates/nu-protocol/src/engine/stack.rs | 39 +++++++- .../src/engine/state_working_set.rs | 6 +- crates/nu-protocol/src/eval_base.rs | 4 +- crates/nu-protocol/src/eval_const.rs | 6 +- 46 files changed, 327 insertions(+), 222 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3820ff26d1..c7608643dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3257,6 +3257,7 @@ dependencies = [ "nu-plugin-core", "nu-plugin-protocol", "nu-protocol", + "nu-utils", "serde", "thiserror", "typetag", @@ -3286,6 +3287,7 @@ dependencies = [ "nu-plugin-protocol", "nu-protocol", "nu-system", + "nu-utils", "serde", "typetag", "windows 0.54.0", diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index ad3a15304d..1459f1ef0d 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -53,9 +53,8 @@ pub fn evaluate_commands( // Parse the source code let (block, delta) = { if let Some(ref t_mode) = table_mode { - let mut config = engine_state.get_config().clone(); - config.table_mode = t_mode.coerce_str()?.parse().unwrap_or_default(); - engine_state.set_config(config); + Arc::make_mut(&mut engine_state.config).table_mode = + t_mode.coerce_str()?.parse().unwrap_or_default(); } let mut working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-cli/src/menus/help_completions.rs b/crates/nu-cli/src/menus/help_completions.rs index c9c1b7bf94..ebe3fe616b 100644 --- a/crates/nu-cli/src/menus/help_completions.rs +++ b/crates/nu-cli/src/menus/help_completions.rs @@ -1,25 +1,31 @@ use nu_engine::documentation::get_flags_section; -use nu_protocol::{engine::EngineState, levenshtein_distance}; +use nu_protocol::{engine::EngineState, levenshtein_distance, Config}; use nu_utils::IgnoreCaseExt; use reedline::{Completer, Suggestion}; use std::{fmt::Write, sync::Arc}; -pub struct NuHelpCompleter(Arc); +pub struct NuHelpCompleter { + engine_state: Arc, + config: Arc, +} impl NuHelpCompleter { - pub fn new(engine_state: Arc) -> Self { - Self(engine_state) + pub fn new(engine_state: Arc, config: Arc) -> Self { + Self { + engine_state, + config, + } } fn completion_helper(&self, line: &str, pos: usize) -> Vec { let folded_line = line.to_folded_case(); let mut commands = self - .0 + .engine_state .get_decls_sorted(false) .into_iter() .filter_map(|(_, decl_id)| { - let decl = self.0.get_decl(decl_id); + let decl = self.engine_state.get_decl(decl_id); (decl.name().to_folded_case().contains(&folded_line) || decl.usage().to_folded_case().contains(&folded_line) || decl @@ -54,9 +60,12 @@ impl NuHelpCompleter { let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature()); if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(Some(&*self.0.clone()), &sig, |v| { - v.to_parsable_string(", ", &self.0.config) - })) + long_desc.push_str(&get_flags_section( + Some(&self.engine_state), + Some(&self.config), + &sig, + |v| v.to_parsable_string(", ", &self.config), + )) } if !sig.required_positional.is_empty() @@ -71,7 +80,7 @@ impl NuHelpCompleter { let opt_suffix = if let Some(value) = &positional.default_value { format!( " (optional, default: {})", - &value.to_parsable_string(", ", &self.0.config), + &value.to_parsable_string(", ", &self.config), ) } else { (" (optional)").to_string() @@ -138,7 +147,8 @@ mod test { ) { let engine_state = nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()); - let mut completer = NuHelpCompleter::new(engine_state.into()); + let config = engine_state.get_config().clone(); + let mut completer = NuHelpCompleter::new(engine_state.into(), config); let suggestions = completer.complete(line, end); assert_eq!( diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs index f4f38296de..0b5f211838 100644 --- a/crates/nu-cli/src/nu_highlight.rs +++ b/crates/nu-cli/src/nu_highlight.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use nu_engine::command_prelude::*; use reedline::{Highlighter, StyledText}; @@ -33,13 +35,10 @@ impl Command for NuHighlight { let head = call.head; let signals = engine_state.signals(); - let engine_state = std::sync::Arc::new(engine_state.clone()); - let config = engine_state.get_config().clone(); let highlighter = crate::NuHighlighter { - engine_state, - stack: std::sync::Arc::new(stack.clone()), - config, + engine_state: Arc::new(engine_state.clone()), + stack: Arc::new(stack.clone()), }; input.map( diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index dd5a3199dc..d5d3272bc1 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -77,13 +77,19 @@ pub(crate) fn add_menus( mut line_editor: Reedline, engine_state_ref: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { //log::trace!("add_menus: config: {:#?}", &config); line_editor = line_editor.clear_menus(); for menu in &config.menus { - line_editor = add_menu(line_editor, menu, engine_state_ref.clone(), stack, config)? + line_editor = add_menu( + line_editor, + menu, + engine_state_ref.clone(), + stack, + config.clone(), + )? } // Checking if the default menus have been added from the config file @@ -100,7 +106,7 @@ pub(crate) fn add_menus( if !config .menus .iter() - .any(|menu| menu.name.to_expanded_string("", config) == name) + .any(|menu| menu.name.to_expanded_string("", &config) == name) { let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); @@ -137,7 +143,7 @@ pub(crate) fn add_menus( &menu, new_engine_state_ref.clone(), stack, - config, + config.clone(), )?; } } @@ -151,27 +157,27 @@ fn add_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { let span = menu.menu_type.span(); if let Value::Record { val, .. } = &menu.menu_type { - let layout = extract_value("layout", val, span)?.to_expanded_string("", config); + let layout = extract_value("layout", val, span)?.to_expanded_string("", &config); match layout.as_str() { - "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config), + "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, &config), "list" => add_list_menu(line_editor, menu, engine_state, stack, config), "ide" => add_ide_menu(line_editor, menu, engine_state, stack, config), "description" => add_description_menu(line_editor, menu, engine_state, stack, config), _ => Err(ShellError::UnsupportedConfigValue { expected: "columnar, list, ide or description".to_string(), - value: menu.menu_type.to_abbreviated_string(config), + value: menu.menu_type.to_abbreviated_string(&config), span: menu.menu_type.span(), }), } } else { Err(ShellError::UnsupportedConfigValue { expected: "only record type".to_string(), - value: menu.menu_type.to_abbreviated_string(config), + value: menu.menu_type.to_abbreviated_string(&config), span: menu.menu_type.span(), }) } @@ -282,9 +288,9 @@ pub(crate) fn add_list_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut list_menu = ListMenu::default().with_name(&name); let span = menu.menu_type.span(); @@ -311,7 +317,7 @@ pub(crate) fn add_list_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); list_menu = list_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -337,7 +343,7 @@ pub(crate) fn add_list_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "block or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span: menu.source.span(), }), } @@ -349,10 +355,10 @@ pub(crate) fn add_ide_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { let span = menu.menu_type.span(); - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut ide_menu = IdeMenu::default().with_name(&name); if let Value::Record { val, .. } = &menu.menu_type { @@ -417,7 +423,7 @@ pub(crate) fn add_ide_menu( } else { return Err(ShellError::UnsupportedConfigValue { expected: "bool or record".to_string(), - value: border.to_abbreviated_string(config), + value: border.to_abbreviated_string(&config), span: border.span(), }); } @@ -441,7 +447,7 @@ pub(crate) fn add_ide_menu( _ => { return Err(ShellError::UnsupportedConfigValue { expected: "\"left\", \"right\" or \"prefer_right\"".to_string(), - value: description_mode.to_abbreviated_string(config), + value: description_mode.to_abbreviated_string(&config), span: description_mode.span(), }); } @@ -509,7 +515,7 @@ pub(crate) fn add_ide_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); ide_menu = ide_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -535,7 +541,7 @@ pub(crate) fn add_ide_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "block or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span, }), } @@ -547,9 +553,9 @@ pub(crate) fn add_description_menu( menu: &ParsedMenu, engine_state: Arc, stack: &Stack, - config: &Config, + config: Arc, ) -> Result { - let name = menu.name.to_expanded_string("", config); + let name = menu.name.to_expanded_string("", &config); let mut description_menu = DescriptionMenu::default().with_name(&name); let span = menu.menu_type.span(); @@ -608,7 +614,7 @@ pub(crate) fn add_description_menu( } } - let marker = menu.marker.to_expanded_string("", config); + let marker = menu.marker.to_expanded_string("", &config); description_menu = description_menu.with_marker(&marker); let only_buffer_difference = menu.only_buffer_difference.as_bool()?; @@ -617,7 +623,7 @@ pub(crate) fn add_description_menu( let span = menu.source.span(); match &menu.source { Value::Nothing { .. } => { - let completer = Box::new(NuHelpCompleter::new(engine_state)); + let completer = Box::new(NuHelpCompleter::new(engine_state, config)); Ok(line_editor.with_menu(ReedlineMenu::WithCompleter { menu: Box::new(description_menu), completer, @@ -638,7 +644,7 @@ pub(crate) fn add_description_menu( } _ => Err(ShellError::UnsupportedConfigValue { expected: "closure or omitted value".to_string(), - value: menu.source.to_abbreviated_string(config), + value: menu.source.to_abbreviated_string(&config), span: menu.source.span(), }), } diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 07272701f3..817950a445 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -297,7 +297,7 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("env-change hook", start_time, use_color); let engine_reference = Arc::new(engine_state.clone()); - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); start_time = std::time::Instant::now(); // Find the configured cursor shapes for each mode @@ -323,7 +323,6 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { engine_state: engine_reference.clone(), // STACK-REFERENCE 1 stack: stack_arc.clone(), - config: config.clone(), })) .with_validator(Box::new(NuValidator { engine_state: engine_reference.clone(), diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 41ef168390..08dcbb1346 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -6,7 +6,7 @@ use nu_parser::{flatten_block, parse, FlatShape}; use nu_protocol::{ ast::{Block, Expr, Expression, PipelineRedirection, RecordItem}, engine::{EngineState, Stack, StateWorkingSet}, - Config, Span, + Span, }; use reedline::{Highlighter, StyledText}; use std::sync::Arc; @@ -14,15 +14,14 @@ use std::sync::Arc; pub struct NuHighlighter { pub engine_state: Arc, pub stack: Arc, - pub config: Config, } impl Highlighter for NuHighlighter { fn highlight(&self, line: &str, _cursor: usize) -> StyledText { trace!("highlighting: {}", line); - let highlight_resolved_externals = - self.engine_state.get_config().highlight_resolved_externals; + let config = self.stack.get_config(&self.engine_state); + let highlight_resolved_externals = config.highlight_resolved_externals; let mut working_set = StateWorkingSet::new(&self.engine_state); let block = parse(&mut working_set, None, line.as_bytes(), false); let (shapes, global_span_offset) = { @@ -88,7 +87,7 @@ impl Highlighter for NuHighlighter { .to_string(); let mut add_colored_token = |shape: &FlatShape, text: String| { - output.push((get_shape_color(shape.as_str(), &self.config), text)); + output.push((get_shape_color(shape.as_str(), &config), text)); }; match shape.1 { @@ -128,9 +127,9 @@ impl Highlighter for NuHighlighter { let start = part.start - span.start; let end = part.end - span.start; let text = next_token[start..end].to_string(); - let mut style = get_shape_color(shape.as_str(), &self.config); + let mut style = get_shape_color(shape.as_str(), &config); if highlight { - style = get_matching_brackets_style(style, &self.config); + style = get_matching_brackets_style(style, &config); } output.push((style, text)); } diff --git a/crates/nu-cmd-extra/src/extra/formats/to/html.rs b/crates/nu-cmd-extra/src/extra/formats/to/html.rs index 83d0b58b3f..f02f11cf84 100644 --- a/crates/nu-cmd-extra/src/extra/formats/to/html.rs +++ b/crates/nu-cmd-extra/src/extra/formats/to/html.rs @@ -239,7 +239,7 @@ fn to_html( let partial = call.has_flag(engine_state, stack, "partial")?; let list = call.has_flag(engine_state, stack, "list")?; let theme: Option> = call.get_flag(engine_state, stack, "theme")?; - let config = engine_state.get_config(); + let config = &stack.get_config(engine_state); let vec_of_values = input.into_iter().collect::>(); let headers = merge_descriptors(&vec_of_values); diff --git a/crates/nu-cmd-extra/src/extra/strings/format/command.rs b/crates/nu-cmd-extra/src/extra/strings/format/command.rs index 08df92ef40..ef9b6f2baf 100644 --- a/crates/nu-cmd-extra/src/extra/strings/format/command.rs +++ b/crates/nu-cmd-extra/src/extra/strings/format/command.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::{ast::PathMember, engine::StateWorkingSet, ListStream}; +use nu_protocol::{ast::PathMember, engine::StateWorkingSet, Config, ListStream}; #[derive(Clone)] pub struct FormatPattern; @@ -43,6 +43,8 @@ impl Command for FormatPattern { let it_id = working_set.add_variable(b"$it".to_vec(), call.head, Type::Any, false); stack.add_var(it_id, input_val.clone()); + let config = stack.get_config(engine_state); + match specified_pattern { Err(e) => Err(e), Ok(pattern) => { @@ -56,7 +58,7 @@ impl Command for FormatPattern { string_span.start + 1, )?; - format(input_val, &ops, engine_state, call.head) + format(input_val, &ops, engine_state, &config, call.head) } } } @@ -181,33 +183,30 @@ fn format( input_data: Value, format_operations: &[FormatOperation], engine_state: &EngineState, + config: &Config, head_span: Span, ) -> Result { let data_as_value = input_data; // We can only handle a Record or a List of Records match data_as_value { - Value::Record { .. } => { - match format_record(format_operations, &data_as_value, engine_state) { - Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), - Err(value) => Err(value), - } - } + Value::Record { .. } => match format_record(format_operations, &data_as_value, config) { + Ok(value) => Ok(PipelineData::Value(Value::string(value, head_span), None)), + Err(value) => Err(value), + }, Value::List { vals, .. } => { let mut list = vec![]; for val in vals.iter() { match val { - Value::Record { .. } => { - match format_record(format_operations, val, engine_state) { - Ok(value) => { - list.push(Value::string(value, head_span)); - } - Err(value) => { - return Err(value); - } + Value::Record { .. } => match format_record(format_operations, val, config) { + Ok(value) => { + list.push(Value::string(value, head_span)); } - } + Err(value) => { + return Err(value); + } + }, Value::Error { error, .. } => return Err(*error.clone()), _ => { return Err(ShellError::OnlySupportsThisInputType { @@ -237,9 +236,8 @@ fn format( fn format_record( format_operations: &[FormatOperation], data_as_value: &Value, - engine_state: &EngineState, + config: &Config, ) -> Result { - let config = engine_state.get_config(); let mut output = String::new(); for op in format_operations { diff --git a/crates/nu-color-config/src/style_computer.rs b/crates/nu-color-config/src/style_computer.rs index 91907c1428..edc8786a53 100644 --- a/crates/nu-color-config/src/style_computer.rs +++ b/crates/nu-color-config/src/style_computer.rs @@ -1,6 +1,6 @@ use crate::{color_record_to_nustyle, lookup_ansi_color_style, text_style::Alignment, TextStyle}; use nu_ansi_term::{Color, Style}; -use nu_engine::{env::get_config, ClosureEvalOnce}; +use nu_engine::ClosureEvalOnce; use nu_protocol::{ cli_error::CliError, engine::{Closure, EngineState, Stack, StateWorkingSet}, @@ -114,7 +114,7 @@ impl<'a> StyleComputer<'a> { // The main constructor. pub fn from_config(engine_state: &'a EngineState, stack: &'a Stack) -> StyleComputer<'a> { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); // Create the hashmap #[rustfmt::skip] diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index c394f9fd21..2b35272b3e 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; use nu_protocol::{into_code, Config}; @@ -7,7 +9,7 @@ use num_format::ToFormattedString; struct Arguments { decimals_value: Option, cell_paths: Option>, - config: Config, + config: Arc, } impl CmdArgument for Arguments { @@ -174,7 +176,7 @@ fn string_helper( }) } } else { - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let args = Arguments { decimals_value, cell_paths, diff --git a/crates/nu-command/src/debug/debug_.rs b/crates/nu-command/src/debug/debug_.rs index f4e5707491..80a9067e49 100644 --- a/crates/nu-command/src/debug/debug_.rs +++ b/crates/nu-command/src/debug/debug_.rs @@ -33,7 +33,7 @@ impl Command for Debug { input: PipelineData, ) -> Result { let head = call.head; - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let raw = call.has_flag(engine_state, stack, "raw")?; // Should PipelineData::Empty result in an error here? diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index d67046ffe1..9eb4fabace 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -170,7 +170,7 @@ fn rm( } let span = call.head; - let rm_always_trash = engine_state.get_config().rm_always_trash; + let rm_always_trash = stack.get_config(engine_state).rm_always_trash; if !TRASH_SUPPORTED { if rm_always_trash { diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index 7669190f7e..9ed0c28a3c 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -213,7 +213,7 @@ fn find_with_regex( input: PipelineData, ) -> Result { let span = call.head; - let config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); let insensitive = call.has_flag(engine_state, stack, "ignore-case")?; let multiline = call.has_flag(engine_state, stack, "multiline")?; @@ -348,8 +348,8 @@ fn find_with_rest_and_highlight( input: PipelineData, ) -> Result { let span = call.head; - let config = engine_state.get_config().clone(); - let filter_config = engine_state.get_config().clone(); + let config = stack.get_config(engine_state); + let filter_config = config.clone(); let invert = call.has_flag(engine_state, stack, "invert")?; let terms = call.rest::(engine_state, stack, 0)?; let lower_terms = terms diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index e2acdd703e..737a328cd3 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -70,8 +70,8 @@ impl Command for ToMd { let head = call.head; let pretty = call.has_flag(engine_state, stack, "pretty")?; let per_element = call.has_flag(engine_state, stack, "per-element")?; - let config = engine_state.get_config(); - to_md(input, pretty, per_element, config, head) + let config = stack.get_config(engine_state); + to_md(input, pretty, per_element, &config, head) } } diff --git a/crates/nu-command/src/formats/to/text.rs b/crates/nu-command/src/formats/to/text.rs index 82b853248c..33ca19f8f2 100644 --- a/crates/nu-command/src/formats/to/text.rs +++ b/crates/nu-command/src/formats/to/text.rs @@ -31,18 +31,19 @@ impl Command for ToText { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let span = call.head; let input = input.try_expand_range()?; + let config = stack.get_config(engine_state); match input { PipelineData::Empty => Ok(Value::string(String::new(), span) .into_pipeline_data_with_metadata(update_metadata(None))), PipelineData::Value(value, ..) => { - let str = local_into_string(value, LINE_ENDING, engine_state.get_config()); + let str = local_into_string(value, LINE_ENDING, &config); Ok( Value::string(str, span) .into_pipeline_data_with_metadata(update_metadata(None)), @@ -50,7 +51,6 @@ impl Command for ToText { } PipelineData::ListStream(stream, meta) => { let span = stream.span(); - let config = engine_state.get_config().clone(); let iter = stream.into_inner().map(move |value| { let mut str = local_into_string(value, LINE_ENDING, &config); str.push_str(LINE_ENDING); diff --git a/crates/nu-command/src/help/help_aliases.rs b/crates/nu-command/src/help/help_aliases.rs index da03fa6398..41af9da0b3 100644 --- a/crates/nu-command/src/help/help_aliases.rs +++ b/crates/nu-command/src/help/help_aliases.rs @@ -143,7 +143,7 @@ pub fn help_aliases( long_desc.push_str("\n\n"); long_desc.push_str(&format!("{G}Expansion{RESET}:\n {alias_expansion}")); - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); if !config.use_ansi_coloring { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/help/help_modules.rs b/crates/nu-command/src/help/help_modules.rs index 5b39133a6d..c2c00cf400 100644 --- a/crates/nu-command/src/help/help_modules.rs +++ b/crates/nu-command/src/help/help_modules.rs @@ -230,7 +230,7 @@ pub fn help_modules( )); } - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); if !config.use_ansi_coloring { long_desc = nu_utils::strip_ansi_string_likely(long_desc); } diff --git a/crates/nu-command/src/network/url/parse.rs b/crates/nu-command/src/network/url/parse.rs index e71c8d472a..287c64c7b9 100644 --- a/crates/nu-command/src/network/url/parse.rs +++ b/crates/nu-command/src/network/url/parse.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; +use nu_protocol::Config; use url::Url; #[derive(Clone)] @@ -38,11 +39,15 @@ impl Command for SubCommand { fn run( &self, engine_state: &EngineState, - _: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { - parse(input.into_value(call.head)?, call.head, engine_state) + parse( + input.into_value(call.head)?, + call.head, + &stack.get_config(engine_state), + ) } fn examples(&self) -> Vec { @@ -68,12 +73,12 @@ impl Command for SubCommand { } } -fn get_url_string(value: &Value, engine_state: &EngineState) -> String { - value.to_expanded_string("", engine_state.get_config()) +fn get_url_string(value: &Value, config: &Config) -> String { + value.to_expanded_string("", config) } -fn parse(value: Value, head: Span, engine_state: &EngineState) -> Result { - let url_string = get_url_string(&value, engine_state); +fn parse(value: Value, head: Span, config: &Config) -> Result { + let url_string = get_url_string(&value, config); let result_url = Url::parse(url_string.as_str()); diff --git a/crates/nu-command/src/platform/ansi/ansi_.rs b/crates/nu-command/src/platform/ansi/ansi_.rs index 23161eb7bf..3daed5e157 100644 --- a/crates/nu-command/src/platform/ansi/ansi_.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -653,7 +653,7 @@ Operating system commands: let list: bool = call.has_flag(engine_state, stack, "list")?; let escape: bool = call.has_flag(engine_state, stack, "escape")?; let osc: bool = call.has_flag(engine_state, stack, "osc")?; - let use_ansi_coloring = engine_state.get_config().use_ansi_coloring; + let use_ansi_coloring = stack.get_config(engine_state).use_ansi_coloring; if list { return Ok(generate_ansi_code_list( diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index 3d59da6a10..4f7f8ea596 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -1,10 +1,12 @@ +use std::sync::Arc; + use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; use nu_protocol::Config; pub struct Arguments { cell_paths: Option>, - config: Config, + config: Arc, } impl CmdArgument for Arguments { @@ -51,11 +53,8 @@ impl Command for SubCommand { ) -> Result { let cell_paths: Vec = call.rest(engine_state, stack, 1)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - let config = engine_state.get_config(); - let args = Arguments { - cell_paths, - config: config.clone(), - }; + let config = stack.get_config(engine_state); + let args = Arguments { cell_paths, config }; operate(action, args, input, call.head, engine_state.signals()) } diff --git a/crates/nu-command/src/platform/input/list.rs b/crates/nu-command/src/platform/input/list.rs index 44b21a8da4..37f16e75d7 100644 --- a/crates/nu-command/src/platform/input/list.rs +++ b/crates/nu-command/src/platform/input/list.rs @@ -79,6 +79,7 @@ impl Command for InputList { let fuzzy = call.has_flag(engine_state, stack, "fuzzy")?; let index = call.has_flag(engine_state, stack, "index")?; let display_path: Option = call.get_flag(engine_state, stack, "display")?; + let config = stack.get_config(engine_state); let options: Vec = match input { PipelineData::Value(Value::Range { .. }, ..) @@ -89,9 +90,9 @@ impl Command for InputList { let display_value = if let Some(ref cellpath) = display_path { val.clone() .follow_cell_path(&cellpath.members, false)? - .to_expanded_string(", ", engine_state.get_config()) + .to_expanded_string(", ", &config) } else { - val.to_expanded_string(", ", engine_state.get_config()) + val.to_expanded_string(", ", &config) }; Ok(Options { name: display_value, diff --git a/crates/nu-command/src/stor/insert.rs b/crates/nu-command/src/stor/insert.rs index b6b8d50906..0e5c9ec6af 100644 --- a/crates/nu-command/src/stor/insert.rs +++ b/crates/nu-command/src/stor/insert.rs @@ -65,7 +65,7 @@ impl Command for StorInsert { let span = call.head; let table_name: Option = call.get_flag(engine_state, stack, "table-name")?; let data_record: Option = call.get_flag(engine_state, stack, "data-record")?; - // let config = engine_state.get_config(); + // let config = stack.get_config(engine_state); let db = Box::new(SQLiteDatabase::new( std::path::Path::new(MEMORY_DB), Signals::empty(), diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 2f1f713b33..37a7cfe303 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use nu_engine::command_prelude::*; -use nu_protocol::Range; -use std::{io::Cursor, iter::Peekable, str::CharIndices}; +use nu_protocol::{Config, Range}; +use std::{io::Cursor, iter::Peekable, str::CharIndices, sync::Arc}; type Input<'t> = Peekable>; @@ -110,11 +110,13 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; let noheader = call.has_flag(engine_state, stack, "no-headers")?; let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; + let config = stack.get_config(engine_state); let args = Arguments { noheader, num_rows_to_skip, range, + config, }; if call.has_flag(engine_state, stack, "guess")? { @@ -133,11 +135,13 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue let num_rows_to_skip: Option = call.get_flag_const(working_set, "skip")?; let noheader = call.has_flag_const(working_set, "no-headers")?; let range: Option = call.get_flag_const(working_set, "combine-columns")?; + let config = working_set.get_config().clone(); let args = Arguments { noheader, num_rows_to_skip, range, + config, }; if call.has_flag_const(working_set, "guess")? { @@ -152,6 +156,7 @@ struct Arguments { num_rows_to_skip: Option, noheader: bool, range: Option, + config: Arc, } fn guess_width( @@ -163,7 +168,7 @@ fn guess_width( use super::guess_width::GuessWidth; let input_span = input.span().unwrap_or(call.head); - let mut input = input.collect_string("", engine_state.get_config())?; + let mut input = input.collect_string("", &args.config)?; if let Some(rows) = args.num_rows_to_skip { input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); } @@ -235,8 +240,7 @@ fn detect_columns( args: Arguments, ) -> Result { let name_span = call.head; - let config = engine_state.get_config(); - let input = input.collect_string("", config)?; + let input = input.collect_string("", &args.config)?; let input: Vec<_> = input .lines() diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 334569c79e..c0c29858d5 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -1,4 +1,4 @@ -use nu_engine::{command_prelude::*, env::get_config, find_in_dirs_env, get_dirs_var_from_call}; +use nu_engine::{command_prelude::*, find_in_dirs_env, get_dirs_var_from_call}; use nu_parser::{parse, parse_module_block, parse_module_file_or_dir, unescape_unquote_string}; use nu_protocol::engine::{FileStack, StateWorkingSet}; use std::path::Path; @@ -59,7 +59,7 @@ impl Command for NuCheck { } } PipelineData::ListStream(stream, ..) => { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); let list_stream = stream.into_string("\n", &config); let contents = Vec::from(list_stream); diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index a5cf343970..a7ca822011 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -366,7 +366,7 @@ pub fn command_not_found( stack: &mut Stack, ) -> ShellError { // Run the `command_not_found` hook if there is one. - if let Some(hook) = &engine_state.config.hooks.command_not_found { + if let Some(hook) = &stack.get_config(engine_state).hooks.command_not_found { let mut stack = stack.start_capture(); // Set a special environment variable to avoid infinite loops when the // `command_not_found` hook triggers itself. diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index cd73c1ca71..c3e003be39 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -61,7 +61,7 @@ prints out the list properly."# let width_param: Option = call.get_flag(engine_state, stack, "width")?; let color_param: bool = call.has_flag(engine_state, stack, "color")?; let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; - let config = engine_state.get_config(); + let config = &stack.get_config(engine_state); let env_str = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => Some(env_to_string("LS_COLORS", &v, engine_state, stack)?), None => None, diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 190c3659af..e5fdf681f7 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -4,7 +4,7 @@ use lscolors::{LsColors, Style}; use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; -use nu_engine::{command_prelude::*, env::get_config, env_to_string}; +use nu_engine::{command_prelude::*, env_to_string}; use nu_pretty_hex::HexConfig; use nu_protocol::{ ByteStream, Config, DataSource, ListStream, PipelineMetadata, Signals, TableMode, ValueIterator, @@ -258,7 +258,7 @@ fn parse_table_config( let flatten_separator: Option = call.get_flag(state, stack, "flatten-separator")?; let abbrivation: Option = call .get_flag(state, stack, "abbreviated")? - .or_else(|| get_config(state, stack).table_abbreviation_threshold); + .or_else(|| stack.get_config(state).table_abbreviation_threshold); let table_view = match (expand, collapse) { (false, false) => TableView::General, (_, true) => TableView::Collapsed, @@ -269,7 +269,7 @@ fn parse_table_config( }, }; let theme = - get_theme_flag(call, state, stack)?.unwrap_or_else(|| get_config(state, stack).table_mode); + get_theme_flag(call, state, stack)?.unwrap_or_else(|| stack.get_config(state).table_mode); let index = get_index_flag(call, state, stack)?; let term_width = get_width_param(width_param); @@ -493,7 +493,11 @@ fn handle_record( cfg: TableConfig, mut record: Record, ) -> Result { - let config = get_config(input.engine_state, input.stack); + let config = { + let state = input.engine_state; + let stack: &Stack = input.stack; + stack.get_config(state) + }; let span = input.data.span().unwrap_or(input.call.head); let styles = &StyleComputer::from_config(input.engine_state, input.stack); @@ -608,7 +612,11 @@ fn handle_row_stream( data_source: DataSource::Ls, .. }) => { - let config = get_config(input.engine_state, input.stack); + let config = { + let state = input.engine_state; + let stack: &Stack = input.stack; + stack.get_config(state) + }; let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") { Some(v) => Some(env_to_string( "LS_COLORS", @@ -758,7 +766,11 @@ impl PagingTableCreator { return Ok(None); } - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); let view = TableView::Expanded { @@ -775,7 +787,11 @@ impl PagingTableCreator { return Ok(None); } - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); @@ -783,7 +799,11 @@ impl PagingTableCreator { } fn build_general(&mut self, batch: Vec) -> StringResult { - let cfg = get_config(&self.engine_state, &self.stack); + let cfg = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; let style_comp = StyleComputer::from_config(&self.engine_state, &self.stack); let opts = self.create_table_opts(&cfg, &style_comp); @@ -872,7 +892,11 @@ impl Iterator for PagingTableCreator { self.row_offset += batch_size; - let config = get_config(&self.engine_state, &self.stack); + let config = { + let state = &self.engine_state; + let stack = &self.stack; + stack.get_config(state) + }; convert_table_to_output( table, &config, @@ -1049,7 +1073,7 @@ fn create_empty_placeholder( engine_state: &EngineState, stack: &Stack, ) -> String { - let config = get_config(engine_state, stack); + let config = stack.get_config(engine_state); if !config.table_show_empty { return String::new(); } diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 7840d03c47..a0dee3d343 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -3,8 +3,8 @@ use nu_protocol::{ ast::{Argument, Call, Expr, Expression, RecordItem}, debugger::WithoutDebug, engine::{Command, EngineState, Stack, UNKNOWN_SPAN_ID}, - record, Category, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, Spanned, - SyntaxShape, Type, Value, + record, Category, Config, Example, IntoPipelineData, PipelineData, Signature, Span, SpanId, + Spanned, SyntaxShape, Type, Value, }; use std::{collections::HashMap, fmt::Write}; @@ -13,7 +13,7 @@ pub fn get_full_help( engine_state: &EngineState, stack: &mut Stack, ) -> String { - let config = engine_state.get_config(); + let config = stack.get_config(engine_state); let doc_config = DocumentationConfig { no_subcommands: false, no_color: !config.use_ansi_coloring, @@ -70,16 +70,30 @@ fn get_documentation( config: &DocumentationConfig, is_parser_keyword: bool, ) -> String { + let nu_config = stack.get_config(engine_state); + // Create ansi colors //todo make these configurable -- pull from enginestate.config - let help_section_name: String = - get_ansi_color_for_component_or_default(engine_state, "shape_string", "\x1b[32m"); // default: green + let help_section_name: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_string", + "\x1b[32m", + ); // default: green - let help_subcolor_one: String = - get_ansi_color_for_component_or_default(engine_state, "shape_external", "\x1b[36m"); // default: cyan - // was const bb: &str = "\x1b[1;34m"; // bold blue - let help_subcolor_two: String = - get_ansi_color_for_component_or_default(engine_state, "shape_block", "\x1b[94m"); // default: light blue (nobold, should be bolding the *names*) + let help_subcolor_one: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_external", + "\x1b[36m", + ); // default: cyan + // was const bb: &str = "\x1b[1;34m"; // bold blue + let help_subcolor_two: String = get_ansi_color_for_component_or_default( + engine_state, + &nu_config, + "shape_block", + "\x1b[94m", + ); // default: light blue (nobold, should be bolding the *names*) const RESET: &str = "\x1b[0m"; // reset @@ -139,13 +153,12 @@ fn get_documentation( } if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(Some(engine_state), sig, |v| { - nu_highlight_string( - &v.to_parsable_string(", ", &engine_state.config), - engine_state, - stack, - ) - })) + long_desc.push_str(&get_flags_section( + Some(engine_state), + Some(&nu_config), + sig, + |v| nu_highlight_string(&v.to_parsable_string(", ", &nu_config), engine_state, stack), + )) } if !sig.required_positional.is_empty() @@ -189,7 +202,7 @@ fn get_documentation( format!( " (optional, default: {})", nu_highlight_string( - &value.to_parsable_string(", ", &engine_state.config), + &value.to_parsable_string(", ", &nu_config), engine_state, stack ) @@ -339,7 +352,7 @@ fn get_documentation( let _ = writeln!( long_desc, " {}", - item.to_expanded_string("", engine_state.get_config()) + item.to_expanded_string("", &nu_config) .replace('\n', "\n ") .trim() ); @@ -358,15 +371,16 @@ fn get_documentation( fn get_ansi_color_for_component_or_default( engine_state: &EngineState, + nu_config: &Config, theme_component: &str, default: &str, ) -> String { - if let Some(color) = &engine_state.get_config().color_config.get(theme_component) { + if let Some(color) = &nu_config.color_config.get(theme_component) { let caller_stack = &mut Stack::new().capture(); let span = Span::unknown(); let span_id = UNKNOWN_SPAN_ID; - let argument_opt = get_argument_for_color_value(engine_state, color, span, span_id); + let argument_opt = get_argument_for_color_value(nu_config, color, span, span_id); // Call ansi command using argument if let Some(argument) = argument_opt { @@ -394,8 +408,8 @@ fn get_ansi_color_for_component_or_default( } fn get_argument_for_color_value( - engine_state: &EngineState, - color: &&Value, + nu_config: &Config, + color: &Value, span: Span, span_id: SpanId, ) -> Option { @@ -412,9 +426,7 @@ fn get_argument_for_color_value( Type::String, ), Expression::new_existing( - Expr::String( - v.clone().to_expanded_string("", engine_state.get_config()), - ), + Expr::String(v.clone().to_expanded_string("", nu_config)), span, span_id, Type::String, @@ -456,6 +468,7 @@ pub fn document_shape(shape: SyntaxShape) -> SyntaxShape { pub fn get_flags_section( engine_state_opt: Option<&EngineState>, + nu_config_opt: Option<&Config>, signature: &Signature, mut value_formatter: F, // format default Value (because some calls cant access config or nu-highlight) ) -> String @@ -470,13 +483,26 @@ where // Sometimes we want to get the flags without engine_state // For example, in nu-plugin. In that case, we fall back on default values if let Some(engine_state) = engine_state_opt { - help_section_name = - get_ansi_color_for_component_or_default(engine_state, "shape_string", "\x1b[32m"); // default: green - help_subcolor_one = - get_ansi_color_for_component_or_default(engine_state, "shape_external", "\x1b[36m"); // default: cyan - // was const bb: &str = "\x1b[1;34m"; // bold blue - help_subcolor_two = - get_ansi_color_for_component_or_default(engine_state, "shape_block", "\x1b[94m"); + let nu_config = nu_config_opt.unwrap_or_else(|| engine_state.get_config()); + help_section_name = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_string", + "\x1b[32m", + ); // default: green + help_subcolor_one = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_external", + "\x1b[36m", + ); // default: cyan + // was const bb: &str = "\x1b[1;34m"; // bold blue + help_subcolor_two = get_ansi_color_for_component_or_default( + engine_state, + nu_config, + "shape_block", + "\x1b[94m", + ); // default: light blue (nobold, should be bolding the *names*) } else { help_section_name = "\x1b[32m".to_string(); diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index ab3a4bc50c..f93e78af88 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -3,7 +3,7 @@ use nu_path::canonicalize_with; use nu_protocol::{ ast::Expr, engine::{Call, EngineState, Stack, StateWorkingSet}, - Config, ShellError, Span, Value, VarId, + ShellError, Span, Value, VarId, }; use std::{ collections::HashMap, @@ -323,18 +323,6 @@ pub fn find_in_dirs_env( Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback))) } -/// Get config -/// -/// This combines config stored in permanent state and any runtime updates to the environment. This -/// is the canonical way to fetch config at runtime when you have Stack available. -pub fn get_config(engine_state: &EngineState, stack: &Stack) -> Config { - if let Some(mut config_record) = stack.get_env_var(engine_state, "config") { - config_record.parse_as_config(engine_state.get_config()).0 - } else { - engine_state.get_config().clone() - } -} - fn get_converted_value( engine_state: &EngineState, stack: &Stack, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 6e171eb46c..42d0aed288 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use crate::eval_ir_block; #[allow(deprecated)] -use crate::{current_dir, get_config, get_full_help}; +use crate::{current_dir, get_full_help}; use nu_path::{expand_path_with, AbsolutePathBuf}; use nu_protocol::{ ast::{ @@ -14,7 +14,7 @@ use nu_protocol::{ Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; -use std::{borrow::Cow, fs::OpenOptions, path::PathBuf}; +use std::{fs::OpenOptions, path::PathBuf, sync::Arc}; pub fn eval_call( engine_state: &EngineState, @@ -196,6 +196,9 @@ pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee for (var, value) in callee_stack.get_stack_env_vars() { caller_stack.add_env_var(var, value); } + + // set config to callee config, to capture any updates to that + caller_stack.config = callee_stack.config.clone(); } fn eval_external( @@ -652,8 +655,8 @@ impl Eval for EvalRuntime { type MutState = Stack; - fn get_config<'a>(engine_state: Self::State<'a>, stack: &mut Stack) -> Cow<'a, Config> { - Cow::Owned(get_config(engine_state, stack)) + fn get_config(engine_state: Self::State<'_>, stack: &mut Stack) -> Arc { + stack.get_config(engine_state) } fn eval_filepath( @@ -843,7 +846,14 @@ impl Eval for EvalRuntime { }); } + let is_config = original_key == "config"; + stack.add_env_var(original_key, value); + + // Trigger the update to config, if we modified that. + if is_config { + stack.update_config(engine_state)?; + } } else { lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?; stack.add_var(*var_id, lhs); diff --git a/crates/nu-explore/src/commands/expand.rs b/crates/nu-explore/src/commands/expand.rs index b748ce2b89..38f92c0b50 100644 --- a/crates/nu-explore/src/commands/expand.rs +++ b/crates/nu-explore/src/commands/expand.rs @@ -64,8 +64,8 @@ fn convert_value_to_string( let has_no_head = cols.is_empty() || (cols.len() == 1 && cols[0].is_empty()); let has_single_value = vals.len() == 1 && vals[0].len() == 1; if !has_no_head && has_single_value { - let config = engine_state.get_config(); - Ok(vals[0][0].to_abbreviated_string(config)) + let config = stack.get_config(engine_state); + Ok(vals[0][0].to_abbreviated_string(&config)) } else { let config = engine_state.get_config(); let style_computer = StyleComputer::from_config(engine_state, stack); diff --git a/crates/nu-explore/src/explore.rs b/crates/nu-explore/src/explore.rs index 8d59591beb..28a3f04a30 100644 --- a/crates/nu-explore/src/explore.rs +++ b/crates/nu-explore/src/explore.rs @@ -63,10 +63,10 @@ impl Command for Explore { let tail: bool = call.has_flag(engine_state, stack, "tail")?; let peek_value: bool = call.has_flag(engine_state, stack, "peek")?; - let nu_config = engine_state.get_config(); + let nu_config = stack.get_config(engine_state); let style_computer = StyleComputer::from_config(engine_state, stack); - let mut explore_config = ExploreConfig::from_nu_config(nu_config); + let mut explore_config = ExploreConfig::from_nu_config(&nu_config); explore_config.table.show_header = show_head; explore_config.table.show_index = show_index; explore_config.table.separator_style = lookup_color(&style_computer, "separator"); @@ -74,7 +74,7 @@ impl Command for Explore { let lscolors = create_lscolors(engine_state, stack); let config = PagerConfig::new( - nu_config, + &nu_config, &explore_config, &style_computer, &lscolors, diff --git a/crates/nu-plugin-engine/Cargo.toml b/crates/nu-plugin-engine/Cargo.toml index f86c1ae703..a6dfa471b8 100644 --- a/crates/nu-plugin-engine/Cargo.toml +++ b/crates/nu-plugin-engine/Cargo.toml @@ -16,6 +16,7 @@ nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-system = { path = "../nu-system", version = "0.95.1" } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.95.1" } serde = { workspace = true } log = { workspace = true } @@ -31,4 +32,4 @@ local-socket = ["nu-plugin-core/local-socket"] windows = { workspace = true, features = [ # For setting process creation flags "Win32_System_Threading", -] } \ No newline at end of file +] } diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index 0df533a11e..aa504a246a 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -20,7 +20,7 @@ pub trait PluginExecutionContext: Send + Sync { /// The pipeline externals state, for tracking the foreground process group, if present fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>; /// Get engine configuration - fn get_config(&self) -> Result; + fn get_config(&self) -> Result, ShellError>; /// Get plugin configuration fn get_plugin_config(&self) -> Result, ShellError>; /// Get an environment variable from `$env` @@ -85,8 +85,8 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { Some(&self.engine_state.pipeline_externals_state) } - fn get_config(&self) -> Result { - Ok(nu_engine::get_config(&self.engine_state, &self.stack)) + fn get_config(&self) -> Result, ShellError> { + Ok(self.stack.get_config(&self.engine_state)) } fn get_plugin_config(&self) -> Result, ShellError> { @@ -239,7 +239,7 @@ impl PluginExecutionContext for PluginExecutionBogusContext { None } - fn get_config(&self) -> Result { + fn get_config(&self) -> Result, ShellError> { Err(ShellError::NushellFailed { msg: "get_config not implemented on bogus".into(), }) diff --git a/crates/nu-plugin-engine/src/declaration.rs b/crates/nu-plugin-engine/src/declaration.rs index d48fa39b85..745ba9a998 100644 --- a/crates/nu-plugin-engine/src/declaration.rs +++ b/crates/nu-plugin-engine/src/declaration.rs @@ -76,7 +76,7 @@ impl Command for PluginDeclaration { EvaluatedCall::try_from_call(call, engine_state, stack, eval_expression)?; // Get the engine config - let engine_config = nu_engine::get_config(engine_state, stack); + let engine_config = stack.get_config(engine_state); // Get, or start, the plugin. let plugin = self diff --git a/crates/nu-plugin-engine/src/interface/mod.rs b/crates/nu-plugin-engine/src/interface/mod.rs index 79ab7f7720..d1b722dedf 100644 --- a/crates/nu-plugin-engine/src/interface/mod.rs +++ b/crates/nu-plugin-engine/src/interface/mod.rs @@ -14,6 +14,7 @@ use nu_protocol::{ ast::Operator, CustomValue, IntoSpanned, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; +use nu_utils::SharedCow; use std::{ collections::{btree_map, BTreeMap}, sync::{mpsc, Arc, OnceLock}, @@ -1260,7 +1261,7 @@ pub(crate) fn handle_engine_call( match call { EngineCall::GetConfig => { - let config = Box::new(context.get_config()?); + let config = SharedCow::from(context.get_config()?); Ok(EngineCallResponse::Config(config)) } EngineCall::GetPluginConfig => { diff --git a/crates/nu-plugin-protocol/src/lib.rs b/crates/nu-plugin-protocol/src/lib.rs index 2f582c3009..739366d910 100644 --- a/crates/nu-plugin-protocol/src/lib.rs +++ b/crates/nu-plugin-protocol/src/lib.rs @@ -25,6 +25,7 @@ use nu_protocol::{ ast::Operator, engine::Closure, ByteStreamType, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Span, Spanned, Value, }; +use nu_utils::SharedCow; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -553,7 +554,7 @@ impl EngineCall { pub enum EngineCallResponse { Error(ShellError), PipelineData(D), - Config(Box), + Config(SharedCow), ValueMap(HashMap), } diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index a3b4455d15..d46a3cc9cb 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -15,6 +15,7 @@ nu-engine = { path = "../nu-engine", version = "0.95.1" } nu-protocol = { path = "../nu-protocol", version = "0.95.1" } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.95.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.95.1" } log = { workspace = true } thiserror = "1.0" @@ -29,4 +30,4 @@ local-socket = ["nu-plugin-core/local-socket"] [target.'cfg(target_family = "unix")'.dependencies] # For setting the process group ID (EnterForeground / LeaveForeground) -nix = { workspace = true, default-features = false, features = ["process"] } \ No newline at end of file +nix = { workspace = true, default-features = false, features = ["process"] } diff --git a/crates/nu-plugin/src/plugin/interface/mod.rs b/crates/nu-plugin/src/plugin/interface/mod.rs index dcb4119dba..60c5964f26 100644 --- a/crates/nu-plugin/src/plugin/interface/mod.rs +++ b/crates/nu-plugin/src/plugin/interface/mod.rs @@ -14,6 +14,7 @@ use nu_protocol::{ engine::Closure, Config, LabeledError, PipelineData, PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value, }; +use nu_utils::SharedCow; use std::{ collections::{btree_map, BTreeMap, HashMap}, sync::{mpsc, Arc}, @@ -525,9 +526,9 @@ impl EngineInterface { /// # Ok(()) /// # } /// ``` - pub fn get_config(&self) -> Result, ShellError> { + pub fn get_config(&self) -> Result, ShellError> { match self.engine_call(EngineCall::GetConfig)? { - EngineCallResponse::Config(config) => Ok(config), + EngineCallResponse::Config(config) => Ok(SharedCow::into_arc(config)), EngineCallResponse::Error(err) => Err(err), _ => Err(ShellError::PluginFailedToDecode { msg: "Received unexpected response for EngineCall::GetConfig".into(), diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 2bf7f9fc07..d809318d68 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -670,7 +670,7 @@ fn print_help(plugin: &impl Plugin, encoder: impl PluginEncoder) { } }) .and_then(|_| { - let flags = get_flags_section(None, &signature, |v| format!("{:#?}", v)); + let flags = get_flags_section(None, None, &signature, |v| format!("{:#?}", v)); write!(help, "{flags}") }) .and_then(|_| writeln!(help, "\nParameters:")) diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index f48fde663b..fce24cebbd 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -301,28 +301,11 @@ impl EngineState { stack: &mut Stack, cwd: impl AsRef, ) -> Result<(), ShellError> { - let mut config_updated = false; - for mut scope in stack.env_vars.drain(..) { for (overlay_name, mut env) in Arc::make_mut(&mut scope).drain() { if let Some(env_vars) = Arc::make_mut(&mut self.env_vars).get_mut(&overlay_name) { // Updating existing overlay - for (k, v) in env.drain() { - if k == "config" { - // Don't insert the record as the "config" env var as-is. - // Instead, mutate a clone of it with into_config(), and put THAT in env_vars. - let mut new_record = v.clone(); - let (config, error) = new_record.parse_as_config(&self.config); - self.config = Arc::new(config); - config_updated = true; - env_vars.insert(k, new_record); - if let Some(e) = error { - return Err(e); - } - } else { - env_vars.insert(k, v); - } - } + env_vars.extend(env.drain()); } else { // Pushing a new overlay Arc::make_mut(&mut self.env_vars).insert(overlay_name, env); @@ -333,7 +316,10 @@ impl EngineState { // TODO: better error std::env::set_current_dir(cwd)?; - if config_updated { + if let Some(config) = stack.config.take() { + // If config was updated in the stack, replace it. + self.config = config; + // Make plugin GC config changes take effect immediately. #[cfg(feature = "plugin")] self.update_plugin_gc_configs(&self.config.plugin_gc); @@ -738,18 +724,24 @@ impl EngineState { &[0u8; 0] } - pub fn get_config(&self) -> &Config { + /// Get the global config from the engine state. + /// + /// Use [`Stack::get_config()`] instead whenever the `Stack` is available, as it takes into + /// account local changes to `$env.config`. + pub fn get_config(&self) -> &Arc { &self.config } - pub fn set_config(&mut self, conf: Config) { + pub fn set_config(&mut self, conf: impl Into>) { + let conf = conf.into(); + #[cfg(feature = "plugin")] if conf.plugin_gc != self.config.plugin_gc { // Make plugin GC config changes take effect immediately. self.update_plugin_gc_configs(&conf.plugin_gc); } - self.config = Arc::new(conf); + self.config = conf; } /// Fetch the configuration for a plugin @@ -1137,7 +1129,7 @@ mod engine_state_tests { let mut plugins = HashMap::new(); plugins.insert("example".into(), Value::string("value", Span::test_data())); - let mut config = engine_state.get_config().clone(); + let mut config = Config::clone(engine_state.get_config()); config.plugins = plugins; engine_state.set_config(config); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index e963227ca8..fbfb586762 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -3,7 +3,7 @@ use crate::{ ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard, StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME, }, - OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, + Config, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, }; use std::{ collections::{HashMap, HashSet}, @@ -51,6 +51,8 @@ pub struct Stack { pub parent_stack: Option>, /// Variables that have been deleted (this is used to hide values from parent stack lookups) pub parent_deletions: Vec, + /// Locally updated config. Use [`.get_config()`] to access correctly. + pub config: Option>, pub(crate) out_dest: StackOutDest, } @@ -80,6 +82,7 @@ impl Stack { recursion_count: 0, parent_stack: None, parent_deletions: vec![], + config: None, out_dest: StackOutDest::new(), } } @@ -100,6 +103,7 @@ impl Stack { recursion_count: parent.recursion_count, vars: vec![], parent_deletions: vec![], + config: parent.config.clone(), out_dest: parent.out_dest.clone(), parent_stack: Some(parent), } @@ -126,6 +130,7 @@ impl Stack { unique_stack.env_vars = child.env_vars; unique_stack.env_hidden = child.env_hidden; unique_stack.active_overlays = child.active_overlays; + unique_stack.config = child.config; unique_stack } @@ -192,6 +197,36 @@ impl Stack { } } + /// Get the local config if set, otherwise the config from the engine state. + /// + /// This is the canonical way to get [`Config`] when [`Stack`] is available. + pub fn get_config(&self, engine_state: &EngineState) -> Arc { + self.config + .clone() + .unwrap_or_else(|| engine_state.config.clone()) + } + + /// Update the local config with the config stored in the `config` environment variable. Run + /// this after assigning to `$env.config`. + /// + /// The config will be updated with successfully parsed values even if an error occurs. + pub fn update_config(&mut self, engine_state: &EngineState) -> Result<(), ShellError> { + if let Some(mut config) = self.get_env_var(engine_state, "config") { + let existing_config = self.get_config(engine_state); + let (new_config, error) = config.parse_as_config(&existing_config); + self.config = Some(new_config.into()); + // The config value is modified by the update, so we should add it again + self.add_env_var("config".into(), config); + match error { + None => Ok(()), + Some(err) => Err(err), + } + } else { + self.config = None; + Ok(()) + } + } + pub fn add_var(&mut self, var_id: VarId, value: Value) { //self.vars.insert(var_id, value); for (id, val) in &mut self.vars { @@ -272,6 +307,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + config: self.config.clone(), out_dest: self.out_dest.clone(), } } @@ -305,6 +341,7 @@ impl Stack { recursion_count: self.recursion_count, parent_stack: None, parent_deletions: vec![], + config: self.config.clone(), out_dest: self.out_dest.clone(), } } diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index 8c3968a824..af1085c1ee 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -627,9 +627,9 @@ impl<'a> StateWorkingSet<'a> { /// Returns a reference to the config stored at permanent state /// - /// At runtime, you most likely want to call nu_engine::env::get_config because this method - /// does not capture environment updates during runtime. - pub fn get_config(&self) -> &Config { + /// At runtime, you most likely want to call [`Stack::get_config()`][super::Stack::get_config()] + /// because this method does not capture environment updates during runtime. + pub fn get_config(&self) -> &Arc { &self.permanent_state.config } diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 9c5878ee87..f49d235482 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -7,7 +7,7 @@ use crate::{ debugger::DebugContext, Config, GetSpan, Range, Record, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, }; -use std::{borrow::Cow, collections::HashMap}; +use std::{collections::HashMap, sync::Arc}; /// To share implementations for regular eval and const eval pub trait Eval { @@ -316,7 +316,7 @@ pub trait Eval { } } - fn get_config<'a>(state: Self::State<'a>, mut_state: &mut Self::MutState) -> Cow<'a, Config>; + fn get_config(state: Self::State<'_>, mut_state: &mut Self::MutState) -> Arc; fn eval_filepath( state: Self::State<'_>, diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index da7570b43c..cba2392338 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -11,8 +11,8 @@ use crate::{ }; use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name}; use std::{ - borrow::Cow, path::{Path, PathBuf}, + sync::Arc, }; /// Create a Value for `$nu`. @@ -364,8 +364,8 @@ impl Eval for EvalConst { type MutState = (); - fn get_config<'a>(state: Self::State<'a>, _: &mut ()) -> Cow<'a, Config> { - Cow::Borrowed(state.get_config()) + fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc { + state.get_config().clone() } fn eval_filepath( From 9fec5883c0650836a625b69538dec92f14d450ec Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 11 Jul 2024 15:18:58 +0200 Subject: [PATCH 37/63] Group dependabot bumps for uutils/coreutils (#13346) Similar to #13084 for polars. This familiy of crates has shared versioning and tends to at least depend on the same `uucore` crate. --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2193598686..07e0b2b891 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,6 +26,13 @@ updates: patterns: - "polars" - "polars-*" + # uutils/coreutils also versions all their workspace crates the same at the moment + # Most of them have bleeding edge version requirements (some not) + # see: https://github.com/uutils/coreutils/blob/main/Cargo.toml + uutils: + patterns: + - "uucore" + - "uu_*" - package-ecosystem: "github-actions" directory: "/" schedule: From a7a5315638cdf34a0b0b5f27d7d95f492fa0b9d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:29:34 +0000 Subject: [PATCH 38/63] Bump crate-ci/typos from 1.23.1 to 1.23.2 (#13347) --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 0f1e4cb5d9..c7c153983d 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.23.1 + uses: crate-ci/typos@v1.23.2 From acd4cb83e8c9d89eefd63f82b9c0d7ce1e3c9214 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:08:42 +0000 Subject: [PATCH 39/63] Bump ureq from 2.9.7 to 2.10.0 (#13348) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7608643dc..c8bfe4ab4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6470,9 +6470,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64 0.22.1", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index eb8a92c630..a0b3ef27f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,7 +164,7 @@ trash = "3.3" umask = "2.1" unicode-segmentation = "1.11" unicode-width = "0.1" -ureq = { version = "2.9", default-features = false } +ureq = { version = "2.10", default-features = false } url = "2.2" uu_cp = "0.0.27" uu_mkdir = "0.0.27" From ccd0160c328941e895be3f01c8d06768065312b3 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Thu, 11 Jul 2024 10:49:46 -0700 Subject: [PATCH 40/63] Make the `store-env` IR instruction also update config (#13351) # Description Follow up fix to #13332, so that changes to config when running under IR actually happen as well. Since I merged them around the same time, I forgot about this. --- crates/nu-engine/src/eval_ir.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index e2ec7ccdac..56bbccee25 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -367,7 +367,11 @@ fn eval_instruction( let key = get_env_var_name_case_insensitive(ctx, key); if !is_automatic_env_var(&key) { + let is_config = key == "config"; ctx.stack.add_env_var(key.into_owned(), value); + if is_config { + ctx.stack.update_config(ctx.engine_state)?; + } Ok(Continue) } else { Err(ShellError::AutomaticEnvVarSetManually { From 4bd87d04962989f47636be9b8279b98ac85fcd5e Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Thu, 11 Jul 2024 23:13:16 +0300 Subject: [PATCH 41/63] Fix unused space when truncation is used and header on border is configured (#13353) Somehow this logic was missed on my end. ( I mean I was not even thinking about it in original patch :smile: ) Please recheck Added a regression test too. close #13336 cc: @fdncred --- crates/nu-command/tests/commands/table.rs | 6 ++ crates/nu-table/src/table.rs | 89 ++++++++++++++++++----- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 16d5eb54ab..a4869f6bfa 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2895,3 +2895,9 @@ fn table_kv_header_on_separator_trim_algorithm() { let actual = nu!("$env.config.table.header_on_separator = true; {key1: '111111111111111111111111111111111111111111111111111111111111'} | table --width=60 --theme basic"); assert_eq!(actual.out, "+------+---------------------------------------------------+| key1 | 1111111111111111111111111111111111111111111111111 || | 11111111111 |+------+---------------------------------------------------+"); } + +#[test] +fn table_general_header_on_separator_trim_algorithm() { + let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic"); + assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index d2cf8f4d63..0dbf1bda7e 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -255,7 +255,8 @@ fn draw_table( align_table(&mut table, alignments, with_index, with_header, with_footer); colorize_table(&mut table, styles, with_index, with_header, with_footer); - let width_ctrl = TableWidthCtrl::new(widths, cfg, termwidth); + let pad = indent.0 + indent.1; + let width_ctrl = TableWidthCtrl::new(widths, cfg, termwidth, pad); if with_header && border_header { set_border_head(&mut table, with_footer, width_ctrl); @@ -336,12 +337,18 @@ fn table_to_string(table: Table, termwidth: usize) -> Option { struct TableWidthCtrl { width: Vec, cfg: NuTableConfig, - max: usize, + width_max: usize, + pad: usize, } impl TableWidthCtrl { - fn new(width: Vec, cfg: NuTableConfig, max: usize) -> Self { - Self { width, cfg, max } + fn new(width: Vec, cfg: NuTableConfig, max: usize, pad: usize) -> Self { + Self { + width, + cfg, + width_max: max, + pad, + } } } @@ -354,19 +361,20 @@ impl TableOption, ColoredConfig> for ) { let total_width = get_total_width2(&self.width, cfg); - if total_width > self.max { + if total_width > self.width_max { let has_header = self.cfg.with_header && rec.count_rows() > 1; let trim_as_head = has_header && self.cfg.header_on_border; - TableTrim { - max: self.max, - strategy: self.cfg.trim, - width: self.width, + TableTrim::new( + self.width, + self.width_max, + self.cfg.trim, trim_as_head, - } + self.pad, + ) .change(rec, cfg, dim); - } else if self.cfg.expand && self.max > total_width { - Settings::new(SetDimensions(self.width), Width::increase(self.max)) + } else if self.cfg.expand && self.width_max > total_width { + Settings::new(SetDimensions(self.width), Width::increase(self.width_max)) .change(rec, cfg, dim) } else { SetDimensions(self.width).change(rec, cfg, dim); @@ -376,9 +384,28 @@ impl TableOption, ColoredConfig> for struct TableTrim { width: Vec, + width_max: usize, strategy: TrimStrategy, - max: usize, trim_as_head: bool, + pad: usize, +} + +impl TableTrim { + fn new( + width: Vec, + width_max: usize, + strategy: TrimStrategy, + trim_as_head: bool, + pad: usize, + ) -> Self { + Self { + width, + strategy, + pad, + width_max, + trim_as_head, + } + } } impl TableOption, ColoredConfig> for TableTrim { @@ -395,13 +422,37 @@ impl TableOption, ColoredConfig> for return; } + // even though it's safe to trim columns by header there might be left unused space + // so we do use it if possible prioritizing left columns + let headers = recs[0].to_owned(); - for (i, head) in headers.into_iter().enumerate() { - let head_width = CellInfo::width(&head); + let headers_widths = headers + .iter() + .map(CellInfo::width) + .map(|v| v + self.pad) + .collect::>(); + + let min_width_use = get_total_width2(&headers_widths, cfg); + + let mut free_width = self.width_max.saturating_sub(min_width_use); + + for (i, head_width) in headers_widths.into_iter().enumerate() { + let head_width = head_width - self.pad; + let column_width = self.width[i] - self.pad; // safe to assume width is bigger then paddding + + let mut use_width = head_width; + if free_width > 0 { + // it's safe to assume that column_width is always bigger or equal to head_width + debug_assert!(column_width >= head_width); + + let additional_width = min(free_width, column_width - head_width); + free_width -= additional_width; + use_width += additional_width; + } match &self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(head_width); + let mut wrap = Width::wrap(use_width); if *try_to_keep_words { wrap = wrap.keep_words(); } @@ -411,7 +462,7 @@ impl TableOption, ColoredConfig> for .change(recs, cfg, dims); } TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(self.max); + let mut truncate = Width::truncate(use_width); if let Some(suffix) = suffix { truncate = truncate.suffix(suffix).suffix_try_color(true); } @@ -428,7 +479,7 @@ impl TableOption, ColoredConfig> for match self.strategy { TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(self.max).priority::(); + let mut wrap = Width::wrap(self.width_max).priority::(); if try_to_keep_words { wrap = wrap.keep_words(); } @@ -436,7 +487,7 @@ impl TableOption, ColoredConfig> for Settings::new(SetDimensions(self.width), wrap).change(recs, cfg, dims); } TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(self.max).priority::(); + let mut truncate = Width::truncate(self.width_max).priority::(); if let Some(suffix) = suffix { truncate = truncate.suffix(suffix).suffix_try_color(true); } From d56457d63e7d053e93175ca352f18d4e6eec7301 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Fri, 12 Jul 2024 02:43:10 +0000 Subject: [PATCH 42/63] Path migration part 2: `nu-test-support` (#13329) # Description Part 2 of replacing `std::path` types with `nu_path` types added in #13115. This PR targets `nu-test-support`. --- crates/nu-cli/tests/completions/mod.rs | 2 +- .../support/completions_helpers.rs | 20 +- crates/nu-command/src/system/run_external.rs | 2 +- .../tests/commands/database/into_sqlite.rs | 2 +- crates/nu-command/tests/commands/rm.rs | 5 +- .../nu-command/tests/commands/source_env.rs | 12 +- crates/nu-command/tests/commands/ucp.rs | 8 +- crates/nu-command/tests/commands/use_.rs | 9 +- crates/nu-test-support/src/fs.rs | 227 +++--------------- crates/nu-test-support/src/macros.rs | 25 +- crates/nu-test-support/src/playground.rs | 2 +- .../src/playground/director.rs | 2 +- .../src/playground/nu_process.rs | 19 +- crates/nu-test-support/src/playground/play.rs | 124 ++++------ .../nu-test-support/src/playground/tests.rs | 11 +- tests/plugins/nu_plugin_nu_example.rs | 2 +- tests/plugins/registry_file.rs | 8 +- 17 files changed, 142 insertions(+), 338 deletions(-) diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index a7f88dc3b0..590979251e 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -721,7 +721,7 @@ fn file_completion_quoted() { "`te#st.txt`".to_string(), "`te'st.txt`".to_string(), "`te(st).txt`".to_string(), - format!("`{}`", folder("test dir".into())), + format!("`{}`", folder("test dir")), ]; match_suggestions(expected_paths, suggestions); diff --git a/crates/nu-cli/tests/completions/support/completions_helpers.rs b/crates/nu-cli/tests/completions/support/completions_helpers.rs index 027ac9d997..623d6fbc5a 100644 --- a/crates/nu-cli/tests/completions/support/completions_helpers.rs +++ b/crates/nu-cli/tests/completions/support/completions_helpers.rs @@ -1,5 +1,6 @@ use nu_engine::eval_block; use nu_parser::parse; +use nu_path::{AbsolutePathBuf, PathBuf}; use nu_protocol::{ debugger::WithoutDebug, engine::{EngineState, Stack, StateWorkingSet}, @@ -7,14 +8,14 @@ use nu_protocol::{ }; use nu_test_support::fs; use reedline::Suggestion; -use std::path::{PathBuf, MAIN_SEPARATOR}; +use std::path::MAIN_SEPARATOR; fn create_default_context() -> EngineState { nu_command::add_shell_command_context(nu_cmd_lang::create_default_context()) } // creates a new engine with the current path into the completions fixtures folder -pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("completions"); let dir_str = dir @@ -69,7 +70,7 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { } // creates a new engine with the current path into the completions fixtures folder -pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_dotnu_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("dotnu_completions"); let dir_str = dir @@ -114,7 +115,7 @@ pub fn new_dotnu_engine() -> (PathBuf, String, EngineState, Stack) { (dir, dir_str, engine_state, stack) } -pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_quote_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("quoted_completions"); let dir_str = dir @@ -149,7 +150,7 @@ pub fn new_quote_engine() -> (PathBuf, String, EngineState, Stack) { (dir, dir_str, engine_state, stack) } -pub fn new_partial_engine() -> (PathBuf, String, EngineState, Stack) { +pub fn new_partial_engine() -> (AbsolutePathBuf, String, EngineState, Stack) { // Target folder inside assets let dir = fs::fixtures().join("partial_completions"); let dir_str = dir @@ -205,16 +206,15 @@ pub fn match_suggestions(expected: Vec, suggestions: Vec) { } // append the separator to the converted path -pub fn folder(path: PathBuf) -> String { +pub fn folder(path: impl Into) -> String { let mut converted_path = file(path); converted_path.push(MAIN_SEPARATOR); - converted_path } // convert a given path to string -pub fn file(path: PathBuf) -> String { - path.into_os_string().into_string().unwrap_or_default() +pub fn file(path: impl Into) -> String { + path.into().into_os_string().into_string().unwrap() } // merge_input executes the given input into the engine @@ -223,7 +223,7 @@ pub fn merge_input( input: &[u8], engine_state: &mut EngineState, stack: &mut Stack, - dir: PathBuf, + dir: AbsolutePathBuf, ) -> Result<(), ShellError> { let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index a7ca822011..5e8527b45e 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -580,7 +580,7 @@ mod test { Playground::setup("test_expand_glob", |dirs, play| { play.with_files(&[Stub::EmptyFile("a.txt"), Stub::EmptyFile("b.txt")]); - let cwd = dirs.test(); + let cwd = dirs.test().as_std_path(); let actual = expand_glob("*.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); let expected = &["a.txt", "b.txt"]; diff --git a/crates/nu-command/tests/commands/database/into_sqlite.rs b/crates/nu-command/tests/commands/database/into_sqlite.rs index 595c88dc38..faa45040b3 100644 --- a/crates/nu-command/tests/commands/database/into_sqlite.rs +++ b/crates/nu-command/tests/commands/database/into_sqlite.rs @@ -465,7 +465,7 @@ fn make_sqlite_db(dirs: &Dirs, nu_table: &str) -> PathBuf { ); assert!(nucmd.status.success()); - testdb_path + testdb_path.into() } fn insert_test_rows(dirs: &Dirs, nu_table: &str, sql_query: Option<&str>, expected: Vec) { diff --git a/crates/nu-command/tests/commands/rm.rs b/crates/nu-command/tests/commands/rm.rs index b3a273ea86..e6cbf88cda 100644 --- a/crates/nu-command/tests/commands/rm.rs +++ b/crates/nu-command/tests/commands/rm.rs @@ -1,3 +1,4 @@ +use nu_path::AbsolutePath; use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; use nu_test_support::nu; use nu_test_support::playground::Playground; @@ -405,10 +406,10 @@ fn removes_file_after_cd() { } struct Cleanup<'a> { - dir_to_clean: &'a Path, + dir_to_clean: &'a AbsolutePath, } -fn set_dir_read_only(directory: &Path, read_only: bool) { +fn set_dir_read_only(directory: &AbsolutePath, read_only: bool) { let mut permissions = fs::metadata(directory).unwrap().permissions(); permissions.set_readonly(read_only); fs::set_permissions(directory, permissions).expect("failed to set directory permissions"); diff --git a/crates/nu-command/tests/commands/source_env.rs b/crates/nu-command/tests/commands/source_env.rs index 9dd0320a45..f9cc3fbc1c 100644 --- a/crates/nu-command/tests/commands/source_env.rs +++ b/crates/nu-command/tests/commands/source_env.rs @@ -1,4 +1,3 @@ -use nu_test_support::fs::AbsolutePath; use nu_test_support::fs::Stub::{FileWithContent, FileWithContentToBeTrimmed}; use nu_test_support::nu; use nu_test_support::pipeline; @@ -8,17 +7,18 @@ use nu_test_support::playground::Playground; #[test] fn sources_also_files_under_custom_lib_dirs_path() { Playground::setup("source_test_1", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - let library_path = AbsolutePath::new(dirs.test().join("lib")); + let file = dirs.test().join("config.toml"); + let library_path = dirs.test().join("lib"); - nu.with_config(&file); + nu.with_config(file); nu.with_files(&[FileWithContent( "config.toml", &format!( r#" - lib_dirs = ["{library_path}"] + lib_dirs = ["{}"] skip_welcome_message = true - "# + "#, + library_path.as_os_str().to_str().unwrap(), ), )]); diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index 728cf1dcb2..2c2b30f252 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -1,6 +1,6 @@ use nu_test_support::fs::file_contents; use nu_test_support::fs::{ - files_exist_at, AbsoluteFile, + files_exist_at, Stub::{EmptyFile, FileWithContent, FileWithPermission}, }; use nu_test_support::nu; @@ -55,7 +55,7 @@ fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { fn copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(progress: bool) { Playground::setup("ucp_test_2", |dirs, _| { - let expected_file = AbsoluteFile::new(dirs.test().join("sample.ini")); + let expected_file = dirs.test().join("sample.ini"); let progress_flag = if progress { "-p" } else { "" }; // Get the hash of the file content to check integrity after copy. @@ -64,13 +64,13 @@ fn copies_the_file_inside_directory_if_path_to_copy_is_directory_impl(progress: cwd: dirs.formats(), "cp {} ../formats/sample.ini {}", progress_flag, - expected_file.dir() + expected_file.parent().unwrap().as_os_str().to_str().unwrap(), ); assert!(dirs.test().join("sample.ini").exists()); // Check the integrity of the file. - let after_cp_hash = get_file_hash(expected_file); + let after_cp_hash = get_file_hash(expected_file.display()); assert_eq!(first_hash, after_cp_hash); }) } diff --git a/crates/nu-command/tests/commands/use_.rs b/crates/nu-command/tests/commands/use_.rs index 63b1f8391a..2895e1635f 100644 --- a/crates/nu-command/tests/commands/use_.rs +++ b/crates/nu-command/tests/commands/use_.rs @@ -1,4 +1,3 @@ -use nu_test_support::fs::AbsolutePath; use nu_test_support::fs::Stub::{FileWithContent, FileWithContentToBeTrimmed}; use nu_test_support::nu; use nu_test_support::pipeline; @@ -7,10 +6,10 @@ use nu_test_support::playground::Playground; #[test] fn use_module_file_within_block() { Playground::setup("use_test_1", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("spam.nu")); + let file = dirs.test().join("spam.nu"); nu.with_files(&[FileWithContent( - &file.to_string(), + file.as_os_str().to_str().unwrap(), r#" export def foo [] { echo "hello world" @@ -37,10 +36,10 @@ fn use_module_file_within_block() { #[test] fn use_keeps_doc_comments() { Playground::setup("use_doc_comments", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("spam.nu")); + let file = dirs.test().join("spam.nu"); nu.with_files(&[FileWithContent( - &file.to_string(), + file.as_os_str().to_str().unwrap(), r#" # this is my foo command export def foo [ diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index 00321e6590..eb90e16ab2 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -1,137 +1,5 @@ -use std::fmt::Display; +use nu_path::{AbsolutePath, AbsolutePathBuf, Path}; use std::io::Read; -use std::ops::Div; -use std::path::{Path, PathBuf}; - -pub struct AbsoluteFile { - inner: PathBuf, -} - -impl AbsoluteFile { - pub fn new(path: impl AsRef) -> AbsoluteFile { - let path = path.as_ref(); - - if !path.is_absolute() { - panic!( - "AbsoluteFile::new must take an absolute path :: {}", - path.display() - ) - } else if path.is_dir() { - // At the moment, this is not an invariant, but rather a way to catch bugs - // in tests. - panic!( - "AbsoluteFile::new must not take a directory :: {}", - path.display() - ) - } else { - AbsoluteFile { - inner: path.to_path_buf(), - } - } - } - - pub fn dir(&self) -> AbsolutePath { - AbsolutePath::new(if let Some(parent) = self.inner.parent() { - parent - } else { - unreachable!("Internal error: could not get parent in dir") - }) - } -} - -impl From for PathBuf { - fn from(file: AbsoluteFile) -> Self { - file.inner - } -} - -pub struct AbsolutePath { - pub inner: PathBuf, -} - -impl AbsolutePath { - pub fn new(path: impl AsRef) -> AbsolutePath { - let path = path.as_ref(); - - if path.is_absolute() { - AbsolutePath { - inner: path.to_path_buf(), - } - } else { - panic!("AbsolutePath::new must take an absolute path") - } - } -} - -impl Div<&str> for &AbsolutePath { - type Output = AbsolutePath; - - fn div(self, rhs: &str) -> Self::Output { - let parts = rhs.split('/'); - let mut result = self.inner.clone(); - - for part in parts { - result = result.join(part); - } - - AbsolutePath::new(result) - } -} - -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &Path { - self.inner.as_path() - } -} - -pub struct RelativePath { - inner: PathBuf, -} - -impl RelativePath { - pub fn new(path: impl Into) -> RelativePath { - let path = path.into(); - - if path.is_relative() { - RelativePath { inner: path } - } else { - panic!("RelativePath::new must take a relative path") - } - } -} - -impl> Div for &RelativePath { - type Output = RelativePath; - - fn div(self, rhs: T) -> Self::Output { - let parts = rhs.as_ref().split('/'); - let mut result = self.inner.clone(); - - for part in parts { - result = result.join(part); - } - - RelativePath::new(result) - } -} - -impl Display for AbsoluteFile { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} - -impl Display for AbsolutePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} - -impl Display for RelativePath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.display()) - } -} pub enum Stub<'a> { FileWithContent(&'a str, &'a str), @@ -140,7 +8,7 @@ pub enum Stub<'a> { FileWithPermission(&'a str, bool), } -pub fn file_contents(full_path: impl AsRef) -> String { +pub fn file_contents(full_path: impl AsRef) -> String { let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); let mut contents = String::new(); file.read_to_string(&mut contents) @@ -148,7 +16,7 @@ pub fn file_contents(full_path: impl AsRef) -> String { contents } -pub fn file_contents_binary(full_path: impl AsRef) -> Vec { +pub fn file_contents_binary(full_path: impl AsRef) -> Vec { let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); let mut contents = Vec::new(); file.read_to_end(&mut contents).expect("can not read file"); @@ -167,56 +35,32 @@ pub fn line_ending() -> String { } } -pub fn delete_file_at(full_path: impl AsRef) { - let full_path = full_path.as_ref(); - - if full_path.exists() { - std::fs::remove_file(full_path).expect("can not delete file"); - } +pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { + let path = path.as_ref(); + files.iter().all(|f| path.join(f.as_ref()).exists()) } -pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { - let full_path = full_path.as_ref(); - - if full_path.parent().is_some() { - panic!("path exists"); - } - - std::fs::write(full_path, b"fake data") -} - -pub fn copy_file_to(source: &str, destination: &str) { - std::fs::copy(source, destination).expect("can not copy file"); -} - -pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { - files.iter().all(|f| { - let mut loc = PathBuf::from(path.as_ref()); - loc.push(f); - loc.exists() - }) -} - -pub fn delete_directory_at(full_path: &str) { - std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); -} - -pub fn executable_path() -> PathBuf { +pub fn executable_path() -> AbsolutePathBuf { let mut path = binaries(); path.push("nu"); path } -pub fn installed_nu_path() -> PathBuf { +pub fn installed_nu_path() -> AbsolutePathBuf { let path = std::env::var_os(crate::NATIVE_PATH_ENV_VAR); - which::which_in("nu", path, ".").unwrap_or_else(|_| executable_path()) + if let Ok(path) = which::which_in("nu", path, ".") { + AbsolutePathBuf::try_from(path).expect("installed nushell path is absolute") + } else { + executable_path() + } } -pub fn root() -> PathBuf { +pub fn root() -> AbsolutePathBuf { let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { - PathBuf::from(manifest_dir) + AbsolutePathBuf::try_from(manifest_dir).expect("CARGO_MANIFEST_DIR is not an absolute path") } else { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) + AbsolutePathBuf::try_from(env!("CARGO_MANIFEST_DIR")) + .expect("CARGO_MANIFEST_DIR is not an absolute path") }; let test_path = manifest_dir.join("Cargo.lock"); @@ -228,11 +72,11 @@ pub fn root() -> PathBuf { .expect("Couldn't find the debug binaries directory") .parent() .expect("Couldn't find the debug binaries directory") - .to_path_buf() + .into() } } -pub fn binaries() -> PathBuf { +pub fn binaries() -> AbsolutePathBuf { let build_target = std::env::var("CARGO_BUILD_TARGET").unwrap_or_default(); let profile = if let Ok(env_profile) = std::env::var("NUSHELL_CARGO_PROFILE") { @@ -244,27 +88,28 @@ pub fn binaries() -> PathBuf { }; std::env::var("CARGO_TARGET_DIR") - .map(PathBuf::from) - .unwrap_or_else(|_| root().join("target")) + .ok() + .and_then(|p| AbsolutePathBuf::try_from(p).ok()) + .unwrap_or_else(|| root().join("target")) .join(build_target) .join(profile) } -pub fn fixtures() -> PathBuf { - root().join("tests").join("fixtures") +pub fn fixtures() -> AbsolutePathBuf { + let mut path = root(); + path.push("tests"); + path.push("fixtures"); + path } -pub fn assets() -> PathBuf { - root().join("tests/assets") -} +// FIXME: re-enable nu_json tests +// pub fn assets() -> AbsolutePathBuf { +// let mut path = root(); +// path.push("tests"); +// path.push("assets"); +// path +// } -pub fn in_directory(str: impl AsRef) -> String { - let path = str.as_ref(); - let path = if path.is_relative() { - root().join(path) - } else { - path.to_path_buf() - }; - - path.display().to_string() +pub fn in_directory(path: impl AsRef) -> AbsolutePathBuf { + root().join(path) } diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index 958a2453f5..6fd3a8c307 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -234,16 +234,16 @@ macro_rules! nu_with_plugins { } use crate::{Outcome, NATIVE_PATH_ENV_VAR}; -use std::ffi::OsStr; +use nu_path::{AbsolutePath, AbsolutePathBuf, Path}; use std::{ - path::Path, + ffi::OsStr, process::{Command, Stdio}, }; use tempfile::tempdir; #[derive(Default)] pub struct NuOpts { - pub cwd: Option, + pub cwd: Option, pub locale: Option, pub envs: Option>, pub collapse_output: Option, @@ -251,19 +251,12 @@ pub struct NuOpts { } pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> Outcome { - let test_bins = crate::fs::binaries(); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let test_bins = nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize dummy binaries path {}: {:?}", - test_bins.display(), - e - ) - }); + let test_bins = crate::fs::binaries() + .canonicalize() + .expect("Could not canonicalize dummy binaries path"); let mut paths = crate::shell_os_paths(); - paths.insert(0, test_bins); + paths.insert(0, test_bins.into()); let commands = commands.as_ref().lines().collect::>().join("; "); @@ -272,7 +265,7 @@ pub fn nu_run_test(opts: NuOpts, commands: impl AsRef, with_std: bool) -> O Err(_) => panic!("Couldn't join paths for PATH var."), }; - let target_cwd = opts.cwd.unwrap_or(".".to_string()); + let target_cwd = opts.cwd.unwrap_or_else(crate::fs::root); let locale = opts.locale.unwrap_or("en_US.UTF-8".to_string()); let executable_path = crate::fs::executable_path(); @@ -450,7 +443,7 @@ fn collapse_output(out: &str) -> String { out.replace('\n', "") } -fn setup_command(executable_path: &Path, target_cwd: &str) -> Command { +fn setup_command(executable_path: &AbsolutePath, target_cwd: &AbsolutePath) -> Command { let mut command = Command::new(executable_path); command diff --git a/crates/nu-test-support/src/playground.rs b/crates/nu-test-support/src/playground.rs index 375f192983..4d33dcebe5 100644 --- a/crates/nu-test-support/src/playground.rs +++ b/crates/nu-test-support/src/playground.rs @@ -6,5 +6,5 @@ mod play; mod tests; pub use director::Director; -pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; +pub use nu_process::{Executable, NuProcess, Outcome}; pub use play::{Dirs, EnvironmentVariable, Playground}; diff --git a/crates/nu-test-support/src/playground/director.rs b/crates/nu-test-support/src/playground/director.rs index 3d04155c25..a361342495 100644 --- a/crates/nu-test-support/src/playground/director.rs +++ b/crates/nu-test-support/src/playground/director.rs @@ -83,7 +83,7 @@ impl Director { } impl Executable for Director { - fn execute(&mut self) -> NuResult { + fn execute(&mut self) -> Result { use std::process::Stdio; match self.executable() { diff --git a/crates/nu-test-support/src/playground/nu_process.rs b/crates/nu-test-support/src/playground/nu_process.rs index 0031a173e8..2eeac703c6 100644 --- a/crates/nu-test-support/src/playground/nu_process.rs +++ b/crates/nu-test-support/src/playground/nu_process.rs @@ -1,12 +1,13 @@ use super::EnvironmentVariable; use crate::fs::{binaries as test_bins_path, executable_path}; -use std::ffi::{OsStr, OsString}; -use std::fmt; -use std::path::Path; -use std::process::{Command, ExitStatus}; +use std::{ + ffi::{OsStr, OsString}, + fmt, + process::{Command, ExitStatus}, +}; pub trait Executable { - fn execute(&mut self) -> NuResult; + fn execute(&mut self) -> Result; } #[derive(Clone, Debug)] @@ -24,8 +25,6 @@ impl Outcome { } } -pub type NuResult = Result; - #[derive(Debug)] pub struct NuError { pub desc: String, @@ -69,14 +68,10 @@ impl NuProcess { self } - pub fn get_cwd(&self) -> Option<&Path> { - self.cwd.as_ref().map(Path::new) - } - pub fn construct(&self) -> Command { let mut command = Command::new(executable_path()); - if let Some(cwd) = self.get_cwd() { + if let Some(cwd) = &self.cwd { command.current_dir(cwd); } diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs index 902997200e..12295c2690 100644 --- a/crates/nu-test-support/src/playground/play.rs +++ b/crates/nu-test-support/src/playground/play.rs @@ -1,8 +1,9 @@ use super::Director; -use crate::fs; -use crate::fs::Stub; +use crate::fs::{self, Stub}; use nu_glob::glob; -use std::path::{Path, PathBuf}; +#[cfg(not(target_arch = "wasm32"))] +use nu_path::Path; +use nu_path::{AbsolutePath, AbsolutePathBuf}; use std::str; use tempfile::{tempdir, TempDir}; @@ -22,46 +23,46 @@ impl EnvironmentVariable { } pub struct Playground<'a> { - root: TempDir, + _root: TempDir, tests: String, - cwd: PathBuf, - config: Option, + cwd: AbsolutePathBuf, + config: Option, environment_vars: Vec, dirs: &'a Dirs, } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct Dirs { - pub root: PathBuf, - pub test: PathBuf, - pub fixtures: PathBuf, + pub root: AbsolutePathBuf, + pub test: AbsolutePathBuf, + pub fixtures: AbsolutePathBuf, } impl Dirs { - pub fn formats(&self) -> PathBuf { + pub fn formats(&self) -> AbsolutePathBuf { self.fixtures.join("formats") } - pub fn root(&self) -> &Path { - self.root.as_path() + pub fn root(&self) -> &AbsolutePath { + &self.root } - pub fn test(&self) -> &Path { - self.test.as_path() + pub fn test(&self) -> &AbsolutePath { + &self.test } } impl<'a> Playground<'a> { - pub fn root(&self) -> &Path { - self.root.path() + pub fn root(&self) -> &AbsolutePath { + &self.dirs.root } - pub fn cwd(&self) -> &Path { + pub fn cwd(&self) -> &AbsolutePath { &self.cwd } pub fn back_to_playground(&mut self) -> &mut Self { - self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); + self.cwd = self.root().join(&self.tests); self } @@ -70,68 +71,46 @@ impl<'a> Playground<'a> { } pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { - let root = tempdir().expect("Couldn't create a tempdir"); - let nuplay_dir = root.path().join(topic); + let temp = tempdir().expect("Could not create a tempdir"); - if PathBuf::from(&nuplay_dir).exists() { - std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); + let root = AbsolutePathBuf::try_from(temp.path()) + .expect("Tempdir is not an absolute path") + .canonicalize() + .expect("Could not canonicalize tempdir"); + + let test = root.join(topic); + if test.exists() { + std::fs::remove_dir_all(&test).expect("Could not remove directory"); } + std::fs::create_dir(&test).expect("Could not create directory"); + let test = test + .canonicalize() + .expect("Could not canonicalize test path"); - std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); - - let fixtures = fs::fixtures(); - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let fixtures = nu_path::canonicalize_with(fixtures.clone(), cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize fixtures path {}: {:?}", - fixtures.display(), - e - ) - }); - - let mut playground = Playground { - root, - tests: topic.to_string(), - cwd: nuplay_dir, - config: None, - environment_vars: Vec::default(), - dirs: &Dirs::default(), - }; - - let playground_root = playground.root.path(); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let test = - nu_path::canonicalize_with(playground_root.join(topic), cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize test path {}: {:?}", - playground_root.join(topic).display(), - e - ) - }); - - let cwd = std::env::current_dir().expect("Could not get current working directory."); - let root = nu_path::canonicalize_with(playground_root, cwd).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize tests root path {}: {:?}", - playground_root.display(), - e - ) - }); + let fixtures = fs::fixtures() + .canonicalize() + .expect("Could not canonicalize fixtures path"); let dirs = Dirs { - root, - test, - fixtures, + root: root.into(), + test: test.as_path().into(), + fixtures: fixtures.into(), }; - playground.dirs = &dirs; + let mut playground = Playground { + _root: temp, + tests: topic.to_string(), + cwd: test.into(), + config: None, + environment_vars: Vec::default(), + dirs: &dirs, + }; block(dirs.clone(), &mut playground); } - pub fn with_config(&mut self, source_file: impl AsRef) -> &mut Self { - self.config = Some(source_file.as_ref().to_path_buf()); + pub fn with_config(&mut self, source_file: AbsolutePathBuf) -> &mut Self { + self.config = Some(source_file); self } @@ -205,7 +184,6 @@ impl<'a> Playground<'a> { files .iter() .map(|f| { - let mut path = PathBuf::from(&self.cwd); let mut permission_set = false; let mut write_able = true; let (file_name, contents) = match *f { @@ -227,7 +205,7 @@ impl<'a> Playground<'a> { } }; - path.push(file_name); + let path = self.cwd.join(file_name); std::fs::write(&path, contents.as_bytes()).expect("can not create file"); if permission_set { @@ -252,7 +230,7 @@ impl<'a> Playground<'a> { self } - pub fn glob_vec(pattern: &str) -> Vec { + pub fn glob_vec(pattern: &str) -> Vec { let glob = glob(pattern); glob.expect("invalid pattern") diff --git a/crates/nu-test-support/src/playground/tests.rs b/crates/nu-test-support/src/playground/tests.rs index cc4daa597b..7c2be0237e 100644 --- a/crates/nu-test-support/src/playground/tests.rs +++ b/crates/nu-test-support/src/playground/tests.rs @@ -1,11 +1,4 @@ use crate::playground::Playground; -use std::path::{Path, PathBuf}; - -fn path(p: &Path) -> PathBuf { - let cwd = std::env::current_dir().expect("Could not get current working directory."); - nu_path::canonicalize_with(p, cwd) - .unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e)) -} #[test] fn current_working_directory_in_sandbox_directory_created() { @@ -13,7 +6,7 @@ fn current_working_directory_in_sandbox_directory_created() { let original_cwd = dirs.test(); nu.within("some_directory_within"); - assert_eq!(path(nu.cwd()), original_cwd.join("some_directory_within")); + assert_eq!(nu.cwd(), original_cwd.join("some_directory_within")); }) } @@ -25,6 +18,6 @@ fn current_working_directory_back_to_root_from_anywhere() { nu.within("some_directory_within"); nu.back_to_playground(); - assert_eq!(path(nu.cwd()), *original_cwd); + assert_eq!(nu.cwd(), original_cwd); }) } diff --git a/tests/plugins/nu_plugin_nu_example.rs b/tests/plugins/nu_plugin_nu_example.rs index f178c64316..afeb487b60 100644 --- a/tests/plugins/nu_plugin_nu_example.rs +++ b/tests/plugins/nu_plugin_nu_example.rs @@ -4,7 +4,7 @@ use assert_cmd::Command; fn call() { // Add the `nu` binaries to the path env let path_env = std::env::join_paths( - std::iter::once(nu_test_support::fs::binaries()).chain( + std::iter::once(nu_test_support::fs::binaries().into()).chain( std::env::var_os(nu_test_support::NATIVE_PATH_ENV_VAR) .as_deref() .map(std::env::split_paths) diff --git a/tests/plugins/registry_file.rs b/tests/plugins/registry_file.rs index c0f0fa0724..2cb1473b6f 100644 --- a/tests/plugins/registry_file.rs +++ b/tests/plugins/registry_file.rs @@ -162,7 +162,7 @@ fn plugin_rm_then_restart_nu() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -238,7 +238,7 @@ fn plugin_rm_from_custom_path() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -286,7 +286,7 @@ fn plugin_rm_using_filename() { contents.upsert_plugin(PluginRegistryItem { name: "foo".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_foo"), + filename: dirs.test().join("nu_plugin_foo").into(), shell: None, data: valid_plugin_item_data(), }); @@ -344,7 +344,7 @@ fn warning_on_invalid_plugin_item() { contents.upsert_plugin(PluginRegistryItem { name: "badtest".into(), // this doesn't exist, but it should be ok - filename: dirs.test().join("nu_plugin_badtest"), + filename: dirs.test().join("nu_plugin_badtest").into(), shell: None, data: PluginRegistryItemData::Invalid, }); From ee875bb8a3106ec445be9a36f44dd7a0e82e6e12 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Fri, 12 Jul 2024 08:23:51 +0000 Subject: [PATCH 43/63] Edit path form doc comments (#13358) # Description Fixes outdated/inaccurate doc comments for `PathForm`s in `nu_path`. --- crates/nu-path/src/form.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/nu-path/src/form.rs b/crates/nu-path/src/form.rs index 4266905a20..edec72c08c 100644 --- a/crates/nu-path/src/form.rs +++ b/crates/nu-path/src/form.rs @@ -75,9 +75,6 @@ impl private::Sealed for Canonical { /// A marker trait for [`PathForm`]s that may be relative paths. /// This includes only the [`Any`] and [`Relative`] path forms. -/// -/// [`push`](crate::PathBuf::push) and [`join`](crate::Path::join) -/// operations only support [`MaybeRelative`] path forms as input. pub trait MaybeRelative: PathForm {} impl MaybeRelative for Any {} impl MaybeRelative for Relative {} @@ -149,13 +146,13 @@ impl PathSet for Any {} impl PathSet for Relative {} impl PathSet for Absolute {} -/// A marker trait for [`PathForm`]s that support pushing [`MaybeRelative`] paths. +/// A marker trait for [`PathForm`]s that support pushing paths. /// /// This includes only [`Any`] and [`Absolute`] path forms. /// Pushing onto a [`Relative`] path could cause it to become [`Absolute`], /// which is why they do not support pushing. /// In the future, a `push_rel` and/or a `try_push` method could be added as an alternative. -/// Similarly, [`Canonical`] paths may become uncanonical if a non-canonical path is pushed onto it. +/// Similarly, [`Canonical`] paths may become uncanonical if a path is pushed onto it. pub trait PathPush: PathSet {} impl PathPush for Any {} impl PathPush for Absolute {} From 0b8d0bcd7aedd4a518eb1789b0537808aaa1c61a Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 01:25:44 -0700 Subject: [PATCH 44/63] Fix order of I/O types in `take until` (#13356) # Description Just a quick one, but `List(Any)` has to come before `Table`, because `List(Any)` is a valid match for `Table`, so it will choose `Table` output even if the input was actually `List(Any)`. I ended up removing `Table` because it's just not needed at all anyway. Though, I'm not really totally sure this is correct - I think the parser should probably actually just have some idea of what the more specific type is, and choose the most specific type match, rather than just doing it in order. I guess this will result in the output just always being `List(Any)` for now. Still better than a bad typecheck error # User-Facing Changes Fixes the following contrived example: ```nushell def foo []: nothing -> list { seq 1 10 | # list each { |n| $n * 20 } | # this causes the type to become list take until { |x| $x < 10 } } # table is first, so now this is type table # ...but table is not compatible with list } ``` # After Submitting - [ ] make typechecker type choice more robust - [ ] release notes --- crates/nu-command/src/filters/take/take_until.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index c7debf5dee..c8544d364a 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -11,13 +11,10 @@ impl Command for TakeUntil { fn signature(&self) -> Signature { Signature::build(self.name()) - .input_output_types(vec![ - (Type::table(), Type::table()), - ( - Type::List(Box::new(Type::Any)), - Type::List(Box::new(Type::Any)), - ), - ]) + .input_output_types(vec![( + Type::List(Box::new(Type::Any)), + Type::List(Box::new(Type::Any)), + )]) .required( "predicate", SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), From 8f981c1eb4a648d82d08bfd97f22e54daf1402f2 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Fri, 12 Jul 2024 11:13:07 +0200 Subject: [PATCH 45/63] Use conventional generic bounds (#13360) https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_bound_locations --- .../nu_plugin_query/src/query_webpage_info.rs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/crates/nu_plugin_query/src/query_webpage_info.rs b/crates/nu_plugin_query/src/query_webpage_info.rs index e61387c8d1..f38f7241f5 100644 --- a/crates/nu_plugin_query/src/query_webpage_info.rs +++ b/crates/nu_plugin_query/src/query_webpage_info.rs @@ -158,9 +158,9 @@ impl<'a> serde::Serializer for &'a ValueSerializer { Ok(Value::nothing(self.span)) } - fn serialize_some(self, value: &T) -> Result + fn serialize_some(self, value: &T) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } @@ -185,18 +185,18 @@ impl<'a> serde::Serializer for &'a ValueSerializer { Ok(Value::nothing(self.span)) } - fn serialize_newtype_struct( + fn serialize_newtype_struct( self, _name: &'static str, value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } - fn serialize_newtype_variant( + fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, @@ -204,7 +204,7 @@ impl<'a> serde::Serializer for &'a ValueSerializer { value: &T, ) -> Result where - T: Serialize, + T: Serialize + ?Sized, { value.serialize(self) } @@ -306,13 +306,9 @@ impl<'a> serde::ser::SerializeStruct for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.record .insert(key.to_owned(), value.serialize(self.serializer)?); @@ -328,9 +324,9 @@ impl<'a> serde::ser::SerializeMap for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { let value = serde_json::to_value(key).map_err(Error::new)?; let key = value @@ -341,9 +337,9 @@ impl<'a> serde::ser::SerializeMap for MapSerializer<'a> { Ok(()) } - fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { let key = self.current_key.take().ok_or(Error::new("key expected"))?; self.record.insert(key, value.serialize(self.serializer)?); @@ -359,13 +355,9 @@ impl<'a> serde::ser::SerializeStructVariant for MapSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.record .insert(key.to_owned(), value.serialize(self.serializer)?); @@ -397,9 +389,9 @@ impl<'a> serde::ser::SerializeSeq for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -414,9 +406,9 @@ impl<'a> serde::ser::SerializeTuple for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -431,9 +423,9 @@ impl<'a> serde::ser::SerializeTupleStruct for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) @@ -448,9 +440,9 @@ impl<'a> serde::ser::SerializeTupleVariant for SeqSerializer<'a> { type Ok = Value; type Error = Error; - fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> where - T: Serialize, + T: Serialize + ?Sized, { self.seq.push(value.serialize(self.serializer)?); Ok(()) From 02659b1c8a3b0703bfdce4ad94fa04b9638cba41 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 02:45:53 -0700 Subject: [PATCH 46/63] Mention the actual output type on an OutputMismatch error (#13355) # Description This improves the error when the determined output of a custom command doesn't match the specified output type by adding the actual determined output type. # User-Facing Changes Previous: `command doesn't output {0}` New: `expected {0}, but command outputs {1}` # Tests + Formatting Passing. # After Submitting - [ ] release notes? (minor change, but helpful) --- crates/nu-parser/src/type_check.rs | 6 +++++- crates/nu-protocol/src/errors/parse_error.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 6999a0469b..6cfe8f64b3 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1055,7 +1055,11 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> .span }; - output_errors.push(ParseError::OutputMismatch(output_type.clone(), span)) + output_errors.push(ParseError::OutputMismatch( + output_type.clone(), + current_output_type.clone(), + span, + )) } } diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index 4d59821f7d..0e20f0dcba 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -61,7 +61,11 @@ pub enum ParseError { #[error("Command output doesn't match {0}.")] #[diagnostic(code(nu::parser::output_type_mismatch))] - OutputMismatch(Type, #[label("command doesn't output {0}")] Span), + OutputMismatch( + Type, + Type, + #[label("expected {0}, but command outputs {1}")] Span, + ), #[error("Type mismatch during operation.")] #[diagnostic(code(nu::parser::type_mismatch))] @@ -554,7 +558,7 @@ impl ParseError { ParseError::TypeMismatch(_, _, s) => *s, ParseError::TypeMismatchHelp(_, _, s, _) => *s, ParseError::InputMismatch(_, s) => *s, - ParseError::OutputMismatch(_, s) => *s, + ParseError::OutputMismatch(_, _, s) => *s, ParseError::MissingRequiredFlag(_, s) => *s, ParseError::IncompleteMathExpression(s) => *s, ParseError::UnknownState(_, s) => *s, From 46b5e510ac32fbb746c3b8259b2e0ac4c27cb961 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:48:27 -0500 Subject: [PATCH 47/63] tweak `parse` usage and examples to be more clear (#13363) # Description This PR just tweaks the `parse` command's usage and examples to make it clearer what's going on "under the hood". # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/strings/parse.rs | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 9bf31de73c..86b27aecbc 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -12,13 +12,17 @@ impl Command for Parse { } fn usage(&self) -> &str { - "Parse columns from string data using a simple pattern." + "Parse columns from string data using a simple pattern or a supplied regular expression." } fn search_terms(&self) -> Vec<&str> { vec!["pattern", "match", "regex", "str extract"] } + fn extra_usage(&self) -> &str { + "The parse command always uses regular expressions even when you use a simple pattern. If a simple pattern is supplied, parse will transform that pattern into a regular expression." + } + fn signature(&self) -> nu_protocol::Signature { Signature::build("parse") .required("pattern", SyntaxShape::String, "The pattern to match.") @@ -32,21 +36,24 @@ impl Command for Parse { } fn examples(&self) -> Vec { - let result = Value::test_list(vec![Value::test_record(record! { - "foo" => Value::test_string("hi"), - "bar" => Value::test_string("there"), - })]); - vec![ Example { description: "Parse a string into two named columns", example: "\"hi there\" | parse \"{foo} {bar}\"", - result: Some(result.clone()), + result: Some(Value::test_list( + vec![Value::test_record(record! { + "foo" => Value::test_string("hi"), + "bar" => Value::test_string("there"), + })])), }, Example { - description: "Parse a string using regex pattern", - example: "\"hi there\" | parse --regex '(?P\\w+) (?P\\w+)'", - result: Some(result), + description: "This is how the first example is interpreted in the source code", + example: "\"hi there\" | parse --regex '(?s)\\A(?P.*?) (?P.*?)\\z'", + result: Some(Value::test_list( + vec![Value::test_record(record! { + "foo" => Value::test_string("hi"), + "bar" => Value::test_string("there"), + })])), }, Example { description: "Parse a string using fancy-regex named capture group pattern", From d42cf554313955095051e5e110ac388c2b3f01c9 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 12 Jul 2024 19:27:23 -0700 Subject: [PATCH 48/63] fix file_count in `Debug` implementation of `IrBlock` (#13367) # Description Oops. --- crates/nu-protocol/src/ir/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index 28677b743c..a1b2439768 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -37,7 +37,7 @@ impl fmt::Debug for IrBlock { .field("data", &self.data) .field("comments", &self.comments) .field("register_count", &self.register_count) - .field("file_count", &self.register_count) + .field("file_count", &self.file_count) .finish_non_exhaustive() } } From a2758e6c40fee2039215be72a776602ac0ae69a1 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sat, 13 Jul 2024 01:58:21 -0700 Subject: [PATCH 49/63] Add IR support to the debugger (#13345) # Description This adds tracing for each individual instruction to the `Debugger` trait. Register contents can be inspected both when entering and leaving an instruction, and if an instruction produced an error, a reference to the error is also available. It's not the full `EvalContext` but it's most of the important parts for getting an idea of what's going on. Added support for all of this to the `Profiler` / `debug profile` as well, and the output is quite incredible - super verbose, but you can see every instruction that's executed and also what the result was if it's an instruction that has a clearly defined output (many do). # User-Facing Changes - Added `--instructions` to `debug profile`, which adds the `pc` and `instruction` columns to the output. - `--expr` only works in AST mode, and `--instructions` only works in IR mode. In the wrong mode, the output for those columns is just blank. # Tests + Formatting All passing. # After Submitting - [ ] release notes --- crates/nu-command/src/debug/profile.rs | 24 ++- crates/nu-engine/src/eval_ir.rs | 17 +- .../src/debugger/debugger_trait.rs | 87 ++++++++- crates/nu-protocol/src/debugger/profiler.rs | 178 ++++++++++++++---- crates/nu-protocol/src/ir/mod.rs | 57 ++++++ 5 files changed, 313 insertions(+), 50 deletions(-) diff --git a/crates/nu-command/src/debug/profile.rs b/crates/nu-command/src/debug/profile.rs index 0cef979094..e8254563df 100644 --- a/crates/nu-command/src/debug/profile.rs +++ b/crates/nu-command/src/debug/profile.rs @@ -1,5 +1,8 @@ use nu_engine::{command_prelude::*, ClosureEvalOnce}; -use nu_protocol::{debugger::Profiler, engine::Closure}; +use nu_protocol::{ + debugger::{Profiler, ProfilerOptions}, + engine::Closure, +}; #[derive(Clone)] pub struct DebugProfile; @@ -28,6 +31,7 @@ impl Command for DebugProfile { Some('v'), ) .switch("expr", "Collect expression types", Some('x')) + .switch("instructions", "Collect IR instructions", Some('i')) .switch("lines", "Collect line numbers", Some('l')) .named( "max-depth", @@ -91,19 +95,23 @@ confusing the id/parent_id hierarchy. The --expr flag is helpful for investigati let collect_expanded_source = call.has_flag(engine_state, stack, "expanded-source")?; let collect_values = call.has_flag(engine_state, stack, "values")?; let collect_exprs = call.has_flag(engine_state, stack, "expr")?; + let collect_instructions = call.has_flag(engine_state, stack, "instructions")?; let collect_lines = call.has_flag(engine_state, stack, "lines")?; let max_depth = call .get_flag(engine_state, stack, "max-depth")? .unwrap_or(2); let profiler = Profiler::new( - max_depth, - collect_spans, - true, - collect_expanded_source, - collect_values, - collect_exprs, - collect_lines, + ProfilerOptions { + max_depth, + collect_spans, + collect_source: true, + collect_expanded_source, + collect_values, + collect_exprs, + collect_instructions, + collect_lines, + }, call.span(), ); diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 56bbccee25..29a677bd69 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -184,11 +184,20 @@ fn eval_ir_block_impl( let instruction = &ir_block.instructions[pc]; let span = &ir_block.spans[pc]; let ast = &ir_block.ast[pc]; - log::trace!( - "{pc:-4}: {}", - instruction.display(ctx.engine_state, ctx.data) + + D::enter_instruction(ctx.engine_state, ir_block, pc, ctx.registers); + + let result = eval_instruction::(ctx, instruction, span, ast); + + D::leave_instruction( + ctx.engine_state, + ir_block, + pc, + ctx.registers, + result.as_ref().err(), ); - match eval_instruction::(ctx, instruction, span, ast) { + + match result { Ok(InstructionResult::Continue) => { pc += 1; } diff --git a/crates/nu-protocol/src/debugger/debugger_trait.rs b/crates/nu-protocol/src/debugger/debugger_trait.rs index 69d395fb98..88cc753548 100644 --- a/crates/nu-protocol/src/debugger/debugger_trait.rs +++ b/crates/nu-protocol/src/debugger/debugger_trait.rs @@ -16,6 +16,7 @@ use crate::{ ast::{Block, PipelineElement}, engine::EngineState, + ir::IrBlock, PipelineData, ShellError, Span, Value, }; use std::{fmt::Debug, ops::DerefMut}; @@ -35,11 +36,11 @@ pub trait DebugContext: Clone + Copy + Debug { #[allow(unused_variables)] fn leave_block(engine_state: &EngineState, block: &Block) {} - /// Called when the evaluator enters a pipeline element + /// Called when the AST evaluator enters a pipeline element #[allow(unused_variables)] fn enter_element(engine_state: &EngineState, element: &PipelineElement) {} - /// Called when the evaluator leaves a pipeline element + /// Called when the AST evaluator leaves a pipeline element #[allow(unused_variables)] fn leave_element( engine_state: &EngineState, @@ -47,6 +48,27 @@ pub trait DebugContext: Clone + Copy + Debug { result: &Result, ) { } + + /// Called before the IR evaluator runs an instruction + #[allow(unused_variables)] + fn enter_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + } + + /// Called after the IR evaluator runs an instruction + #[allow(unused_variables)] + fn leave_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + } } /// Marker struct signalizing that evaluation should use a Debugger @@ -85,6 +107,40 @@ impl DebugContext for WithDebug { .leave_element(engine_state, element, result); } } + + fn enter_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + if let Ok(mut debugger) = engine_state.debugger.lock() { + debugger.deref_mut().enter_instruction( + engine_state, + ir_block, + instruction_index, + registers, + ) + } + } + + fn leave_instruction( + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + if let Ok(mut debugger) = engine_state.debugger.lock() { + debugger.deref_mut().leave_instruction( + engine_state, + ir_block, + instruction_index, + registers, + error, + ) + } + } } /// Marker struct signalizing that evaluation should NOT use a Debugger @@ -118,11 +174,11 @@ pub trait Debugger: Send + Debug { #[allow(unused_variables)] fn leave_block(&mut self, engine_state: &EngineState, block: &Block) {} - /// Called when the evaluator enters a pipeline element + /// Called when the AST evaluator enters a pipeline element #[allow(unused_variables)] fn enter_element(&mut self, engine_state: &EngineState, pipeline_element: &PipelineElement) {} - /// Called when the evaluator leaves a pipeline element + /// Called when the AST evaluator leaves a pipeline element #[allow(unused_variables)] fn leave_element( &mut self, @@ -132,6 +188,29 @@ pub trait Debugger: Send + Debug { ) { } + /// Called before the IR evaluator runs an instruction + #[allow(unused_variables)] + fn enter_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + ) { + } + + /// Called after the IR evaluator runs an instruction + #[allow(unused_variables)] + fn leave_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + } + /// Create a final report as a Value /// /// Intended to be called after deactivate() diff --git a/crates/nu-protocol/src/debugger/profiler.rs b/crates/nu-protocol/src/debugger/profiler.rs index c3066c0a14..c61ee04d12 100644 --- a/crates/nu-protocol/src/debugger/profiler.rs +++ b/crates/nu-protocol/src/debugger/profiler.rs @@ -7,10 +7,11 @@ use crate::{ ast::{Block, Expr, PipelineElement}, debugger::Debugger, engine::EngineState, + ir::IrBlock, record, PipelineData, ShellError, Span, Value, }; -use std::io::BufRead; use std::time::Instant; +use std::{borrow::Borrow, io::BufRead}; #[derive(Debug, Clone, Copy)] struct ElementId(usize); @@ -24,6 +25,7 @@ struct ElementInfo { element_span: Span, element_output: Option, expr: Option, + instruction: Option<(usize, String)>, children: Vec, } @@ -36,57 +38,53 @@ impl ElementInfo { element_span, element_output: None, expr: None, + instruction: None, children: vec![], } } } +/// Options for [`Profiler`] +#[derive(Debug, Clone)] +pub struct ProfilerOptions { + pub max_depth: i64, + pub collect_spans: bool, + pub collect_source: bool, + pub collect_expanded_source: bool, + pub collect_values: bool, + pub collect_exprs: bool, + pub collect_instructions: bool, + pub collect_lines: bool, +} + /// Basic profiler, used in `debug profile` #[derive(Debug, Clone)] pub struct Profiler { depth: i64, - max_depth: i64, - collect_spans: bool, - collect_source: bool, - collect_expanded_source: bool, - collect_values: bool, - collect_exprs: bool, - collect_lines: bool, + opts: ProfilerOptions, elements: Vec, element_stack: Vec, } impl Profiler { #[allow(clippy::too_many_arguments)] - pub fn new( - max_depth: i64, - collect_spans: bool, - collect_source: bool, - collect_expanded_source: bool, - collect_values: bool, - collect_exprs: bool, - collect_lines: bool, - span: Span, - ) -> Self { + pub fn new(opts: ProfilerOptions, span: Span) -> Self { let first = ElementInfo { start: Instant::now(), duration_sec: 0.0, depth: 0, element_span: span, - element_output: collect_values.then(|| Value::nothing(span)), - expr: collect_exprs.then(|| "call".to_string()), + element_output: opts.collect_values.then(|| Value::nothing(span)), + expr: opts.collect_exprs.then(|| "call".to_string()), + instruction: opts + .collect_instructions + .then(|| (0, "".to_string())), children: vec![], }; Profiler { depth: 0, - max_depth, - collect_spans, - collect_source, - collect_expanded_source, - collect_values, - collect_exprs, - collect_lines, + opts, elements: vec![first], element_stack: vec![ElementId(0)], } @@ -130,7 +128,7 @@ impl Debugger for Profiler { } fn enter_element(&mut self, engine_state: &EngineState, element: &PipelineElement) { - if self.depth > self.max_depth { + if self.depth > self.opts.max_depth { return; } @@ -140,6 +138,7 @@ impl Debugger for Profiler { }; let expr_opt = self + .opts .collect_exprs .then(|| expr_to_string(engine_state, &element.expr.expr)); @@ -165,13 +164,13 @@ impl Debugger for Profiler { element: &PipelineElement, result: &Result, ) { - if self.depth > self.max_depth { + if self.depth > self.opts.max_depth { return; } let element_span = element.expr.span; - let out_opt = self.collect_values.then(|| match result { + let out_opt = self.opts.collect_values.then(|| match result { Ok(pipeline_data) => match pipeline_data { PipelineData::Value(val, ..) => val.clone(), PipelineData::ListStream(..) => Value::string("list stream", element_span), @@ -192,6 +191,91 @@ impl Debugger for Profiler { self.element_stack.pop(); } + fn enter_instruction( + &mut self, + engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + _registers: &[PipelineData], + ) { + if self.depth > self.opts.max_depth { + return; + } + + let Some(parent_id) = self.last_element_id() else { + eprintln!("Profiler Error: Missing parent element ID."); + return; + }; + + let instruction = &ir_block.instructions[instruction_index]; + let span = ir_block.spans[instruction_index]; + + let instruction_opt = self.opts.collect_instructions.then(|| { + ( + instruction_index, + instruction + .display(engine_state, &ir_block.data) + .to_string(), + ) + }); + + let new_id = ElementId(self.elements.len()); + + let mut new_element = ElementInfo::new(self.depth, span); + new_element.instruction = instruction_opt; + + self.elements.push(new_element); + + let Some(parent) = self.elements.get_mut(parent_id.0) else { + eprintln!("Profiler Error: Missing parent element."); + return; + }; + + parent.children.push(new_id); + self.element_stack.push(new_id); + } + + fn leave_instruction( + &mut self, + _engine_state: &EngineState, + ir_block: &IrBlock, + instruction_index: usize, + registers: &[PipelineData], + error: Option<&ShellError>, + ) { + if self.depth > self.opts.max_depth { + return; + } + + let instruction = &ir_block.instructions[instruction_index]; + let span = ir_block.spans[instruction_index]; + + let out_opt = self + .opts + .collect_values + .then(|| { + error + .map(Err) + .or_else(|| { + instruction + .output_register() + .map(|register| Ok(®isters[register.0 as usize])) + }) + .map(|result| format_result(&result, span)) + }) + .flatten(); + + let Some(last_element) = self.last_element_mut() else { + eprintln!("Profiler Error: Missing last element."); + return; + }; + + last_element.duration_sec = last_element.start.elapsed().as_secs_f64(); + last_element.element_output = out_opt; + + self.element_stack.pop(); + } + fn report(&self, engine_state: &EngineState, profiler_span: Span) -> Result { Ok(Value::list( collect_data( @@ -268,6 +352,21 @@ fn expr_to_string(engine_state: &EngineState, expr: &Expr) -> String { } } +fn format_result( + result: &Result, impl Borrow>, + element_span: Span, +) -> Value { + match result { + Ok(pipeline_data) => match pipeline_data.borrow() { + PipelineData::Value(val, ..) => val.clone(), + PipelineData::ListStream(..) => Value::string("list stream", element_span), + PipelineData::ByteStream(..) => Value::string("byte stream", element_span), + _ => Value::nothing(element_span), + }, + Err(e) => Value::error(e.borrow().clone(), element_span), + } +} + // Find a file name and a line number (indexed from 1) of a span fn find_file_of_span(engine_state: &EngineState, span: Span) -> Option<(&str, usize)> { for file in engine_state.files() { @@ -308,7 +407,7 @@ fn collect_data( "parent_id" => Value::int(parent_id.0 as i64, profiler_span), }; - if profiler.collect_lines { + if profiler.opts.collect_lines { if let Some((fname, line_num)) = find_file_of_span(engine_state, element.element_span) { row.push("file", Value::string(fname, profiler_span)); row.push("line", Value::int(line_num as i64, profiler_span)); @@ -318,7 +417,7 @@ fn collect_data( } } - if profiler.collect_spans { + if profiler.opts.collect_spans { let span_start = i64::try_from(element.element_span.start) .map_err(|_| profiler_error("error converting span start to i64", profiler_span))?; let span_end = i64::try_from(element.element_span.end) @@ -336,12 +435,12 @@ fn collect_data( ); } - if profiler.collect_source { + if profiler.opts.collect_source { let val = String::from_utf8_lossy(engine_state.get_span_contents(element.element_span)); let val = val.trim(); let nlines = val.lines().count(); - let fragment = if profiler.collect_expanded_source { + let fragment = if profiler.opts.collect_expanded_source { val.to_string() } else { let mut first_line = val.lines().next().unwrap_or("").to_string(); @@ -360,6 +459,17 @@ fn collect_data( row.push("expr", Value::string(expr_string.clone(), profiler_span)); } + if let Some((instruction_index, instruction)) = &element.instruction { + row.push( + "pc", + (*instruction_index) + .try_into() + .map(|index| Value::int(index, profiler_span)) + .unwrap_or(Value::nothing(profiler_span)), + ); + row.push("instruction", Value::string(instruction, profiler_span)); + } + if let Some(val) = &element.element_output { row.push("output", val.clone()); } diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index a1b2439768..aff5f42c8c 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -276,6 +276,63 @@ impl Instruction { } } + /// Get the output register, for instructions that produce some kind of immediate result. + pub fn output_register(&self) -> Option { + match *self { + Instruction::Unreachable => None, + Instruction::LoadLiteral { dst, .. } => Some(dst), + Instruction::LoadValue { dst, .. } => Some(dst), + Instruction::Move { dst, .. } => Some(dst), + Instruction::Clone { dst, .. } => Some(dst), + Instruction::Collect { src_dst } => Some(src_dst), + Instruction::Span { src_dst } => Some(src_dst), + Instruction::Drop { .. } => None, + Instruction::Drain { .. } => None, + Instruction::LoadVariable { dst, .. } => Some(dst), + Instruction::StoreVariable { .. } => None, + Instruction::LoadEnv { dst, .. } => Some(dst), + Instruction::LoadEnvOpt { dst, .. } => Some(dst), + Instruction::StoreEnv { .. } => None, + Instruction::PushPositional { .. } => None, + Instruction::AppendRest { .. } => None, + Instruction::PushFlag { .. } => None, + Instruction::PushShortFlag { .. } => None, + Instruction::PushNamed { .. } => None, + Instruction::PushShortNamed { .. } => None, + Instruction::PushParserInfo { .. } => None, + Instruction::RedirectOut { .. } => None, + Instruction::RedirectErr { .. } => None, + Instruction::CheckErrRedirected { .. } => None, + Instruction::OpenFile { .. } => None, + Instruction::WriteFile { .. } => None, + Instruction::CloseFile { .. } => None, + Instruction::Call { src_dst, .. } => Some(src_dst), + Instruction::StringAppend { src_dst, .. } => Some(src_dst), + Instruction::GlobFrom { src_dst, .. } => Some(src_dst), + Instruction::ListPush { src_dst, .. } => Some(src_dst), + Instruction::ListSpread { src_dst, .. } => Some(src_dst), + Instruction::RecordInsert { src_dst, .. } => Some(src_dst), + Instruction::RecordSpread { src_dst, .. } => Some(src_dst), + Instruction::Not { src_dst } => Some(src_dst), + Instruction::BinaryOp { lhs_dst, .. } => Some(lhs_dst), + Instruction::FollowCellPath { src_dst, .. } => Some(src_dst), + Instruction::CloneCellPath { dst, .. } => Some(dst), + Instruction::UpsertCellPath { src_dst, .. } => Some(src_dst), + Instruction::Jump { .. } => None, + Instruction::BranchIf { .. } => None, + Instruction::BranchIfEmpty { .. } => None, + Instruction::Match { .. } => None, + Instruction::CheckMatchGuard { .. } => None, + Instruction::Iterate { dst, .. } => Some(dst), + Instruction::OnError { .. } => None, + Instruction::OnErrorInto { .. } => None, + Instruction::PopErrorHandler => None, + Instruction::CheckExternalFailed { dst, .. } => Some(dst), + Instruction::ReturnEarly { .. } => None, + Instruction::Return { .. } => None, + } + } + /// Returns the branch target index of the instruction if this is a branching instruction. pub fn branch_target(&self) -> Option { match self { From b0bf54614f7f7fe3ef7637243eda49618d30f7e3 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:52:39 -0400 Subject: [PATCH 50/63] Don't add touch command to default context twice (#13371) # Description Touch was added to the shell command context twice, once with the other filesystem commands and once with the format commands. I removed that second occurrence of touch, because I'm assuming it was only added there because "Touch" starts with "To." # User-Facing Changes None --- crates/nu-command/src/default_context.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index b270a78dce..4f35a1c711 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -290,7 +290,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ToText, ToToml, ToTsv, - Touch, Upsert, Where, ToXml, From f5bff8c9c80802d3b45b88a012e30a41bfbf7f27 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sat, 13 Jul 2024 16:54:34 +0200 Subject: [PATCH 51/63] Fix `select` cell path renaming behavior (#13361) # Description Fixes #13359 In an attempt to generate names for flat columns resulting from a nested accesses #3016 generated new column names on nested selection, out of convenience, that composed the cell path as a string (including `.`) and then simply replaced all `.` with `_`. As we permit `.` in column names as long as you quote this surprisingly alters `select`ed columns. # User-Facing Changes New columns generated by selection with nested cell paths will for now be named with a string containing the keys separated by `.` instead of `_`. We may want to reconsider the semantics for nested access. # Tests + Formatting - Alter test to breaking change on nested `select` --- crates/nu-command/src/filters/select.rs | 6 +++--- crates/nu-command/tests/commands/select.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 2ca04b3999..2fe4da3c36 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -240,7 +240,7 @@ fn select( //FIXME: improve implementation to not clone match input_val.clone().follow_cell_path(&path.members, false) { Ok(fetcher) => { - record.push(path.to_string().replace('.', "_"), fetcher); + record.push(path.to_string(), fetcher); if !columns_with_value.contains(&path) { columns_with_value.push(path); } @@ -271,7 +271,7 @@ fn select( // FIXME: remove clone match v.clone().follow_cell_path(&cell_path.members, false) { Ok(result) => { - record.push(cell_path.to_string().replace('.', "_"), result); + record.push(cell_path.to_string(), result); } Err(e) => return Err(e), } @@ -295,7 +295,7 @@ fn select( //FIXME: improve implementation to not clone match x.clone().follow_cell_path(&path.members, false) { Ok(value) => { - record.push(path.to_string().replace('.', "_"), value); + record.push(path.to_string(), value); } Err(e) => return Err(e), } diff --git a/crates/nu-command/tests/commands/select.rs b/crates/nu-command/tests/commands/select.rs index 741386998a..004aa16d6b 100644 --- a/crates/nu-command/tests/commands/select.rs +++ b/crates/nu-command/tests/commands/select.rs @@ -50,7 +50,7 @@ fn complex_nested_columns() { r#" {sample} | select nu."0xATYKARNU" nu.committers.name nu.releases.version - | get nu_releases_version + | get "nu.releases.version" | where $it > "0.8" | get 0 "# From c5aa15c7f6b3e44b6c211d5e83617ced33424aec Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sun, 14 Jul 2024 10:10:41 +0200 Subject: [PATCH 52/63] Add top-level crate documentation/READMEs (#12907) # Description Add `README.md` files to each crate in our workspace (-plugins) and also include it in the `lib.rs` documentation for (if there is no existing `lib.rs` crate documentation) In all new README I added the defensive comment that the crates are not considered stable for public consumption. If necessary we can adjust this if we deem a crate useful for plugin authors. --- crates/nu-cli/README.md | 7 +++++++ crates/nu-cli/src/lib.rs | 1 + crates/nu-cmd-base/README.md | 5 +++++ crates/nu-cmd-base/src/lib.rs | 1 + crates/nu-cmd-extra/src/lib.rs | 1 + crates/nu-cmd-lang/src/lib.rs | 1 + crates/nu-color-config/README.md | 5 +++++ crates/nu-color-config/src/lib.rs | 1 + crates/nu-command/README.md | 7 +++++++ crates/nu-command/src/lib.rs | 1 + crates/nu-engine/README.md | 9 +++++++++ crates/nu-engine/src/lib.rs | 1 + crates/nu-explore/README.md | 5 +++++ crates/nu-explore/src/lib.rs | 1 + crates/nu-json/README.md | 4 ++-- crates/nu-json/src/lib.rs | 1 + crates/nu-lsp/README.md | 7 +++++++ crates/nu-lsp/src/lib.rs | 1 + crates/nu-parser/README.md | 14 +++++++------- crates/nu-parser/src/lib.rs | 1 + crates/nu-path/src/lib.rs | 1 + crates/nu-protocol/src/lib.rs | 1 + crates/nu-protocol/src/pipeline/byte_stream.rs | 4 ++-- crates/nu-std/README.md | 2 +- crates/nu-std/src/lib.rs | 1 + crates/nu-system/README.md | 7 +++++++ crates/nu-system/src/lib.rs | 1 + crates/nu-table/README.md | 7 +++++++ crates/nu-table/src/lib.rs | 1 + crates/nu-term-grid/README.md | 5 +++++ crates/nu-term-grid/src/lib.rs | 1 + crates/nu-test-support/README.md | 7 +++++++ crates/nu-test-support/src/lib.rs | 1 + crates/nu-utils/README.md | 6 ++++++ crates/nu-utils/src/lib.rs | 1 + crates/nuon/README.md | 2 +- 36 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 crates/nu-cli/README.md create mode 100644 crates/nu-cmd-base/README.md create mode 100644 crates/nu-color-config/README.md create mode 100644 crates/nu-command/README.md create mode 100644 crates/nu-engine/README.md create mode 100644 crates/nu-explore/README.md create mode 100644 crates/nu-lsp/README.md create mode 100644 crates/nu-system/README.md create mode 100644 crates/nu-table/README.md create mode 100644 crates/nu-term-grid/README.md create mode 100644 crates/nu-test-support/README.md create mode 100644 crates/nu-utils/README.md diff --git a/crates/nu-cli/README.md b/crates/nu-cli/README.md new file mode 100644 index 0000000000..e0eca3c9bd --- /dev/null +++ b/crates/nu-cli/README.md @@ -0,0 +1,7 @@ +This crate implements the core functionality of the interactive Nushell REPL and interfaces with `reedline`. +Currently implements the syntax highlighting and completions logic. +Furthermore includes a few commands that are specific to `reedline` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 6f151adad1..d584802cfb 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod commands; mod completions; mod config_files; diff --git a/crates/nu-cmd-base/README.md b/crates/nu-cmd-base/README.md new file mode 100644 index 0000000000..28d299928a --- /dev/null +++ b/crates/nu-cmd-base/README.md @@ -0,0 +1,5 @@ +Utilities used by the different `nu-command`/`nu-cmd-*` crates, should not contain any full `Command` implementations. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-cmd-base/src/lib.rs b/crates/nu-cmd-base/src/lib.rs index 250a57d741..c2608f8466 100644 --- a/crates/nu-cmd-base/src/lib.rs +++ b/crates/nu-cmd-base/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod formats; pub mod hook; pub mod input_handler; diff --git a/crates/nu-cmd-extra/src/lib.rs b/crates/nu-cmd-extra/src/lib.rs index 2a3d70e6cd..f6f97c2f96 100644 --- a/crates/nu-cmd-extra/src/lib.rs +++ b/crates/nu-cmd-extra/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod example_test; pub mod extra; pub use extra::*; diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs index 427a535d5d..41022cc231 100644 --- a/crates/nu-cmd-lang/src/lib.rs +++ b/crates/nu-cmd-lang/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod core_commands; mod default_context; pub mod example_support; diff --git a/crates/nu-color-config/README.md b/crates/nu-color-config/README.md new file mode 100644 index 0000000000..f03753685c --- /dev/null +++ b/crates/nu-color-config/README.md @@ -0,0 +1,5 @@ +Logic to resolve colors for syntax highlighting and output formatting + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-color-config/src/lib.rs b/crates/nu-color-config/src/lib.rs index 3bd4dc9e4d..77ec9d0e79 100644 --- a/crates/nu-color-config/src/lib.rs +++ b/crates/nu-color-config/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod color_config; mod matching_brackets_style; mod nu_style; diff --git a/crates/nu-command/README.md b/crates/nu-command/README.md new file mode 100644 index 0000000000..1cbb8cd9f2 --- /dev/null +++ b/crates/nu-command/README.md @@ -0,0 +1,7 @@ +This crate contains the majority of our commands + +We allow ourselves to move some of the commands in `nu-command` to `nu-cmd-*` crates as needed. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index a854fe3507..55c4974325 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod bytes; mod charting; mod conversions; diff --git a/crates/nu-engine/README.md b/crates/nu-engine/README.md new file mode 100644 index 0000000000..d5198cfc3f --- /dev/null +++ b/crates/nu-engine/README.md @@ -0,0 +1,9 @@ +This crate primarily drives the evaluation of expressions. + +(Some overlap with nu-protocol) + +- Provides `CallExt` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 7ed246e975..5a5bb9a5d0 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod call_ext; mod closure_eval; pub mod column; diff --git a/crates/nu-explore/README.md b/crates/nu-explore/README.md new file mode 100644 index 0000000000..d18a576c43 --- /dev/null +++ b/crates/nu-explore/README.md @@ -0,0 +1,5 @@ +Implementation of the interactive `explore` command pager. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index 043c85eb02..d645572c89 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod commands; mod default_context; mod explore; diff --git a/crates/nu-json/README.md b/crates/nu-json/README.md index acf3c6b296..c982f1a818 100644 --- a/crates/nu-json/README.md +++ b/crates/nu-json/README.md @@ -24,7 +24,7 @@ nu-json = "0.76" ## From the Commandline Add with: -``` +```sh cargo add serde cargo add nu-json ``` @@ -43,7 +43,7 @@ fn main() { let sample_text=r#" { - # specify rate in requests/second + ## specify rate in requests/second rate: 1000 array: [ diff --git a/crates/nu-json/src/lib.rs b/crates/nu-json/src/lib.rs index eead282786..1cf2c0a9fd 100644 --- a/crates/nu-json/src/lib.rs +++ b/crates/nu-json/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub use self::de::{ from_iter, from_reader, from_slice, from_str, Deserializer, StreamDeserializer, }; diff --git a/crates/nu-lsp/README.md b/crates/nu-lsp/README.md new file mode 100644 index 0000000000..d9e666d6c2 --- /dev/null +++ b/crates/nu-lsp/README.md @@ -0,0 +1,7 @@ +Implementation of the Nushell language server. + +See [the Language Server Protocol specification](https://microsoft.github.io/language-server-protocol/) + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index fdeededac5..aee2ab240d 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use lsp_server::{Connection, IoThreads, Message, Response, ResponseError}; use lsp_types::{ request::{Completion, GotoDefinition, HoverRequest, Request}, diff --git a/crates/nu-parser/README.md b/crates/nu-parser/README.md index 02f61a31a6..cbb7729e4d 100644 --- a/crates/nu-parser/README.md +++ b/crates/nu-parser/README.md @@ -4,7 +4,7 @@ Nushell's parser is a type-directed parser, meaning that the parser will use typ Nushell's base language is whitespace-separated tokens with the command (Nushell's term for a function) name in the head position: -``` +```nushell head1 arg1 arg2 | head2 ``` @@ -12,7 +12,7 @@ head1 arg1 arg2 | head2 The first job of the parser is to a lexical analysis to find where the tokens start and end in the input. This turns the above into: -``` +```text , , , , ``` @@ -24,7 +24,7 @@ As Nushell is a language of pipelines, pipes form a key role in both separating The above tokens are converted the following during the lite parse phase: -``` +```text Pipeline: Command #1: , , @@ -45,7 +45,7 @@ Each command has a shape assigned to each of the arguments it reads in. These sh For example, if the command is written as: -```sql +```text where $x > 10 ``` @@ -53,7 +53,7 @@ When the parsing happens, the parser will look up the `where` command and find i In the above example, if the Signature of `where` said that it took three String values, the result would be: -``` +```text CallInfo: Name: `where` Args: @@ -64,7 +64,7 @@ CallInfo: Or, the Signature could state that it takes in three positional arguments: a Variable, an Operator, and a Number, which would give: -``` +```text CallInfo: Name: `where` Args: @@ -77,7 +77,7 @@ Note that in this case, each would be checked at compile time to confirm that th Finally, some Shapes can consume more than one token. In the above, if the `where` command stated it took in a single required argument, and that the Shape of this argument was a MathExpression, then the parser would treat the remaining tokens as part of the math expression. -``` +```text CallInfo: Name: `where` Args: diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 3c97012e8a..8f871aa815 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod deparse; mod exportable; mod flatten; diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index 8553495439..2cb415264a 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod assert_path_eq; mod components; pub mod dots; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index cae5d3fd0a..e143a19819 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod alias; pub mod ast; pub mod config; diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index 69391b39a9..bd384e88c2 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -159,14 +159,14 @@ impl From for Type { /// Try not to use this method if possible. Rather, please use [`reader`](ByteStream::reader) /// (or [`lines`](ByteStream::lines) if it matches the situation). /// -/// Additionally, there are few methods to collect a [`Bytestream`] into memory: +/// Additionally, there are few methods to collect a [`ByteStream`] into memory: /// - [`into_bytes`](ByteStream::into_bytes): collects all bytes into a [`Vec`]. /// - [`into_string`](ByteStream::into_string): collects all bytes into a [`String`], erroring if utf-8 decoding failed. /// - [`into_value`](ByteStream::into_value): collects all bytes into a value typed appropriately /// for the [type](.type_()) of this stream. If the type is [`Unknown`](ByteStreamType::Unknown), /// it will produce a string value if the data is valid UTF-8, or a binary value otherwise. /// -/// There are also a few other methods to consume all the data of a [`Bytestream`]: +/// There are also a few other methods to consume all the data of a [`ByteStream`]: /// - [`drain`](ByteStream::drain): consumes all bytes and outputs nothing. /// - [`write_to`](ByteStream::write_to): writes all bytes to the given [`Write`] destination. /// - [`print`](ByteStream::print): a convenience wrapper around [`write_to`](ByteStream::write_to). diff --git a/crates/nu-std/README.md b/crates/nu-std/README.md index d6e4d6e9f6..23bd684a50 100644 --- a/crates/nu-std/README.md +++ b/crates/nu-std/README.md @@ -7,7 +7,7 @@ The standard library is a pure-`nushell` collection of custom commands which provide interactive utilities and building blocks for users writing casual scripts or complex applications. To see what's here: -``` +```text > use std > scope commands | select name usage | where name =~ "std " #┬───────────name────────────┬──────────────────────usage────────────────────── diff --git a/crates/nu-std/src/lib.rs b/crates/nu-std/src/lib.rs index a20e3fc5da..ff6910e4ba 100644 --- a/crates/nu-std/src/lib.rs +++ b/crates/nu-std/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use log::trace; use nu_engine::eval_block; use nu_parser::parse; diff --git a/crates/nu-system/README.md b/crates/nu-system/README.md new file mode 100644 index 0000000000..d847bf4e0f --- /dev/null +++ b/crates/nu-system/README.md @@ -0,0 +1,7 @@ +Operating system specific bindings used by Nushell. + +Currently primarily wrappers around processes and ways to gather process info from the system + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-system/src/lib.rs b/crates/nu-system/src/lib.rs index 2fc97d2e7e..3633d62990 100644 --- a/crates/nu-system/src/lib.rs +++ b/crates/nu-system/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod foreground; #[cfg(target_os = "freebsd")] diff --git a/crates/nu-table/README.md b/crates/nu-table/README.md new file mode 100644 index 0000000000..945d515e62 --- /dev/null +++ b/crates/nu-table/README.md @@ -0,0 +1,7 @@ +The layout logic for Nushell's table viewer. + +See also the separate `table` command implementation + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs index 66cc2a3a82..182585f193 100644 --- a/crates/nu-table/src/lib.rs +++ b/crates/nu-table/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod table; mod table_theme; mod types; diff --git a/crates/nu-term-grid/README.md b/crates/nu-term-grid/README.md new file mode 100644 index 0000000000..5bdd32077d --- /dev/null +++ b/crates/nu-term-grid/README.md @@ -0,0 +1,5 @@ +Implementation of the layout engine for the `grid` command. + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-term-grid/src/lib.rs b/crates/nu-term-grid/src/lib.rs index 79b146593f..ca8ccd4e23 100644 --- a/crates/nu-term-grid/src/lib.rs +++ b/crates/nu-term-grid/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod grid; pub use grid::Grid; diff --git a/crates/nu-test-support/README.md b/crates/nu-test-support/README.md new file mode 100644 index 0000000000..f384b15067 --- /dev/null +++ b/crates/nu-test-support/README.md @@ -0,0 +1,7 @@ +This crate provides utilities for testing of Nushell + +Plugin authors should instead refer to `nu-plugin-test-support` + +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs index 5319830920..c6cadbcbd8 100644 --- a/crates/nu-test-support/src/lib.rs +++ b/crates/nu-test-support/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] pub mod commands; pub mod fs; pub mod locale_override; diff --git a/crates/nu-utils/README.md b/crates/nu-utils/README.md new file mode 100644 index 0000000000..de50a8ae93 --- /dev/null +++ b/crates/nu-utils/README.md @@ -0,0 +1,6 @@ +Collection of small utilities that are shared across Nushell crates. + +This crate should compile early in the crate graph and thus not depend on major dependencies or core-nushell crates itself. +## Internal Nushell crate + +This crate implements components of Nushell and is not designed to support plugin authors or other users directly. diff --git a/crates/nu-utils/src/lib.rs b/crates/nu-utils/src/lib.rs index cb834e5e0f..0c021f1dc9 100644 --- a/crates/nu-utils/src/lib.rs +++ b/crates/nu-utils/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] mod casing; mod deansi; pub mod emoji; diff --git a/crates/nuon/README.md b/crates/nuon/README.md index d11aa8104c..158a9d2e7d 100644 --- a/crates/nuon/README.md +++ b/crates/nuon/README.md @@ -4,7 +4,7 @@ The NUON format is a superset of JSON designed to fit the feel of Nushell. Some of its extra features are - trailing commas are allowed - commas are optional in lists -- quotes are not required around keys or any _bare_ string that do not contain spaces +- quotes are not required around keys or any _bare_ string that do not contain spaces or special characters - comments are allowed, though not preserved when using [`from_nuon`] ## Example From ae40d56fc59565588ec1f004f70e5445cf54c727 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Sun, 14 Jul 2024 01:12:55 -0700 Subject: [PATCH 53/63] Report parse warns and compile errs when running script files (#13369) # Description Report parse warnings and compile errors when running script files. It's useful to report this information to script authors and users so they can know if they need to update something in the near future. I also found that moving some errors from eval to compile time meant some error tests can fail (in `tests/repl`) so this is a good idea to keep those passing. # User-Facing Changes - Report parse warnings when running script files - Report compile errors when running script files --- crates/nu-cli/src/eval_file.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index ff6ba36fe3..bac4378d1b 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -76,12 +76,21 @@ pub fn evaluate_file( trace!("parsing file: {}", file_path_str); let block = parse(&mut working_set, Some(file_path_str), &file, false); + if let Some(warning) = working_set.parse_warnings.first() { + report_error(&working_set, warning); + } + // If any parse errors were found, report the first error and exit. if let Some(err) = working_set.parse_errors.first() { report_error(&working_set, err); std::process::exit(1); } + if let Some(err) = working_set.compile_errors.first() { + report_error(&working_set, err); + // Not a fatal error, for now + } + // Look for blocks whose name starts with "main" and replace it with the filename. for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) { if block.signature.name == "main" { From cf4864a9cd4e0d840fef429d45b2f724b187c233 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 14 Jul 2024 09:19:09 +0100 Subject: [PATCH 54/63] JSON format output keeps braces on same line (issue #13326) (#13352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is a minor breaking change to JSON output syntax/style of the `to json` command. This fixes #13326 by setting `braces_same_line` to true when creating a new `HjsonFormatter`. This then simply tells `HjsonFormatter` to keep the braces on the same line when outputting which is what I expected nu's `to json` command to do. There are almost no changes to nushell itself, all changes are contained within `nu-json` crate (minus any documentation updates). Oh, almost forgot to mention, to get the tests compiling, I added fancy_regex as a _dev_ dependency to nu-json. I could look into eliminating that if desirable. # User-Facing Changes **Breaking Change** nushell now outputs the desired result using the reproduction command from the issue: ``` echo '{"version": "v0.4.4","notes": "blablabla","pub_date": "2024-05-04T16:05:00Z","platforms":{"windows-x86_64":{"signature": "blablabla","url": "https://blablabla"}}}' | from json | to json ``` outputs: ``` { "version": "v0.4.4", "notes": "blablabla", "pub_date": "2024-05-04T16:05:00Z", "platforms": { "windows-x86_64": { "signature": "blablabla", "url": "https://blablabla" } } } ``` whereas previously it would push the opening braces onto a new line: ``` { "version": "v0.4.4", "notes": "blablabla", "pub_date": "2024-05-04T16:05:00Z", "platforms": { "windows-x86_64": { "signature": "blablabla", "url": "https://blablabla" } } } ``` # Tests + Formatting toolkit check pr mostly passes - there are regrettably some tests not passing on my windows machine _before making any changes_ (I may look into this as a separate issue) I have re-enabled the [hjson tests](https://github.com/nushell/nushell/blob/main/crates/nu-json/tests/main.rs). This is done in the second commit 🙂 They have a crucial difference to what they were previously asserting: * nu-json outputs in json syntax, not hjson syntax I think this is desirable, but I'm not aware of the history of these tests. # After Submitting I suspect there `to json` command examples will need updating to match, haven't checked yet! --- Cargo.lock | 3 +++ crates/nu-json/Cargo.toml | 13 +++++++++---- crates/nu-json/src/ser.rs | 2 +- crates/nu-json/tests/main.rs | 27 ++++++++------------------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8bfe4ab4e..2247e462fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3195,7 +3195,10 @@ dependencies = [ name = "nu-json" version = "0.95.1" dependencies = [ + "fancy-regex", "linked-hash-map", + "nu-path", + "nu-test-support", "num-traits", "serde", "serde_json", diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 04c39bdac5..a3b61042b1 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -1,5 +1,8 @@ [package] -authors = ["The Nushell Project Developers", "Christian Zangl "] +authors = [ + "The Nushell Project Developers", + "Christian Zangl ", +] description = "Fork of serde-hjson" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-json" edition = "2021" @@ -19,9 +22,11 @@ default = ["preserve_order"] [dependencies] linked-hash-map = { version = "0.5", optional = true } num-traits = { workspace = true } -serde = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } [dev-dependencies] -# nu-path = { path="../nu-path", version = "0.95.1" } -# serde_json = "1.0" \ No newline at end of file +nu-test-support = { path = "../nu-test-support", version = "0.95.1" } +nu-path = { path = "../nu-path", version = "0.95.1" } +serde_json = "1.0" +fancy-regex = "0.13.0" diff --git a/crates/nu-json/src/ser.rs b/crates/nu-json/src/ser.rs index d9df1aea56..4b15740602 100644 --- a/crates/nu-json/src/ser.rs +++ b/crates/nu-json/src/ser.rs @@ -714,7 +714,7 @@ impl<'a> HjsonFormatter<'a> { stack: Vec::new(), at_colon: false, indent, - braces_same_line: false, + braces_same_line: true, } } } diff --git a/crates/nu-json/tests/main.rs b/crates/nu-json/tests/main.rs index c209bc0a52..0ac3e403e2 100644 --- a/crates/nu-json/tests/main.rs +++ b/crates/nu-json/tests/main.rs @@ -1,7 +1,5 @@ -// FIXME: re-enable tests -/* -use nu_json::Value; use fancy_regex::Regex; +use nu_json::Value; use std::fs; use std::io; use std::path::{Path, PathBuf}; @@ -11,7 +9,7 @@ fn txt(text: &str) -> String { #[cfg(windows)] { - out.replace("\r\n", "").replace("\n", "") + out.replace("\r\n", "").replace('\n', "") } #[cfg(not(windows))] @@ -21,15 +19,7 @@ fn txt(text: &str) -> String { } fn hjson_expectations() -> PathBuf { - let assets = nu_test_support::fs::assets().join("nu_json"); - - nu_path::canonicalize(assets.clone()).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize hjson assets path {}: {:?}", - assets.display(), - e - ) - }) + nu_test_support::fs::assets().join("nu_json") } fn get_test_content(name: &str) -> io::Result { @@ -50,7 +40,7 @@ fn get_result_content(name: &str) -> io::Result<(String, String)> { let p1 = format!("{}/{}_result.json", expectations.display(), name); let p2 = format!("{}/{}_result.hjson", expectations.display(), name); - Ok((fs::read_to_string(&p1)?, fs::read_to_string(&p2)?)) + Ok((fs::read_to_string(p1)?, fs::read_to_string(p2)?)) } macro_rules! run_test { @@ -73,7 +63,8 @@ macro_rules! run_test { let actual_hjson = txt(&actual_hjson); let actual_json = $fix(serde_json::to_string_pretty(&udata).unwrap()); let actual_json = txt(&actual_json); - if rhjson != actual_hjson { + // nu_json::to_string now outputs json instead of hjson! + if rjson != actual_hjson { println!( "{:?}\n---hjson expected\n{}\n---hjson actual\n{}\n---\n", name, rhjson, actual_hjson @@ -85,7 +76,7 @@ macro_rules! run_test { name, rjson, actual_json ); } - assert!(rhjson == actual_hjson && rjson == actual_json); + assert!(rjson == actual_hjson && rjson == actual_json); } }}; } @@ -198,7 +189,7 @@ fn test_hjson() { let missing = all .into_iter() - .filter(|x| done.iter().find(|y| &x == y) == None) + .filter(|x| !done.iter().any(|y| x == y)) .collect::>(); if !missing.is_empty() { @@ -208,5 +199,3 @@ fn test_hjson() { panic!(); } } - -*/ From 3d1145e75983de00354edda0ea8dfd2629bba06f Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sun, 14 Jul 2024 10:37:57 +0200 Subject: [PATCH 55/63] Fix CI test failure on main (nu-json) (#13374) Conflict resulting from #13329 and #13326 --- crates/nu-json/tests/main.rs | 2 +- crates/nu-test-support/src/fs.rs | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/nu-json/tests/main.rs b/crates/nu-json/tests/main.rs index 0ac3e403e2..73c68f3d8e 100644 --- a/crates/nu-json/tests/main.rs +++ b/crates/nu-json/tests/main.rs @@ -19,7 +19,7 @@ fn txt(text: &str) -> String { } fn hjson_expectations() -> PathBuf { - nu_test_support::fs::assets().join("nu_json") + nu_test_support::fs::assets().join("nu_json").into() } fn get_test_content(name: &str) -> io::Result { diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index eb90e16ab2..230e5eafe3 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -102,13 +102,12 @@ pub fn fixtures() -> AbsolutePathBuf { path } -// FIXME: re-enable nu_json tests -// pub fn assets() -> AbsolutePathBuf { -// let mut path = root(); -// path.push("tests"); -// path.push("assets"); -// path -// } +pub fn assets() -> AbsolutePathBuf { + let mut path = root(); + path.push("tests"); + path.push("assets"); + path +} pub fn in_directory(path: impl AsRef) -> AbsolutePathBuf { root().join(path) From 0918050ac84145900f556c9c1bb4d3e98dfe9e48 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 16 Jul 2024 03:49:00 +0000 Subject: [PATCH 56/63] Deprecate `group` in favor of `chunks` (#13377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description The name of the `group` command is a little unclear/ambiguous. Everything I look at it, I think of `group-by`. I think `chunks` more clearly conveys what the `group` command does. Namely, it divides the input list into chunks of a certain size. For example, [`slice::chunks`](https://doc.rust-lang.org/std/primitive.slice.html#method.chunks) has the same name. So, this PR adds a new `chunks` command to replace the now deprecated `group` command. The `chunks` command is a refactored version of `group`. As such, there is a small performance improvement: ```nushell # $data is a very large list > bench { $data | chunks 2 } --rounds 30 | get mean 474ms 921µs 190ns # deprecation warning was disabled here for fairness > bench { $data | group 2 } --rounds 30 | get mean 592ms 702µs 440ns > bench { $data | chunks 200 } --rounds 30 | get mean 374ms 188µs 318ns > bench { $data | group 200 } --rounds 30 | get mean 481ms 264µs 869ns > bench { $data | chunks 1 } --rounds 30 | get mean 642ms 574µs 42ns > bench { $data | group 1 } --rounds 30 | get mean 981ms 602µs 513ns ``` # User-Facing Changes - `group` command has been deprecated in favor of new `chunks` command. - `chunks` errors when given a chunk size of `0` whereas `group` returns chunks with one element. # Tests + Formatting Added tests for `chunks`, since `group` did not have any tests. # After Submitting Update book if necessary. --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/chunks.rs | 153 ++++++++++++++++++ crates/nu-command/src/filters/group.rs | 13 +- crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/tests/commands/chunks.rs | 43 +++++ crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-protocol/src/errors/cli_error.rs | 20 +++ crates/nu-protocol/src/errors/mod.rs | 4 +- .../nu-protocol/src/errors/parse_warning.rs | 4 +- .../nu-protocol/src/pipeline/pipeline_data.rs | 28 ++++ crates/nu-protocol/src/ty.rs | 4 + 11 files changed, 269 insertions(+), 4 deletions(-) create mode 100644 crates/nu-command/src/filters/chunks.rs create mode 100644 crates/nu-command/tests/commands/chunks.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4f35a1c711..4adcfe9214 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -31,6 +31,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { All, Any, Append, + Chunks, Columns, Compact, Default, diff --git a/crates/nu-command/src/filters/chunks.rs b/crates/nu-command/src/filters/chunks.rs new file mode 100644 index 0000000000..11a59c2aa9 --- /dev/null +++ b/crates/nu-command/src/filters/chunks.rs @@ -0,0 +1,153 @@ +use nu_engine::command_prelude::*; +use nu_protocol::ListStream; + +#[derive(Clone)] +pub struct Chunks; + +impl Command for Chunks { + fn name(&self) -> &str { + "chunks" + } + + fn signature(&self) -> Signature { + Signature::build("chunks") + .input_output_types(vec![ + (Type::table(), Type::list(Type::table())), + (Type::list(Type::Any), Type::list(Type::list(Type::Any))), + ]) + .required("chunk_size", SyntaxShape::Int, "The size of each chunk.") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Divide a list or table into chunks of `chunk_size`." + } + + fn extra_usage(&self) -> &str { + "This command will error if `chunk_size` is negative or zero." + } + + fn search_terms(&self) -> Vec<&str> { + vec!["batch", "group"] + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[1 2 3 4] | chunks 2", + description: "Chunk a list into pairs", + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_int(1), Value::test_int(2)]), + Value::test_list(vec![Value::test_int(3), Value::test_int(4)]), + ])), + }, + Example { + example: "[[foo bar]; [0 1] [2 3] [4 5] [6 7] [8 9]] | chunks 3", + description: "Chunk the rows of a table into triplets", + result: Some(Value::test_list(vec![ + Value::test_list(vec![ + Value::test_record(record! { + "foo" => Value::test_int(0), + "bar" => Value::test_int(1), + }), + Value::test_record(record! { + "foo" => Value::test_int(2), + "bar" => Value::test_int(3), + }), + Value::test_record(record! { + "foo" => Value::test_int(4), + "bar" => Value::test_int(5), + }), + ]), + Value::test_list(vec![ + Value::test_record(record! { + "foo" => Value::test_int(6), + "bar" => Value::test_int(7), + }), + Value::test_record(record! { + "foo" => Value::test_int(8), + "bar" => Value::test_int(9), + }), + ]), + ])), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let chunk_size: Value = call.req(engine_state, stack, 0)?; + + let size = + usize::try_from(chunk_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue { + span: chunk_size.span(), + })?; + + if size == 0 { + return Err(ShellError::IncorrectValue { + msg: "`chunk_size` cannot be zero".into(), + val_span: chunk_size.span(), + call_span: head, + }); + } + + match input { + PipelineData::Value(Value::List { vals, .. }, metadata) => { + let chunks = ChunksIter::new(vals, size, head); + let stream = ListStream::new(chunks, head, engine_state.signals().clone()); + Ok(PipelineData::ListStream(stream, metadata)) + } + PipelineData::ListStream(stream, metadata) => { + let stream = stream.modify(|iter| ChunksIter::new(iter, size, head)); + Ok(PipelineData::ListStream(stream, metadata)) + } + input => Err(input.unsupported_input_error("list", head)), + } + } +} + +struct ChunksIter> { + iter: I, + size: usize, + span: Span, +} + +impl> ChunksIter { + fn new(iter: impl IntoIterator, size: usize, span: Span) -> Self { + Self { + iter: iter.into_iter(), + size, + span, + } + } +} + +impl> Iterator for ChunksIter { + type Item = Value; + + fn next(&mut self) -> Option { + let first = self.iter.next()?; + let mut chunk = Vec::with_capacity(self.size); // delay allocation to optimize for empty iter + chunk.push(first); + chunk.extend((&mut self.iter).take(self.size - 1)); + Some(Value::list(chunk, self.span)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Chunks {}) + } +} diff --git a/crates/nu-command/src/filters/group.rs b/crates/nu-command/src/filters/group.rs index 13b53850d2..60c720e325 100644 --- a/crates/nu-command/src/filters/group.rs +++ b/crates/nu-command/src/filters/group.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::ValueIterator; +use nu_protocol::{report_warning_new, ParseWarning, ValueIterator}; #[derive(Clone)] pub struct Group; @@ -54,6 +54,17 @@ impl Command for Group { input: PipelineData, ) -> Result { let head = call.head; + + report_warning_new( + engine_state, + &ParseWarning::DeprecatedWarning { + old_command: "group".into(), + new_suggestion: "the new `chunks` command".into(), + span: head, + url: "`help chunks`".into(), + }, + ); + let group_size: Spanned = call.req(engine_state, stack, 0)?; let metadata = input.metadata(); diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index f43bdb10b2..4ffb0fc6db 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,6 +1,7 @@ mod all; mod any; mod append; +mod chunks; mod columns; mod compact; mod default; @@ -58,6 +59,7 @@ mod zip; pub use all::All; pub use any::Any; pub use append::Append; +pub use chunks::Chunks; pub use columns::Columns; pub use compact::Compact; pub use default::Default; diff --git a/crates/nu-command/tests/commands/chunks.rs b/crates/nu-command/tests/commands/chunks.rs new file mode 100644 index 0000000000..eb0c580f15 --- /dev/null +++ b/crates/nu-command/tests/commands/chunks.rs @@ -0,0 +1,43 @@ +use nu_test_support::nu; + +#[test] +fn chunk_size_negative() { + let actual = nu!("[0 1 2] | chunks -1"); + assert!(actual.err.contains("positive")); +} + +#[test] +fn chunk_size_zero() { + let actual = nu!("[0 1 2] | chunks 0"); + assert!(actual.err.contains("zero")); +} + +#[test] +fn chunk_size_not_int() { + let actual = nu!("[0 1 2] | chunks (if true { 1sec })"); + assert!(actual.err.contains("can't convert")); +} + +#[test] +fn empty() { + let actual = nu!("[] | chunks 2 | is-empty"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn list_stream() { + let actual = nu!("([0 1 2] | every 1 | chunks 2) == ([0 1 2] | chunks 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn table_stream() { + let actual = nu!("([[foo bar]; [0 1] [2 3] [4 5]] | every 1 | chunks 2) == ([[foo bar]; [0 1] [2 3] [4 5]] | chunks 2)"); + assert_eq!(actual.out, "true"); +} + +#[test] +fn no_empty_chunks() { + let actual = nu!("([0 1 2 3 4 5] | chunks 3 | length) == 2"); + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index a1cb82a4b4..4c9f8e86f8 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -7,6 +7,7 @@ mod break_; mod bytes; mod cal; mod cd; +mod chunks; mod compact; mod complete; mod config_env_default; diff --git a/crates/nu-protocol/src/errors/cli_error.rs b/crates/nu-protocol/src/errors/cli_error.rs index c12c601c4d..9ce6829b7b 100644 --- a/crates/nu-protocol/src/errors/cli_error.rs +++ b/crates/nu-protocol/src/errors/cli_error.rs @@ -47,6 +47,26 @@ pub fn report_error_new( report_error(&working_set, error); } +pub fn report_warning( + working_set: &StateWorkingSet, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + eprintln!("Warning: {:?}", CliError(error, working_set)); + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = nu_utils::enable_vt_processing(); + } +} + +pub fn report_warning_new( + engine_state: &EngineState, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, error); +} + impl std::fmt::Debug for CliError<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let config = self.1.get_config(); diff --git a/crates/nu-protocol/src/errors/mod.rs b/crates/nu-protocol/src/errors/mod.rs index 3f895cd65f..59369cd54f 100644 --- a/crates/nu-protocol/src/errors/mod.rs +++ b/crates/nu-protocol/src/errors/mod.rs @@ -5,7 +5,9 @@ mod parse_error; mod parse_warning; mod shell_error; -pub use cli_error::{format_error, report_error, report_error_new}; +pub use cli_error::{ + format_error, report_error, report_error_new, report_warning, report_warning_new, +}; pub use compile_error::CompileError; pub use labeled_error::{ErrorLabel, LabeledError}; pub use parse_error::{DidYouMean, ParseError}; diff --git a/crates/nu-protocol/src/errors/parse_warning.rs b/crates/nu-protocol/src/errors/parse_warning.rs index a30bc731d8..5f34deb20c 100644 --- a/crates/nu-protocol/src/errors/parse_warning.rs +++ b/crates/nu-protocol/src/errors/parse_warning.rs @@ -6,11 +6,11 @@ use thiserror::Error; #[derive(Clone, Debug, Error, Diagnostic, Serialize, Deserialize)] pub enum ParseWarning { #[error("Deprecated: {old_command}")] - #[diagnostic(help("for more info: {url}"))] + #[diagnostic(help("for more info see {url}"))] DeprecatedWarning { old_command: String, new_suggestion: String, - #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead")] + #[label("`{old_command}` is deprecated and will be removed in a future release. Please {new_suggestion} instead.")] span: Span, url: String, }, diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index a89337a6c2..0d1c0b3092 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -635,6 +635,34 @@ impl PipelineData { Ok(None) } } + + pub fn unsupported_input_error( + self, + expected_type: impl Into, + span: Span, + ) -> ShellError { + match self { + PipelineData::Empty => ShellError::PipelineEmpty { dst_span: span }, + PipelineData::Value(value, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: value.get_type().get_non_specified_string(), + dst_span: span, + src_span: value.span(), + }, + PipelineData::ListStream(stream, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: "list (stream)".into(), + dst_span: span, + src_span: stream.span(), + }, + PipelineData::ByteStream(stream, ..) => ShellError::OnlySupportsThisInputType { + exp_input_type: expected_type.into(), + wrong_type: stream.type_().describe().into(), + dst_span: span, + src_span: stream.span(), + }, + } + } } enum PipelineIteratorInner { diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index d5ea8c1554..7c004d81de 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -36,6 +36,10 @@ pub enum Type { } impl Type { + pub fn list(inner: Type) -> Self { + Self::List(Box::new(inner)) + } + pub fn record() -> Self { Self::Record([].into()) } From fcb8e36caa7551313e55640884bab4190a6adea6 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Tue, 16 Jul 2024 05:54:24 +0200 Subject: [PATCH 57/63] Remove `default` list-diving behaviour (#13386) # Description Before this change `default` would dive into lists and replace `null` elements with the default value. Therefore there was no easy way to process `null|list` input and receive `list` (IOW to apply the default on the top level only). However it's trivially easy to apply default values to list elements with the `each` command: ```nushell [null, "a", null] | each { default "b" } ``` So there's no need to have this behavior in `default` command. # User-Facing Changes * `default` no longer dives into lists to replace `null` elements with the default value. # Tests + Formatting Added a couple of tests for the new (and old) behavior. # After Submitting * Update docs. --- crates/nu-command/src/filters/default.rs | 14 +++----------- crates/nu-command/tests/commands/default.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs index a12d78f0b7..9b08ce6331 100644 --- a/crates/nu-command/src/filters/default.rs +++ b/crates/nu-command/src/filters/default.rs @@ -51,11 +51,11 @@ impl Command for Default { description: "Get the env value of `MY_ENV` with a default value 'abc' if not present", example: "$env | get --ignore-errors MY_ENV | default 'abc'", - result: None, // Some(Value::test_string("abc")), + result: Some(Value::test_string("abc")), }, Example { description: "Replace the `null` value in a list", - example: "[1, 2, null, 4] | default 3", + example: "[1, 2, null, 4] | each { default 3 }", result: Some(Value::list( vec![ Value::test_int(1), @@ -113,15 +113,7 @@ fn default( } else if input.is_nothing() { Ok(value.into_pipeline_data()) } else { - input - .map( - move |item| match item { - Value::Nothing { .. } => value.clone(), - x => x, - }, - engine_state.signals(), - ) - .map(|x| x.set_metadata(metadata)) + Ok(input) } } diff --git a/crates/nu-command/tests/commands/default.rs b/crates/nu-command/tests/commands/default.rs index 4fd41d7ec0..6ecd14960a 100644 --- a/crates/nu-command/tests/commands/default.rs +++ b/crates/nu-command/tests/commands/default.rs @@ -32,3 +32,15 @@ fn default_after_empty_filter() { assert_eq!(actual.out, "d"); } + +#[test] +fn keeps_nulls_in_lists() { + let actual = nu!(r#"[null, 2, 3] | default [] | to json -r"#); + assert_eq!(actual.out, "[null,2,3]"); +} + +#[test] +fn replaces_null() { + let actual = nu!(r#"null | default 1"#); + assert_eq!(actual.out, "1"); +} From 1981c50c8fbef4f2fdb6f8d8d8a570394d3b8581 Mon Sep 17 00:00:00 2001 From: Ian Manske Date: Tue, 16 Jul 2024 09:28:31 +0000 Subject: [PATCH 58/63] Remove unused field in `StateWorkingSet` (#13387) # Description Removes the unused `external_commands` field from `StateWorkingSet`. --- crates/nu-protocol/src/engine/state_working_set.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index af1085c1ee..822bf35e3a 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -25,7 +25,6 @@ use crate::{PluginIdentity, PluginRegistryItem, RegisteredPlugin}; pub struct StateWorkingSet<'a> { pub permanent_state: &'a EngineState, pub delta: StateDelta, - pub external_commands: Vec>, pub files: FileStack, /// Whether or not predeclarations are searched when looking up a command (used with aliases) pub search_predecls: bool, @@ -46,7 +45,6 @@ impl<'a> StateWorkingSet<'a> { Self { delta: StateDelta::new(permanent_state), permanent_state, - external_commands: vec![], files, search_predecls: true, parse_errors: vec![], From b66671d339325dc34f96af4d893f9f94cafc4f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Tue, 16 Jul 2024 14:16:26 +0200 Subject: [PATCH 59/63] Switch from dirs_next 2.0 to dirs 5.0 (#13384) # Description Replaces the `dirs_next` family of crates with `dirs`. `dirs_next` was born when the `dirs` crates were abandoned three years ago, but they're being maintained again and most projects depend on `dirs` nowadays. `dirs_next` has been abandoned since. This came up while working on https://github.com/nushell/nushell/pull/13382. # User-Facing Changes None. # Tests + Formatting Tests and formatter have been run. # After Submitting --- Cargo.lock | 30 +++++++++++-------- Cargo.toml | 6 ++-- crates/nu-command/Cargo.toml | 4 +-- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/tests/commands/cd.rs | 2 +- .../nu-command/tests/commands/run_external.rs | 2 +- crates/nu-path/Cargo.toml | 4 +-- crates/nu-path/src/helpers.rs | 8 ++--- crates/nu-path/src/tilde.rs | 4 +-- src/main.rs | 2 +- tests/repl/test_config_path.rs | 4 +-- 11 files changed, 37 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2247e462fa..0e63b83844 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1219,24 +1219,24 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "cfg-if", - "dirs-sys-next", + "dirs-sys", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "dirs-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -2873,7 +2873,7 @@ dependencies = [ "assert_cmd", "crossterm", "ctrlc", - "dirs-next", + "dirs", "log", "miette", "mimalloc", @@ -3047,7 +3047,7 @@ dependencies = [ "deunicode", "dialoguer", "digest", - "dirs-next", + "dirs", "dtparse", "encoding_rs", "fancy-regex", @@ -3245,7 +3245,7 @@ dependencies = [ name = "nu-path" version = "0.95.1" dependencies = [ - "dirs-next", + "dirs", "omnipath", "pwd", ] @@ -3836,6 +3836,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-multimap" version = "0.7.3" diff --git a/Cargo.toml b/Cargo.toml index a0b3ef27f5..20f41f1b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ ctrlc = "3.4" deunicode = "1.6.0" dialoguer = { default-features = false, version = "0.11" } digest = { default-features = false, version = "0.10" } -dirs-next = "2.0" +dirs = "5.0" dtparse = "2.0" encoding_rs = "0.8" fancy-regex = "0.13" @@ -201,7 +201,7 @@ reedline = { workspace = true, features = ["bashisms", "sqlite"] } crossterm = { workspace = true } ctrlc = { workspace = true } -dirs-next = { workspace = true } +dirs = { workspace = true } log = { workspace = true } miette = { workspace = true, features = ["fancy-no-backtrace", "fancy"] } mimalloc = { version = "0.1.42", default-features = false, optional = true } @@ -229,7 +229,7 @@ nu-test-support = { path = "./crates/nu-test-support", version = "0.95.1" } nu-plugin-protocol = { path = "./crates/nu-plugin-protocol", version = "0.95.1" } nu-plugin-core = { path = "./crates/nu-plugin-core", version = "0.95.1" } assert_cmd = "2.0" -dirs-next = { workspace = true } +dirs = { workspace = true } tango-bench = "0.5" pretty_assertions = { workspace = true } regex = { workspace = true } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9c49a69ff5..ea501578b6 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -140,10 +140,10 @@ trash-support = ["trash"] nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.95.1" } nu-test-support = { path = "../nu-test-support", version = "0.95.1" } -dirs-next = { workspace = true } +dirs = { workspace = true } mockito = { workspace = true, default-features = false } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } pretty_assertions = { workspace = true } -tempfile = { workspace = true } \ No newline at end of file +tempfile = { workspace = true } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 5e8527b45e..0b9f1950ea 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -606,7 +606,7 @@ mod test { assert_eq!(actual, expected); let actual = expand_glob("~/foo.txt", cwd, Span::unknown(), &Signals::empty()).unwrap(); - let home = dirs_next::home_dir().expect("failed to get home dir"); + let home = dirs::home_dir().expect("failed to get home dir"); let expected: Vec = vec![home.join("foo.txt").into()]; assert_eq!(actual, expected); }) diff --git a/crates/nu-command/tests/commands/cd.rs b/crates/nu-command/tests/commands/cd.rs index 87af52aa4d..f58638cc35 100644 --- a/crates/nu-command/tests/commands/cd.rs +++ b/crates/nu-command/tests/commands/cd.rs @@ -151,7 +151,7 @@ fn filesystem_change_to_home_directory() { " ); - assert_eq!(Some(PathBuf::from(actual.out)), dirs_next::home_dir()); + assert_eq!(Some(PathBuf::from(actual.out)), dirs::home_dir()); }) } diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 154c31b71c..17667c9bb3 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -309,7 +309,7 @@ fn external_arg_expand_tilde() { "# )); - let home = dirs_next::home_dir().expect("failed to find home dir"); + let home = dirs::home_dir().expect("failed to find home dir"); assert_eq!( actual.out, diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index dc2f870a9d..62908e7cb7 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -12,10 +12,10 @@ exclude = ["/fuzz"] bench = false [dependencies] -dirs-next = { workspace = true } +dirs = { workspace = true } [target.'cfg(windows)'.dependencies] omnipath = { workspace = true } [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "android")))'.dependencies] -pwd = { workspace = true } \ No newline at end of file +pwd = { workspace = true } diff --git a/crates/nu-path/src/helpers.rs b/crates/nu-path/src/helpers.rs index 5b389410e4..a6e35bddfe 100644 --- a/crates/nu-path/src/helpers.rs +++ b/crates/nu-path/src/helpers.rs @@ -3,14 +3,14 @@ use omnipath::WinPathExt; use std::path::PathBuf; pub fn home_dir() -> Option { - dirs_next::home_dir() + dirs::home_dir() } /// Return the data directory for the current platform or XDG_DATA_HOME if specified. pub fn data_dir() -> Option { match std::env::var("XDG_DATA_HOME").map(PathBuf::from) { Ok(xdg_data) if xdg_data.is_absolute() => Some(canonicalize(&xdg_data).unwrap_or(xdg_data)), - _ => get_canonicalized_path(dirs_next::data_dir()), + _ => get_canonicalized_path(dirs::data_dir()), } } @@ -20,7 +20,7 @@ pub fn cache_dir() -> Option { Ok(xdg_cache) if xdg_cache.is_absolute() => { Some(canonicalize(&xdg_cache).unwrap_or(xdg_cache)) } - _ => get_canonicalized_path(dirs_next::cache_dir()), + _ => get_canonicalized_path(dirs::cache_dir()), } } @@ -30,7 +30,7 @@ pub fn config_dir() -> Option { Ok(xdg_config) if xdg_config.is_absolute() => { Some(canonicalize(&xdg_config).unwrap_or(xdg_config)) } - _ => get_canonicalized_path(dirs_next::config_dir()), + _ => get_canonicalized_path(dirs::config_dir()), } } diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index 60cc7d11eb..95c91addf8 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -77,7 +77,7 @@ fn user_home_dir(username: &str) -> PathBuf { fn user_home_dir(username: &str) -> PathBuf { use std::path::Component; - match dirs_next::home_dir() { + match dirs::home_dir() { None => { // Termux always has the same home directory #[cfg(target_os = "android")] @@ -145,7 +145,7 @@ fn expand_tilde_with_another_user_home(path: &Path) -> PathBuf { /// Expand tilde ("~") into a home directory if it is the first path component pub fn expand_tilde(path: impl AsRef) -> PathBuf { // TODO: Extend this to work with "~user" style of home paths - expand_tilde_with_home(path, dirs_next::home_dir()) + expand_tilde_with_home(path, dirs::home_dir()) } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index 2c0c4d5e4b..f220731d1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,7 +100,7 @@ fn main() -> Result<()> { }, ); } else if let Some(old_config) = - nu_path::get_canonicalized_path(dirs_next::config_dir()).map(|p| p.join("nushell")) + nu_path::get_canonicalized_path(dirs::config_dir()).map(|p| p.join("nushell")) { let xdg_config_empty = nushell_config_path .read_dir() diff --git a/tests/repl/test_config_path.rs b/tests/repl/test_config_path.rs index 534ac38a27..895b1bd8bf 100644 --- a/tests/repl/test_config_path.rs +++ b/tests/repl/test_config_path.rs @@ -235,7 +235,7 @@ fn test_xdg_config_empty() { playground.with_env("XDG_CONFIG_HOME", ""); let actual = run(playground, "$nu.default-config-dir"); - let expected = dirs_next::config_dir().unwrap().join("nushell"); + let expected = dirs::config_dir().unwrap().join("nushell"); assert_eq!( actual, adjust_canonicalization(expected.canonicalize().unwrap_or(expected)) @@ -250,7 +250,7 @@ fn test_xdg_config_bad() { playground.with_env("XDG_CONFIG_HOME", xdg_config_home); let actual = run(playground, "$nu.default-config-dir"); - let expected = dirs_next::config_dir().unwrap().join("nushell"); + let expected = dirs::config_dir().unwrap().join("nushell"); assert_eq!( actual, adjust_canonicalization(expected.canonicalize().unwrap_or(expected)) From 5417c89387b67c3192ae9043473b556cd669ee15 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 17 Jul 2024 00:21:42 +0300 Subject: [PATCH 60/63] Fix issue with truncation when head on border is used (#13389) close #13365 @fdncred thanks for reproducible hopefully there won't be issues with it after this one :sweat_smile: --- crates/nu-command/tests/commands/table.rs | 6 ++ crates/nu-table/src/table.rs | 122 ++++++++++++---------- 2 files changed, 70 insertions(+), 58 deletions(-) diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index a4869f6bfa..272369a1c9 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2901,3 +2901,9 @@ fn table_general_header_on_separator_trim_algorithm() { let actual = nu!("$env.config.table.header_on_separator = true; [[a b]; ['11111111111111111111111111111111111111' 2] ] | table --width=20 --theme basic"); assert_eq!(actual.out, "+-#-+----a-----+-b-+| 0 | 11111111 | 2 || | 11111111 | || | 11111111 | || | 11111111 | || | 111111 | |+---+----------+---+"); } + +#[test] +fn table_general_header_on_separator_issue1() { + let actual = nu!("$env.config.table.header_on_separator = true; [['Llll oo Bbbbbbbb' 'Bbbbbbbb Aaaa' Nnnnnn Ggggg 'Xxxxx Llllllll #' Bbb 'Pppp Ccccc' 'Rrrrrrrr Dddd' Rrrrrr 'Rrrrrr Ccccc II' 'Rrrrrr Ccccc Ppppppp II' 'Pppppp Dddddddd Tttt' 'Pppppp Dddddddd Dddd' 'Rrrrrrrrr Trrrrrr' 'Pppppp Ppppp Dddd' 'Ppppp Dddd' Hhhh]; [RRRRRRR FFFFFFFF UUUU VV 202407160001 BBB 1 '7/16/2024' '' AAA-1111 AAA-1111-11 '7 YEARS' 2555 'RRRRRRRR DDDD' '7/16/2031' '7/16/2031' NN]] | table --width=87 --theme basic"); + assert_eq!(actual.out, "+-#-+-Llll oo Bbbbbbbb-+-Bbbbbbbb Aaaa-+-Nnnnnn-+-Ggggg-+-Xxxxx Llllllll #-+-...-+| 0 | RRRRRRR | FFFFFFFF | UUUU | VV | 202407160001 | ... |+---+------------------+---------------+--------+-------+------------------+-----+"); +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 0dbf1bda7e..bc23e48248 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -418,62 +418,7 @@ impl TableOption, ColoredConfig> for // we already must have been estimated that it's safe to do. // and all dims will be suffitient if self.trim_as_head { - if recs.is_empty() { - return; - } - - // even though it's safe to trim columns by header there might be left unused space - // so we do use it if possible prioritizing left columns - - let headers = recs[0].to_owned(); - let headers_widths = headers - .iter() - .map(CellInfo::width) - .map(|v| v + self.pad) - .collect::>(); - - let min_width_use = get_total_width2(&headers_widths, cfg); - - let mut free_width = self.width_max.saturating_sub(min_width_use); - - for (i, head_width) in headers_widths.into_iter().enumerate() { - let head_width = head_width - self.pad; - let column_width = self.width[i] - self.pad; // safe to assume width is bigger then paddding - - let mut use_width = head_width; - if free_width > 0 { - // it's safe to assume that column_width is always bigger or equal to head_width - debug_assert!(column_width >= head_width); - - let additional_width = min(free_width, column_width - head_width); - free_width -= additional_width; - use_width += additional_width; - } - - match &self.strategy { - TrimStrategy::Wrap { try_to_keep_words } => { - let mut wrap = Width::wrap(use_width); - if *try_to_keep_words { - wrap = wrap.keep_words(); - } - - Modify::new(Columns::single(i)) - .with(wrap) - .change(recs, cfg, dims); - } - TrimStrategy::Truncate { suffix } => { - let mut truncate = Width::truncate(use_width); - if let Some(suffix) = suffix { - truncate = truncate.suffix(suffix).suffix_try_color(true); - } - - Modify::new(Columns::single(i)) - .with(truncate) - .change(recs, cfg, dims); - } - } - } - + trim_as_header(recs, cfg, dims, self); return; } @@ -498,6 +443,67 @@ impl TableOption, ColoredConfig> for } } +fn trim_as_header( + recs: &mut VecRecords>, + cfg: &mut ColoredConfig, + dims: &mut CompleteDimensionVecRecords, + trim: TableTrim, +) { + if recs.is_empty() { + return; + } + + let headers = recs[0].to_owned(); + let headers_widths = headers + .iter() + .map(CellInfo::width) + .map(|v| v + trim.pad) + .collect::>(); + let min_width_use = get_total_width2(&headers_widths, cfg); + let mut free_width = trim.width_max.saturating_sub(min_width_use); + + // even though it's safe to trim columns by header there might be left unused space + // so we do use it if possible prioritizing left columns + + for (i, head_width) in headers_widths.into_iter().enumerate() { + let head_width = head_width - trim.pad; + let column_width = trim.width[i] - trim.pad; // safe to assume width is bigger then paddding + + let mut use_width = head_width; + if free_width > 0 { + // it's safe to assume that column_width is always bigger or equal to head_width + debug_assert!(column_width >= head_width); + + let additional_width = min(free_width, column_width - head_width); + free_width -= additional_width; + use_width += additional_width; + } + + match &trim.strategy { + TrimStrategy::Wrap { try_to_keep_words } => { + let mut wrap = Width::wrap(use_width); + if *try_to_keep_words { + wrap = wrap.keep_words(); + } + + Modify::new(Columns::single(i)) + .with(wrap) + .change(recs, cfg, dims); + } + TrimStrategy::Truncate { suffix } => { + let mut truncate = Width::truncate(use_width); + if let Some(suffix) = suffix { + truncate = truncate.suffix(suffix).suffix_try_color(true); + } + + Modify::new(Columns::single(i)) + .with(truncate) + .change(recs, cfg, dims); + } + } + } +} + fn align_table( table: &mut Table, alignments: Alignments, @@ -793,14 +799,14 @@ fn truncate_columns_by_head( let mut truncate_pos = 0; for (i, column_header) in head.iter().enumerate() { let column_header_width = Cell::width(column_header); - width += column_header_width; + width += column_header_width + pad; if i > 0 { width += has_vertical as usize; } if width >= termwidth { - width -= column_header_width + (i > 0 && has_vertical) as usize; + width -= column_header_width + (i > 0 && has_vertical) as usize + pad; break; } From 63cea4413041f8821b235d730c61588b36cd7f14 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:46:59 +0800 Subject: [PATCH 61/63] Bump uuid from 1.9.1 to 1.10.0 (#13390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.9.1 to 1.10.0.
Release notes

Sourced from uuid's releases.

1.10.0

Deprecations

This release deprecates and renames the following functions:

  • Builder::from_rfc4122_timestamp -> Builder::from_gregorian_timestamp
  • Builder::from_sorted_rfc4122_timestamp -> Builder::from_sorted_gregorian_timestamp
  • Timestamp::from_rfc4122 -> Timestamp::from_gregorian
  • Timestamp::to_rfc4122 -> Timestamp::to_gregorian

What's Changed

New Contributors

Full Changelog: https://github.com/uuid-rs/uuid/compare/1.9.1...1.10.0

Commits
  • 4b4c590 Merge pull request #766 from uuid-rs/cargo/1.10.0
  • 68eff32 Merge pull request #765 from uuid-rs/chore/time-fn-deprecations
  • 3d5384d update docs and deprecation messages for timestamp fns
  • de50f20 renaming rfc4122 functions
  • 4a88417 prepare for 1.10.0 release
  • 66b4fce Merge pull request #764 from Vrajs16/main
  • 8896e26 Use expr instead of ident
  • 09973d6 Added changes
  • 6edf3e8 Use const identifer in uuid macro
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=uuid&package-manager=cargo&previous-version=1.9.1&new-version=1.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/nu_plugin_polars/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e63b83844..3006f933f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6638,9 +6638,9 @@ checksum = "425a23c7b7145bc7620c9c445817c37b1f78b6790aee9f208133f3c028975b60" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 20f41f1b49..9941d495f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,7 +173,7 @@ uu_mv = "0.0.27" uu_whoami = "0.0.27" uu_uname = "0.0.27" uucore = "0.0.27" -uuid = "1.9.1" +uuid = "1.10.0" v_htmlescape = "0.15.0" wax = "0.6" which = "6.0.0" diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index f9b4295cb9..99c88e47d6 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -39,7 +39,7 @@ polars-utils = { version = "0.41"} typetag = "0.2" env_logger = "0.11.3" log.workspace = true -uuid = { version = "1.9", features = ["v4", "serde"] } +uuid = { version = "1.10", features = ["v4", "serde"] } [dependencies.polars] features = [ From ac18e436035b9d1319d23432d728ad9f0711ed2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:47:09 +0800 Subject: [PATCH 62/63] Bump rust-embed from 8.4.0 to 8.5.0 (#13392) Bumps [rust-embed](https://github.com/pyros2097/rust-embed) from 8.4.0 to 8.5.0.
Changelog

Sourced from rust-embed's changelog.

[8.5.0] - 2024-07-09

  • Re-export RustEmbed as Embed #246. Thanks to krant
  • Allow users to specify a custom path to the rust_embed crate in generated code#232. Thanks to Wulf
  • Increase minimum rust-version to v1.7.0.0
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rust-embed&package-manager=cargo&previous-version=8.4.0&new-version=8.5.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3006f933f3..2506c6bc67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5216,9 +5216,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", diff --git a/Cargo.toml b/Cargo.toml index 9941d495f9..8ce82c274a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,7 +145,7 @@ ropey = "1.6.1" roxmltree = "0.19" rstest = { version = "0.18", default-features = false } rusqlite = "0.31" -rust-embed = "8.4.0" +rust-embed = "8.5.0" same-file = "1.0" serde = { version = "1.0", default-features = false } serde_json = "1.0" From f976c318870730a1674376ff33141626faf67a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:47:17 +0800 Subject: [PATCH 63/63] Bump open from 5.2.0 to 5.3.0 (#13391) Bumps [open](https://github.com/Byron/open-rs) from 5.2.0 to 5.3.0.
Release notes

Sourced from open's releases.

v5.3.0

New Features

  • add GNU/Hurd support Handle it like most of the other Unix platforms (e.g. Linux, BSDs, etc).

Commit Statistics

  • 2 commits contributed to the release.
  • 7 days passed between releases.
  • 1 commit was understood as conventional.
  • 0 issues like '(#ID)' were seen in commit messages

Commit Details

  • Uncategorized
    • Merge pull request #101 from pinotree/hurd (a060608)
    • Add GNU/Hurd support (58142a6)
Changelog

Sourced from open's changelog.

5.3.0 (2024-07-10)

New Features

  • add GNU/Hurd support Handle it like most of the other Unix platforms (e.g. Linux, BSDs, etc).

Commit Statistics

  • 2 commits contributed to the release.
  • 7 days passed between releases.
  • 1 commit was understood as conventional.
  • 0 issues like '(#ID)' were seen in commit messages

Commit Details

  • Uncategorized
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=open&package-manager=cargo&previous-version=5.2.0&new-version=5.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2506c6bc67..49770a9a0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3773,9 +3773,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "open" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", diff --git a/Cargo.toml b/Cargo.toml index 8ce82c274a..335e2f0384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ num-format = "0.4" num-traits = "0.2" omnipath = "0.1" once_cell = "1.18" -open = "5.2" +open = "5.3" os_pipe = { version = "1.2", features = ["io_safety"] } pathdiff = "0.2" percent-encoding = "2"