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<TAB>`
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 <aclopte@gmail.com>

Closes #12764
This commit is contained in:
Wes Higbee
2026-05-17 13:00:19 -05:00
committed by Johannes Altmanninger
parent c10e782838
commit 4b2aba31ee
3 changed files with 46 additions and 11 deletions

View File

@@ -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
-------------------------------

View File

@@ -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());
}

View File

@@ -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