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 '\<foo\>' in this buffer
+                       " (Note that it also updates search count)
+                       let result = searchcount(#{pattern: '\<foo\>'})
+ 
+                       " 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 -- [email protected] -- 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    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202006011529.051FT3Ar947035%40masaka.moolenaar.net.

Raspunde prin e-mail lui