From 7133a04e2fbe7ecdbd9535c8e0220af28ae13e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrej=20Kol=C4=8Din?= Date: Wed, 13 Aug 2025 11:25:18 +0000 Subject: [PATCH] Improve wrong flag help (#16427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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 --- crates/nu-command/tests/commands/ls.rs | 17 ++++++++------ crates/nu-parser/src/parser.rs | 9 +++++--- crates/nu-protocol/src/signature.rs | 31 -------------------------- 3 files changed, 16 insertions(+), 41 deletions(-) diff --git a/crates/nu-command/tests/commands/ls.rs b/crates/nu-command/tests/commands/ls.rs index a8d602b53b..48158653db 100644 --- a/crates/nu-command/tests/commands/ls.rs +++ b/crates/nu-command/tests/commands/ls.rs @@ -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] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9c9522b474..91dc4720b5 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -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(), )); } diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 08d93b07ca..bdbd6d2d94 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -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)]