From 023e244958428877953b7c2ff816532541f9f1d7 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 9 Feb 2023 11:35:23 -0600 Subject: [PATCH] view span & view files commands (#7989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR does the following: 1. Adds a new command called `view span` - which shows what is at the location of the span parameters 2. Adds a new command called `view` - which just lists all the `view` commands. 3. Renames `view-source` to `view source`. 4. Adds a new command called `view files` - which shows you what files are loaded into nushell's EngineState memory. 5. Added a `Category::Debug` and put these commands (and others) into it. (`inspect` needs to be added to it, but it's not landed yet) Spans are important to nushell. One of their uses is to show where errors are. For instance, in this example, the leader lines pointing to parts of the command line are able to point to `10`, `/`, and `"bob"` because each of those items have a span. ``` > 10 / "bob" Error: nu::parser::unsupported_operation (link) × Types mismatched for operation. ╭─[entry #8:1:1] 1 │ 10 / "bob" · ─┬ ┬ ──┬── · │ │ ╰── string · │ ╰── doesn't support these values. · ╰── int ╰──── help: Change int or string to be the right types and try again. ``` # Examples ## view span Example: ``` > $env.config | get keybindings | first | debug -r ... bunch of stuff span: Span { start: 68065, end: 68090, }, }, ], span: Span { start: 68050, end: 68101, }, }, ], span: Span { start: 67927, end: 68108, }, } ``` To view the last span: ``` > view span 67927 68108 { name: clear_everything modifier: control keycode: char_l mode: emacs event: [ { send: clearscrollback } ] } ``` > To view the 2nd to last span: ``` view span 68065 68090 { send: clearscrollback } ``` > To view the 3rd to last span: ``` view span 68050 68101 [ { send: clearscrollback } ] ``` ## view files ``` > view files ╭────┬───────────────────────────────────────────────────────┬────────┬────────┬───────╮ │ # │ filename │ start │ end │ size │ ├────┼───────────────────────────────────────────────────────┼────────┼────────┼───────┤ │ 0 │ source │ 0 │ 2 │ 2 │ │ 1 │ Host Environment Variables │ 2 │ 6034 │ 6032 │ │ 2 │ C:\Users\a_username\AppData\Roaming\nushell\plugin.nu │ 6034 │ 31236 │ 25202 │ │ 3 │ C:\Users\a_username\AppData\Roaming\nushell\env.nu │ 31236 │ 44961 │ 13725 │ │ 4 │ C:\Users\a_username\AppData\Roaming\nushell\config.nu │ 44961 │ 76134 │ 31173 │ │ 5 │ defs.nu │ 76134 │ 91944 │ 15810 │ │ 6 │ prompt\oh-my.nu │ 91944 │ 111523 │ 19579 │ │ 7 │ weather\get-weather.nu │ 111523 │ 125556 │ 14033 │ │ 8 │ .zoxide.nu │ 125556 │ 127504 │ 1948 │ │ 9 │ source │ 127504 │ 127561 │ 57 │ │ 10 │ entry #1 │ 127561 │ 127585 │ 24 │ │ 11 │ entry #2 │ 127585 │ 127595 │ 10 │ ╰────┴───────────────────────────────────────────────────────┴────────┴────────┴───────╯ ``` `entry #x` will be each command you type in the repl (i think). so, it may be good to filter those out sometimes. ``` > view files | where filename !~ entry ╭───┬───────────────────────────────────────────────────────┬────────┬────────┬───────╮ │ # │ filename │ start │ end │ size │ ├───┼───────────────────────────────────────────────────────┼────────┼────────┼───────┤ │ 0 │ source │ 0 │ 2 │ 2 │ │ 1 │ Host Environment Variables │ 2 │ 6034 │ 6032 │ │ 2 │ C:\Users\a_username\AppData\Roaming\nushell\plugin.nu │ 6034 │ 31236 │ 25202 │ │ 3 │ C:\Users\a_username\AppData\Roaming\nushell\env.nu │ 31236 │ 44961 │ 13725 │ │ 4 │ C:\Users\a_username\AppData\Roaming\nushell\config.nu │ 44961 │ 76134 │ 31173 │ │ 5 │ defs.nu │ 76134 │ 91944 │ 15810 │ │ 6 │ prompt\oh-my.nu │ 91944 │ 111523 │ 19579 │ │ 7 │ weather\get-weather.nu │ 111523 │ 125556 │ 14033 │ │ 8 │ .zoxide.nu │ 125556 │ 127504 │ 1948 │ │ 9 │ source │ 127504 │ 127561 │ 57 │ ╰───┴───────────────────────────────────────────────────────┴────────┴────────┴───────╯ ``` # User-Facing Changes I renamed `view-source` to `view source` just to make a group of commands. No functionality has changed in `view source`. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. --- crates/nu-command/src/core_commands/debug.rs | 2 +- crates/nu-command/src/default_context.rs | 5 +- crates/nu-command/src/experimental/mod.rs | 6 ++ crates/nu-command/src/experimental/view.rs | 49 +++++++++++++ .../nu-command/src/experimental/view_files.rs | 70 +++++++++++++++++++ .../src/experimental/view_source.rs | 16 ++--- .../nu-command/src/experimental/view_span.rs | 68 ++++++++++++++++++ crates/nu-protocol/src/signature.rs | 38 +++++----- 8 files changed, 226 insertions(+), 28 deletions(-) create mode 100644 crates/nu-command/src/experimental/view.rs create mode 100644 crates/nu-command/src/experimental/view_files.rs create mode 100644 crates/nu-command/src/experimental/view_span.rs diff --git a/crates/nu-command/src/core_commands/debug.rs b/crates/nu-command/src/core_commands/debug.rs index bd4f07b97..255bbc0b5 100644 --- a/crates/nu-command/src/core_commands/debug.rs +++ b/crates/nu-command/src/core_commands/debug.rs @@ -24,7 +24,7 @@ impl Command for Debug { (Type::Table(vec![]), Type::List(Box::new(Type::String))), (Type::Any, Type::String), ]) - .category(Category::Core) + .category(Category::Debug) .switch("raw", "Prints the raw value representation", Some('r')) } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 37e343ac0..a27a462bb 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -473,8 +473,11 @@ pub fn create_default_context() -> EngineState { // Experimental bind_command! { - ViewSource, IsAdmin, + View, + ViewFiles, + ViewSource, + ViewSpan, }; // Deprecated diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index b912a315a..534329067 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -1,5 +1,11 @@ mod is_admin; +mod view; +mod view_files; mod view_source; +mod view_span; pub use is_admin::IsAdmin; +pub use view::View; +pub use view_files::ViewFiles; pub use view_source::ViewSource; +pub use view_span::ViewSpan; diff --git a/crates/nu-command/src/experimental/view.rs b/crates/nu-command/src/experimental/view.rs new file mode 100644 index 000000000..387cf5482 --- /dev/null +++ b/crates/nu-command/src/experimental/view.rs @@ -0,0 +1,49 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, +}; + +#[derive(Clone)] +pub struct View; + +impl Command for View { + fn name(&self) -> &str { + "view" + } + + fn signature(&self) -> Signature { + Signature::build("view") + .category(Category::Debug) + .input_output_types(vec![(Type::Nothing, Type::String)]) + } + + fn usage(&self) -> &str { + "Various commands for viewing debug information" + } + + fn extra_usage(&self) -> &str { + "You must use one of the following subcommands. Using this command as-is will only produce this help message." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help( + &View.signature(), + &View.examples(), + engine_state, + stack, + self.is_parser_keyword(), + ), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/experimental/view_files.rs b/crates/nu-command/src/experimental/view_files.rs new file mode 100644 index 000000000..5ccd2a4d5 --- /dev/null +++ b/crates/nu-command/src/experimental/view_files.rs @@ -0,0 +1,70 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value, +}; + +#[derive(Clone)] +pub struct ViewFiles; + +impl Command for ViewFiles { + fn name(&self) -> &str { + "view files" + } + + fn usage(&self) -> &str { + "View the files registered in nushell's EngineState memory" + } + + fn extra_usage(&self) -> &str { + "These are files parsed and loaded at runtime." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("view files") + .input_output_types(vec![(Type::Nothing, Type::String)]) + .category(Category::Debug) + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let mut records = vec![]; + + for (file, start, end) in engine_state.files() { + records.push(Value::Record { + cols: vec![ + "filename".to_string(), + "start".to_string(), + "end".to_string(), + "size".to_string(), + ], + vals: vec![ + Value::string(file, call.head), + Value::int(*start as i64, call.head), + Value::int(*end as i64, call.head), + Value::int(*end as i64 - *start as i64, call.head), + ], + span: call.head, + }); + } + + Ok(Value::List { + vals: records, + span: call.head, + } + .into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "View the files registered in nushell's EngineState memory", + example: r#"view files"#, + result: None, + }] + } +} diff --git a/crates/nu-command/src/experimental/view_source.rs b/crates/nu-command/src/experimental/view_source.rs index bb7167d14..b4e63d258 100644 --- a/crates/nu-command/src/experimental/view_source.rs +++ b/crates/nu-command/src/experimental/view_source.rs @@ -12,7 +12,7 @@ pub struct ViewSource; impl Command for ViewSource { fn name(&self) -> &str { - "view-source" + "view source" } fn usage(&self) -> &str { @@ -20,10 +20,10 @@ impl Command for ViewSource { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("view-source") + Signature::build("view source") .input_output_types(vec![(Type::Nothing, Type::String)]) .required("item", SyntaxShape::Any, "name or block to view") - .category(Category::Core) + .category(Category::Debug) } fn run( @@ -166,27 +166,27 @@ impl Command for ViewSource { vec![ Example { description: "View the source of a code block", - example: r#"let abc = { echo 'hi' }; view-source $abc"#, + example: r#"let abc = { echo 'hi' }; view source $abc"#, result: Some(Value::test_string("{ echo 'hi' }")), }, Example { description: "View the source of a custom command", - example: r#"def hi [] { echo 'Hi!' }; view-source hi"#, + example: r#"def hi [] { echo 'Hi!' }; view source hi"#, result: Some(Value::test_string("{ echo 'Hi!' }")), }, Example { description: "View the source of a custom command, which participates in the caller environment", - example: r#"def-env foo [] { let-env BAR = 'BAZ' }; view-source foo"#, + example: r#"def-env foo [] { let-env BAR = 'BAZ' }; view source foo"#, result: Some(Value::test_string("{ let-env BAR = 'BAZ' }")), }, Example { description: "View the source of a module", - example: r#"module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } }; view-source mod-foo"#, + example: r#"module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } }; view source mod-foo"#, result: Some(Value::test_string(" export-env { let-env FOO_ENV = 'BAZ' }")), }, Example { description: "View the source of an alias", - example: r#"alias hello = echo hi; view-source hello"#, + example: r#"alias hello = echo hi; view source hello"#, result: Some(Value::test_string("echo hi")), }, ] diff --git a/crates/nu-command/src/experimental/view_span.rs b/crates/nu-command/src/experimental/view_span.rs new file mode 100644 index 000000000..0423a90a6 --- /dev/null +++ b/crates/nu-command/src/experimental/view_span.rs @@ -0,0 +1,68 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct ViewSpan; + +impl Command for ViewSpan { + fn name(&self) -> &str { + "view span" + } + + fn usage(&self) -> &str { + "View the contents of a span" + } + + fn extra_usage(&self) -> &str { + "This command is meant for debugging purposes.\nIt allows you to view the contents of nushell spans.\nOne way to get spans is to pipe something into 'debug --raw'.\nThen you can use the Span { start, end } values as the start and end values for this command." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("view span") + .input_output_types(vec![(Type::Nothing, Type::String)]) + .required("start", SyntaxShape::Int, "start of the span") + .required("end", SyntaxShape::Int, "end of the span") + .category(Category::Debug) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let start_span: Spanned = call.req(engine_state, stack, 0)?; + let end_span: Spanned = call.req(engine_state, stack, 1)?; + + if start_span.item < end_span.item { + let bin_contents = + engine_state.get_span_contents(&Span::new(start_span.item, end_span.item)); + Ok( + Value::string(String::from_utf8_lossy(bin_contents), call.head) + .into_pipeline_data(), + ) + } else { + Err(ShellError::GenericError( + "Cannot view span".to_string(), + "this start and end does not correspond to a viewable value".to_string(), + Some(call.head), + None, + Vec::new(), + )) + } + } + + fn examples(&self) -> Vec { + vec![Example { + description: "View the source of a span. 1 and 2 are just example values. Use the return of debug -r to get the actual values", + example: r#"some | pipeline | or | variable | debug -r; view span 1 2"#, + result: None, + }] + } +} diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 964e0ae04..491ba06ad 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -40,61 +40,63 @@ pub struct PositionalArg { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Category { - Default, - Conversions, - Core, Bits, Bytes, + Chart, + Conversions, + Core, + Custom(String), Date, + Debug, + Default, + Deprecated, Env, Experimental, FileSystem, Filters, Formats, + Generators, + Hash, Math, Misc, Network, - Random, Platform, + Random, Shells, Strings, System, Viewers, - Hash, - Generators, - Chart, - Custom(String), - Deprecated, } impl std::fmt::Display for Category { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let msg = match self { - Category::Default => "default", + Category::Bits => "bits", + Category::Bytes => "bytes", + Category::Chart => "chart", Category::Conversions => "conversions", Category::Core => "core", + Category::Custom(name) => name, Category::Date => "date", + Category::Debug => "debug", + Category::Default => "default", + Category::Deprecated => "deprecated", Category::Env => "env", Category::Experimental => "experimental", Category::FileSystem => "filesystem", Category::Filters => "filters", Category::Formats => "formats", + Category::Generators => "generators", + Category::Hash => "hash", Category::Math => "math", Category::Misc => "misc", Category::Network => "network", - Category::Random => "random", Category::Platform => "platform", + Category::Random => "random", Category::Shells => "shells", Category::Strings => "strings", Category::System => "system", Category::Viewers => "viewers", - Category::Hash => "hash", - Category::Generators => "generators", - Category::Chart => "chart", - Category::Custom(name) => name, - Category::Deprecated => "deprecated", - Category::Bytes => "bytes", - Category::Bits => "bits", }; write!(f, "{msg}")