patch 9.1.1374: completion: 'smartcase' not respected when filtering matches

Problem:  Currently, 'smartcase' is respected when completing keywords
          using <C-N>, <C-P>, <C-X><C-N>, and <C-X><C-P>. However, when
          a user continues typing and the completion menu is filtered
          using cached matches, 'smartcase' is not applied. This leads
          to poor-quality or irrelevant completion suggestions, as shown
          in the example below.
Solution: When filtering cached completion items after typing additional
          characters, apply case-sensitive comparison if 'smartcase' is
          enabled and the typed pattern includes uppercase characters.
          This ensures consistent and expected completion behavior.
          (Girish Palya)

closes: #17271

Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Girish Palya
2025-05-08 23:28:52 +02:00
committed by Christian Brabandt
parent c3fbaa086e
commit dc314053e1
8 changed files with 109 additions and 9 deletions

View File

@ -1,4 +1,4 @@
*insert.txt* For Vim version 9.1. Last change: 2025 Apr 14 *insert.txt* For Vim version 9.1. Last change: 2025 May 08
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -1347,6 +1347,7 @@ use all space available.
The 'pumwidth' option can be used to set a minimum width. The default is 15 The 'pumwidth' option can be used to set a minimum width. The default is 15
characters. characters.
*compl-states*
There are three states: There are three states:
1. A complete match has been inserted, e.g., after using CTRL-N or CTRL-P. 1. A complete match has been inserted, e.g., after using CTRL-N or CTRL-P.
2. A cursor key has been used to select another match. The match was not 2. A cursor key has been used to select another match. The match was not

View File

@ -1,4 +1,4 @@
*options.txt* For Vim version 9.1. Last change: 2025 May 07 *options.txt* For Vim version 9.1. Last change: 2025 May 08
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -7742,9 +7742,11 @@ A jump table for the options with a short description can be found at |Q_op|.
Override the 'ignorecase' option if the search pattern contains upper Override the 'ignorecase' option if the search pattern contains upper
case characters. Only used when the search pattern is typed and case characters. Only used when the search pattern is typed and
'ignorecase' option is on. Used for the commands "/", "?", "n", "N", 'ignorecase' option is on. Used for the commands "/", "?", "n", "N",
":g" and ":s". Not used for "*", "#", "gd", tag search, etc. After ":g" and ":s" and when filtering matches for the completion menu
"*" and "#" you can make 'smartcase' used by doing a "/" command, |compl-states|.
recalling the search pattern from history and hitting <Enter>. Not used for "*", "#", "gd", tag search, etc. After "*" and "#" you
can make 'smartcase' used by doing a "/" command, recalling the search
pattern from history and hitting <Enter>.
NOTE: This option is reset when 'compatible' is set. NOTE: This option is reset when 'compatible' is set.
*'smartindent'* *'si'* *'nosmartindent'* *'nosi'* *'smartindent'* *'si'* *'nosmartindent'* *'nosi'*

View File

