forked from aniani/vim
patch 8.2.0877: cannot get the search statistics
Problem: Cannot get the search statistics. Solution: Add the searchcount() function. (Fujiwara Takuya, closes #4446)
This commit is contained in:
@@ -2714,6 +2714,7 @@ screenrow() Number current cursor row
|
|||||||
screenstring({row}, {col}) String characters at screen position
|
screenstring({row}, {col}) String characters at screen position
|
||||||
search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
|
search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
|
||||||
Number search for {pattern}
|
Number search for {pattern}
|
||||||
|
searchcount([{options}]) Dict get or update search stats
|
||||||
searchdecl({name} [, {global} [, {thisblock}]])
|
searchdecl({name} [, {global} [, {thisblock}]])
|
||||||
Number search for variable declaration
|
Number search for variable declaration
|
||||||
searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
|
searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
|
||||||
@@ -8429,6 +8430,126 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
|
|||||||
Can also be used as a |method|: >
|
Can also be used as a |method|: >
|
||||||
GetPattern()->search()
|
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()*
|
searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()*
|
||||||
Search for the declaration of {name}.
|
Search for the declaration of {name}.
|
||||||
|
|
||||||
|
@@ -801,6 +801,7 @@ static funcentry_T global_functions[] =
|
|||||||
{"screenrow", 0, 0, 0, ret_number, f_screenrow},
|
{"screenrow", 0, 0, 0, ret_number, f_screenrow},
|
||||||
{"screenstring", 2, 2, FEARG_1, ret_string, f_screenstring},
|
{"screenstring", 2, 2, FEARG_1, ret_string, f_screenstring},
|
||||||
{"search", 1, 4, FEARG_1, ret_number, f_search},
|
{"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},
|
{"searchdecl", 1, 3, FEARG_1, ret_number, f_searchdecl},
|
||||||
{"searchpair", 3, 7, 0, ret_number, f_searchpair},
|
{"searchpair", 3, 7, 0, ret_number, f_searchpair},
|
||||||
{"searchpairpos", 3, 7, 0, ret_list_number, f_searchpairpos},
|
{"searchpairpos", 3, 7, 0, ret_list_number, f_searchpairpos},
|
||||||
|
@@ -33,6 +33,7 @@
|
|||||||
: (a)->coladd < (b)->coladd)
|
: (a)->coladd < (b)->coladd)
|
||||||
#define EQUAL_POS(a, b) (((a).lnum == (b).lnum) && ((a).col == (b).col) && ((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 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))
|
#define LTOREQ_POS(a, b) (LT_POS(a, b) || EQUAL_POS(a, b))
|
||||||
|
|
||||||
|
@@ -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);
|
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);
|
spat_T *get_spat(int idx);
|
||||||
int get_spat_last_idx(void);
|
int get_spat_last_idx(void);
|
||||||
|
void f_searchcount(typval_T *argvars, typval_T *rettv);
|
||||||
/* vim: set ft=c : */
|
/* vim: set ft=c : */
|
||||||
|
391
src/search.c
391
src/search.c
@@ -21,7 +21,24 @@ static int check_linecomment(char_u *line);
|
|||||||
static void show_pat_in_path(char_u *, int,
|
static void show_pat_in_path(char_u *, int,
|
||||||
int, int, FILE *, linenr_T *, long);
|
int, int, FILE *, linenr_T *, long);
|
||||||
#endif
|
#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
|
* This file contains various searching-related routines. These fall into
|
||||||
@@ -1203,7 +1220,6 @@ do_search(
|
|||||||
char_u *msgbuf = NULL;
|
char_u *msgbuf = NULL;
|
||||||
size_t len;
|
size_t len;
|
||||||
int has_offset = FALSE;
|
int has_offset = FALSE;
|
||||||
#define SEARCH_STAT_BUF_LEN 12
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A line offset is not remembered, this is vi compatible.
|
* A line offset is not remembered, this is vi compatible.
|
||||||
@@ -1591,13 +1607,17 @@ do_search(
|
|||||||
&& c != FAIL
|
&& c != FAIL
|
||||||
&& !shortmess(SHM_SEARCHCOUNT)
|
&& !shortmess(SHM_SEARCHCOUNT)
|
||||||
&& msgbuf != NULL)
|
&& msgbuf != NULL)
|
||||||
search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
|
cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
|
||||||
|
show_top_bot_msg, msgbuf,
|
||||||
(count != 1 || has_offset
|
(count != 1 || has_offset
|
||||||
#ifdef FEAT_FOLDING
|
#ifdef FEAT_FOLDING
|
||||||
|| (!(fdo_flags & FDO_SEARCH) &&
|
|| (!(fdo_flags & FDO_SEARCH)
|
||||||
hasFolding(curwin->w_cursor.lnum, NULL, NULL))
|
&& hasFolding(curwin->w_cursor.lnum,
|
||||||
|
NULL, NULL))
|
||||||
#endif
|
#endif
|
||||||
));
|
),
|
||||||
|
SEARCH_STAT_DEF_MAX_COUNT,
|
||||||
|
SEARCH_STAT_DEF_TIMEOUT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The search command can be followed by a ';' to do another search.
|
* The search command can be followed by a ';' to do another search.
|
||||||
@@ -3061,107 +3081,57 @@ linewhite(linenr_T lnum)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Add the search count "[3/19]" to "msgbuf".
|
* Add the search count "[3/19]" to "msgbuf".
|
||||||
* When "recompute" is TRUE always recompute the numbers.
|
* See update_search_stat() for other arguments.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
search_stat(
|
cmdline_search_stat(
|
||||||
int dirc,
|
int dirc,
|
||||||
pos_T *pos,
|
pos_T *pos,
|
||||||
|
pos_T *cursor_pos,
|
||||||
int show_top_bot_msg,
|
int show_top_bot_msg,
|
||||||
char_u *msgbuf,
|
char_u *msgbuf,
|
||||||
int recompute)
|
int recompute,
|
||||||
|
int maxcount,
|
||||||
|
long timeout)
|
||||||
{
|
{
|
||||||
int save_ws = p_ws;
|
searchstat_T stat;
|
||||||
int wraparound = FALSE;
|
|
||||||
pos_T p = (*pos);
|
|
||||||
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))
|
update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
|
||||||
|| (dirc == '/' && LT_POS(p, lastpos)));
|
timeout);
|
||||||
|
if (stat.cur > 0)
|
||||||
// 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;
|
char t[SEARCH_STAT_BUF_LEN];
|
||||||
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;
|
size_t len;
|
||||||
|
|
||||||
#ifdef FEAT_RIGHTLEFT
|
#ifdef FEAT_RIGHTLEFT
|
||||||
if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
|
if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
|
||||||
{
|
{
|
||||||
if (cur == OUT_OF_TIME)
|
if (stat.incomplete == 1)
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
|
||||||
else if (cnt > 99 && cur > 99)
|
else if (stat.cnt > maxcount && stat.cur > maxcount)
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
|
||||||
else if (cnt > 99)
|
maxcount, maxcount);
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur);
|
else if (stat.cnt > maxcount)
|
||||||
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
|
||||||
|
maxcount, stat.cur);
|
||||||
else
|
else
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur);
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
|
||||||
|
stat.cnt, stat.cur);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (cur == OUT_OF_TIME)
|
if (stat.incomplete == 1)
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
|
||||||
else if (cnt > 99 && cur > 99)
|
else if (stat.cnt > maxcount && stat.cur > maxcount)
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
|
||||||
else if (cnt > 99)
|
maxcount, maxcount);
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur);
|
else if (stat.cnt > maxcount)
|
||||||
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
|
||||||
|
stat.cur, maxcount);
|
||||||
else
|
else
|
||||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt);
|
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
|
||||||
|
stat.cur, stat.cnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
len = STRLEN(t);
|
len = STRLEN(t);
|
||||||
@@ -3174,20 +3144,140 @@ search_stat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
|
mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
|
||||||
if (dirc == '?' && cur == 100)
|
if (dirc == '?' && stat.cur == maxcount + 1)
|
||||||
cur = -1;
|
stat.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
|
// keep the message even after redraw, but don't put in history
|
||||||
msg_hist_off = TRUE;
|
msg_hist_off = TRUE;
|
||||||
give_warning(msgbuf, FALSE);
|
give_warning(msgbuf, FALSE);
|
||||||
msg_hist_off = 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;
|
||||||
|
pos_T p = (*pos);
|
||||||
|
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)));
|
||||||
|
|
||||||
|
// 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;
|
p_ws = save_ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3959,3 +4049,118 @@ get_spat_last_idx(void)
|
|||||||
return last_idx;
|
return last_idx;
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
||||||
|
@@ -9,14 +9,44 @@ func Test_search_stat()
|
|||||||
" Append 50 lines with text to search for, "foobar" appears 20 times
|
" Append 50 lines with text to search for, "foobar" appears 20 times
|
||||||
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
|
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
|
||||||
|
|
||||||
" match at second line
|
|
||||||
call cursor(1, 1)
|
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 messages_before = execute('messages')
|
||||||
let @/ = 'fo*\(bar\?\)\?'
|
let @/ = 'fo*\(bar\?\)\?'
|
||||||
let g:a = execute(':unsilent :norm! n')
|
let g:a = execute(':unsilent :norm! n')
|
||||||
let stat = '\[2/50\]'
|
let stat = '\[2/50\]'
|
||||||
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(
|
||||||
|
\ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||||
|
\ 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'))
|
||||||
|
|
||||||
@@ -25,6 +55,9 @@ func Test_search_stat()
|
|||||||
let g:a = execute(':unsilent :norm! n')
|
let g:a = execute(':unsilent :norm! n')
|
||||||
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(
|
||||||
|
\ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||||
|
\ searchcount(#{recompute: 0}))
|
||||||
|
|
||||||
" No search stat
|
" No search stat
|
||||||
set shortmess+=S
|
set shortmess+=S
|
||||||
@@ -32,6 +65,14 @@ func Test_search_stat()
|
|||||||
let stat = '\[2/50\]'
|
let stat = '\[2/50\]'
|
||||||
let g:a = execute(':unsilent :norm! n')
|
let g:a = execute(':unsilent :norm! n')
|
||||||
call assert_notmatch(pat .. stat, g:a)
|
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
|
set shortmess-=S
|
||||||
|
|
||||||
" Many matches
|
" Many matches
|
||||||
@@ -41,10 +82,28 @@ func Test_search_stat()
|
|||||||
let g:a = execute(':unsilent :norm! n')
|
let g:a = execute(':unsilent :norm! n')
|
||||||
let stat = '\[>99/>99\]'
|
let stat = '\[>99/>99\]'
|
||||||
call assert_match(pat .. stat, g:a)
|
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)
|
call cursor(line('$'), 1)
|
||||||
let g:a = execute(':unsilent :norm! n')
|
let g:a = execute(':unsilent :norm! n')
|
||||||
let stat = 'W \[1/>99\]'
|
let stat = 'W \[1/>99\]'
|
||||||
call assert_match(pat .. stat, g:a)
|
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
|
" Many matches
|
||||||
call cursor(1, 1)
|
call cursor(1, 1)
|
||||||
@@ -180,6 +239,12 @@ func Test_search_stat()
|
|||||||
call assert_match('^\s\+' .. stat, g:b)
|
call assert_match('^\s\+' .. stat, g:b)
|
||||||
unmap n
|
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
|
" Clean up
|
||||||
set shortmess+=S
|
set shortmess+=S
|
||||||
" close the window
|
" close the window
|
||||||
|
@@ -746,6 +746,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
877,
|
||||||
/**/
|
/**/
|
||||||
876,
|
876,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user