mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 22:47:43 +02:00
refactor(completion, parser): move custom_completion info from Expression to Signature (#15613)
Restricts custom completion from universal to internal arguments only. Pros: 1. Less memory 2. More flexible for later customizations, e.g. #14923 Cons: 1. limited customization capabilities, but at least covers all currently existing features in nushell. # Description Mostly vibe coded by [Zed AI](https://zed.dev/ai) with a single prompt. LGTM, but I'm not so sure @ysthakur # User-Facing Changes Hopefully none. # Tests + Formatting +3 # After Submitting --------- Co-authored-by: Yash Thakur <45539777+ysthakur@users.noreply.github.com>
This commit is contained in:
@ -348,8 +348,43 @@ impl NuCompleter {
|
||||
for (arg_idx, arg) in call.arguments.iter().enumerate() {
|
||||
let span = arg.span();
|
||||
if span.contains(pos) {
|
||||
// if customized completion specified, it has highest priority
|
||||
if let Some(decl_id) = arg.expr().and_then(|e| e.custom_completion) {
|
||||
// Get custom completion from PositionalArg or Flag
|
||||
let custom_completion_decl_id = {
|
||||
// Check PositionalArg or Flag from Signature
|
||||
let signature = working_set.get_decl(call.decl_id).signature();
|
||||
|
||||
match arg {
|
||||
// For named arguments, check Flag
|
||||
Argument::Named((name, short, value)) => {
|
||||
if value.as_ref().is_none_or(|e| !e.span.contains(pos)) {
|
||||
None
|
||||
} else {
|
||||
// If we're completing the value of the flag,
|
||||
// search for the matching custom completion decl_id (long or short)
|
||||
let flag =
|
||||
signature.get_long_flag(&name.item).or_else(|| {
|
||||
short.as_ref().and_then(|s| {
|
||||
signature.get_short_flag(
|
||||
s.item.chars().next().unwrap_or('_'),
|
||||
)
|
||||
})
|
||||
});
|
||||
flag.and_then(|f| f.custom_completion)
|
||||
}
|
||||
}
|
||||
// For positional arguments, check PositionalArg
|
||||
Argument::Positional(_) => {
|
||||
// Find the right positional argument by index
|
||||
let arg_pos = positional_arg_indices.len();
|
||||
signature
|
||||
.get_positional(arg_pos)
|
||||
.and_then(|pos_arg| pos_arg.custom_completion)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(decl_id) = custom_completion_decl_id {
|
||||
// for `--foo <tab>` and `--foo=<tab>`, the arg span should be trimmed
|
||||
let (new_span, prefix) = if matches!(arg, Argument::Named(_)) {
|
||||
strip_placeholder_with_rsplit(
|
||||
|
@ -97,8 +97,11 @@ fn extern_completer() -> NuCompleter {
|
||||
// Add record value as example
|
||||
let record = r#"
|
||||
def animals [] { [ "cat", "dog", "eel" ] }
|
||||
def fruits [] { [ "apple", "banana" ] }
|
||||
extern spam [
|
||||
animal: string@animals
|
||||
fruit?: string@fruits
|
||||
...rest: string@animals
|
||||
--foo (-f): string@animals
|
||||
-b: string@animals
|
||||
]
|
||||
@ -2261,6 +2264,22 @@ fn extern_custom_completion_positional(mut extern_completer: NuCompleter) {
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_optional(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam foo -f bar ", 16);
|
||||
let expected: Vec<_> = vec!["apple", "banana"];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_rest(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam foo -f bar baz ", 20);
|
||||
let expected: Vec<_> = vec!["cat", "dog", "eel"];
|
||||
match_suggestions(&expected, &suggestions);
|
||||
let suggestions = extern_completer.complete("spam foo -f bar baz qux ", 24);
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_custom_completion_long_flag_1(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --foo=", 11);
|
||||
@ -2289,6 +2308,17 @@ fn extern_custom_completion_short_flag(mut extern_completer: NuCompleter) {
|
||||
match_suggestions(&expected, &suggestions);
|
||||
}
|
||||
|
||||
/// When we're completing the flag name itself, not its value,
|
||||
/// custom completions should not be used
|
||||
#[rstest]
|
||||
fn custom_completion_flag_name_not_value(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam --f", 8);
|
||||
match_suggestions(&vec!["--foo"], &suggestions);
|
||||
// Also test with partial short flag
|
||||
let suggestions = extern_completer.complete("spam -f", 7);
|
||||
match_suggestions(&vec!["-f"], &suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn extern_complete_flags(mut extern_completer: NuCompleter) {
|
||||
let suggestions = extern_completer.complete("spam -", 6);
|
||||
|
Reference in New Issue
Block a user