Also show case-insensitive prefix matches in completion pager

[ja: made some changes and added commit message]

Fixes #7944
Closes #11910
This commit is contained in:
Asuka Minato
2025-11-23 22:38:49 +09:00
committed by Johannes Altmanninger
parent fceb600be5
commit 656b39a0b3
5 changed files with 71 additions and 29 deletions

View File

@@ -11,6 +11,7 @@ Interactive improvements
------------------------
- When typing immediately after starting fish, the first prompt is now rendered correctly.
- Completion accuracy was improved for file paths containing ``=`` or ``:`` (:issue:`5363`).
- Prefix-matching completions are now shown even if they don't have the case typed by the user (:issue:`7944`).
Improved terminal support
-------------------------

View File

@@ -110,6 +110,8 @@ pub struct CompleteFlags: u16 {
const KEEP_VARIABLE_OVERRIDE_PREFIX = 1 << 8;
/// This is a variable name.
const VARIABLE_NAME = 1 << 9;
/// Suppress showing the pager prefix for this completion.
const SUPPRESS_PAGER_PREFIX = 1 << 10;
}
}

View File

@@ -6,7 +6,7 @@
use crate::common::{
EscapeFlags, EscapeStringStyle, escape_string, get_ellipsis_char, get_ellipsis_str,
};
use crate::complete::Completion;
use crate::complete::{CompleteFlags, Completion};
use crate::editable_line::EditableLine;
use crate::highlight::{HighlightRole, HighlightSpec, highlight_shell};
use crate::operation_context::OperationContext;
@@ -349,6 +349,11 @@ fn measure_completion_infos(&mut self) {
for comp in &mut self.unfiltered_completion_infos {
let comp_strings = &mut comp.comp;
let show_prefix = !comp
.representative
.flags
.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX);
for (j, comp_string) in comp_strings.iter().enumerate() {
// If there's more than one, append the length of ', '.
if j >= 1 {
@@ -357,7 +362,9 @@ fn measure_completion_infos(&mut self) {
// This can return -1 if it can't calculate the width. So be cautious.
let comp_width = wcswidth_rendered(comp_string);
comp.comp_width += usize::try_from(prefix_len).unwrap_or_default();
if show_prefix {
comp.comp_width += usize::try_from(prefix_len).unwrap_or_default();
}
comp.comp_width += usize::try_from(comp_width).unwrap_or_default();
}
@@ -432,9 +439,13 @@ fn completion_print(
let is_selected = Some(idx) == effective_selected_idx;
// Print this completion on its own "line".
let show_prefix = !el
.representative
.flags
.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX);
let mut line = self.completion_print_item(
CharOffset::Pager(idx),
prefix,
show_prefix.then_some(prefix),
el,
col_width,
row % 2 != 0,
@@ -459,7 +470,7 @@ fn completion_print(
fn completion_print_item(
&self,
offset_in_cmdline: CharOffset,
prefix: &wstr,
prefix: Option<&wstr>,
c: &PagerComp,
width: usize,
secondary: bool,
@@ -531,14 +542,16 @@ fn completion_print_item(
);
}
comp_remaining -= print_max(
offset_in_cmdline,
prefix,
prefix_col,
comp_remaining,
!comp.is_empty(),
&mut line_data,
);
if let Some(prefix) = prefix {
comp_remaining -= print_max(
offset_in_cmdline,
prefix,
prefix_col,
comp_remaining,
!comp.is_empty(),
&mut line_data,
);
}
comp_remaining -= print_max_impl(
offset_in_cmdline,
comp,

View File

@@ -6646,8 +6646,16 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
// Only use completions that match replace_token.
let completion_replaces_token = c.flags.contains(CompleteFlags::REPLACES_TOKEN);
let replaces_only_due_to_case_mismatch = {
c.flags.contains(CompleteFlags::REPLACES_TOKEN)
&& c.r#match.is_exact_or_prefix()
&& !matches!(c.r#match.case_fold, CaseSensitivity::Sensitive)
};
if completion_replaces_token != will_replace_token {
continue;
// Keep smart/samecase results even if we prefer not to replace the token.
if will_replace_token || !replaces_only_due_to_case_mismatch {
continue;
}
}
// Don't use completions that want to replace, if we cannot replace them.
@@ -6655,10 +6663,13 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
continue;
}
// This completion survived.
surviving_completions.push(c.clone());
all_matches_exact_or_prefix =
all_matches_exact_or_prefix && c.r#match.is_exact_or_prefix();
all_matches_exact_or_prefix &= c.r#match.is_exact_or_prefix();
let mut completion = c.clone();
if replaces_only_due_to_case_mismatch && !will_replace_token {
completion.flags |= CompleteFlags::SUPPRESS_PAGER_PREFIX;
}
surviving_completions.push(completion);
}
if surviving_completions.len() == 1 {
@@ -6735,6 +6746,11 @@ fn handle_completions(&mut self, token_range: Range<usize>) -> bool {
if use_prefix {
for c in &mut surviving_completions {
if c.flags.contains(CompleteFlags::SUPPRESS_PAGER_PREFIX) {
// Keep replacement semantics and the original prefix so these completions can
// fix casing when selected.
continue;
}
c.flags &= !CompleteFlags::REPLACES_TOKEN;
c.completion.replace_range(0..common_prefix.len(), L!(""));
}

View File

@@ -13,7 +13,7 @@ isolated-tmux send-keys 'HOME=$PWD ls ~/' Tab
tmux-sleep
isolated-tmux capture-pane -p
# Note the contents may or may not have the autosuggestion appended - it is a race.
# CHECK: prompt 0> HOME=$PWD ls ~/file-{{1?}}
# CHECK: prompt {{\d+}}> HOME=$PWD ls ~/file-{{1?}}
# CHECK: ~/file-1 ~/file-2
# No pager on single smartcase completion (#7738).
@@ -21,7 +21,17 @@ isolated-tmux send-keys C-u C-l 'mkdir cmake CMakeFiles' Enter C-l \
'cat cmake' Tab
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 1> cat cmake/
# CHECK: prompt {{\d+}}> cat cmake/
# CHECK: cmake/ CMakeFiles/
# Keep mixed-case completions visible when typing lowercase and ignore non-matching prefixes (#7944).
isolated-tmux send-keys C-u C-l 'rm -rf dog Dodo Voodo' Enter \
'mkdir dog Dodo Voodo docker doc_internal doc_src' Enter C-l \
'cd do' Tab
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt {{\d+}}> cd do{{(cker/)?}}
# CHECK: docker/ doc_internal/ doc_src/ Dodo/ dog/
# Correct case in pager when prefixes differ in case (#7743).
isolated-tmux send-keys C-u C-l 'complete -c foo2 -a "aabc aaBd" -f' Enter C-l \
@@ -29,7 +39,7 @@ isolated-tmux send-keys C-u C-l 'complete -c foo2 -a "aabc aaBd" -f' Enter C-l \
tmux-sleep
isolated-tmux capture-pane -p
# The "bc" part is the autosuggestion - we could use "capture-pane -e" to check colors.
# CHECK: prompt 2> foo2 aabc
# CHECK: prompt {{\d+}}> foo2 aabc
# CHECK: aabc aaBd
# Check that a larger-than-screen completion list does not stomp a multiline commandline (#8509).
@@ -39,7 +49,7 @@ isolated-tmux send-keys C-u 'complete -c foo3 -fa "(seq $LINES)\t(string repeat
tmux-sleep
isolated-tmux capture-pane -p | sed -n '1p;$p'
# Assert that we didn't change the command line.
# CHECK: prompt 3> begin
# CHECK: prompt {{\d+}}> begin
# Also ensure that the pager is actually fully disclosed.
# CHECK: rows 1 to {{\d+}} of {{\d+}}
@@ -50,7 +60,7 @@ tmux-sleep
isolated-tmux send-keys C-l foo2 Space BTab b BSpace b Escape
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 3> foo2 aa
# CHECK: prompt {{\d+}}> foo2 aa
# Check that down-or-search works even when the pager is not selected.
isolated-tmux send-keys C-u foo2 Space Tab
@@ -59,7 +69,7 @@ isolated-tmux send-keys Down
tmux-sleep
isolated-tmux capture-pane -p
# Also check that we show an autosuggestion.
# CHECK: prompt 3> foo2 aabc aabc
# CHECK: prompt {{\d+}}> foo2 aabc aabc
# CHECK: aabc{{ *}}aaBd
# Check that a larger-than-screen completion does not break down-or-search.
@@ -76,14 +86,14 @@ isolated-tmux capture-pane -p | head -1
isolated-tmux send-keys C-u echo Space old-arg Enter C-l foo2 Space Tab Tab M-.
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 5> foo2 aabc old-arg
# CHECK: prompt {{\d+}}> foo2 aabc old-arg
isolated-tmux send-keys C-u 'echo suggest this' Enter C-l
tmux-sleep
isolated-tmux send-keys 'echo sug' C-w C-z
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 6> echo suggest this
# CHECK: prompt {{\d+}}> echo suggest this
isolated-tmux send-keys C-u 'bind ctrl-s forward-single-char' Enter C-l
isolated-tmux send-keys 'echo suggest thi'
@@ -93,7 +103,7 @@ tmux-sleep
isolated-tmux send-keys C-s
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 7> echo suggest this
# CHECK: prompt {{\d+}}> echo suggest this
isolated-tmux send-keys C-u
isolated-tmux send-keys 'echo sugg' C-a
@@ -101,7 +111,7 @@ tmux-sleep
isolated-tmux send-keys C-e M-f Space nothing
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 7> echo suggest nothing
# CHECK: prompt {{\d+}}> echo suggest nothing
isolated-tmux send-keys C-u 'bind \cs forward-char-passive' Enter C-l
isolated-tmux send-keys C-u 'bind \cb backward-char-passive' Enter C-l
@@ -111,8 +121,8 @@ isolated-tmux send-keys 'echo do not accept thi' C-b C-b DC C-b C-s 'h'
tmux-sleep
isolated-tmux send-keys C-s C-s C-s 'x'
isolated-tmux capture-pane -p
# CHECK: prompt 10> echo do not accept thix
# CHECK: prompt {{\d+}}> echo do not accept thix
isolated-tmux send-keys C-u C-l ': {*,' Tab Tab Space ,
tmux-sleep
isolated-tmux capture-pane -p
# CHECK: prompt 10> : {*,cmake/ ,{{.*}}
# CHECK: prompt {{\d+}}> : {*,cmake/ ,{{.*}}