Change help commands to use name from scope instead of the name from the declaration (#14490)

# 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
This commit is contained in:
132ikl 2024-12-10 10:27:30 -05:00 committed by GitHub
parent 7d2e8875e0
commit 8f9aa1a250
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 45 deletions

View File

@ -87,21 +87,10 @@ pub fn help_commands(
name.push_str(&r.item);
}
let output = engine_state
.get_decls_sorted(false)
.into_iter()
.filter_map(|(_, decl_id)| {
let decl = engine_state.get_decl(decl_id);
(decl.name() == name).then_some(decl)
})
.map(|cmd| get_full_help(cmd, engine_state, stack))
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(
Value::string(output.join("======================\n\n"), call.head)
.into_pipeline_data(),
)
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)),

View File

@ -107,21 +107,10 @@ pub fn help_externs(
name.push_str(&r.item);
}
let output = engine_state
.get_decls_sorted(false)
.into_iter()
.filter_map(|(_, decl_id)| {
let decl = engine_state.get_decl(decl_id);
(decl.name() == name).then_some(decl)
})
.map(|cmd| get_full_help(cmd, engine_state, stack))
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(
Value::string(output.join("======================\n\n"), call.head)
.into_pipeline_data(),
)
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)),

View File

@ -20,12 +20,11 @@ pub(crate) fn compile_call(
// 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,
);
let name = working_set
.find_decl_name(call.decl_id) // check for name in scope
.and_then(|name| std::str::from_utf8(name).ok())
.unwrap_or(decl.name()); // fall back to decl's name
return compile_help(working_set, builder, name.into_spanned(call.head), io_reg);
}
// Try to figure out if this is a keyword call like `if`, and handle those specially

View File

@ -459,21 +459,48 @@ impl<'a> StateWorkingSet<'a> {
}
// check overlay in perma
for overlay_frame in self
.permanent_state
.active_overlays(&removed_overlays)
.rev()
{
visibility.append(&overlay_frame.visibility);
self.permanent_state.find_decl(name, &removed_overlays)
}
pub fn find_decl_name(&self, decl_id: DeclId) -> Option<&[u8]> {
let mut removed_overlays = vec![];
let mut visibility: Visibility = Visibility::new();
for scope_frame in self.delta.scope.iter().rev() {
if self.search_predecls {
for (name, id) in scope_frame.predecls.iter() {
if id == &decl_id {
return Some(name);
}
}
}
// check overlay in delta
for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() {
visibility.append(&overlay_frame.visibility);
if self.search_predecls {
for (name, id) in overlay_frame.predecls.iter() {
if id == &decl_id {
return Some(name);
}
}
}
if let Some(decl_id) = overlay_frame.get_decl(name) {
if visibility.is_decl_id_visible(&decl_id) {
return Some(decl_id);
for (name, id) in overlay_frame.decls.iter() {
if id == &decl_id {
return Some(name);
}
}
}
}
}
None
// check overlay in perma
self.permanent_state
.find_decl_name(decl_id, &removed_overlays)
}
pub fn find_module(&self, name: &[u8]) -> Option<ModuleId> {