mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
vim-patch:8.2.0877: cannot get the search statistics
Problem: Cannot get the search statistics.
Solution: Add the searchcount() function. (Fujiwara Takuya, closes vim/vim#4446)
e8f5ec0d30
Additional changes:
- Tests weren't passing because the test ran assuming the cursor was at
start of buffer but append() left the cursor at end of buffer .
So cursor is moved to start of buffer after append.
- searchcount() added to list of builtin functions.
This commit is contained in:
parent
f8173df4d7
commit
e498f265f4
@ -2385,6 +2385,7 @@ screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character
|
||||
screenrow() Number current cursor row
|
||||
search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
|
||||
Number search for {pattern}
|
||||
searchcount([{options}]) Dict Get or update the last search count
|
||||
searchdecl({name} [, {global} [, {thisblock}]])
|
||||
Number search for variable declaration
|
||||
searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
|
||||
@ -7284,6 +7285,126 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
|
||||
The 'n' flag tells the function not to move the cursor.
|
||||
|
||||
|
||||
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}.
|
||||
|
||||
|
@ -286,6 +286,7 @@ return {
|
||||
screenpos={args=3},
|
||||
screenrow={},
|
||||
search={args={1, 4}},
|
||||
searchcount={args={0,1}},
|
||||
searchdecl={args={1, 3}},
|
||||
searchpair={args={3, 7}},
|
||||
searchpairpos={args={3, 7}},
|
||||
|
@ -238,5 +238,6 @@
|
||||
# define PRAGMA_DIAG_POP
|
||||
#endif
|
||||
|
||||
#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
|
||||
|
||||
#endif // NVIM_MACROS_H
|
||||
|
@ -46,38 +46,36 @@
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/time.h"
|
||||
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "search.c.generated.h"
|
||||
#endif
|
||||
/*
|
||||
* This file contains various searching-related routines. These fall into
|
||||
* three groups:
|
||||
* 1. string searches (for /, ?, n, and N)
|
||||
* 2. character searches within a single line (for f, F, t, T, etc)
|
||||
* 3. "other" kinds of searches like the '%' command, and 'word' searches.
|
||||
*/
|
||||
|
||||
/*
|
||||
* String searches
|
||||
*
|
||||
* The string search functions are divided into two levels:
|
||||
* lowest: searchit(); uses a pos_T for starting position and found match.
|
||||
* Highest: do_search(); uses curwin->w_cursor; calls searchit().
|
||||
*
|
||||
* The last search pattern is remembered for repeating the same search.
|
||||
* This pattern is shared between the :g, :s, ? and / commands.
|
||||
* This is in search_regcomp().
|
||||
*
|
||||
* The actual string matching is done using a heavily modified version of
|
||||
* Henry Spencer's regular expression library. See regexp.c.
|
||||
*/
|
||||
// This file contains various searching-related routines. These fall into
|
||||
// three groups:
|
||||
// 1. string searches (for /, ?, n, and N)
|
||||
// 2. character searches within a single line (for f, F, t, T, etc)
|
||||
// 3. "other" kinds of searches like the '%' command, and 'word' searches.
|
||||
//
|
||||
//
|
||||
// String searches
|
||||
//
|
||||
// The string search functions are divided into two levels:
|
||||
// lowest: searchit(); uses a pos_T for starting position and found match.
|
||||
// Highest: do_search(); uses curwin->w_cursor; calls searchit().
|
||||
//
|
||||
// The last search pattern is remembered for repeating the same search.
|
||||
// This pattern is shared between the :g, :s, ? and / commands.
|
||||
// This is in search_regcomp().
|
||||
//
|
||||
// The actual string matching is done using a heavily modified version of
|
||||
// Henry Spencer's regular expression library. See regexp.c.
|
||||
//
|
||||
//
|
||||
//
|
||||
// Two search patterns are remembered: One for the :substitute command and
|
||||
// one for other searches. last_idx points to the one that was used the last
|
||||
// time.
|
||||
|
||||
/*
|
||||
* Two search patterns are remembered: One for the :substitute command and
|
||||
* one for other searches. last_idx points to the one that was used the last
|
||||
* time.
|
||||
*/
|
||||
static struct spat spats[2] =
|
||||
{
|
||||
// Last used search pattern
|
||||
@ -1002,7 +1000,7 @@ static int first_submatch(regmmatch_T *rp)
|
||||
/*
|
||||
* Highest level string search function.
|
||||
* Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
|
||||
* If 'dirc' is 0: use previous dir.
|
||||
* If 'dirc' is 0: use previous dir.
|
||||
* If 'pat' is NULL or empty : use previous string.
|
||||
* If 'options & SEARCH_REV' : go in reverse of previous dir.
|
||||
* If 'options & SEARCH_ECHO': echo the search command and handle options
|
||||
@ -1042,7 +1040,6 @@ int do_search(
|
||||
char_u *msgbuf = NULL;
|
||||
size_t len;
|
||||
bool has_offset = false;
|
||||
#define SEARCH_STAT_BUF_LEN 12
|
||||
|
||||
/*
|
||||
* A line offset is not remembered, this is vi compatible.
|
||||
@ -1383,11 +1380,14 @@ int do_search(
|
||||
&& c != FAIL
|
||||
&& !shortmess(SHM_SEARCHCOUNT)
|
||||
&& msgbuf != NULL) {
|
||||
search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
|
||||
(count != 1
|
||||
|| has_offset
|
||||
|| (!(fdo_flags & FDO_SEARCH)
|
||||
&& hasFolding(curwin->w_cursor.lnum, NULL, NULL))));
|
||||
cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
|
||||
show_top_bot_msg, msgbuf,
|
||||
(count != 1 || has_offset
|
||||
|| (!(fdo_flags & FDO_SEARCH)
|
||||
&& hasFolding(curwin->w_cursor.lnum, NULL,
|
||||
NULL))),
|
||||
SEARCH_STAT_DEF_MAX_COUNT,
|
||||
SEARCH_STAT_DEF_TIMEOUT);
|
||||
}
|
||||
|
||||
// The search command can be followed by a ';' to do another search.
|
||||
@ -1715,10 +1715,10 @@ static void find_mps_values(int *initc, int *findc, bool *backwards,
|
||||
* '#' look for preprocessor directives
|
||||
* 'R' look for raw string start: R"delim(text)delim" (only backwards)
|
||||
*
|
||||
* flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
|
||||
* FM_FORWARD search forwards (when initc is '/', '*' or '#')
|
||||
* FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
|
||||
* FM_SKIPCOMM skip comments (not implemented yet!)
|
||||
* flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
|
||||
* FM_FORWARD search forwards (when initc is '/', '*' or '#')
|
||||
* FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
|
||||
* FM_SKIPCOMM skip comments (not implemented yet!)
|
||||
*
|
||||
* "oap" is only used to set oap->motion_type for a linewise motion, it can be
|
||||
* NULL
|
||||
@ -3003,7 +3003,7 @@ current_word(
|
||||
|
||||
/*
|
||||
* If the start is on white space, and white space should be included
|
||||
* (" word"), or start is not on white space, and white space should
|
||||
* (" word"), or start is not on white space, and white space should
|
||||
* not be included ("word"), find end of word.
|
||||
*/
|
||||
if ((cls() == 0) == include) {
|
||||
@ -3012,8 +3012,8 @@ current_word(
|
||||
} else {
|
||||
/*
|
||||
* If the start is not on white space, and white space should be
|
||||
* included ("word "), or start is on white space and white
|
||||
* space should not be included (" "), find start of word.
|
||||
* included ("word "), or start is on white space and white
|
||||
* space should not be included (" "), find start of word.
|
||||
* If we end up in the first column of the next line (single char
|
||||
* word) back up to end of the line.
|
||||
*/
|
||||
@ -4347,92 +4347,44 @@ int linewhite(linenr_T lnum)
|
||||
}
|
||||
|
||||
// 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,
|
||||
bool show_top_bot_msg, char_u *msgbuf, bool recompute)
|
||||
// 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)
|
||||
{
|
||||
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 chgtick = 0;
|
||||
static char_u *lastpat = NULL;
|
||||
static buf_T *lbuf = NULL;
|
||||
proftime_T start;
|
||||
#define OUT_OF_TIME 999
|
||||
searchstat_T stat;
|
||||
|
||||
wraparound = ((dirc == '?' && lt(lastpos, p))
|
||||
|| (dirc == '/' && lt(p, lastpos)));
|
||||
|
||||
// If anything relevant changed the count has to be recomputed.
|
||||
// STRNICMP ignores case, but we should not ignore case.
|
||||
// Unfortunately, there is no STRNICMP function.
|
||||
if (!(chgtick == buf_get_changedtick(curbuf)
|
||||
&& lastpat != NULL // supress clang/NULL passed as nonnull parameter
|
||||
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
|
||||
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
|
||||
&& equalpos(lastpos, curwin->w_cursor)
|
||||
&& lbuf == curbuf)
|
||||
|| wraparound || cur < 0 || cur > 99 || recompute) {
|
||||
cur = 0;
|
||||
cnt = 0;
|
||||
clearpos(&lastpos);
|
||||
lbuf = curbuf;
|
||||
}
|
||||
|
||||
if (equalpos(lastpos, curwin->w_cursor) && !wraparound
|
||||
&& (dirc == '/' ? cur < cnt : cur > 0)) {
|
||||
cur += dirc == '/' ? 1 : -1;
|
||||
} else {
|
||||
p_ws = false;
|
||||
start = profile_setlimit(20L);
|
||||
while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
|
||||
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
|
||||
NULL) != FAIL) {
|
||||
// Stop after passing the time limit.
|
||||
if (profile_passed_limit(start)) {
|
||||
cnt = OUT_OF_TIME;
|
||||
cur = OUT_OF_TIME;
|
||||
break;
|
||||
}
|
||||
cnt++;
|
||||
if (ltoreq(lastpos, p)) {
|
||||
cur++;
|
||||
}
|
||||
fast_breakcheck();
|
||||
if (cnt > 99) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (got_int) {
|
||||
cur = -1; // abort
|
||||
}
|
||||
}
|
||||
if (cur > 0) {
|
||||
char t[SEARCH_STAT_BUF_LEN] = "";
|
||||
int len;
|
||||
update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
|
||||
timeout);
|
||||
if (stat.cur > 0) {
|
||||
char t[SEARCH_STAT_BUF_LEN];
|
||||
size_t len;
|
||||
|
||||
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);
|
||||
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]", cnt, cur);
|
||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
|
||||
stat.cnt, stat.cur);
|
||||
}
|
||||
} else {
|
||||
if (cur == OUT_OF_TIME) {
|
||||
if (stat.incomplete == 1) {
|
||||
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 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]", cur, cnt);
|
||||
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
|
||||
stat.cur, stat.cnt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4445,25 +4397,234 @@ static void search_stat(int dirc, pos_T *pos,
|
||||
}
|
||||
|
||||
memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
|
||||
if (dirc == '?' && cur == 100) {
|
||||
cur = -1;
|
||||
if (dirc == '?' && stat.cur == maxcount + 1) {
|
||||
stat.cur = -1;
|
||||
}
|
||||
|
||||
xfree(lastpat);
|
||||
lastpat = vim_strsave(spats[last_idx].pat);
|
||||
chgtick = buf_get_changedtick(curbuf);
|
||||
lbuf = curbuf;
|
||||
lastpos = p;
|
||||
|
||||
// keep the message even after redraw, but don't put in history
|
||||
msg_hist_off = true;
|
||||
msg_ext_set_kind("search_count");
|
||||
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;
|
||||
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;
|
||||
proftime_T start;
|
||||
|
||||
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(lastpos, p))
|
||||
|| (dirc == '/' && lt(p, lastpos)));
|
||||
|
||||
// If anything relevant changed the count has to be recomputed.
|
||||
// STRNICMP ignores case, but we should not ignore case.
|
||||
// Unfortunately, there is no STRNICMP function.
|
||||
// XXX: above comment should be "no MB_STRCMP function" ?
|
||||
if (!(chgtick == buf_get_changedtick(curbuf)
|
||||
&& lastpat != NULL // supress clang/NULL passed as nonnull parameter
|
||||
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
|
||||
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
|
||||
&& equalpos(lastpos, *cursor_pos)
|
||||
&& lbuf == curbuf)
|
||||
|| wraparound || cur < 0 || (maxcount > 0 && cur > maxcount)
|
||||
|| recompute) {
|
||||
cur = 0;
|
||||
cnt = 0;
|
||||
exact_match = false;
|
||||
incomplete = 0;
|
||||
clearpos(&lastpos);
|
||||
lbuf = curbuf;
|
||||
}
|
||||
|
||||
if (equalpos(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;
|
||||
if (timeout > 0) {
|
||||
start = profile_setlimit(timeout);
|
||||
}
|
||||
while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
|
||||
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
|
||||
NULL) != FAIL) {
|
||||
done_search = true;
|
||||
// Stop after passing the time limit.
|
||||
if (timeout > 0 && profile_passed_limit(start)) {
|
||||
incomplete = 1;
|
||||
break;
|
||||
}
|
||||
cnt++;
|
||||
if (ltoreq(lastpos, p)) {
|
||||
cur = cnt;
|
||||
if (ltoreq(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) {
|
||||
xfree(lastpat);
|
||||
lastpat = vim_strsave(spats[last_idx].pat);
|
||||
chgtick = buf_get_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;
|
||||
}
|
||||
|
||||
// "searchcount()" function
|
||||
void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
{
|
||||
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;
|
||||
|
||||
tv_dict_alloc_ret(rettv);
|
||||
|
||||
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;
|
||||
bool error = false;
|
||||
|
||||
di = tv_dict_find(dict, (const char *)"timeout", -1);
|
||||
if (di != NULL) {
|
||||
timeout = (long)tv_get_number_chk(&di->di_tv, &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
di = tv_dict_find(dict, (const char *)"maxcount", -1);
|
||||
if (di != NULL) {
|
||||
maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
di = tv_dict_find(dict, (const char *)"recompute", -1);
|
||||
if (di != NULL) {
|
||||
recompute = tv_get_number_chk(&di->di_tv, &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
di = tv_dict_find(dict, (const char *)"pattern", -1);
|
||||
if (di != NULL) {
|
||||
pattern = (char_u *)tv_get_string_chk(&di->di_tv);
|
||||
if (pattern == NULL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
di = tv_dict_find(dict, (const char *)"pos", -1);
|
||||
if (di != NULL) {
|
||||
if (di->di_tv.v_type != VAR_LIST) {
|
||||
EMSG2(_(e_invarg2), "pos");
|
||||
return;
|
||||
}
|
||||
if (tv_list_len(di->di_tv.vval.v_list) != 3) {
|
||||
EMSG2(_(e_invarg2), "List format should be [lnum, col, off]");
|
||||
return;
|
||||
}
|
||||
li = tv_list_find(di->di_tv.vval.v_list, 0L);
|
||||
if (li != NULL) {
|
||||
pos.lnum = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
li = tv_list_find(di->di_tv.vval.v_list, 1L);
|
||||
if (li != NULL) {
|
||||
pos.col = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
li = tv_list_find(di->di_tv.vval.v_list, 2L);
|
||||
if (li != NULL) {
|
||||
pos.coladd = tv_get_number_chk(TV_LIST_ITEM_TV(li), &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);
|
||||
|
||||
tv_dict_add_nr(rettv->vval.v_dict, S_LEN("current"), stat.cur);
|
||||
tv_dict_add_nr(rettv->vval.v_dict, S_LEN("total"), stat.cnt);
|
||||
tv_dict_add_nr(rettv->vval.v_dict, S_LEN("exact_match"), stat.exact_match);
|
||||
tv_dict_add_nr(rettv->vval.v_dict, S_LEN("incomplete"), stat.incomplete);
|
||||
tv_dict_add_nr(rettv->vval.v_dict, S_LEN("maxcount"), stat.last_maxcount);
|
||||
|
||||
the_end:
|
||||
restore_last_search_pattern();
|
||||
}
|
||||
|
||||
/*
|
||||
* Find identifiers or defines in included files.
|
||||
* If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/buffer_defs.h"
|
||||
#include "nvim/eval/funcs.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/os/time.h"
|
||||
@ -49,6 +50,11 @@
|
||||
#define RE_BOTH 2 /* save pat in both patterns */
|
||||
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
|
||||
|
||||
// Values for searchcount()
|
||||
#define SEARCH_STAT_DEF_TIMEOUT 20L
|
||||
#define SEARCH_STAT_DEF_MAX_COUNT 99
|
||||
#define SEARCH_STAT_BUF_LEN 12
|
||||
|
||||
/// Structure containing offset definition for the last search pattern
|
||||
///
|
||||
/// @note Only offset for the last search pattern is used, not for the last
|
||||
@ -78,6 +84,16 @@ typedef struct {
|
||||
int sa_wrapped; ///< search wrapped around
|
||||
} searchit_arg_T;
|
||||
|
||||
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;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "search.h.generated.h"
|
||||
|
@ -8,6 +8,33 @@ func Test_search_stat()
|
||||
set shortmess-=S
|
||||
" Append 50 lines with text to search for, "foobar" appears 20 times
|
||||
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
|
||||
call nvim_win_set_cursor(0, [1, 0])
|
||||
|
||||
" 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
|
||||
call cursor(1, 1)
|
||||
@ -17,6 +44,9 @@ func Test_search_stat()
|
||||
let stat = '\[2/50\]'
|
||||
let pat = escape(@/, '()*?'). '\s\+'
|
||||
call assert_match(pat .. stat, g:a)
|
||||
call assert_equal(
|
||||
\ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||
\ searchcount(#{recompute: 0}))
|
||||
" didn't get added to message history
|
||||
call assert_equal(messages_before, execute('messages'))
|
||||
|
||||
@ -25,6 +55,9 @@ func Test_search_stat()
|
||||
let g:a = execute(':unsilent :norm! n')
|
||||
let stat = '\[50/50\]'
|
||||
call assert_match(pat .. stat, g:a)
|
||||
call assert_equal(
|
||||
\ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||
\ searchcount(#{recompute: 0}))
|
||||
|
||||
" No search stat
|
||||
set shortmess+=S
|
||||
@ -32,6 +65,14 @@ func Test_search_stat()
|
||||
let stat = '\[2/50\]'
|
||||
let g:a = execute(':unsilent :norm! n')
|
||||
call assert_notmatch(pat .. stat, g:a)
|
||||
call writefile(getline(1, '$'), 'sample.txt')
|
||||
" n does not update search stat
|
||||
call assert_equal(
|
||||
\ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||
\ searchcount(#{recompute: 0}))
|
||||
call assert_equal(
|
||||
\ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
|
||||
\ searchcount(#{recompute: v:true}))
|
||||
set shortmess-=S
|
||||
|
||||
" Many matches
|
||||
@ -41,10 +82,28 @@ func Test_search_stat()
|
||||
let g:a = execute(':unsilent :norm! n')
|
||||
let stat = '\[>99/>99\]'
|
||||
call assert_match(pat .. stat, g:a)
|
||||
call assert_equal(
|
||||
\ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
|
||||
\ searchcount(#{recompute: 0}))
|
||||
call assert_equal(
|
||||
\ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
|
||||
\ searchcount(#{recompute: v:true, maxcount: 0}))
|
||||
call assert_equal(
|
||||
\ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
|
||||
\ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0]}))
|
||||
call cursor(line('$'), 1)
|
||||
let g:a = execute(':unsilent :norm! n')
|
||||
let stat = 'W \[1/>99\]'
|
||||
call assert_match(pat .. stat, g:a)
|
||||
call assert_equal(
|
||||
\ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
|
||||
\ searchcount(#{recompute: 0}))
|
||||
call assert_equal(
|
||||
\ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
|
||||
\ searchcount(#{recompute: 1, maxcount: 0}))
|
||||
call assert_equal(
|
||||
\ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
|
||||
\ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0]}))
|
||||
|
||||
" Many matches
|
||||
call cursor(1, 1)
|
||||
@ -180,6 +239,12 @@ func Test_search_stat()
|
||||
call assert_match('^\s\+' .. stat, g:b)
|
||||
unmap n
|
||||
|
||||
" Time out
|
||||
%delete _
|
||||
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
|
||||
call cursor(1, 1)
|
||||
call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
|
||||
|
||||
" Clean up
|
||||
set shortmess+=S
|
||||
" close the window
|
||||
@ -252,9 +317,9 @@ func Test_searchcount_in_statusline()
|
||||
function TestSearchCount() abort
|
||||
let search_count = searchcount()
|
||||
if !empty(search_count)
|
||||
return '[' . search_count.current . '/' . search_count.total . ']'
|
||||
return '[' . search_count.current . '/' . search_count.total . ']'
|
||||
else
|
||||
return ''
|
||||
return ''
|
||||
endif
|
||||
endfunction
|
||||
set hlsearch
|
||||
|
Loading…
Reference in New Issue
Block a user