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
This commit is contained in:
zc he
2025-07-15 09:35:36 +08:00
committed by GitHub
parent 316d2d6af2
commit a506d3f9b5
2 changed files with 45 additions and 36 deletions

View File

@ -370,7 +370,8 @@ impl NuCompleter {
FileCompletion, 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; break;
} }
@ -384,7 +385,10 @@ impl NuCompleter {
}; };
self.process_completion(&mut flag_completions, &ctx) self.process_completion(&mut flag_completions, &ctx)
}; };
suggestions.extend(match arg { // Prioritize argument completions over (sub)commands
suggestions.splice(
0..0,
match arg {
// flags // flags
Argument::Named(_) | Argument::Unknown(_) Argument::Named(_) | Argument::Unknown(_)
if prefix.starts_with(b"-") => if prefix.starts_with(b"-") =>
@ -392,7 +396,9 @@ impl NuCompleter {
flag_completion_helper() flag_completion_helper()
} }
// only when `strip` == false // only when `strip` == false
Argument::Positional(_) if prefix == b"-" => flag_completion_helper(), Argument::Positional(_) if prefix == b"-" => {
flag_completion_helper()
}
// complete according to expression type and command head // complete according to expression type and command head
Argument::Positional(expr) => { Argument::Positional(expr) => {
let command_head = working_set.get_decl(call.decl_id).name(); let command_head = working_set.get_decl(call.decl_id).name();
@ -410,7 +416,8 @@ impl NuCompleter {
) )
} }
_ => vec![], _ => vec![],
}); },
);
break; break;
} else if !matches!(arg, Argument::Named(_)) { } else if !matches!(arg, Argument::Named(_)) {
positional_arg_indices.push(arg_idx); positional_arg_indices.push(arg_idx);
@ -462,7 +469,8 @@ impl NuCompleter {
if let Some(external_result) = if let Some(external_result) =
self.external_completion(closure, &text_spans, offset, new_span) 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; return suggestions;
} }
} }
@ -471,8 +479,7 @@ impl NuCompleter {
let (new_span, prefix) = let (new_span, prefix) =
strip_placeholder_if_any(working_set, &span, strip); strip_placeholder_if_any(working_set, &span, strip);
let ctx = Context::new(working_set, new_span, prefix, offset); let ctx = Context::new(working_set, new_span, prefix, offset);
suggestions.extend(self.process_completion(&mut FileCompletion, &ctx)); return self.process_completion(&mut FileCompletion, &ctx);
return suggestions;
} }
break; break;
} }

View File

@ -309,10 +309,10 @@ fn custom_arguments_and_subcommands() {
let suggestions = completer.complete(completion_str, completion_str.len()); let suggestions = completer.complete(completion_str, completion_str.len());
// including both subcommand and directory completions // including both subcommand and directory completions
let expected = [ let expected = [
"foo test bar".into(),
folder("test_a"), folder("test_a"),
file("test_a_symlink"), file("test_a_symlink"),
folder("test_b"), folder("test_b"),
"foo test bar".into(),
]; ];
match_suggestions_by_string(&expected, &suggestions); match_suggestions_by_string(&expected, &suggestions);
} }
@ -330,7 +330,7 @@ fn custom_flags_and_subcommands() {
let completion_str = "foo --test"; let completion_str = "foo --test";
let suggestions = completer.complete(completion_str, completion_str.len()); let suggestions = completer.complete(completion_str, completion_str.len());
// including both flag and directory completions // 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); match_suggestions(&expected, &suggestions);
} }
@ -1480,11 +1480,12 @@ fn command_watch_with_filecompletion() {
match_suggestions(&expected_paths, &suggestions) match_suggestions(&expected_paths, &suggestions)
} }
#[rstest] #[test]
fn subcommand_completions() { fn subcommand_vs_external_completer() {
let (_, _, mut engine, mut stack) = new_engine(); let (_, _, mut engine, mut stack) = new_engine();
let commands = r#" let commands = r#"
$env.config.completions.algorithm = "fuzzy" $env.config.completions.algorithm = "fuzzy"
$env.config.completions.external.completer = {|spans| ["external"]}
def foo-test-command [] {} def foo-test-command [] {}
def "foo-test-command bar" [] {} def "foo-test-command bar" [] {}
def "foo-test-command aagap bcr" [] {} def "foo-test-command aagap bcr" [] {}
@ -1497,6 +1498,7 @@ fn subcommand_completions() {
let suggestions = subcommand_completer.complete(prefix, prefix.len()); let suggestions = subcommand_completer.complete(prefix, prefix.len());
match_suggestions( match_suggestions(
&vec![ &vec![
"external",
"food bar", "food bar",
"foo-test-command bar", "foo-test-command bar",
"foo-test-command aagap bcr", "foo-test-command aagap bcr",
@ -1506,7 +1508,7 @@ fn subcommand_completions() {
let prefix = "foot bar"; let prefix = "foot bar";
let suggestions = subcommand_completer.complete(prefix, prefix.len()); 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] #[test]