patch 9.1.1374: completion: 'smartcase' not respected when filtering matches
Commit: https://github.com/vim/vim/commit/dc314053e121b0a995bdfbcdd2f03ce228e14eb3 Author: Girish Palya <giris...@gmail.com> Date: Thu May 8 23:28:52 2025 +0200 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 <giris...@gmail.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index f52cf40ad..f3d92b290 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -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 @@ -1347,6 +1347,7 @@ use all space available. The 'pumwidth' option can be used to set a minimum width. The default is 15 characters. + *compl-states* There are three states: 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 diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index bed14abb1..f3824cdbf 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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 @@ -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 case characters. Only used when the search pattern is typed and 'ignorecase' option is on. Used for the commands "/", "?", "n", "N", - ":g" and ":s". 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>. + ":g" and ":s" and when filtering matches for the completion menu + |compl-states|. + 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. *'smartindent'* *'si'* *'nosmartindent'* *'nosi'* diff --git a/runtime/doc/tags b/runtime/doc/tags index bb0558dab..ae80f0e6d 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6645,6 +6645,7 @@ compl-keyword insert.txt /*compl-keyword* compl-omni insert.txt /*compl-omni* compl-omni-filetypes insert.txt /*compl-omni-filetypes* compl-spelling insert.txt /*compl-spelling* +compl-states insert.txt /*compl-states* compl-stop insert.txt /*compl-stop* compl-tag insert.txt /*compl-tag* compl-thesaurus insert.txt /*compl-thesaurus* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index afa800d86..371e4b314 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -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 @@ -41626,6 +41626,7 @@ Completion: ~ "{flag}^<limit>" notation - add ":filetype" command completion - add "filetypecmd" completion type for |getcompletion()| +- 'smartcase' applies to completion filtering Options: ~ - the default for 'commentstring' contains whitespace padding to have diff --git a/src/insexpand.c b/src/insexpand.c index 3839586db..7bbff4ecb 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -1507,7 +1507,7 @@ ins_compl_build_pum(void) match_count = 1; 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; 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) && !max_matches_found && (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))) { ++compl_match_arraysize; diff --git a/src/search.c b/src/search.c index 5222a433d..ea7e65492 100644 --- a/src/search.c +++ b/src/search.c @@ -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 ignorecase_opt(char_u *pat, int ic_in, int scs) diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 73565a56e..6f342ae46 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -4213,6 +4213,93 @@ func Test_complete_append_selected_match_default() delfunc PrintMenuWords 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' func Test_nearest_cpt_option() diff --git a/src/version.c b/src/version.c index 5b85a2bbd..236306eb2 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1374, /**/ 1373, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1uD93A-00GFwo-Is%40256bit.org.