@ -6645,6 +6645,7 @@ compl-keyword insert.txt /*compl-keyword*
compl-omni insert.txt /*compl-omni* compl-omni insert.txt /*compl-omni*
compl-omni-filetypes insert.txt /*compl-omni-filetypes* compl-omni-filetypes insert.txt /*compl-omni-filetypes*
compl-spelling insert.txt /*compl-spelling* compl-spelling insert.txt /*compl-spelling*
compl-states insert.txt /*compl-states*
compl-stop insert.txt /*compl-stop* compl-stop insert.txt /*compl-stop*
compl-tag insert.txt /*compl-tag* compl-tag insert.txt /*compl-tag*
compl-thesaurus insert.txt /*compl-thesaurus* compl-thesaurus insert.txt /*compl-thesaurus*

View File

@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.1. Last change: 2025 May 07 *version9.txt* For Vim version 9.1. Last change: 2025 May 08
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -41626,6 +41626,7 @@ Completion: ~
"{flag}^<limit>" notation "{flag}^<limit>" notation
- add ":filetype" command completion - add ":filetype" command completion
- add "filetypecmd" completion type for |getcompletion()| - add "filetypecmd" completion type for |getcompletion()|
- 'smartcase' applies to completion filtering
Options: ~ Options: ~
- the default for 'commentstring' contains whitespace padding to have - the default for 'commentstring' contains whitespace padding to have

View File

@ -1507,7 +1507,7 @@ ins_compl_build_pum(void)
match_count = 1; match_count = 1;
max_matches_found = FALSE; max_matches_found = FALSE;
} }
else if (cpt_sources_array && !max_matches_found) else if (cpt_sources_array != NULL && !max_matches_found)
{ {
int max_matches = cpt_sources_array[cur_source].max_matches; int max_matches = cpt_sources_array[cur_source].max_matches;
if (max_matches > 0 && match_count > max_matches) if (max_matches > 0 && match_count > max_matches)
@ -1515,10 +1515,16 @@ ins_compl_build_pum(void)
} }
} }
// Apply 'smartcase' behavior during normal mode
if (ctrl_x_mode_normal() && !p_inf && compl_leader.string
&& !ignorecase(compl_leader.string) && !fuzzy_filter)
compl->cp_flags &= ~CP_ICASE;
if (!match_at_original_text(compl) if (!match_at_original_text(compl)
&& !max_matches_found && !max_matches_found
&& (compl_leader.string == NULL && (compl_leader.string == NULL
|| ins_compl_equal(compl, compl_leader.string, (int)compl_leader.length) || ins_compl_equal(compl, compl_leader.string,
(int)compl_leader.length)
|| (fuzzy_filter && compl->cp_score > 0))) || (fuzzy_filter && compl->cp_score > 0)))
{ {
++compl_match_arraysize; ++compl_match_arraysize;

View File

@ -439,7 +439,7 @@ ignorecase(char_u *pat)
} }
/* /*
* As ignorecase() put pass the "ic" and "scs" flags. * As ignorecase() but pass the "ic" and "scs" flags.
*/ */
int int
ignorecase_opt(char_u *pat, int ic_in, int scs) ignorecase_opt(char_u *pat, int ic_in, int scs)

View File

@ -4213,6 +4213,93 @@ func Test_complete_append_selected_match_default()
delfunc PrintMenuWords delfunc PrintMenuWords
endfunc endfunc
" Test normal mode (^N/^P/^X^N/^X^P) with smartcase when 1) matches are first
" found and 2) matches are filtered (when a character is typed).
func Test_smartcase_normal_mode()
func! PrintMenu()
let info = complete_info(["matches"])
call map(info.matches, {_, v -> v.word})
return info
endfunc
func! TestInner(key)
let pr = "\<c-r>=PrintMenu()\<cr>"
new
set completeopt=menuone,noselect ignorecase smartcase
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}{pr}"
call assert_equal('F{''matches'': [''Fast'', ''FAST'', ''False'',
\ ''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}a{pr}"
call assert_equal('Fa{''matches'': [''Fast'', ''False'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}a\<bs>{pr}"
call assert_equal('F{''matches'': [''Fast'', ''FAST'', ''False'',
\ ''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}ax{pr}"
call assert_equal('Fax{''matches'': []}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}ax\<bs>{pr}"
call assert_equal('Fa{''matches'': [''Fast'', ''False'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}A{pr}"
call assert_equal('FA{''matches'': [''FAST'', ''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}A\<bs>{pr}"
call assert_equal('F{''matches'': [''Fast'', ''FAST'', ''False'',
\ ''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}AL{pr}"
call assert_equal('FAL{''matches'': [''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}ALx{pr}"
call assert_equal('FALx{''matches'': []}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOF{a:key}ALx\<bs>{pr}"
call assert_equal('FAL{''matches'': [''FALSE'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOf{a:key}{pr}"
call assert_equal('f{''matches'': [''Fast'', ''FAST'', ''False'', ''FALSE'',
\ ''fast'', ''false'']}', getline(1))
%d
call setline(1, ["Fast", "FAST", "False", "FALSE", "fast", "false"])
exe $"normal! ggOf{a:key}a{pr}"
call assert_equal('fa{''matches'': [''Fast'', ''FAST'', ''False'', ''FALSE'',
\ ''fast'', ''false'']}', getline(1))
%d
exe $"normal! ggOf{a:key}{pr}"
call assert_equal('f{''matches'': []}', getline(1))
exe $"normal! ggOf{a:key}a\<bs>{pr}"
call assert_equal('f{''matches'': []}', getline(1))
set ignorecase& smartcase& completeopt&
bw!
endfunc
call TestInner("\<c-n>")
call TestInner("\<c-p>")
call TestInner("\<c-x>\<c-n>")
call TestInner("\<c-x>\<c-p>")
delfunc PrintMenu
delfunc TestInner
endfunc
" Test 'nearest' flag of 'completeopt' " Test 'nearest' flag of 'completeopt'
func Test_nearest_cpt_option() func Test_nearest_cpt_option()

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1374,
/**/ /**/
1373, 1373,
/**/ /**/