diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 32f07e84ef..29643c3bed 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -233,6 +233,7 @@ OPTIONS • 'completeopt' flag "nearset" sorts completion results by distance to cursor. • 'diffopt' `inline:` configures diff highlighting for changes within a line. • 'grepformat' is now a |global-local| option. +• 'maxsearchcount' sets maximum value for |searchcount()| and defaults to 999. • 'pummaxwidth' sets maximum width for the completion popup menu. • 'winborder' "bold" style, custom border style. • |g:clipboard| accepts a string name to force any builtin clipboard tool. @@ -296,7 +297,6 @@ These existing features changed their behavior. • '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`. -• |searchcount()|'s maximal value is raised from 99 to 999. ============================================================================== REMOVED FEATURES *news-removed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 1871b33e99..5e527f281f 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4286,6 +4286,15 @@ A jump table for the options with a short description can be found at |Q_op|. Vim may run out of memory before hitting the 'maxmempattern' limit, in which case you get an "Out of memory" error instead. + *'maxsearchcount'* *'msc'* +'maxsearchcount' 'msc' number (default 999) + global + Maximum number of matches shown for the search count status |shm-S| + When the number of matches exceeds this value, Vim shows ">" instead + of the exact count to keep searching fast. + Note: larger values may impact performance. + The value must be between 1 and 9999. + *'menuitems'* *'mis'* 'menuitems' 'mis' number (default 25) global @@ -5681,7 +5690,8 @@ A jump table for the options with a short description can be found at |Q_op|. is shown), the "search hit BOTTOM, continuing at TOP" and "search hit TOP, continuing at BOTTOM" messages are only indicated by a "W" (Mnemonic: Wrapped) letter before the - search count statistics. + search count statistics. The maximum limit can be set with + the 'maxsearchcount' option. This gives you the opportunity to avoid that a change between buffers requires you to hit , but still gives as useful a message as diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index 38f1fba007..7f20f90424 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -176,6 +176,7 @@ The following options affect how a search is performed in Vim: 'ignorecase' ignore case when searching 'imsearch' use IME when entering the search pattern 'incsearch' show matches incrementally as the pattern is typed + 'maxsearchcount' maximum number for the search count |shm-S| 'shortmess' suppress messages |shm-s|; show search count |shm-S| 'smartcase' override 'ignorecase' if pattern contains uppercase 'wrapscan' continue searching from the start of the file diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 74e9a597c2..c26ddf2125 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -69,6 +69,7 @@ Defaults *defaults* *nvim-defaults* - 'langremap' is disabled - 'laststatus' defaults to 2 (statusline is always shown) - 'listchars' defaults to "tab:> ,trail:-,nbsp:+" +- 'maxsearchcount' defaults t0 999 - 'mouse' defaults to "nvi", see |default-mouse| for details - 'mousemodel' defaults to "popup_setpos" - 'nrformats' defaults to "bin,hex" @@ -340,7 +341,6 @@ Functions: - |matchadd()| can be called before highlight group is defined - |tempname()| tries to recover if the Nvim |tempdir| disappears. - |writefile()| with "p" flag creates parent directories. -- |searchcount()|'s maximal value is raised from 99 to 999. - |prompt_getinput()| Highlight groups: diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index d5db159855..7e13ee6598 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -4351,6 +4351,18 @@ vim.o.mmp = vim.o.maxmempattern vim.go.maxmempattern = vim.o.maxmempattern vim.go.mmp = vim.go.maxmempattern +--- Maximum number of matches shown for the search count status `shm-S` +--- When the number of matches exceeds this value, Vim shows ">" instead +--- of the exact count to keep searching fast. +--- Note: larger values may impact performance. +--- The value must be between 1 and 9999. +--- +--- @type integer +vim.o.maxsearchcount = 999 +vim.o.msc = vim.o.maxsearchcount +vim.go.maxsearchcount = vim.o.maxsearchcount +vim.go.msc = vim.go.maxsearchcount + --- Maximum number of items to use in a menu. Used for menus that are --- generated from a list of items, e.g., the Buffers menu. Changing this --- option has no direct effect, the menu must be refreshed first. @@ -6004,7 +6016,8 @@ vim.bo.sw = vim.bo.shiftwidth --- is shown), the "search hit BOTTOM, continuing at TOP" and --- "search hit TOP, continuing at BOTTOM" messages are only --- indicated by a "W" (Mnemonic: Wrapped) letter before the ---- search count statistics. +--- search count statistics. The maximum limit can be set with +--- the 'maxsearchcount' option. --- --- This gives you the opportunity to avoid that a change between buffers --- requires you to hit , but still gives as useful a message as diff --git a/runtime/optwin.vim b/runtime/optwin.vim index eed625a7c7..8b5518e22f 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project -" Last Change: 2025 Apr 24 +" Last Change: 2025 Jul 10 " Former Maintainer: Bram Moolenaar " If there already is an option window, jump to that one. @@ -261,6 +261,8 @@ call AddOption("ignorecase", gettext("ignore case when using a search patte call BinOptionG("ic", &ic) call AddOption("smartcase", gettext("override 'ignorecase' when pattern has upper case characters")) call BinOptionG("scs", &scs) +call AddOption("maxsearchcount", gettext("Maximum number for the search count feature")) +call OptionG("msc", &msc) call AddOption("casemap", gettext("what method to use for changing case of letters")) call OptionG("cmp", &cmp) call AddOption("maxmempattern", gettext("maximum amount of memory in Kbyte used for pattern matching")) diff --git a/src/nvim/option.c b/src/nvim/option.c index 2391f9bdd5..95db55d83b 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2840,6 +2840,9 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e return e_invarg; } + // if you increase this, also increase SEARCH_STAT_BUF_LEN in search.c + enum { MAX_SEARCH_COUNT = 9999, }; + switch (opt_idx) { case kOptHelpheight: case kOptTitlelen: @@ -2972,6 +2975,13 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e return e_cannot_have_more_than_hundred_quickfix; } break; + case kOptMaxsearchcount: + if (value <= 0) { + return e_positive; + } else if (value > MAX_SEARCH_COUNT) { + return e_invarg; + } + break; default: break; } diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 5911694879..8b6a602a06 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -413,6 +413,7 @@ EXTERN OptInt p_mmd; ///< 'maxmapdepth' EXTERN OptInt p_mmp; ///< 'maxmempattern' EXTERN OptInt p_mis; ///< 'menuitems' EXTERN char *p_mopt; ///< 'messagesopt' +EXTERN OptInt p_msc; ///< 'maxsearchcount' EXTERN char *p_msm; ///< 'mkspellmem' EXTERN int p_ml; ///< 'modeline' EXTERN int p_mle; ///< 'modelineexpr' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 0816a2578f..2b1c1d7114 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -5655,6 +5655,22 @@ local options = { type = 'number', varname = 'p_mmp', }, + { + abbreviation = 'msc', + defaults = 999, + desc = [=[ + Maximum number of matches shown for the search count status |shm-S| + When the number of matches exceeds this value, Vim shows ">" instead + of the exact count to keep searching fast. + Note: larger values may impact performance. + The value must be between 1 and 9999. + ]=], + full_name = 'maxsearchcount', + scope = { 'global' }, + short_desc = N_('maximum number for the search count feature'), + type = 'number', + varname = 'p_msc', + }, { abbreviation = 'mis', defaults = 25, @@ -7884,7 +7900,8 @@ local options = { is shown), the "search hit BOTTOM, continuing at TOP" and "search hit TOP, continuing at BOTTOM" messages are only indicated by a "W" (Mnemonic: Wrapped) letter before the - search count statistics. + search count statistics. The maximum limit can be set with + the 'maxsearchcount' option. This gives you the opportunity to avoid that a change between buffers requires you to hit , but still gives as useful a message as diff --git a/src/nvim/search.c b/src/nvim/search.c index cee63eeaf2..76e0b0e2cd 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1427,7 +1427,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen || (!(fdo_flags & kOptFdoFlagSearch) && hasFolding(curwin, curwin->w_cursor.lnum, NULL, NULL))), - SEARCH_STAT_DEF_MAX_COUNT, + (int)p_msc, SEARCH_STAT_DEF_TIMEOUT); } @@ -2704,7 +2704,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static int cnt = 0; static bool exact_match = false; static int incomplete = 0; - static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; + static int last_maxcount = 0; static int chgtick = 0; static char *lastpat = NULL; static size_t lastpatlen = 0; @@ -2717,7 +2717,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst stat->cnt = cnt; stat->exact_match = exact_match; stat->incomplete = incomplete; - stat->last_maxcount = last_maxcount; + stat->last_maxcount = (int)p_msc; return; } last_maxcount = maxcount; @@ -2801,7 +2801,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; char *pattern = NULL; - int maxcount = SEARCH_STAT_DEF_MAX_COUNT; + int maxcount = (int)p_msc; int timeout = SEARCH_STAT_DEF_TIMEOUT; bool recompute = true; searchstat_T stat; diff --git a/src/nvim/search.h b/src/nvim/search.h index 96c8366ade..b93b3c73f5 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -63,8 +63,9 @@ enum { // Values for searchcount() enum { SEARCH_STAT_DEF_TIMEOUT = 40, }; -enum { SEARCH_STAT_DEF_MAX_COUNT = 999, }; -enum { SEARCH_STAT_BUF_LEN = 14, }; +// 'W ': 2 + +// '[>9999/>9999]': 13 + 1 (NUL) +enum { SEARCH_STAT_BUF_LEN = 16, }; enum { /// Maximum number of characters that can be fuzzy matched diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 3a52c77260..92266a2cf7 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -638,7 +638,7 @@ describe('ui/ext_messages', function() {10:line} 2 | {1:~ }|*3 ]], - messages = { { content = { { '/line W [1/2]' } }, kind = 'search_count' } }, + messages = { { content = { { '/line W [1/2]' } }, kind = 'search_count' } }, } feed('n') @@ -648,7 +648,7 @@ describe('ui/ext_messages', function() {10:^line} 2 | {1:~ }|*3 ]], - messages = { { content = { { '/line [2/2]' } }, kind = 'search_count' } }, + messages = { { content = { { '/line [2/2]' } }, kind = 'search_count' } }, } end) diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index 776162fb32..f1bc18a75b 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -308,6 +308,7 @@ let test_values = { "\ 'renderoptions': [[''], ['xxx']], \ 'rightleftcmd': [['search'], ['xxx']], \ 'rulerformat': [['', 'xxx'], ['%-', '%(', '%15(%%']], + \ 'maxsearchcount': [[1, 10, 100, 1000], [0, -1, 10000]], \ 'selection': [['old', 'inclusive', 'exclusive'], ['', 'xxx']], \ 'selectmode': [['', 'mouse', 'key', 'cmd', 'key,cmd'], ['xxx']], \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 4887238ea2..a9cf25a8b4 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -10,24 +10,25 @@ if exists('s:did_load') set formatoptions=tcq set fsync set include=^\\s*#\\s*include - set laststatus=1 - set listchars=eol:$ set joinspaces set jumpoptions= + set laststatus=1 + set listchars=eol:$ + set maxsearchcount=99 set mousemodel=extend set nohidden nosmarttab noautoindent noautoread noruler noshowcmd set nohlsearch noincsearch set nrformats=bin,octal,hex - set shortmess=filnxtToOS + set sessionoptions+=options set shelltemp + set shortmess=filnxtToOS set sidescroll=0 + set startofline + set switchbuf= set tags=./tags,tags set undodir^=. - set wildoptions= - set startofline - set sessionoptions+=options set viewoptions+=options - set switchbuf= + set wildoptions= if has('win32') set isfname+=: endif diff --git a/test/old/testdir/test_search_stat.vim b/test/old/testdir/test_search_stat.vim index e74d2ed5ab..2e6d4f63f3 100644 --- a/test/old/testdir/test_search_stat.vim +++ b/test/old/testdir/test_search_stat.vim @@ -16,27 +16,27 @@ func Test_search_stat() " but setting @/ should also work (even 'n' nor 'N' was executed) " recompute the count when the last position is different. call assert_equal( - \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 999}, + \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'foo'})) call assert_equal( - \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar'})) call assert_equal( - \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]})) call assert_equal( - \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]})) " on last char of match call assert_equal( - \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]})) " on char after match call assert_equal( - \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]})) call assert_equal( - \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 999}, + \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99}, \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]})) call assert_equal( \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1}, @@ -53,7 +53,7 @@ func Test_search_stat() let pat = escape(@/, '()*?'). '\s\+' call assert_match(pat .. stat, g:a) call assert_equal( - \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 999}, + \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, \ searchcount(#{recompute: 0})) " didn't get added to message history call assert_equal(messages_before, execute('messages')) @@ -64,7 +64,7 @@ func Test_search_stat() let stat = '\[50/50\]' call assert_match(pat .. stat, g:a) call assert_equal( - \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 999}, + \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, \ searchcount(#{recompute: 0})) " No search stat @@ -75,65 +75,53 @@ func Test_search_stat() call assert_notmatch(pat .. stat, g:a) " n does not update search stat call assert_equal( - \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 999}, + \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, \ searchcount(#{recompute: 0})) call assert_equal( - \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 999}, + \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, \ searchcount(#{recompute: v:true})) set shortmess-=S - " Nvim: max search count is 999; append more lines to surpass it. - " Create a new undo block so we can revert the change later. - " Also allow "[?/??]" in case of timeouts from the increased max, but set a - " larger timeout for the recomputing searchcount()s. - let &l:undolevels = &l:undolevels - call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 99)) - " Many matches call cursor(line('$')-2, 1) let @/ = '.' let pat = escape(@/, '()*?'). '\s\+' let g:a = execute(':unsilent :norm! n') - let stat = '\[\%(>999/>999\|?/??\)\]' + let stat = '\[>99/>99\]' call assert_match(pat .. stat, g:a) call assert_equal( - \ #{current: 1000, exact_match: 0, total: 1000, incomplete: 2, maxcount: 999}, - "\ Nvim: must recompute if the previous search timed out. - \ searchcount(#{recompute: (g:a =~# pat .. '\[?/??\]'), timeout: 500})) + \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99}, + \ searchcount(#{recompute: 0})) call assert_equal( - \ #{current: 3044, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, - \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 500})) + \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200})) call assert_equal( - \ #{current: 1, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, - \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 500})) + \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200})) call cursor(line('$'), 1) let g:a = execute(':unsilent :norm! n') - let stat = 'W \[\%(1/>999\|?/??\)\]' + let stat = 'W \[1/>99\]' call assert_match(pat .. stat, g:a) call assert_equal( - \ #{current: 1, exact_match: 1, total: 1000, incomplete: 2, maxcount: 999}, - "\ Nvim: must recompute if the previous search timed out. - \ searchcount(#{recompute: (g:a =~# pat .. '\[?/??\]'), timeout: 500})) + \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99}, + \ searchcount(#{recompute: 0})) call assert_equal( - \ #{current: 1, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, - \ searchcount(#{recompute: 1, maxcount: 0, timeout: 500})) + \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200})) call assert_equal( - \ #{current: 3043, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, - \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 500})) + \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200})) " Many matches call cursor(1, 1) let g:a = execute(':unsilent :norm! n') - let stat = '\[\%(2/>999\|?/??\)\]' + let stat = '\[2/>99\]' call assert_match(pat .. stat, g:a) call cursor(1, 1) let g:a = execute(':unsilent :norm! N') - let stat = 'W \[\%(>999/>999\|?/??\)\]' + let stat = 'W \[>99/>99\]' call assert_match(pat .. stat, g:a) - " Nvim: undo the extra lines. - undo - " right-left if exists("+rightleft") set rl @@ -490,4 +478,166 @@ func Test_search_stat_smartcase_ignorecase() call StopVimInTerminal(buf) endfunc +func Test_search_stat_option_values() + call assert_fails(':set maxsearchcount=0', 'E487:') + call assert_fails(':set maxsearchcount=10000', 'E474:') + set maxsearchcount=9999 + call assert_equal(9999, &msc) + set maxsearchcount=1 + call assert_equal(1, &msc) + set maxsearchcount=999 + call assert_equal(999, &msc) + set maxsearchcount&vim +endfunc + +func Test_search_stat_option() + " Asan causes wrong results, because the search times out + CheckNotAsan + enew + set shortmess-=S + set maxsearchcount=999 + " Append 1000 lines with text to search for, "foobar" appears 20 times + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 1000)) + + call cursor(1, 1) + call assert_equal( + \ #{exact_match: 1, current: 1, incomplete: 2, maxcount: 999, total: 1000}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]})) + " on last char of match + call assert_equal( + \ #{exact_match: 1, current: 1, incomplete: 2, maxcount: 999, total: 1000}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]})) + " on char after match + call assert_equal( + \ #{exact_match: 0, current: 1, incomplete: 2, maxcount: 999, total: 1000}, + \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]})) + + " match at second line + let messages_before = execute('messages') + let @/ = 'fo*\(bar\?\)\?' + let g:a = execute(':unsilent :norm! n') + let stat = '\[2/>999\]' + let pat = escape(@/, '()*?'). '\s\+' + call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{exact_match: 1, current: 2, incomplete: 2, maxcount: 999, total: 1000}, + \ searchcount(#{recompute: 0})) + " didn't get added to message history + call assert_equal(messages_before, execute('messages')) + + " Many matches + call cursor(line('$')-2, 1) + let @/ = '.' + let pat = escape(@/, '()*?'). '\s\+' + let g:a = execute(':unsilent :norm! n') + let stat = '\[>999/>999\]' + call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{exact_match: 0, current: 1000, incomplete: 2, maxcount: 999, total: 1000}, + \ searchcount(#{recompute: 0})) + call assert_equal( + \ #{exact_match: 1, current: 27992, incomplete: 0, maxcount:0, total: 28000}, + \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200})) + call assert_equal( + \ #{exact_match: 1, current: 1, incomplete: 0, maxcount: 0, total: 28000}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200})) + call cursor(line('$'), 1) + let g:a = execute(':unsilent :norm! n') + let stat = 'W \[1/>999\]' + call assert_match(pat .. stat, g:a) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 1000, incomplete: 2, maxcount: 999}, + \ searchcount(#{recompute: 0})) + call assert_equal( + \ #{current: 1, exact_match: 1, total: 28000, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200})) + call assert_equal( + \ #{current: 27991, exact_match: 1, total: 28000, incomplete: 0, maxcount: 0}, + \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200})) + + " Many matches + call cursor(1, 1) + let g:a = execute(':unsilent :norm! n') + let stat = '\[2/>999\]' + call assert_match(pat .. stat, g:a) + call cursor(1, 1) + let g:a = execute(':unsilent :norm! N') + let stat = '\[>999/>999\]' + call assert_match(pat .. stat, g:a) + set maxsearchcount=500 + call cursor(1, 1) + let g:a = execute(':unsilent :norm! n') + let stat = '\[2/>500\]' + call assert_match(pat .. stat, g:a) + call cursor(1, 1) + let g:a = execute(':unsilent :norm! N') + let stat = '\[>500/>500\]' + call assert_match(pat .. stat, g:a) + set maxsearchcount=20 + call cursor(1, 1) + let g:a = execute(':unsilent :norm! n') + let stat = '\[2/>20\]' + call assert_match(pat .. stat, g:a) + call cursor(1, 1) + let g:a = execute(':unsilent :norm! N') + let stat = '\[>20/>20\]' + call assert_match(pat .. stat, g:a) + set maxsearchcount=999 + + " right-left + if exists("+rightleft") + set rl + call cursor(1,1) + let @/ = 'foobar' + let pat = 'raboof/\s\+' + let g:a = execute(':unsilent :norm! n') + let stat = '\[>999/2\]' + call assert_match(pat .. stat, g:a) + + " right-left bottom + call cursor('$',1) + let pat = 'raboof?\s\+' + let g:a = execute(':unsilent :norm! N') + let stat = '\[>999/>999\]' + call assert_match(pat .. stat, g:a) + + " right-left back at top + call cursor('$',1) + let pat = 'raboof/\s\+' + let g:a = execute(':unsilent :norm! n') + let stat = 'W \[>999/1\]' + call assert_match(pat .. stat, g:a) + set norl + endif + + " normal, back at bottom + call cursor(1,1) + let @/ = 'foobar' + let pat = '?foobar\s\+' + let g:a = execute(':unsilent :norm! N') + let stat = 'W \[>999/>999\]' + call assert_match(pat .. stat, g:a) + call assert_match('W \[>999/>999\]', Screenline(&lines)) + + " normal, no match + call cursor(1,1) + let @/ = 'zzzzzz' + let g:a = '' + try + let g:a = execute(':unsilent :norm! n') + catch /^Vim\%((\a\+)\)\=:E486/ + let stat = '' + " error message is not redir'ed to g:a, it is empty + call assert_true(empty(g:a)) + catch + call assert_false(1) + endtry + + " Clean up + set shortmess+=S + set maxsearchcount&vim + " close the window + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab