vim-patch:9.1.1535: the maximum search count uses hard-coded value 99 (#34873)

Problem:  The maximum search count uses a hard-coded value of 99
          (Andres Monge, Joschua Kesper)
Solution: Make it configurable using the 'maxsearchcount' option.

related: vim/vim#8855
fixes: vim/vim#17527
closes: vim/vim#17695

b7b7fa04bf

Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
zeertzjq
2025-07-11 09:17:05 +08:00
committed by GitHub
parent 54cde0674b
commit 00f8f94d5b
15 changed files with 267 additions and 60 deletions

View File

@ -233,6 +233,7 @@ OPTIONS
• 'completeopt' flag "nearset" sorts completion results by distance to cursor. • 'completeopt' flag "nearset" sorts completion results by distance to cursor.
• 'diffopt' `inline:` configures diff highlighting for changes within a line. • 'diffopt' `inline:` configures diff highlighting for changes within a line.
• 'grepformat' is now a |global-local| option. • '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. • 'pummaxwidth' sets maximum width for the completion popup menu.
• 'winborder' "bold" style, custom border style. • 'winborder' "bold" style, custom border style.
• |g:clipboard| accepts a string name to force any builtin clipboard tool. • |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 • 'spellfile' location defaults to `stdpath("data").."/site/spell/"` instead of
the first writable directory in 'runtimepath'. the first writable directory in 'runtimepath'.
• |vim.version.range()| doesn't exclude `to` if it is equal to `from`. • |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* REMOVED FEATURES *news-removed*

View File

@ -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 Vim may run out of memory before hitting the 'maxmempattern' limit, in
which case you get an "Out of memory" error instead. 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'*
'menuitems' 'mis' number (default 25) 'menuitems' 'mis' number (default 25)
global 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 is shown), the "search hit BOTTOM, continuing at TOP" and
"search hit TOP, continuing at BOTTOM" messages are only "search hit TOP, continuing at BOTTOM" messages are only
indicated by a "W" (Mnemonic: Wrapped) letter before the 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 This gives you the opportunity to avoid that a change between buffers
requires you to hit <Enter>, but still gives as useful a message as requires you to hit <Enter>, but still gives as useful a message as

View File

@ -176,6 +176,7 @@ The following options affect how a search is performed in Vim:
'ignorecase' ignore case when searching 'ignorecase' ignore case when searching
'imsearch' use IME when entering the search pattern 'imsearch' use IME when entering the search pattern
'incsearch' show matches incrementally as the pattern is typed '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| 'shortmess' suppress messages |shm-s|; show search count |shm-S|
'smartcase' override 'ignorecase' if pattern contains uppercase 'smartcase' override 'ignorecase' if pattern contains uppercase
'wrapscan' continue searching from the start of the file 'wrapscan' continue searching from the start of the file

View File

@ -69,6 +69,7 @@ Defaults *defaults* *nvim-defaults*
- 'langremap' is disabled - 'langremap' is disabled
- 'laststatus' defaults to 2 (statusline is always shown) - 'laststatus' defaults to 2 (statusline is always shown)
- 'listchars' defaults to "tab:> ,trail:-,nbsp:+" - 'listchars' defaults to "tab:> ,trail:-,nbsp:+"
- 'maxsearchcount' defaults t0 999
- 'mouse' defaults to "nvi", see |default-mouse| for details - 'mouse' defaults to "nvi", see |default-mouse| for details
- 'mousemodel' defaults to "popup_setpos" - 'mousemodel' defaults to "popup_setpos"
- 'nrformats' defaults to "bin,hex" - 'nrformats' defaults to "bin,hex"
@ -340,7 +341,6 @@ Functions:
- |matchadd()| can be called before highlight group is defined - |matchadd()| can be called before highlight group is defined
- |tempname()| tries to recover if the Nvim |tempdir| disappears. - |tempname()| tries to recover if the Nvim |tempdir| disappears.
- |writefile()| with "p" flag creates parent directories. - |writefile()| with "p" flag creates parent directories.
- |searchcount()|'s maximal value is raised from 99 to 999.
- |prompt_getinput()| - |prompt_getinput()|
Highlight groups: Highlight groups:

View File

@ -4351,6 +4351,18 @@ vim.o.mmp = vim.o.maxmempattern
vim.go.maxmempattern = vim.o.maxmempattern vim.go.maxmempattern = vim.o.maxmempattern
vim.go.mmp = vim.go.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 --- 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 --- generated from a list of items, e.g., the Buffers menu. Changing this
--- option has no direct effect, the menu must be refreshed first. --- 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 --- is shown), the "search hit BOTTOM, continuing at TOP" and
--- "search hit TOP, continuing at BOTTOM" messages are only --- "search hit TOP, continuing at BOTTOM" messages are only
--- indicated by a "W" (Mnemonic: Wrapped) letter before the --- 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 --- This gives you the opportunity to avoid that a change between buffers
--- requires you to hit <Enter>, but still gives as useful a message as --- requires you to hit <Enter>, but still gives as useful a message as

