diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 58d4f5899a..28de19324c 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1297,6 +1297,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/news.txt b/runtime/doc/news.txt index 17542733fc..11a9854800 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -211,6 +211,7 @@ CHANGED FEATURES *news-changed* These existing features changed their behavior. +• 'smartcase' applies to completion filtering. • 'spellfile' location defaults to `stdpath("data").."/site/spell/"` instead of the first writable directory in 'runtimepath'. • |vim.version.range()| doesn't exclude `to` if it is equal to `from`. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 951d463b0e..dd5dbf0ed2 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5718,9 +5718,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 . + ":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 . *'smartindent'* *'si'* *'nosmartindent'* *'nosi'* 'smartindent' 'si' boolean (default off) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 7898773cb4..bc22d2f047 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6084,9 +6084,11 @@ vim.wo.scl = vim.wo.signcolumn --- 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 . +--- ":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 . --- --- @type boolean vim.o.smartcase = false diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 36aaa9de72..0d58d22e8c 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -820,8 +820,7 @@ static inline void free_cptext(char *const *const cptext) /// Returns true if matches should be sorted based on proximity to the cursor. static bool is_nearest_active(void) { - unsigned flags = get_cot_flags(); - return (flags & kOptCotFlagNearest) && !(flags & kOptCotFlagFuzzy); + return (get_cot_flags() & (kOptCotFlagNearest|kOptCotFlagFuzzy)) == kOptCotFlagNearest; } /// Repositions a match in the completion list based on its proximity score. @@ -984,9 +983,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1; if (cptext != NULL) { - int i; - - for (i = 0; i < CPT_COUNT; i++) { + for (int i = 0; i < CPT_COUNT; i++) { if (cptext[i] == NULL) { continue; } @@ -1394,6 +1391,12 @@ static int ins_compl_build_pum(void) comp->cp_score = fuzzy_match_str(comp->cp_str.data, compl_leader.data); } + // Apply 'smartcase' behavior during normal mode + if (ctrl_x_mode_normal() && !p_inf && compl_leader.data + && !ignorecase(compl_leader.data) && !fuzzy_filter) { + comp->cp_flags &= ~CP_ICASE; + } + if (!match_at_original_text(comp) && (compl_leader.data == NULL || ins_compl_equal(comp, compl_leader.data, compl_leader.size) @@ -4834,6 +4837,7 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) } startcol -= head_off; } + compl_col += ++startcol; compl_length = (int)curs_col - startcol; if (compl_length == 1) { @@ -4972,6 +4976,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) if (col == -2 || aborting()) { return FAIL; } + // Return value -3 does the same as -2 and leaves CTRL-X mode. if (col == -3) { ctrl_x_mode = CTRL_X_NORMAL; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 0441317f03..076b229b61 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8095,9 +8095,11 @@ local options = { 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 . + ":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 . ]=], full_name = 'smartcase', scope = { 'global' }, diff --git a/src/nvim/search.c b/src/nvim/search.c index 2fa343b759..50f36a888e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -391,7 +391,7 @@ int ignorecase(char *pat) return ignorecase_opt(pat, p_ic, p_scs); } -/// As ignorecase() put pass the "ic" and "scs" flags. +/// As ignorecase() but pass the "ic" and "scs" flags. int ignorecase_opt(char *pat, int ic_in, int scs) { int ic = ic_in; diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 5053dad210..63607170be 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -3484,6 +3484,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 = "\=PrintMenu()\" + + 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\{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\{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\{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\{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\{pr}" + call assert_equal('f{''matches'': []}', getline(1)) + set ignorecase& smartcase& completeopt& + bw! + endfunc + + call TestInner("\") + call TestInner("\") + call TestInner("\\") + call TestInner("\\") + delfunc PrintMenu + delfunc TestInner +endfunc + " Test 'nearest' flag of 'completeopt' func Test_nearest_cpt_option()