From f9febba2b0e9c9c2af8da0f8f65c95b4aa88b29e Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 14 Dec 2024 08:30:34 +0100 Subject: [PATCH] Fix replacing completions with a -foo prefix Fixes #10904 --- src/complete.rs | 17 ++++++++++++----- src/wchar_ext.rs | 4 ++++ tests/checks/complete.fish | 6 ++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/complete.rs b/src/complete.rs index 752a57b06..e7809df57 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -1284,14 +1284,14 @@ impl<'ctx> Completer<'ctx> { // Check if we are entering a combined option and argument (like --color=auto or // -I/usr/include). for o in &options { - let arg = if o.typ == CompleteOptionType::Short { + let arg_offset = if o.typ == CompleteOptionType::Short { let Some(short_opt_pos) = short_opt_pos else { continue; }; if o.option.char_at(0) != s.char_at(short_opt_pos) { continue; } - Some(s.slice_from(short_opt_pos + 1)) + Some(short_opt_pos + 1) } else { param_match2(o, s) }; @@ -1304,7 +1304,7 @@ impl<'ctx> Completer<'ctx> { .get_or_insert(o.result_mode.requires_param) &= o.result_mode.requires_param; } - if let Some(arg) = arg { + if let Some(arg_offset) = arg_offset { if o.result_mode.requires_param { use_common = false; } @@ -1314,7 +1314,14 @@ impl<'ctx> Completer<'ctx> { if o.result_mode.force_files { has_force = true; } + let (arg_prefix, arg) = s.split_once(arg_offset); + let first_new = self.completions.completions.len(); self.complete_from_args(arg, &o.comp, o.localized_desc(), o.flags); + for compl in &mut self.completions.completions[first_new..] { + if compl.replaces_token() { + compl.completion.insert_utfstr(0, arg_prefix); + } + } } } } @@ -2213,7 +2220,7 @@ fn param_match(e: &CompleteEntryOpt, optstr: &wstr) -> bool { } /// Test if a string is an option with an argument, like --color=auto or -I/usr/include. -fn param_match2<'s>(e: &CompleteEntryOpt, optstr: &'s wstr) -> Option<&'s wstr> { +fn param_match2(e: &CompleteEntryOpt, optstr: &wstr) -> Option { // We may get a complete_entry_opt_t with no options if it's just arguments. if e.option.is_empty() { return None; @@ -2238,7 +2245,7 @@ fn param_match2<'s>(e: &CompleteEntryOpt, optstr: &'s wstr) -> Option<&'s wstr> return None; } cursor += 1; - Some(optstr.slice_from(cursor)) + Some(cursor) } /// Parses a token of short options plus one optional parameter like diff --git a/src/wchar_ext.rs b/src/wchar_ext.rs index 8095f1f1f..e155f61f9 100644 --- a/src/wchar_ext.rs +++ b/src/wchar_ext.rs @@ -244,6 +244,10 @@ pub trait WExt { } } + fn split_once(&self, pos: usize) -> (&wstr, &wstr) { + (self.slice_to(pos), self.slice_from(pos)) + } + /// Returns the index of the first match against the provided substring or `None`. fn find(&self, search: impl AsRef<[char]>) -> Option { subslice_position(self.as_char_slice(), search.as_ref()) diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index cbdbf7f4d..1db09fd59 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -133,6 +133,12 @@ complete -C'foo -y' | string match -- -y-single-long # CHECK: -zARGZ complete -C'foo -z' +function foo2; end +complete -c foo2 -s s -l long -xa "hello-world goodbye-friend" +complete -C"foo2 -sfrie" +# CHECK: -sgoodbye-friend +complete -C"foo2 --long=frien" +# CHECK: --long=goodbye-friend # Builtins (with subcommands; #2705) complete -c complete_test_subcommand -n 'test (commandline -xp)[1] = complete_test_subcommand' -xa ok