View File

@ -1,7 +1,7 @@
" These commands create the option window. " These commands create the option window.
" "
" Maintainer: The Vim Project <https://github.com/vim/vim> " Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2025 Apr 24 " Last Change: 2025 Jul 10
" Former Maintainer: Bram Moolenaar <Bram@vim.org> " Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one. " If there already is an option window, jump to that one.
@ -261,6 +261,8 @@ call <SID>AddOption("ignorecase", gettext("ignore case when using a search patte
call <SID>BinOptionG("ic", &ic) call <SID>BinOptionG("ic", &ic)
call <SID>AddOption("smartcase", gettext("override 'ignorecase' when pattern has upper case characters")) call <SID>AddOption("smartcase", gettext("override 'ignorecase' when pattern has upper case characters"))
call <SID>BinOptionG("scs", &scs) call <SID>BinOptionG("scs", &scs)
call <SID>AddOption("maxsearchcount", gettext("Maximum number for the search count feature"))
call <SID>OptionG("msc", &msc)
call <SID>AddOption("casemap", gettext("what method to use for changing case of letters")) call <SID>AddOption("casemap", gettext("what method to use for changing case of letters"))
call <SID>OptionG("cmp", &cmp) call <SID>OptionG("cmp", &cmp)
call <SID>AddOption("maxmempattern", gettext("maximum amount of memory in Kbyte used for pattern matching")) call <SID>AddOption("maxmempattern", gettext("maximum amount of memory in Kbyte used for pattern matching"))

View File

@ -2840,6 +2840,9 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e
return e_invarg; return e_invarg;
} }
// if you increase this, also increase SEARCH_STAT_BUF_LEN in search.c
enum { MAX_SEARCH_COUNT = 9999, };
switch (opt_idx) { switch (opt_idx) {
case kOptHelpheight: case kOptHelpheight:
case kOptTitlelen: 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; return e_cannot_have_more_than_hundred_quickfix;
} }
break; break;
case kOptMaxsearchcount:
if (value <= 0) {
return e_positive;
} else if (value > MAX_SEARCH_COUNT) {
return e_invarg;
}
break;
default: default:
break; break;
} }

View File

@ -413,6 +413,7 @@ EXTERN OptInt p_mmd; ///< 'maxmapdepth'
EXTERN OptInt p_mmp; ///< 'maxmempattern' EXTERN OptInt p_mmp; ///< 'maxmempattern'
EXTERN OptInt p_mis; ///< 'menuitems' EXTERN OptInt p_mis; ///< 'menuitems'
EXTERN char *p_mopt; ///< 'messagesopt' EXTERN char *p_mopt; ///< 'messagesopt'
EXTERN OptInt p_msc; ///< 'maxsearchcount'
EXTERN char *p_msm; ///< 'mkspellmem' EXTERN char *p_msm; ///< 'mkspellmem'
EXTERN int p_ml; ///< 'modeline' EXTERN int p_ml; ///< 'modeline'
EXTERN int p_mle; ///< 'modelineexpr' EXTERN int p_mle; ///< 'modelineexpr'

View File

@ -5655,6 +5655,22 @@ local options = {
type = 'number', type = 'number',
varname = 'p_mmp', 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', abbreviation = 'mis',
defaults = 25, defaults = 25,
@ -7884,7 +7900,8 @@ local options = {
is shown), the "search hit BOTTOM, continuing at TOP" and is shown), the "search hit BOTTOM, continuing at TOP" and
"search hit TOP, continuing at BOTTOM" messages are only "search hit TOP, continuing at BOTTOM" messages are only
indicated by a "W" (Mnemonic: Wrapped) letter before the 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 This gives you the opportunity to avoid that a change between buffers
requires you to hit <Enter>, but still gives as useful a message as requires you to hit <Enter>, but still gives as useful a message as

View File

