To: vim_dev@googlegroups.com Subject: Patch 8.2.0877 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0877 Problem: Cannot get the search statistics. Solution: Add the searchcount() function. (Fujiwara Takuya, closes #4446) Files: runtime/doc/eval.txt, src/evalfunc.c, src/macros.h, src/proto/search.pro, src/search.c, src/testdir/test_search_stat.vim *** ../vim-8.2.0876/runtime/doc/eval.txt 2020-06-01 16:09:30.266292734 +0200 --- runtime/doc/eval.txt 2020-06-01 16:54:39.484490917 +0200 *************** *** 2708,2713 **** --- 2714,2720 ---- screenstring({row}, {col}) String characters at screen position search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) Number search for {pattern} + searchcount([{options}]) Dict get or update search stats searchdecl({name} [, {global} [, {thisblock}]]) Number search for variable declaration searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]]) *************** *** 8413,8418 **** --- 8430,8555 ---- Can also be used as a |method|: > GetPattern()->search() + searchcount([{options}]) *searchcount()* + Get or update the last search count, like what is displayed + without the "S" flag in 'shortmess'. This works even if + 'shortmess' does contain the "S" flag. + + This returns a Dictionary. The dictionary is empty if the + previous pattern was not set and "pattern" was not specified. + + key type meaning ~ + current |Number| current position of match; + 0 if the cursor position is + before the first match + exact_match |Boolean| 1 if "current" is matched on + "pos", otherwise 0 + total |Number| total count of matches found + incomplete |Number| 0: search was fully completed + 1: recomputing was timed out + 2: max count exceeded + + For {options} see further down. + + To get the last search count when |n| or |N| was pressed, call + this function with `recompute: 0` . This sometimes returns + wrong information because |n| and |N|'s maximum count is 99. + If it exceeded 99 the result must be max count + 1 (100). If + you want to get correct information, specify `recompute: 1`: > + + " result == maxcount + 1 (100) when many matches + let result = searchcount(#{recompute: 0}) + + " Below returns correct result (recompute defaults + " to 1) + let result = searchcount() + < + The function is useful to add the count to |statusline|: > + function! LastSearchCount() abort + let result = searchcount(#{recompute: 0}) + if empty(result) + return '' + endif + if result.incomplete ==# 1 " timed out + return printf(' /%s [?/??]', @/) + elseif result.incomplete ==# 2 " max count exceeded + if result.total > result.maxcount && + \ result.current > result.maxcount + return printf(' /%s [>%d/>%d]', @/, + \ result.current, result.total) + elseif result.total > result.maxcount + return printf(' /%s [%d/>%d]', @/, + \ result.current, result.total) + endif + endif + return printf(' /%s [%d/%d]', @/, + \ result.current, result.total) + endfunction + let &statusline .= '%{LastSearchCount()}' + + " Or if you want to show the count only when + " 'hlsearch' was on + " let &statusline .= + " \ '%{v:hlsearch ? LastSearchCount() : ""}' + < + You can also update the search count, which can be useful in a + |CursorMoved| or |CursorMovedI| autocommand: > + + autocmd CursorMoved,CursorMovedI * + \ let s:searchcount_timer = timer_start( + \ 200, function('s:update_searchcount')) + function! s:update_searchcount(timer) abort + if a:timer ==# s:searchcount_timer + call searchcount(#{ + \ recompute: 1, maxcount: 0, timeout: 100}) + redrawstatus + endif + endfunction + < + This can also be used to count matched texts with specified + pattern in the current buffer using "pattern": > + + " Count '\' in this buffer + " (Note that it also updates search count) + let result = searchcount(#{pattern: '\'}) + + " To restore old search count by old pattern, + " search again + call searchcount() + < + {options} must be a Dictionary. It can contain: + key type meaning ~ + recompute |Boolean| if |TRUE|, recompute the count + like |n| or |N| was executed. + otherwise returns the last + result by |n|, |N|, or this + function is returned. + (default: |TRUE|) + pattern |String| recompute if this was given + and different with |@/|. + this works as same as the + below command is executed + before calling this function > + let @/ = pattern + < (default: |@/|) + timeout |Number| 0 or negative number is no + timeout. timeout milliseconds + for recomputing the result + (default: 0) + maxcount |Number| 0 or negative number is no + limit. max count of matched + text while recomputing the + result. if search exceeded + total count, "total" value + becomes `maxcount + 1` + (default: 0) + pos |List| `[lnum, col, off]` value + when recomputing the result. + this changes "current" result + value. see |cursor()|, |getpos() + (default: cursor's position) + + searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()* Search for the declaration of {name}. *** ../vim-8.2.0876/src/evalfunc.c 2020-06-01 16:09:30.266292734 +0200 --- src/evalfunc.c 2020-06-01 16:39:31.655883511 +0200 *************** *** 801,806 **** --- 801,807 ---- {"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}, *** ../vim-8.2.0876/src/macros.h 2020-05-13 22:44:18.134288832 +0200 --- src/macros.h 2020-06-01 16:37:50.912267051 +0200 *************** *** 33,38 **** --- 33,39 ---- : (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)) *** ../vim-8.2.0876/src/proto/search.pro 2020-04-29 21:03:51.115170232 +0200 --- src/proto/search.pro 2020-06-01 16:55:24.784323145 +0200 *************** *** 35,38 **** --- 35,39 ---- 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 : */ *** ../vim-8.2.0876/src/search.c 2020-05-29 22:49:21.428024187 +0200 --- src/search.c 2020-06-01 17:01:01.727077838 +0200 *************** *** 21,27 **** 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); /* * This file contains various searching-related routines. These fall into --- 21,44 ---- static void show_pat_in_path(char_u *, int, int, int, FILE *, linenr_T *, long); #endif ! ! 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,1209 **** 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. --- 1220,1225 ---- *************** *** 1591,1603 **** && c != FAIL && !shortmess(SHM_SEARCHCOUNT) && msgbuf != NULL) ! search_stat(dirc, &pos, show_top_bot_msg, msgbuf, ! (count != 1 || has_offset #ifdef FEAT_FOLDING ! || (!(fdo_flags & FDO_SEARCH) && ! hasFolding(curwin->w_cursor.lnum, NULL, NULL)) ! #endif ! )); /* * The search command can be followed by a ';' to do another search. --- 1607,1623 ---- && c != FAIL && !shortmess(SHM_SEARCHCOUNT) && msgbuf != NULL) ! 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)) ! #endif ! ), ! SEARCH_STAT_DEF_MAX_COUNT, ! SEARCH_STAT_DEF_TIMEOUT); /* * The search command can be followed by a ';' to do another search. *************** *** 3061,3075 **** /* * Add the search count "[3/19]" to "msgbuf". * When "recompute" is TRUE always recompute the numbers. */ static void ! search_stat( ! int dirc, ! pos_T *pos, ! int show_top_bot_msg, ! char_u *msgbuf, ! int recompute) { int save_ws = p_ws; int wraparound = FALSE; --- 3081,3176 ---- /* * 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 ! 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,3089 **** static pos_T lastpos = {0, 0, 0}; static int cur = 0; static int cnt = 0; 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 wraparound = ((dirc == '?' && LT_POS(lastpos, p)) || (dirc == '/' && LT_POS(p, lastpos))); --- 3178,3205 ---- 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 ! ! 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,3193 **** // 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. 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) { cur = 0; cnt = 0; CLEAR_POS(&lastpos); lbuf = curbuf; } ! if (EQUAL_POS(lastpos, curwin->w_cursor) && !wraparound ! && (dirc == '/' ? cur < cnt : cur > 0)) ! cur += dirc == '/' ? 1 : -1; else { p_ws = FALSE; #ifdef FEAT_RELTIME ! profile_setlimit(20L, &start); #endif ! while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL) { #ifdef FEAT_RELTIME // Stop after passing the time limit. ! if (profile_passed_limit(&start)) { ! cnt = OUT_OF_TIME; ! cur = OUT_OF_TIME; break; } #endif cnt++; if (LTOREQ_POS(lastpos, p)) ! cur++; fast_breakcheck(); ! if (cnt > 99) 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 (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); } - 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; } p_ws = save_ws; } --- 3207,3283 ---- // 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, *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, *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 ! if (timeout > 0) ! profile_setlimit(timeout, &start); #endif ! 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 (timeout > 0 && profile_passed_limit(&start)) { ! incomplete = 1; break; } #endif cnt++; if (LTOREQ_POS(lastpos, p)) ! { ! cur = cnt; ! if (LTOREQ_POS(p, endpos)) ! exact_match = TRUE; ! } fast_breakcheck(); ! if (maxcount > 0 && cnt > maxcount) ! { ! incomplete = 2; // max count exceeded break; + } } if (got_int) cur = -1; // abort ! if (done_search) { ! vim_free(lastpat); ! lastpat = vim_strsave(spats[last_idx].pat); ! chgtick = CHANGEDTICK(curbuf); ! lbuf = curbuf; ! lastpos = p; } } + stat->cur = cur; + stat->cnt = cnt; + stat->exact_match = exact_match; + stat->incomplete = incomplete; + stat->last_maxcount = last_maxcount; p_ws = save_ws; } *************** *** 3959,3961 **** --- 4049,4166 ---- 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 *** ../vim-8.2.0876/src/testdir/test_search_stat.vim 2020-05-29 22:49:21.432024171 +0200 --- src/testdir/test_search_stat.vim 2020-06-01 16:37:50.916267035 +0200 *************** *** 9,22 **** " 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) 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) " didn't get added to message history call assert_equal(messages_before, execute('messages')) --- 9,52 ---- " Append 50 lines with text to search for, "foobar" appears 20 times call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10)) 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,30 **** --- 55,63 ---- 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,37 **** --- 65,78 ---- 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,50 **** --- 82,109 ---- 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,185 **** --- 239,250 ---- 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 *** ../vim-8.2.0876/src/version.c 2020-06-01 16:26:15.338852818 +0200 --- src/version.c 2020-06-01 16:43:01.787090184 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 877, /**/ -- "Marriage is when a man and woman become as one; the trouble starts when they try to decide which one" /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///