From 4b2aba31eecf9a7675fd2a678e74dbcb936424a5 Mon Sep 17 00:00:00 2001 From: Wes Higbee Date: Sun, 17 May 2026 13:00:19 -0500 Subject: [PATCH] complete: tab-complete anywhere-position abbrs in non-command position Make abbreviations with `--position anywhere` appear in the completion menu when tabbing in argument position (e.g. `cat foo.log pg` shows pgr, pgrv, pjq, etc.), not just in command position. Lift "do_file" into an enum, so we don't have to check "is_redirection" twice, and can express more accurately the reason for not completing abbreviations after redirection. Co-authored-by: Johannes Altmanninger Closes #12764 --- CHANGELOG.rst | 1 + src/complete.rs | 40 +++++++++++++++++++++++++++----------- tests/checks/complete.fish | 16 +++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9d3a076b3..8f482eb5c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Interactive improvements - ``fish_hg_prompt``, ``fish_git_prompt`` and ``fish_fossil_prompt`` now strip control characters from VCS state read off disk, matching ``prompt_pwd``. - The sample informative and minimalist prompts now use ``prompt_pwd`` instead of printing ``$PWD`` directly. - ``bind`` shows the file where bindings were defined (:issue:`12504`). +- Abbreviations with ``--position=anywhere`` can now be completed in argument position, not just in command position (:issue:`12630`). For distributors and developers ------------------------------- diff --git a/src/complete.rs b/src/complete.rs index e6f295eb9..004b7bf07 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -1,5 +1,5 @@ use crate::{ - abbrs::with_abbrs, + abbrs::{Position, with_abbrs}, ast::unescape_keyword, autoload::{Autoload, AutoloadResult}, builtins::shared::{builtin_exists, builtin_get_desc, builtin_get_names}, @@ -689,7 +689,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { return; } self.complete_cmd(WString::new()); - self.complete_abbr(L!("")); + self.complete_abbr(L!(""), true); return; }; @@ -744,7 +744,7 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { return; } // Complete command filename. - self.complete_abbr(current_token); + self.complete_abbr(current_token, true); self.complete_cmd(current_token.to_owned()); return; } @@ -784,10 +784,17 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { } } - let mut do_file = false; + #[derive(Eq, PartialEq)] + enum DoFile { + No, + Yes, + Only, + } + + let mut do_file = DoFile::No; let mut handle_as_special_cd = false; if in_redirection { - do_file = true; + do_file = DoFile::Only; } else { // Try completing as an argument. let mut arg_data = CustomArgData::new(&mut var_assignments); @@ -817,11 +824,15 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { command_range, &mut arg_data, ); - do_file = arg_data.do_file; + do_file = if arg_data.do_file { + DoFile::Yes + } else { + DoFile::No + }; // If we're autosuggesting, and the token is empty, don't do file suggestions. if is_autosuggest && arg_data.current_argument.is_empty() { - do_file = false; + do_file = DoFile::No; } } @@ -833,10 +844,14 @@ fn perform_for_commandline_impl(&mut self, cmdline: WString) { // Maybe apply variable assignments. let block = self.apply_var_assignments(&var_assignments); if !self.ctx.check_cancel() { + if do_file != DoFile::Only { + self.complete_abbr(current_argument, false); + } + // This function wants the unescaped string. self.complete_param_expand( current_argument, - do_file, + do_file != DoFile::No, handle_as_special_cd, cur_tok.is_unterminated_brace, ); @@ -1144,15 +1159,18 @@ fn complete_cmd(&mut self, str_cmd: WString) { } } - /// Attempt to complete an abbreviation for the given string. - fn complete_abbr(&mut self, cmd: &wstr) { + /// Attempt to complete a non-regex abbreviation for the given string. + fn complete_abbr(&mut self, cmd: &wstr, is_command_position: bool) { // Copy the list of names and descriptions so as not to hold the lock across the call to // complete_strings. let mut possible_comp = Vec::new(); let mut descs = HashMap::new(); with_abbrs(|set| { for abbr in set.list() { - if !abbr.is_regex() { + if abbr.is_regex() { + continue; + } + if abbr.position == Position::Anywhere || is_command_position { possible_comp.push(Completion::from_completion(abbr.key.clone())); descs.insert(abbr.key.clone(), abbr.replacement.clone()); } diff --git a/tests/checks/complete.fish b/tests/checks/complete.fish index f0c3371f5..340303db1 100644 --- a/tests/checks/complete.fish +++ b/tests/checks/complete.fish @@ -645,6 +645,22 @@ abbr cat cat complete -C ca | string match -r '^cat(?:\t.*)?$' # CHECK: cat{{\t}}Abbreviation: cat +# anywhere-position abbrs complete in non-command position +abbr --position anywhere __test_pgr '| grep -i' +complete -C'echo __test_pgr' | string match -r '^__test_pgr.*' +# CHECK: __test_pgr{{\t}}Abbreviation: | grep -i + +# anywhere-position abbrs still complete in command position (regression guard) +complete -C__test_pgr | string match -r '^__test_pgr.*' +# CHECK: __test_pgr{{\t}}Abbreviation: | grep -i + +# command-position-only abbrs do NOT complete in non-command position +abbr --position command __test_cmd_only 'git status' +complete -C'echo __test_cmd' | string match -rq '^__test_cmd' +echo $status +# CHECK: 1 +abbr --erase __test_pgr __test_cmd_only + complete complete-list -xa '(__fish_complete_list , "seq 2")' complete -C "complete-list 1," # CHECK: 1,1