@ -1427,7 +1427,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
|| (!(fdo_flags & kOptFdoFlagSearch) || (!(fdo_flags & kOptFdoFlagSearch)
&& hasFolding(curwin, curwin->w_cursor.lnum, NULL, && hasFolding(curwin, curwin->w_cursor.lnum, NULL,
NULL))), NULL))),
SEARCH_STAT_DEF_MAX_COUNT, (int)p_msc,
SEARCH_STAT_DEF_TIMEOUT); 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 int cnt = 0;
static bool exact_match = false; static bool exact_match = false;
static int incomplete = 0; static int incomplete = 0;
static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; static int last_maxcount = 0;
static int chgtick = 0; static int chgtick = 0;
static char *lastpat = NULL; static char *lastpat = NULL;
static size_t lastpatlen = 0; 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->cnt = cnt;
stat->exact_match = exact_match; stat->exact_match = exact_match;
stat->incomplete = incomplete; stat->incomplete = incomplete;
stat->last_maxcount = last_maxcount; stat->last_maxcount = (int)p_msc;
return; return;
} }
last_maxcount = maxcount; last_maxcount = maxcount;
@ -2801,7 +2801,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{ {
pos_T pos = curwin->w_cursor; pos_T pos = curwin->w_cursor;
char *pattern = NULL; char *pattern = NULL;
int maxcount = SEARCH_STAT_DEF_MAX_COUNT; int maxcount = (int)p_msc;
int timeout = SEARCH_STAT_DEF_TIMEOUT; int timeout = SEARCH_STAT_DEF_TIMEOUT;
bool recompute = true; bool recompute = true;
searchstat_T stat; searchstat_T stat;

View File

@ -63,8 +63,9 @@ enum {
// Values for searchcount() // Values for searchcount()
enum { SEARCH_STAT_DEF_TIMEOUT = 40, }; enum { SEARCH_STAT_DEF_TIMEOUT = 40, };
enum { SEARCH_STAT_DEF_MAX_COUNT = 999, }; // 'W ': 2 +
enum { SEARCH_STAT_BUF_LEN = 14, }; // '[>9999/>9999]': 13 + 1 (NUL)
enum { SEARCH_STAT_BUF_LEN = 16, };
enum { enum {
/// Maximum number of characters that can be fuzzy matched /// Maximum number of characters that can be fuzzy matched

View File

@ -638,7 +638,7 @@ describe('ui/ext_messages', function()
{10:line} 2 | {10:line} 2 |
{1:~ }|*3 {1:~ }|*3
]], ]],
messages = { { content = { { '/line W [1/2]' } }, kind = 'search_count' } }, messages = { { content = { { '/line W [1/2]' } }, kind = 'search_count' } },
} }
feed('n') feed('n')
@ -648,7 +648,7 @@ describe('ui/ext_messages', function()
{10:^line} 2 | {10:^line} 2 |
{1:~ }|*3 {1:~ }|*3
]], ]],
messages = { { content = { { '/line [2/2]' } }, kind = 'search_count' } }, messages = { { content = { { '/line [2/2]' } }, kind = 'search_count' } },
} }
end) end)

View File

