From 656b39a0b3e6e4ed699b52e52b10448269c40271 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Sun, 23 Nov 2025 22:38:49 +0900 Subject: [PATCH] Also show case-insensitive prefix matches in completion pager [ja: made some changes and added commit message] Fixes #7944 Closes #11910 --- CHANGELOG.rst | 1 + src/complete.rs | 2 ++ src/pager.rs | 37 ++++++++++++++++++++++----------- src/reader/reader.rs | 26 ++++++++++++++++++----- tests/checks/tmux-complete.fish | 34 +++++++++++++++++++----------- 5 files changed, 71 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 80e8dcc25..6332f1e37 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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 ------------------------- diff --git a/src/complete.rs b/src/complete.rs index d3ec0fec6..1e00f8c42 100644 --- a/src/complete.rs +++ b/src/complete.rs @@ -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; } } diff --git a/src/pager.rs b/src/pager.rs index 69111abdb..818c298d6 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -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, diff --git a/src/reader/reader.rs b/src/reader/reader.rs index 2713ff408..0d4b8b74e 100644 --- a/src/reader/reader.rs +++ b/src/reader/reader.rs @@ -6646,8 +6646,16 @@ fn handle_completions(&mut self, token_range: Range) -> 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) -> 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) -> 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!("")); } diff --git a/tests/checks/tmux-complete.fish b/tests/checks/tmux-complete.fish index 820af9619..5efb99518 100644 --- a/tests/checks/tmux-complete.fish +++ b/tests/checks/tmux-complete.fish @@ -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/ ,{{.*}}