Fix option substr completions not being filtered out

Commit 3546ffa3ef (reader handle_completions(): remove dead filtering code,
2026-01-02) gives a proof of correctness that still makes sense;
The first lemma ("if will_replace_token") is trivially true, so no need to
assert it.
The second lemma ("if !will_replace_token") is violated in some edge cases:
we claim that given a token "-c", the option completion "--clip" is an exact match,
which is not true, it's a substring match.

Fix that, asserting the claim.
This commit is contained in:
Johannes Altmanninger
2026-04-16 22:37:05 +08:00
parent fee4288122
commit 85e76ba356
2 changed files with 37 additions and 6 deletions

View File

@@ -32,8 +32,7 @@
use fish_util::wcsfilecmp; use fish_util::wcsfilecmp;
use fish_wcstringutil::{ use fish_wcstringutil::{
StringFuzzyMatch, string_fuzzy_match_string, string_prefixes_string, StringFuzzyMatch, string_fuzzy_match_string, string_prefixes_string,
string_prefixes_string_case_insensitive, string_suffixes_string_case_insensitive, string_suffixes_string_case_insensitive, strip_executable_suffix,
strip_executable_suffix,
}; };
use fish_widestring::{WExt as _, charptr2wcstring}; use fish_widestring::{WExt as _, charptr2wcstring};
use std::{ use std::{
@@ -160,12 +159,14 @@ pub fn new(
flags: CompleteFlags, flags: CompleteFlags,
) -> Self { ) -> Self {
let flags = resolve_auto_space(&completion, flags); let flags = resolve_auto_space(&completion, flags);
Self { let zelf = Self {
completion, completion,
description, description,
r#match, r#match,
flags, flags,
} };
assert!(!zelf.r#match.requires_full_replacement() || zelf.replaces_token());
zelf
} }
pub fn from_completion(completion: WString) -> Self { pub fn from_completion(completion: WString) -> Self {
@@ -1508,7 +1509,7 @@ fn complete_param_for_command(
if !self.completions.add(Completion::new( if !self.completions.add(Completion::new(
completion, completion,
o.desc.localize().to_owned(), o.desc.localize().to_owned(),
StringFuzzyMatch::exact_match(), r#match,
flags | CompleteFlags::NO_SPACE, flags | CompleteFlags::NO_SPACE,
)) { )) {
return false; return false;
@@ -1519,7 +1520,7 @@ fn complete_param_for_command(
if !self.completions.add(Completion::new( if !self.completions.add(Completion::new(
whole_opt.slice_from(offset).to_owned(), whole_opt.slice_from(offset).to_owned(),
o.desc.localize().to_owned(), o.desc.localize().to_owned(),
StringFuzzyMatch::exact_match(), r#match,
flags, flags,
)) { )) {
return false; return false;

View File

@@ -0,0 +1,30 @@
#RUN: %fish %s
#REQUIRES: command -v tmux
#REQUIRES: uname -r | grep -qv Microsoft
isolated-tmux-start -C '
complete : -s c -l clip
complete : -s q -l qrcode
set -g fish_autosuggestion_enabled 0
'
touch somefile1
touch somefile2
isolated-tmux send-keys C-l ': -c'
function tab
isolated-tmux send-keys Tab
tmux-sleep
isolated-tmux capture-pane -p | sed '/./ { s,^,[,; s,$,], }'
end
tab
# CHECK: [prompt 0> : -cq]
tab
# CHECK: [prompt 0> : -cq somefile]
# CHECK: [somefile1 somefile2]
tab
# CHECK: [prompt 0> : -cq somefile1]
# CHECK: [somefile1 somefile2]