@ -308,6 +308,7 @@ let test_values = {
"\ 'renderoptions': [[''], ['xxx']], "\ 'renderoptions': [[''], ['xxx']],
\ 'rightleftcmd': [['search'], ['xxx']], \ 'rightleftcmd': [['search'], ['xxx']],
\ 'rulerformat': [['', 'xxx'], ['%-', '%(', '%15(%%']], \ 'rulerformat': [['', 'xxx'], ['%-', '%(', '%15(%%']],
\ 'maxsearchcount': [[1, 10, 100, 1000], [0, -1, 10000]],
\ 'selection': [['old', 'inclusive', 'exclusive'], ['', 'xxx']], \ 'selection': [['old', 'inclusive', 'exclusive'], ['', 'xxx']],
\ 'selectmode': [['', 'mouse', 'key', 'cmd', 'key,cmd'], ['xxx']], \ 'selectmode': [['', 'mouse', 'key', 'cmd', 'key,cmd'], ['xxx']],
\ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir',

View File

@ -10,24 +10,25 @@ if exists('s:did_load')
set formatoptions=tcq set formatoptions=tcq
set fsync set fsync
set include=^\\s*#\\s*include set include=^\\s*#\\s*include
set laststatus=1
set listchars=eol:$
set joinspaces set joinspaces
set jumpoptions= set jumpoptions=
set laststatus=1
set listchars=eol:$
set maxsearchcount=99
set mousemodel=extend set mousemodel=extend
set nohidden nosmarttab noautoindent noautoread noruler noshowcmd set nohidden nosmarttab noautoindent noautoread noruler noshowcmd
set nohlsearch noincsearch set nohlsearch noincsearch
set nrformats=bin,octal,hex set nrformats=bin,octal,hex
set shortmess=filnxtToOS set sessionoptions+=options
set shelltemp set shelltemp
set shortmess=filnxtToOS
set sidescroll=0 set sidescroll=0
set startofline
set switchbuf=
set tags=./tags,tags set tags=./tags,tags
set undodir^=. set undodir^=.
set wildoptions=
set startofline
set sessionoptions+=options
set viewoptions+=options set viewoptions+=options
set switchbuf= set wildoptions=
if has('win32') if has('win32')
set isfname+=: set isfname+=:
endif endif

View File

@ -16,27 +16,27 @@ func Test_search_stat()
" but setting @/ should also work (even 'n' nor 'N' was executed) " but setting @/ should also work (even 'n' nor 'N' was executed)
" recompute the count when the last position is different. " recompute the count when the last position is different.
call assert_equal( 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'})) \ searchcount(#{pattern: 'foo'}))
call assert_equal( 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'})) \ searchcount(#{pattern: 'fooooobar'}))
call assert_equal( 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]})) \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
call assert_equal( 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]})) \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
" on last char of match " on last char of match
call assert_equal( 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]})) \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]}))
" on char after match " on char after match
call assert_equal( 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]})) \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]}))
call assert_equal( 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]})) \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]}))
call assert_equal( call assert_equal(
\ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1}, \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
@ -53,7 +53,7 @@ func Test_search_stat()
let pat = escape(@/, '()*?'). '\s\+' let pat = escape(@/, '()*?'). '\s\+'
call assert_match(pat .. stat, g:a) call assert_match(pat .. stat, g:a)
call assert_equal( 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})) \ searchcount(#{recompute: 0}))
" didn't get added to message history " didn't get added to message history
call assert_equal(messages_before, execute('messages')) call assert_equal(messages_before, execute('messages'))
@ -64,7 +64,7 @@ func Test_search_stat()
let stat = '\[50/50\]' let stat = '\[50/50\]'
call assert_match(pat .. stat, g:a) call assert_match(pat .. stat, g:a)
call assert_equal( 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})) \ searchcount(#{recompute: 0}))
" No search stat " No search stat
@ -75,65 +75,53 @@ func Test_search_stat()
call assert_notmatch(pat .. stat, g:a) call assert_notmatch(pat .. stat, g:a)
" n does not update search stat " n does not update search stat
call assert_equal( 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})) \ searchcount(#{recompute: 0}))
call assert_equal( 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})) \ searchcount(#{recompute: v:true}))
set shortmess-=S 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 " Many matches
call cursor(line('$')-2, 1) call cursor(line('$')-2, 1)
let @/ = '.' let @/ = '.'
let pat = escape(@/, '()*?'). '\s\+' let pat = escape(@/, '()*?'). '\s\+'
let g:a = execute(':unsilent :norm! n') let g:a = execute(':unsilent :norm! n')
let stat = '\[\%(>999/>999\|?/??\)\]' let stat = '\[>99/>99\]'
call assert_match(pat .. stat, g:a) call assert_match(pat .. stat, g:a)
call assert_equal( call assert_equal(
\ #{current: 1000, exact_match: 0, total: 1000, incomplete: 2, maxcount: 999}, \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
"\ Nvim: must recompute if the previous search timed out. \ searchcount(#{recompute: 0}))
\ searchcount(#{recompute: (g:a =~# pat .. '\[?/??\]'), timeout: 500}))
call assert_equal( call assert_equal(
\ #{current: 3044, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
\ searchcount(#{recompute: v:true, maxcount: 0, timeout: 500})) \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200}))
call assert_equal( call assert_equal(
\ #{current: 1, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
\ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 500})) \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200}))
call cursor(line('$'), 1) call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n') 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_match(pat .. stat, g:a)
call assert_equal( call assert_equal(
\ #{current: 1, exact_match: 1, total: 1000, incomplete: 2, maxcount: 999}, \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
"\ Nvim: must recompute if the previous search timed out. \ searchcount(#{recompute: 0}))
\ searchcount(#{recompute: (g:a =~# pat .. '\[?/??\]'), timeout: 500}))
call assert_equal( call assert_equal(
\ #{current: 1, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
\ searchcount(#{recompute: 1, maxcount: 0, timeout: 500})) \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200}))
call assert_equal( call assert_equal(
\ #{current: 3043, exact_match: 1, total: 3052, incomplete: 0, maxcount: 0}, \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
\ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 500})) \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200}))
" Many matches " Many matches
call cursor(1, 1) call cursor(1, 1)
let g:a = execute(':unsilent :norm! n') let g:a = execute(':unsilent :norm! n')
let stat = '\[\%(2/>999\|?/??\)\]' let stat = '\[2/>99\]'
call assert_match(pat .. stat, g:a) call assert_match(pat .. stat, g:a)
call cursor(1, 1) call cursor(1, 1)
let g:a = execute(':unsilent :norm! N') let g:a = execute(':unsilent :norm! N')
let stat = 'W \[\%(>999/>999\|?/??\)\]' let stat = 'W \[>99/>99\]'
call assert_match(pat .. stat, g:a) call assert_match(pat .. stat, g:a)
" Nvim: undo the extra lines.
undo
" right-left " right-left
if exists("+rightleft") if exists("+rightleft")
set rl set rl
@ -490,4 +478,166 @@ func Test_search_stat_smartcase_ignorecase()
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
endfunc 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 " vim: shiftwidth=2 sts=2 expandtab