From a506d3f9b5bef8ffdd1aa40ecfac29fe80dbc125 Mon Sep 17 00:00:00 2001 From: zc he Date: Tue, 15 Jul 2025 09:35:36 +0800 Subject: [PATCH] fix(completion): put argument completion results before commands (#16112) #16075 # Description # User-Facing Changes Improved experience: more meaningful items on top of the completion menu. Mostly for the fuzzy/substring matcher. # Tests + Formatting Adjusted # After Submitting --- crates/nu-cli/src/completions/completer.rs | 69 ++++++++++++---------- crates/nu-cli/tests/completions/mod.rs | 12 ++-- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index f6b1cdfdac..fef01df010 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -370,7 +370,8 @@ impl NuCompleter { FileCompletion, ); - suggestions.extend(self.process_completion(&mut completer, &ctx)); + // Prioritize argument completions over (sub)commands + suggestions.splice(0..0, self.process_completion(&mut completer, &ctx)); break; } @@ -384,33 +385,39 @@ impl NuCompleter { }; self.process_completion(&mut flag_completions, &ctx) }; - suggestions.extend(match arg { - // flags - Argument::Named(_) | Argument::Unknown(_) - if prefix.starts_with(b"-") => - { - flag_completion_helper() - } - // only when `strip` == false - Argument::Positional(_) if prefix == b"-" => flag_completion_helper(), - // complete according to expression type and command head - Argument::Positional(expr) => { - let command_head = working_set.get_decl(call.decl_id).name(); - positional_arg_indices.push(arg_idx); - self.argument_completion_helper( - PositionalArguments { - command_head, - positional_arg_indices, - arguments: &call.arguments, - expr, - }, - pos, - &ctx, - suggestions.is_empty(), - ) - } - _ => vec![], - }); + // Prioritize argument completions over (sub)commands + suggestions.splice( + 0..0, + match arg { + // flags + Argument::Named(_) | Argument::Unknown(_) + if prefix.starts_with(b"-") => + { + flag_completion_helper() + } + // only when `strip` == false + Argument::Positional(_) if prefix == b"-" => { + flag_completion_helper() + } + // complete according to expression type and command head + Argument::Positional(expr) => { + let command_head = working_set.get_decl(call.decl_id).name(); + positional_arg_indices.push(arg_idx); + self.argument_completion_helper( + PositionalArguments { + command_head, + positional_arg_indices, + arguments: &call.arguments, + expr, + }, + pos, + &ctx, + suggestions.is_empty(), + ) + } + _ => vec![], + }, + ); break; } else if !matches!(arg, Argument::Named(_)) { positional_arg_indices.push(arg_idx); @@ -462,7 +469,8 @@ impl NuCompleter { if let Some(external_result) = self.external_completion(closure, &text_spans, offset, new_span) { - suggestions.extend(external_result); + // Prioritize external results over (sub)commands + suggestions.splice(0..0, external_result); return suggestions; } } @@ -471,8 +479,7 @@ impl NuCompleter { let (new_span, prefix) = strip_placeholder_if_any(working_set, &span, strip); let ctx = Context::new(working_set, new_span, prefix, offset); - suggestions.extend(self.process_completion(&mut FileCompletion, &ctx)); - return suggestions; + return self.process_completion(&mut FileCompletion, &ctx); } break; } diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index c4bf802e22..6b90481930 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -309,10 +309,10 @@ fn custom_arguments_and_subcommands() { let suggestions = completer.complete(completion_str, completion_str.len()); // including both subcommand and directory completions let expected = [ - "foo test bar".into(), folder("test_a"), file("test_a_symlink"), folder("test_b"), + "foo test bar".into(), ]; match_suggestions_by_string(&expected, &suggestions); } @@ -330,7 +330,7 @@ fn custom_flags_and_subcommands() { let completion_str = "foo --test"; let suggestions = completer.complete(completion_str, completion_str.len()); // including both flag and directory completions - let expected: Vec<_> = vec!["foo --test bar", "--test"]; + let expected: Vec<_> = vec!["--test", "foo --test bar"]; match_suggestions(&expected, &suggestions); } @@ -1480,11 +1480,12 @@ fn command_watch_with_filecompletion() { match_suggestions(&expected_paths, &suggestions) } -#[rstest] -fn subcommand_completions() { +#[test] +fn subcommand_vs_external_completer() { let (_, _, mut engine, mut stack) = new_engine(); let commands = r#" $env.config.completions.algorithm = "fuzzy" + $env.config.completions.external.completer = {|spans| ["external"]} def foo-test-command [] {} def "foo-test-command bar" [] {} def "foo-test-command aagap bcr" [] {} @@ -1497,6 +1498,7 @@ fn subcommand_completions() { let suggestions = subcommand_completer.complete(prefix, prefix.len()); match_suggestions( &vec![ + "external", "food bar", "foo-test-command bar", "foo-test-command aagap bcr", @@ -1506,7 +1508,7 @@ fn subcommand_completions() { let prefix = "foot bar"; let suggestions = subcommand_completer.complete(prefix, prefix.len()); - match_suggestions(&vec!["foo-test-command bar"], &suggestions); + match_suggestions(&vec!["external", "foo-test-command bar"], &suggestions); } #[test]