Improve wrong flag help (#16427)

# Description

Currently, when Nushell encounters an unknown flag, it prints all
options in the help string. This is pretty verbose and uses the
`formatted_flags` signature method, which isn't used anywhere else. This
commit refactors the parser to use `did_you_mean` instead, which only
suggest one closest option or sends the user to `help` if nothing close
is found.


# User-Facing Changes (Bug fixes and other changes)

## Improved error messages for misspelled flags

Previously, the help text for a missing flag would list all of them,
which could get verbose on a single line:

```nushell
~> ls --full-path
Error: nu::parser::unknown_flag

  × The `ls` command doesn't have flag `full-path`.
   ╭─[entry #8:1:4]
 1 │ ls --full-path
   ·    ─────┬─────
   ·         ╰── unknown flag
   ╰────
  help: Available flags: --help(-h), --all(-a), --long(-l), --short-names(-s), --full-paths(-f), --du(-d), --directory(-D), --mime-type(-m), --threads(-t). Use
        `--help` for more information.
```

The new error message only suggests the closest flag:

```nushell
> ls --full-path
Error: nu::parser::unknown_flag

  × The `ls` command doesn't have flag `full-path`.
   ╭─[entry #23:1:4]
 1 │ ls --full-path
   ·    ─────┬─────
   ·         ╰── unknown flag
   ╰────
  help: Did you mean: `--full-paths`?
```


---

Closes #16418
This commit is contained in:
Andrej Kolčin
2025-08-13 11:25:18 +00:00
committed by GitHub
parent 79a6c78032
commit 7133a04e2f
3 changed files with 16 additions and 41 deletions

View File

@ -652,7 +652,7 @@ fn list_ignores_ansi() {
let actual = nu!(
cwd: dirs.test(), pipeline(
"
ls | find .txt | each {|| ls $in.name }
ls | find .txt | each {|| ls $in.name }
"
));
@ -661,14 +661,17 @@ fn list_ignores_ansi() {
}
#[test]
fn list_unknown_flag() {
fn list_unknown_long_flag() {
let actual = nu!("ls --full-path");
assert!(actual.err.contains("Did you mean: `--full-paths`?"));
}
#[test]
fn list_unknown_short_flag() {
let actual = nu!("ls -r");
assert!(
actual
.err
.contains("Available flags: --help(-h), --all(-a),")
);
assert!(actual.err.contains("Use `--help` to see available flags"));
}
#[test]

View File

@ -15,7 +15,7 @@ use nu_engine::DIR_VAR_PARSER_INFO;
use nu_protocol::{
BlockId, DeclId, DidYouMean, ENV_VARIABLE_ID, FilesizeUnit, Flag, IN_VARIABLE_ID, ParseError,
PositionalArg, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, VarId, ast::*,
casing::Casing, engine::StateWorkingSet, eval_const::eval_constant,
casing::Casing, did_you_mean, engine::StateWorkingSet, eval_const::eval_constant,
};
use std::{
collections::{HashMap, HashSet},
@ -641,11 +641,14 @@ fn parse_long_flag(
}
}
} else {
let suggestion = did_you_mean(sig.get_names(), &long_name)
.map(|name| format!("Did you mean: `--{name}`?"))
.unwrap_or("Use `--help` to see available flags".to_owned());
working_set.error(ParseError::UnknownFlag(
sig.name.clone(),
long_name.clone(),
arg_span,
sig.clone().formatted_flags(),
suggestion,
));
(
Some(Spanned {
@ -725,7 +728,7 @@ fn parse_short_flags(
sig.name.clone(),
format!("-{}", String::from_utf8_lossy(contents)),
*first,
sig.clone().formatted_flags(),
"Use `--help` to see available flags".to_owned(),
));
}

View File

@ -624,37 +624,6 @@ impl Signature {
examples,
})
}
pub fn formatted_flags(self) -> String {
if self.named.len() < 11 {
let mut s = "Available flags:".to_string();
for flag in self.named {
if let Some(short) = flag.short {
let _ = write!(s, " --{}(-{}),", flag.long, short);
} else {
let _ = write!(s, " --{},", flag.long);
}
}
s.remove(s.len() - 1);
let _ = write!(s, ". Use `--help` for more information.");
s
} else {
let mut s = "Some available flags:".to_string();
for flag in self.named {
if let Some(short) = flag.short {
let _ = write!(s, " --{}(-{}),", flag.long, short);
} else {
let _ = write!(s, " --{},", flag.long);
}
}
s.remove(s.len() - 1);
let _ = write!(
s,
"... Use `--help` for a full list of flags and more information."
);
s
}
}
}
#[derive(Clone)]