From 3d0b1ef1ce60aeefe29a14a77d32dde06d67bd4e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 10:05:19 -0500 Subject: [PATCH] Highlight help tutor (#838) * WIP * Syntax highlight help, add tutor --- crates/nu-cli/src/lib.rs | 2 + crates/nu-cli/src/nu_highlight.rs | 63 +++ .../src/conversions/into/command.rs | 4 +- crates/nu-command/src/core_commands/export.rs | 3 +- crates/nu-command/src/core_commands/help.rs | 4 +- crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/core_commands/tutor.rs | 466 ++++++++++++++++++ .../nu-command/src/dataframe/eager/command.rs | 9 +- crates/nu-command/src/date/command.rs | 4 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/hash/command.rs | 4 +- crates/nu-command/src/math/command.rs | 3 +- crates/nu-command/src/network/url/command.rs | 4 +- crates/nu-command/src/path/command.rs | 3 +- crates/nu-command/src/random/command.rs | 3 +- .../nu-command/src/strings/split/command.rs | 3 +- .../src/strings/str_/case/command.rs | 4 +- crates/nu-engine/src/documentation.rs | 76 ++- crates/nu-engine/src/eval.rs | 7 +- src/main.rs | 9 + 20 files changed, 640 insertions(+), 34 deletions(-) create mode 100644 crates/nu-cli/src/nu_highlight.rs create mode 100644 crates/nu-command/src/core_commands/tutor.rs diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 20fbe2e77f..c00d104f2c 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,11 +1,13 @@ mod completions; mod errors; +mod nu_highlight; mod prompt; mod syntax_highlight; mod validation; pub use completions::NuCompleter; pub use errors::CliError; +pub use nu_highlight::NuHighlight; pub use prompt::NushellPrompt; pub use syntax_highlight::NuHighlighter; pub use validation::NuValidator; diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs new file mode 100644 index 0000000000..2e96d9616b --- /dev/null +++ b/crates/nu-cli/src/nu_highlight.rs @@ -0,0 +1,63 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value}; +use reedline::Highlighter; + +#[derive(Clone)] +pub struct NuHighlight; + +impl Command for NuHighlight { + fn name(&self) -> &str { + "nu-highlight" + } + + fn signature(&self) -> Signature { + Signature::build("nu-highlight").category(Category::Strings) + } + + fn usage(&self) -> &str { + "Syntax highlight the input string." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + let config = stack.get_config()?; + + let highlighter = crate::NuHighlighter { + engine_state, + config, + }; + + input.map( + move |x| match x.as_string() { + Ok(line) => { + let highlights = highlighter.highlight(&line); + + Value::String { + val: highlights.render_simple(), + span: head, + } + } + Err(err) => Value::Error { error: err }, + }, + ctrlc, + ) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Describe the type of a string", + example: "'let x = 3' | nu-highlight", + result: None, + }] + } +} diff --git a/crates/nu-command/src/conversions/into/command.rs b/crates/nu-command/src/conversions/into/command.rs index 9fa1ec600d..3344930d6b 100644 --- a/crates/nu-command/src/conversions/into/command.rs +++ b/crates/nu-command/src/conversions/into/command.rs @@ -24,12 +24,12 @@ impl Command for Into { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Into.signature(), &[], engine_state), + val: get_full_help(&Into.signature(), &[], engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/core_commands/export.rs b/crates/nu-command/src/core_commands/export.rs index 93e3f91cda..b73337ea45 100644 --- a/crates/nu-command/src/core_commands/export.rs +++ b/crates/nu-command/src/core_commands/export.rs @@ -24,7 +24,7 @@ impl Command for ExportCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for ExportCommand { &ExportCommand.signature(), &ExportCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index f444fc0127..141a8a513a 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -210,7 +210,9 @@ fn help( let output = full_commands .iter() .filter(|(signature, _, _, _)| signature.name == name) - .map(|(signature, examples, _, _)| get_full_help(signature, examples, engine_state)) + .map(|(signature, examples, _, _)| { + get_full_help(signature, examples, engine_state, stack) + }) .collect::>(); if !output.is_empty() { diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 0bb930efc4..4488aa2f09 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -17,6 +17,7 @@ mod let_; mod metadata; mod module; mod source; +mod tutor; mod use_; mod version; @@ -39,6 +40,7 @@ pub use let_::Let; pub use metadata::Metadata; pub use module::Module; pub use source::Source; +pub use tutor::Tutor; pub use use_::Use; pub use version::Version; #[cfg(feature = "plugin")] diff --git a/crates/nu-command/src/core_commands/tutor.rs b/crates/nu-command/src/core_commands/tutor.rs new file mode 100644 index 0000000000..6d28767b71 --- /dev/null +++ b/crates/nu-command/src/core_commands/tutor.rs @@ -0,0 +1,466 @@ +use itertools::Itertools; +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, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct Tutor; + +impl Command for Tutor { + fn name(&self) -> &str { + "tutor" + } + + fn signature(&self) -> Signature { + Signature::build("tutor") + .optional( + "search", + SyntaxShape::String, + "item to search for, or 'list' to list available tutorials", + ) + .named( + "find", + SyntaxShape::String, + "Search tutorial for a phrase", + Some('f'), + ) + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Run the tutorial. To begin, run: tutor" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + tutor(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Begin the tutorial", + example: "tutor begin", + result: None, + }, + Example { + description: "Search a tutorial by phrase", + example: "tutor -f \"$in\"", + result: None, + }, + ] + } +} + +fn tutor( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let span = call.head; + + let search: Option = call.opt(engine_state, stack, 0).unwrap_or(None); + let find: Option = call.get_flag(engine_state, stack, "find")?; + + let search_space = [ + (vec!["begin"], begin_tutor()), + ( + vec!["table", "tables", "row", "rows", "column", "columns"], + table_tutor(), + ), + (vec!["cell", "cells"], cell_tutor()), + ( + vec![ + "expr", + "exprs", + "expressions", + "subexpression", + "subexpressions", + "sub-expression", + "sub-expressions", + ], + expression_tutor(), + ), + (vec!["echo"], echo_tutor()), + (vec!["each", "iteration", "iter"], each_tutor()), + ( + vec!["var", "vars", "variable", "variables"], + variable_tutor(), + ), + (vec!["engine-q", "e-q"], engineq_tutor()), + (vec!["block", "blocks"], block_tutor()), + (vec!["shorthand", "shorthands"], shorthand_tutor()), + ]; + + if let Some(find) = find { + let mut results = vec![]; + for search_group in search_space { + if search_group.1.contains(&find) { + results.push(search_group.0[0].to_string()) + } + } + + let message = format!("You can find '{}' in the following topics:\n{}\n\nYou can learn about a topic using `tutor` followed by the name of the topic.\nFor example: `tutor table` to open the table topic.\n\n", + find, + results.into_iter().map(|x| format!("- {}", x)).join("\n") + ); + + return Ok(display(&message, engine_state, stack, span)); + } else if let Some(search) = search { + for search_group in search_space { + if search_group.0.contains(&search.as_str()) { + return Ok(display(search_group.1, engine_state, stack, span)); + } + } + } + Ok(display(default_tutor(), engine_state, stack, span)) +} + +fn default_tutor() -> &'static str { + r#" +Welcome to the Nushell tutorial! + +With the `tutor` command, you'll be able to learn a lot about how Nushell +works along with many fun tips and tricks to speed up everyday tasks. + +To get started, you can use `tutor begin`. + +"# +} + +fn begin_tutor() -> &'static str { + r#" +Nushell is a structured shell and programming language. One way to begin +using it is to try a few of the commands. + +The first command to try is `ls`. The `ls` command will show you a list +of the files in the current directory. Notice that these files are shown +as a table. Each column of this table not only tells us what is being +shown, but also gives us a way to work with the data. + +You can combine the `ls` command with other commands using the pipeline +symbol '|'. This allows data to flow from one command to the next. + +For example, if we only wanted the name column, we could do: +``` +ls | select name +``` +Notice that we still get a table, but this time it only has one column: +the name column. + +You can continue to learn more about tables by running: +``` +tutor tables +``` +If at any point, you'd like to restart this tutorial, you can run: +``` +tutor begin +``` +"# +} + +fn table_tutor() -> &'static str { + r#" +The most common form of data in Nushell is the table. Tables contain rows and +columns of data. In each cell of the table, there is data that you can access +using Nushell commands. + +To get the 3rd row in the table, you can use the `nth` command: +``` +ls | nth 2 +``` +This will get the 3rd (note that `nth` is zero-based) row in the table created +by the `ls` command. You can use `nth` on any table created by other commands +as well. + +You can also access the column of data in one of two ways. If you want +to keep the column as part of a new table, you can use `select`. +``` +ls | select name +``` +This runs `ls` and returns only the "name" column of the table. + +If, instead, you'd like to get access to the values inside of the column, you +can use the `get` command. +``` +ls | get name +``` +This allows us to get to the list of strings that are the filenames rather +than having a full table. In some cases, this can make the names easier to +work with. + +You can continue to learn more about working with cells of the table by +running: +``` +tutor cells +``` +"# +} + +fn cell_tutor() -> &'static str { + r#" +Working with cells of data in the table is a key part of working with data in +Nushell. Because of this, there is a rich list of commands to work with cells +as well as handy shorthands for accessing cells. + +Cells can hold simple values like strings and numbers, or more complex values +like lists and tables. + +To reach a cell of data from a table, you can combine a row operation and a +column operation. +``` +ls | nth 4 | get name +``` +You can combine these operations into one step using a shortcut. +``` +(ls).4.name +``` +Names/strings represent columns names and numbers represent row numbers. + +The `(ls)` is a form of expression. You can continue to learn more about +expressions by running: +``` +tutor expressions +``` +You can also learn about these cell shorthands by running: +``` +tutor shorthands +``` +"# +} + +fn expression_tutor() -> &'static str { + r#" +Expressions give you the power to mix calls to commands with math. The +simplest expression is a single value like a string or number. +``` +3 +``` +Expressions can also include math operations like addition or division. +``` +10 / 2 +``` +Normally, an expression is one type of operation: math or commands. You can +mix these types by using subexpressions. Subexpressions are just like +expressions, but they're wrapped in parentheses `()`. +``` +10 * (3 + 4) +``` +Here we use parentheses to create a higher math precedence in the math +expression. +``` +echo (2 + 3) +``` +You can continue to learn more about the `echo` command by running: +``` +tutor echo +``` +"# +} + +fn echo_tutor() -> &'static str { + r#" +The `echo` command in Nushell is a powerful tool for not only seeing values, +but also for creating new ones. +``` +echo "Hello" +``` +You can echo output. This output, if it's not redirected using a "|" pipeline +will be displayed to the screen. +``` +echo 1..10 +``` +You can also use echo to work with individual values of a range. In this +example, `echo` will create the values from 1 to 10 as a list. +``` +echo 1 2 3 4 5 +``` +You can also create lists of values by passing `echo` multiple arguments. +This can be helpful if you want to later processes these values. + +The `echo` command can pair well with the `each` command which can run +code on each row, or item, of input. + +You can continue to learn more about the `each` command by running: +``` +tutor each +``` +"# +} + +fn each_tutor() -> &'static str { + r#" +The `each` command gives us a way of working with each individual row or +element of a list one at a time. It reads these in from the pipeline and +runs a block on each element. A block is a group of pipelines. +``` +echo 1 2 3 | each { $it + 10} +``` +This example iterates over each element sent by `echo`, giving us three new +values that are the original value + 10. Here, the `$it` is a variable that +is the name given to the block's parameter by default. + +You can learn more about blocks by running: +``` +tutor blocks +``` +You can also learn more about variables by running: +``` +tutor variables +``` +"# +} + +fn variable_tutor() -> &'static str { + r#" +Variables are an important way to store values to be used later. To create a +variable, you can use the `let` keyword. The `let` command will create a +variable and then assign it a value in one step. +``` +let $x = 3 +``` +Once created, we can refer to this variable by name. +``` +$x +``` +Nushell also comes with built-in variables. The `$nu` variable is a reserved +variable that contains a lot of information about the currently running +instance of Nushell. The `$it` variable is the name given to block parameters +if you don't specify one. And `$in` is the variable that allows you to work +with all of the data coming in from the pipeline in one place. + +"# +} + +fn block_tutor() -> &'static str { + r#" +Blocks are a special form of expression that hold code to be run at a later +time. Often, you'll see blocks as one of the arguments given to commands +like `each` and `if`. +``` +ls | each {|x| $x.name} +``` +The above will create a list of the filenames in the directory. +``` +if $true { echo "it's true" } { echo "it's not true" } +``` +This `if` call will run the first block if the expression is true, or the +second block if the expression is false. + +"# +} + +fn shorthand_tutor() -> &'static str { + r#" +You can access cells in a table using a shorthand notation sometimes called a +"column path" or "cell path". These paths allow you to go from a table to +rows, columns, or cells inside of the table. + +Shorthand paths are made from rows numbers, column names, or both. You can use +them on any variable or subexpression. +``` +$nu.cwd +``` +The above accesses the built-in `$nu` variable, gets its table, and then uses +the shorthand path to retrieve only the cell data inside the "cwd" column. +``` +(ls).name.4 +``` +This will retrieve the cell data in the "name" column on the 5th row (note: +row numbers are zero-based). + +Rows and columns don't need to come in any specific order. You can get the +same value using: +``` +(ls).4.name +``` +"# +} + +fn engineq_tutor() -> &'static str { + r#" +Engine-q is the upcoming engine for Nushell. Build for speed and correctness, +it also comes with a set of changes from Nushell versions prior to 0.60. To +get ready for engine-q look for some of these changes that might impact your +current scripts: + +* Engine-q now uses a few new data structures, including a record syntax + that allows you to model key-value pairs similar to JSON objects. +* Environment variables can now contain more than just strings. Structured + values are converted to strings for external commands using converters. +* `if` will now use an `else` keyword before the else block. +* We're moving from "config.toml" to "config.nu". This means startup will + now be a script file. +* `config` and its subcommands are being replaced by a record that you can + update in the shell which contains all the settings under the variable + `$config`. +* bigint/bigdecimal values are now machine i64 and f64 values +* And more, you can read more about upcoming changes in the up-to-date list + at: https://github.com/nushell/engine-q/issues/522 +"# +} + +fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span) -> PipelineData { + let help = help.split('`'); + + let mut build = String::new(); + let mut code_mode = false; + + for item in help { + if code_mode { + code_mode = false; + + //TODO: support no-color mode + if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + let decl = engine_state.get_decl(highlighter); + + if let Ok(output) = decl.run( + engine_state, + stack, + &Call::new(), + Value::String { + val: item.to_string(), + span: Span { start: 0, end: 0 }, + } + .into_pipeline_data(), + ) { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + build.push_str(&s); + } + _ => { + build.push_str(item); + } + } + } + } + } else { + code_mode = true; + build.push_str(item); + } + } + + Value::string(build, span).into_pipeline_data() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Tutor) + } +} diff --git a/crates/nu-command/src/dataframe/eager/command.rs b/crates/nu-command/src/dataframe/eager/command.rs index c51e67a640..f8ca63593e 100644 --- a/crates/nu-command/src/dataframe/eager/command.rs +++ b/crates/nu-command/src/dataframe/eager/command.rs @@ -24,12 +24,17 @@ impl Command for Dataframe { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Dataframe.signature(), &Dataframe.examples(), engine_state), + val: get_full_help( + &Dataframe.signature(), + &Dataframe.examples(), + engine_state, + stack, + ), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/date/command.rs b/crates/nu-command/src/date/command.rs index 86329ceb04..ed68f35b7b 100644 --- a/crates/nu-command/src/date/command.rs +++ b/crates/nu-command/src/date/command.rs @@ -34,13 +34,13 @@ impl Command for Date { fn date( engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, ) -> Result { let head = call.head; Ok(Value::String { - val: get_full_help(&Date.signature(), &Date.examples(), engine_state), + val: get_full_help(&Date.signature(), &Date.examples(), engine_state, stack), span: head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 5689afc0e9..13ce4b2daa 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -44,6 +44,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Metadata, Module, Source, + Tutor, Use, Version, }; diff --git a/crates/nu-command/src/hash/command.rs b/crates/nu-command/src/hash/command.rs index a0859dde2d..8a3b1d1938 100644 --- a/crates/nu-command/src/hash/command.rs +++ b/crates/nu-command/src/hash/command.rs @@ -22,12 +22,12 @@ impl Command for Hash { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Self.signature(), &Self.examples(), engine_state), + val: get_full_help(&Self.signature(), &Self.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/math/command.rs b/crates/nu-command/src/math/command.rs index 266a0cc166..ae2e13c186 100644 --- a/crates/nu-command/src/math/command.rs +++ b/crates/nu-command/src/math/command.rs @@ -24,7 +24,7 @@ impl Command for MathCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for MathCommand { &MathCommand.signature(), &MathCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/network/url/command.rs b/crates/nu-command/src/network/url/command.rs index 14cb545206..52ba716079 100644 --- a/crates/nu-command/src/network/url/command.rs +++ b/crates/nu-command/src/network/url/command.rs @@ -24,12 +24,12 @@ impl Command for Url { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Url.signature(), &Url.examples(), engine_state), + val: get_full_help(&Url.signature(), &Url.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/path/command.rs b/crates/nu-command/src/path/command.rs index 4a47c6cf84..b9af3b3949 100644 --- a/crates/nu-command/src/path/command.rs +++ b/crates/nu-command/src/path/command.rs @@ -39,7 +39,7 @@ the path literal."# fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -48,6 +48,7 @@ the path literal."# &PathCommand.signature(), &PathCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/random/command.rs b/crates/nu-command/src/random/command.rs index f7d0ece199..8e8f26129b 100644 --- a/crates/nu-command/src/random/command.rs +++ b/crates/nu-command/src/random/command.rs @@ -24,7 +24,7 @@ impl Command for RandomCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for RandomCommand { &RandomCommand.signature(), &RandomCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/strings/split/command.rs b/crates/nu-command/src/strings/split/command.rs index 60ab4f31a4..30d4c86908 100644 --- a/crates/nu-command/src/strings/split/command.rs +++ b/crates/nu-command/src/strings/split/command.rs @@ -24,7 +24,7 @@ impl Command for SplitCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for SplitCommand { &SplitCommand.signature(), &SplitCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/strings/str_/case/command.rs b/crates/nu-command/src/strings/str_/case/command.rs index ac43e6a9b0..cc12c404df 100644 --- a/crates/nu-command/src/strings/str_/case/command.rs +++ b/crates/nu-command/src/strings/str_/case/command.rs @@ -24,12 +24,12 @@ impl Command for Str { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Str.signature(), &Str.examples(), engine_state), + val: get_full_help(&Str.signature(), &Str.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index a7ff88c7d2..2f32fd3d3b 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -1,5 +1,9 @@ use itertools::Itertools; -use nu_protocol::{engine::EngineState, Example, Signature, Span, Value}; +use nu_protocol::{ + ast::Call, + engine::{EngineState, Stack}, + Example, IntoPipelineData, Signature, Span, Value, +}; use std::collections::HashMap; const COMMANDS_DOCS_DIR: &str = "docs/commands"; @@ -13,7 +17,12 @@ pub struct DocumentationConfig { brief: bool, } -fn generate_doc(name: &str, engine_state: &EngineState, head: Span) -> (Vec, Vec) { +fn generate_doc( + name: &str, + engine_state: &EngineState, + stack: &mut Stack, + head: Span, +) -> (Vec, Vec) { let mut cols = vec![]; let mut vals = vec![]; @@ -48,6 +57,7 @@ fn generate_doc(name: &str, engine_state: &EngineState, head: Span) -> (Vec (Vec Value { +pub fn generate_docs(engine_state: &EngineState, stack: &mut Stack, head: Span) -> Value { let signatures = engine_state.get_signatures(true); // cmap will map parent commands to it's subcommands e.g. to -> [to csv, to yaml, to bson] @@ -88,11 +98,11 @@ pub fn generate_docs(engine_state: &EngineState, head: Span) -> Value { if !cmap.contains_key(&sig.name) { continue; } - let mut row_entries = generate_doc(&sig.name, engine_state, head); + let mut row_entries = generate_doc(&sig.name, engine_state, stack, head); // Iterate over all the subcommands of the parent command let mut sub_table = Vec::new(); for sub_name in cmap.get(&sig.name).unwrap_or(&Vec::new()) { - let (cols, vals) = generate_doc(sub_name, engine_state, head); + let (cols, vals) = generate_doc(sub_name, engine_state, stack, head); sub_table.push(Value::Record { cols, vals, @@ -139,6 +149,7 @@ pub fn get_documentation( sig: &Signature, examples: &[Example], engine_state: &EngineState, + stack: &mut Stack, config: &DocumentationConfig, ) -> String { let cmd_name = &sig.name; @@ -206,14 +217,32 @@ pub fn get_documentation( long_desc.push_str(" "); long_desc.push_str(example.description); - // if config.no_color { - long_desc.push_str(&format!("\n > {}\n", example.example)); - // } else { - // let colored_example = + if config.no_color { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + let decl = engine_state.get_decl(highlighter); - // crate::shell::painter::Painter::paint_string(example.example, scope, &palette); - // long_desc.push_str(&format!("\n > {}\n", colored_example)); - // } + if let Ok(output) = decl.run( + engine_state, + stack, + &Call::new(), + Value::String { + val: example.example.to_string(), + span: Span { start: 0, end: 0 }, + } + .into_pipeline_data(), + ) { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + long_desc.push_str(&format!("\n > {}\n", s)); + } + _ => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } + } + } + } } long_desc.push('\n'); @@ -294,11 +323,17 @@ fn get_flags_section(signature: &Signature) -> String { long_desc } -pub fn get_brief_help(sig: &Signature, examples: &[Example], engine_state: &EngineState) -> String { +pub fn get_brief_help( + sig: &Signature, + examples: &[Example], + engine_state: &EngineState, + stack: &mut Stack, +) -> String { get_documentation( sig, examples, engine_state, + stack, &DocumentationConfig { no_subcommands: false, no_color: false, @@ -307,6 +342,17 @@ pub fn get_brief_help(sig: &Signature, examples: &[Example], engine_state: &Engi ) } -pub fn get_full_help(sig: &Signature, examples: &[Example], engine_state: &EngineState) -> String { - get_documentation(sig, examples, engine_state, &DocumentationConfig::default()) +pub fn get_full_help( + sig: &Signature, + examples: &[Example], + engine_state: &EngineState, + stack: &mut Stack, +) -> String { + get_documentation( + sig, + examples, + engine_state, + stack, + &DocumentationConfig::default(), + ) } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 49aa704d9e..52ae24030b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -33,7 +33,12 @@ fn eval_call( let decl = engine_state.get_decl(call.decl_id); if call.named.iter().any(|(flag, _)| flag.item == "help") { - let full_help = get_full_help(&decl.signature(), &decl.examples(), engine_state); + let full_help = get_full_help( + &decl.signature(), + &decl.examples(), + engine_state, + caller_stack, + ); Ok(Value::String { val: full_help, span: call.head, diff --git a/src/main.rs b/src/main.rs index 4d347ec07f..77be11b1ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,15 @@ fn main() -> Result<()> { let init_cwd = utils::get_init_cwd(); let mut engine_state = create_default_context(&init_cwd); + // Custom additions + let delta = { + let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(nu_cli::NuHighlight)); + + working_set.render() + }; + let _ = engine_state.merge_delta(delta, None, &init_cwd); + // TODO: make this conditional in the future // Ctrl-c protection section let ctrlc = Arc::new(AtomicBool::new(false));