summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-06-01 17:28:35 +0200
committerBram Moolenaar <Bram@vim.org>2020-06-01 17:28:35 +0200
commite8f5ec0d30b629d7166f0ad03434065d8bc822df (patch)
tree47f18763d1948a5ceb7f421af8f904ca0db5d997 /src
parent950587242cad52d067a15f0f0c83528a28f75731 (diff)
downloadvim-git-e8f5ec0d30b629d7166f0ad03434065d8bc822df.tar.gz
patch 8.2.0877: cannot get the search statisticsv8.2.0877
Problem: Cannot get the search statistics. Solution: Add the searchcount() function. (Fujiwara Takuya, closes #4446)
Diffstat (limited to 'src')
-rw-r--r--src/evalfunc.c1
-rw-r--r--src/macros.h1
-rw-r--r--src/proto/search.pro1
-rw-r--r--src/search.c361
-rw-r--r--src/testdir/test_search_stat.vim67
-rw-r--r--src/version.c2
6 files changed, 354 insertions, 79 deletions
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 45937fabf..06de421eb 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -801,6 +801,7 @@ static funcentry_T global_functions[] =
{"screenrow", 0, 0, 0, ret_number, f_screenrow},
{"screenstring", 2, 2, FEARG_1, ret_string, f_screenstring},
{"search", 1, 4, FEARG_1, ret_number, f_search},
+ {"searchcount", 0, 1, FEARG_1, ret_dict_any, f_searchcount},
{"searchdecl", 1, 3, FEARG_1, ret_number, f_searchdecl},
{"searchpair", 3, 7, 0, ret_number, f_searchpair},
{"searchpairpos", 3, 7, 0, ret_list_number, f_searchpairpos},
diff --git a/src/macros.h b/src/macros.h
index 2ac705d4a..16421d261 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -33,6 +33,7 @@
: (a)->coladd < (b)->coladd)
#define EQUAL_POS(a, b) (((a).lnum == (b).lnum) && ((a).col == (b).col) && ((a).coladd == (b).coladd))
#define CLEAR_POS(a) do {(a)->lnum = 0; (a)->col = 0; (a)->coladd = 0;} while (0)
+#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
#define LTOREQ_POS(a, b) (LT_POS(a, b) || EQUAL_POS(a, b))
diff --git a/src/proto/search.pro b/src/proto/search.pro
index 1e15a8828..ccad0fb8e 100644
--- a/src/proto/search.pro
+++ b/src/proto/search.pro
@@ -35,4 +35,5 @@ int linewhite(linenr_T lnum);
void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum);
spat_T *get_spat(int idx);
int get_spat_last_idx(void);
+void f_searchcount(typval_T *argvars, typval_T *rettv);
/* vim: set ft=c : */
diff --git a/src/search.c b/src/search.c
index 7bda6f983..802718d18 100644
--- a/src/search.c
+++ b/src/search.c
@@ -21,7 +21,24 @@ static int check_linecomment(char_u *line);
static void show_pat_in_path(char_u *, int,
int, int, FILE *, linenr_T *, long);
#endif
-static void search_stat(int dirc, pos_T *pos, int show_top_bot_msg, char_u *msgbuf, int recompute);
+
+typedef struct searchstat
+{
+ int cur; // current position of found words
+ int cnt; // total count of found words
+ int exact_match; // TRUE if matched exactly on specified position
+ int incomplete; // 0: search was fully completed
+ // 1: recomputing was timed out
+ // 2: max count exceeded
+ int last_maxcount; // the max count of the last search
+} searchstat_T;
+
+static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, int recompute, int maxcount, long timeout);
+static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
+
+#define SEARCH_STAT_DEF_TIMEOUT 20L
+#define SEARCH_STAT_DEF_MAX_COUNT 99
+#define SEARCH_STAT_BUF_LEN 12
/*
* This file contains various searching-related routines. These fall into
@@ -1203,7 +1220,6 @@ do_search(
char_u *msgbuf = NULL;
size_t len;
int has_offset = FALSE;
-#define SEARCH_STAT_BUF_LEN 12
/*
* A line offset is not remembered, this is vi compatible.
@@ -1591,13 +1607,17 @@ do_search(
&& c != FAIL
&& !shortmess(SHM_SEARCHCOUNT)
&& msgbuf != NULL)
- search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
- (count != 1 || has_offset
+ cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
+ show_top_bot_msg, msgbuf,
+ (count != 1 || has_offset
#ifdef FEAT_FOLDING
- || (!(fdo_flags & FDO_SEARCH) &&
- hasFolding(curwin->w_cursor.lnum, NULL, NULL))
+ || (!(fdo_flags & FDO_SEARCH)
+ && hasFolding(curwin->w_cursor.lnum,
+ NULL, NULL))
#endif
- ));
+ ),
+ SEARCH_STAT_DEF_MAX_COUNT,
+ SEARCH_STAT_DEF_TIMEOUT);
/*
* The search command can be followed by a ';' to do another search.
@@ -3061,15 +3081,96 @@ linewhite(linenr_T lnum)
/*
* Add the search count "[3/19]" to "msgbuf".
+ * See update_search_stat() for other arguments.
+ */
+ static void
+cmdline_search_stat(
+ int dirc,
+ pos_T *pos,
+ pos_T *cursor_pos,
+ int show_top_bot_msg,
+ char_u *msgbuf,
+ int recompute,
+ int maxcount,
+ long timeout)
+{
+ searchstat_T stat;
+
+ update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
+ timeout);
+ if (stat.cur > 0)
+ {
+ char t[SEARCH_STAT_BUF_LEN];
+ size_t len;
+
+#ifdef FEAT_RIGHTLEFT
+ if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
+ {
+ if (stat.incomplete == 1)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ else if (stat.cnt > maxcount && stat.cur > maxcount)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ else if (stat.cnt > maxcount)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
+ maxcount, stat.cur);
+ else
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cnt, stat.cur);
+ }
+ else
+#endif
+ {
+ if (stat.incomplete == 1)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ else if (stat.cnt > maxcount && stat.cur > maxcount)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ else if (stat.cnt > maxcount)
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
+ stat.cur, maxcount);
+ else
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cur, stat.cnt);
+ }
+
+ len = STRLEN(t);
+ if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
+ {
+ mch_memmove(t + 2, t, len);
+ t[0] = 'W';
+ t[1] = ' ';
+ len += 2;
+ }
+
+ mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
+ if (dirc == '?' && stat.cur == maxcount + 1)
+ stat.cur = -1;
+
+ // keep the message even after redraw, but don't put in history
+ msg_hist_off = TRUE;
+ give_warning(msgbuf, FALSE);
+ msg_hist_off = FALSE;
+ }
+}
+
+/*
+ * Add the search count information to "stat".
+ * "stat" must not be NULL.
* When "recompute" is TRUE always recompute the numbers.
+ * dirc == 0: don't find the next/previous match (only set the result to "stat")
+ * dirc == '/': find the next match
+ * dirc == '?': find the previous match
*/
static void
-search_stat(
- int dirc,
- pos_T *pos,
- int show_top_bot_msg,
- char_u *msgbuf,
- int recompute)
+update_search_stat(
+ int dirc,
+ pos_T *pos,
+ pos_T *cursor_pos,
+ searchstat_T *stat,
+ int recompute,
+ int maxcount,
+ long timeout)
{
int save_ws = p_ws;
int wraparound = FALSE;
@@ -3077,13 +3178,28 @@ search_stat(
static pos_T lastpos = {0, 0, 0};
static int cur = 0;
static int cnt = 0;
+ static int exact_match = FALSE;
+ static int incomplete = 0;
+ static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
static int chgtick = 0;
static char_u *lastpat = NULL;
static buf_T *lbuf = NULL;
#ifdef FEAT_RELTIME
proftime_T start;
#endif
-#define OUT_OF_TIME 999
+
+ vim_memset(stat, 0, sizeof(searchstat_T));
+
+ if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
+ {
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
+ return;
+ }
+ last_maxcount = maxcount;
wraparound = ((dirc == '?' && LT_POS(lastpos, p))
|| (dirc == '/' && LT_POS(p, lastpos)));
@@ -3091,103 +3207,77 @@ search_stat(
// If anything relevant changed the count has to be recomputed.
// MB_STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no MB_STRNICMP function.
+ // XXX: above comment should be "no MB_STRCMP function" ?
if (!(chgtick == CHANGEDTICK(curbuf)
&& MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
- && EQUAL_POS(lastpos, curwin->w_cursor)
- && lbuf == curbuf) || wraparound || cur < 0 || cur > 99 || recompute)
+ && EQUAL_POS(lastpos, *cursor_pos)
+ && lbuf == curbuf) || wraparound || cur < 0
+ || (maxcount > 0 && cur > maxcount) || recompute)
{
cur = 0;
cnt = 0;
+ exact_match = FALSE;
+ incomplete = 0;
CLEAR_POS(&lastpos);
lbuf = curbuf;
}
- if (EQUAL_POS(lastpos, curwin->w_cursor) && !wraparound
- && (dirc == '/' ? cur < cnt : cur > 0))
- cur += dirc == '/' ? 1 : -1;
+ if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
+ && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0))
+ cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
else
{
+ int done_search = FALSE;
+ pos_T endpos = {0, 0, 0};
+
p_ws = FALSE;
#ifdef FEAT_RELTIME
- profile_setlimit(20L, &start);
+ if (timeout > 0)
+ profile_setlimit(timeout, &start);
#endif
- while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
+ while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
{
+ done_search = TRUE;
#ifdef FEAT_RELTIME
// Stop after passing the time limit.
- if (profile_passed_limit(&start))
+ if (timeout > 0 && profile_passed_limit(&start))
{
- cnt = OUT_OF_TIME;
- cur = OUT_OF_TIME;
+ incomplete = 1;
break;
}
#endif
cnt++;
if (LTOREQ_POS(lastpos, p))
- cur++;
+ {
+ cur = cnt;
+ if (LTOREQ_POS(p, endpos))
+ exact_match = TRUE;
+ }
fast_breakcheck();
- if (cnt > 99)
+ if (maxcount > 0 && cnt > maxcount)
+ {
+ incomplete = 2; // max count exceeded
break;
+ }
}
if (got_int)
cur = -1; // abort
- }
- if (cur > 0)
- {
- char t[SEARCH_STAT_BUF_LEN] = "";
- size_t len;
-
-#ifdef FEAT_RIGHTLEFT
- if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
+ if (done_search)
{
- if (cur == OUT_OF_TIME)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
- else if (cnt > 99 && cur > 99)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- else if (cnt > 99)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur);
- else
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur);
+ vim_free(lastpat);
+ lastpat = vim_strsave(spats[last_idx].pat);
+ chgtick = CHANGEDTICK(curbuf);
+ lbuf = curbuf;
+ lastpos = p;
}
- else
-#endif
- {
- if (cur == OUT_OF_TIME)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
- else if (cnt > 99 && cur > 99)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- else if (cnt > 99)
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur);
- else
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt);
- }
-
- len = STRLEN(t);
- if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
- {
- mch_memmove(t + 2, t, len);
- t[0] = 'W';
- t[1] = ' ';
- len += 2;
- }
-
- mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
- if (dirc == '?' && cur == 100)
- cur = -1;
-
- vim_free(lastpat);
- lastpat = vim_strsave(spats[last_idx].pat);
- chgtick = CHANGEDTICK(curbuf);
- lbuf = curbuf;
- lastpos = p;
-
- // keep the message even after redraw, but don't put in history
- msg_hist_off = TRUE;
- give_warning(msgbuf, FALSE);
- msg_hist_off = FALSE;
}
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
p_ws = save_ws;
}
@@ -3959,3 +4049,118 @@ get_spat_last_idx(void)
return last_idx;
}
#endif
+
+#ifdef FEAT_EVAL
+/*
+ * "searchcount()" function
+ */
+ void
+f_searchcount(typval_T *argvars, typval_T *rettv)
+{
+ pos_T pos = curwin->w_cursor;
+ char_u *pattern = NULL;
+ int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
+ long timeout = SEARCH_STAT_DEF_TIMEOUT;
+ int recompute = TRUE;
+ searchstat_T stat;
+
+ if (rettv_dict_alloc(rettv) == FAIL)
+ return;
+
+ if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
+ recompute = TRUE;
+
+ if (argvars[0].v_type != VAR_UNKNOWN)
+ {
+ dict_T *dict = argvars[0].vval.v_dict;
+ dictitem_T *di;
+ listitem_T *li;
+ int error = FALSE;
+
+ di = dict_find(dict, (char_u *)"timeout", -1);
+ if (di != NULL)
+ {
+ timeout = (long)tv_get_number_chk(&di->di_tv, &error);
+ if (error)
+ return;
+ }
+ di = dict_find(dict, (char_u *)"maxcount", -1);
+ if (di != NULL)
+ {
+ maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
+ if (error)
+ return;
+ }
+ di = dict_find(dict, (char_u *)"recompute", -1);
+ if (di != NULL)
+ {
+ recompute = tv_get_number_chk(&di->di_tv, &error);
+ if (error)
+ return;
+ }
+ di = dict_find(dict, (char_u *)"pattern", -1);
+ if (di != NULL)
+ {
+ pattern = tv_get_string_chk(&di->di_tv);
+ if (pattern == NULL)
+ return;
+ }
+ di = dict_find(dict, (char_u *)"pos", -1);
+ if (di != NULL)
+ {
+ if (di->di_tv.v_type != VAR_LIST)
+ {
+ semsg(_(e_invarg2), "pos");
+ return;
+ }
+ if (list_len(di->di_tv.vval.v_list) != 3)
+ {
+ semsg(_(e_invarg2), "List format should be [lnum, col, off]");
+ return;
+ }
+ li = list_find(di->di_tv.vval.v_list, 0L);
+ if (li != NULL)
+ {
+ pos.lnum = tv_get_number_chk(&li->li_tv, &error);
+ if (error)
+ return;
+ }
+ li = list_find(di->di_tv.vval.v_list, 1L);
+ if (li != NULL)
+ {
+ pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
+ if (error)
+ return;
+ }
+ li = list_find(di->di_tv.vval.v_list, 2L);
+ if (li != NULL)
+ {
+ pos.coladd = tv_get_number_chk(&li->li_tv, &error);
+ if (error)
+ return;
+ }
+ }
+ }
+
+ save_last_search_pattern();
+ if (pattern != NULL)
+ {
+ if (*pattern == NUL)
+ goto the_end;
+ spats[last_idx].pat = vim_strsave(pattern);
+ }
+ if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
+ goto the_end; // the previous pattern was never defined
+
+ update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
+
+ dict_add_number(rettv->vval.v_dict, "current", stat.cur);
+ dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
+ dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
+ dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
+ dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
+
+the_end:
+ restore_last_search_pattern();
+}
+#endif
diff --git a/src/testdir/test_search_stat.vim b/src/testdir/test_search_stat.vim
index 68d339fb3..7c2a19585 100644
--- a/src/testdir/test_search_stat.vim
+++ b/src/testdir/test_search_stat.vim
@@ -9,14 +9,44 @@ func Test_search_stat()
" Append 50 lines with text to search for, "foobar" appears 20 times
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
- " match at second line
call cursor(1, 1)
+
+ " searchcount() returns an empty dictionary when previous pattern was not set
+ call assert_equal({}, searchcount(#{pattern: ''}))
+ " 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: 99},
+ \ searchcount(#{pattern: 'foo'}))
+ call assert_equal(
+ \ #{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: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
+ call assert_equal(
+ \ #{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},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
+
+ " match at second line
let messages_before = execute('messages')
let @/ = 'fo*\(bar\?\)\?'
let g:a = execute(':unsilent :norm! n')
let stat = '\[2/50\]'
let pat = escape(@/, '()*?'). '\s\+'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{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'))
@@ -25,6 +55,9 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[50/50\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
" No search stat
set shortmess+=S
@@ -32,6 +65,14 @@ func Test_search_stat()
let stat = '\[2/50\]'
let g:a = execute(':unsilent :norm! n')
call assert_notmatch(pat .. stat, g:a)
+ call writefile(getline(1, '$'), 'sample.txt')
+ " n does not update search stat
+ call assert_equal(
+ \ #{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: 99},
+ \ searchcount(#{recompute: v:true}))
set shortmess-=S
" Many matches
@@ -41,10 +82,28 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[>99/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: v:true, maxcount: 0}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0]}))
call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n')
let stat = 'W \[1/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0}))
+ call assert_equal(
+ \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0]}))
" Many matches
call cursor(1, 1)
@@ -180,6 +239,12 @@ func Test_search_stat()
call assert_match('^\s\+' .. stat, g:b)
unmap n
+ " Time out
+ %delete _
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
+ call cursor(1, 1)
+ call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
+
" Clean up
set shortmess+=S
" close the window
diff --git a/src/version.c b/src/version.c
index ce0e5452b..af3aadf4d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 877,
+/**/
876,
/**/
875,