mirror of
https://github.com/nushell/nushell.git
synced 2025-04-29 23:54:26 +02:00
# Description Before this PR, `help commands` uses the name from a command's declaration rather than the name in the scope. This is problematic when trying to view the help page for the `main` command of a module. For example, `std bench`: ```nushell use std/bench help bench # => Error: nu::parser::not_found # => # => × Not found. # => ╭─[entry #10:1:6] # => 1 │ help bench # => · ──┬── # => · ╰── did not find anything under this name # => ╰──── ``` This can also cause confusion when importing specific commands from modules. Furthermore, if there are multiple commands with the same name from different modules, the help text for _both_ will appear when querying their help text (this is especially problematic for `main` commands, see #14033): ```nushell use std/iter help iter find # => Error: nu::parser::not_found # => # => × Not found. # => ╭─[entry #3:1:6] # => 1│ help iter find # => · ────┬──── # => · ╰── did not find anything under this name # => ╰──── help find # => Searches terms in the input. # => # => Search terms: filter, regex, search, condition # => # => Usage: # => > find {flags} ...(rest) # [...] # => Returns the first element of the list that matches the # => closure predicate, `null` otherwise # [...] # (full text omitted for brevity) ``` This PR changes `help commands` to use the name as it is in scope, so prefixing any command in scope with `help` will show the correct help text. ```nushell use std/bench help bench # [help text for std bench] use std/iter help iter find # [help text for std iter find] use std help std bench # [help text for std bench] help std iter find # [help text for std iter find] ``` Additionally, the IR code generation for commands called with the `--help` text has been updated to reflect this change. This does have one side effect: when a module has a `main` command defined, running `help <name>` (which checks `help aliases`, then `help commands`, then `help modules`) will show the help text for the `main` command rather than the module. The help text for the module is still accessible with `help modules <name>`. Fixes #10499, #10311, #11609, #13470, #14033, and #14402. Partially fixes #10707. Does **not** fix #11447. # User-Facing Changes * Help text for commands can be obtained by running `help <command name>`, where the command name is the same thing you would type in order to execute the command. Previously, it was the name of the function as written in the source file. * For example, for the following module `spam` with command `meow`: ```nushell module spam { # help text export def meow [] {} } ``` * Before this PR: * Regardless of how `meow` is `use`d, the help text is viewable by running `help meow`. * After this PR: * When imported with `use spam`: The `meow` command is executed by running `spam meow` and the `help` text is viewable by running `help spam meow`. * When imported with `use spam foo`: The `meow` command is executed by running `meow` and the `help` text is viewable by running `meow`. * When a module has a `main` command defined, `help <module name>` will return help for the main command, rather than the module. To access the help for the module, use `help modules <module name>`. # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting N/A
137 lines
4.2 KiB
Rust
137 lines
4.2 KiB
Rust
use crate::help::highlight_search_in_table;
|
|
use nu_color_config::StyleComputer;
|
|
use nu_engine::{command_prelude::*, get_full_help, scope::ScopeData};
|
|
|
|
#[derive(Clone)]
|
|
pub struct HelpExterns;
|
|
|
|
impl Command for HelpExterns {
|
|
fn name(&self) -> &str {
|
|
"help externs"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Show help on nushell externs."
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
Signature::build("help externs")
|
|
.category(Category::Core)
|
|
.rest(
|
|
"rest",
|
|
SyntaxShape::String,
|
|
"The name of extern to get help on.",
|
|
)
|
|
.named(
|
|
"find",
|
|
SyntaxShape::String,
|
|
"string to find in extern names and descriptions",
|
|
Some('f'),
|
|
)
|
|
.input_output_types(vec![(Type::Nothing, Type::table())])
|
|
.allow_variants_without_examples(true)
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "show all externs",
|
|
example: "help externs",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "show help for single extern",
|
|
example: "help externs smth",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "search for string in extern names and descriptions",
|
|
example: "help externs --find smth",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
help_externs(engine_state, stack, call)
|
|
}
|
|
}
|
|
|
|
pub fn help_externs(
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let head = call.head;
|
|
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
|
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
|
|
|
// 🚩The following two-lines are copied from filters/find.rs:
|
|
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").
|
|
let string_style = style_computer.compute("string", &Value::string("search result", head));
|
|
let highlight_style =
|
|
style_computer.compute("search_result", &Value::string("search result", head));
|
|
|
|
if let Some(f) = find {
|
|
let all_cmds_vec = build_help_externs(engine_state, stack, head);
|
|
let found_cmds_vec = highlight_search_in_table(
|
|
all_cmds_vec,
|
|
&f.item,
|
|
&["name", "description"],
|
|
&string_style,
|
|
&highlight_style,
|
|
)?;
|
|
|
|
return Ok(Value::list(found_cmds_vec, head).into_pipeline_data());
|
|
}
|
|
|
|
if rest.is_empty() {
|
|
let found_cmds_vec = build_help_externs(engine_state, stack, head);
|
|
Ok(Value::list(found_cmds_vec, head).into_pipeline_data())
|
|
} else {
|
|
let mut name = String::new();
|
|
|
|
for r in &rest {
|
|
if !name.is_empty() {
|
|
name.push(' ');
|
|
}
|
|
name.push_str(&r.item);
|
|
}
|
|
|
|
if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) {
|
|
let cmd = engine_state.get_decl(decl);
|
|
let help_text = get_full_help(cmd, engine_state, stack);
|
|
Ok(Value::string(help_text, call.head).into_pipeline_data())
|
|
} else {
|
|
Err(ShellError::CommandNotFound {
|
|
span: Span::merge_many(rest.iter().map(|s| s.span)),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_help_externs(engine_state: &EngineState, stack: &Stack, span: Span) -> Vec<Value> {
|
|
let mut scope = ScopeData::new(engine_state, stack);
|
|
scope.populate_decls();
|
|
scope.collect_externs(span)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
#[test]
|
|
fn test_examples() {
|
|
use super::HelpExterns;
|
|
use crate::test_examples;
|
|
test_examples(HelpExterns {})
|
|
}
|
|
}
|