aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/freebasic.vim3
-rw-r--r--runtime/doc/builtin.txt21
-rw-r--r--src/nvim/eval.c61
-rw-r--r--src/nvim/eval.h16
-rw-r--r--src/nvim/eval.lua4
-rw-r--r--src/nvim/eval/funcs.c46
-rw-r--r--src/nvim/testdir/test_syntax.vim50
7 files changed, 189 insertions, 12 deletions
diff --git a/runtime/autoload/freebasic.vim b/runtime/autoload/freebasic.vim
index fe6d2745be..428cf1382b 100644
--- a/runtime/autoload/freebasic.vim
+++ b/runtime/autoload/freebasic.vim
@@ -23,8 +23,7 @@ function! freebasic#GetDialect() abort
let save_cursor = getcurpos()
call cursor(1, 1)
- " let lnum = search(pat, 'n', '', '', skip) " 'skip' needs 8.2.0915
- let lnum = search(pat, 'n', '', '')
+ let lnum = search(pat, 'n', '', '', skip)
call setpos('.', save_cursor)
if lnum
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index f371ad92cc..35a232c0c2 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -380,7 +380,7 @@ screencol() Number current cursor column
screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character
screenrow() Number current cursor row
screenstring({row}, {col}) String characters at screen position
-search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
+search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Number search for {pattern}
searchcount([{options}]) Dict Get or update the last search count
searchdecl({name} [, {global} [, {thisblock}]])
@@ -389,7 +389,7 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
Number search for other end of start/end pair
searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
List search for other end of start/end pair
-searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]])
+searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
List search for {pattern}
server2client({clientid}, {string})
Number send reply string
@@ -6169,8 +6169,9 @@ screenstring({row}, {col}) *screenstring()*
Can also be used as a |method|: >
GetRow()->screenstring(col)
-
-search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
+<
+ *search()*
+search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Search for regexp pattern {pattern}. The search starts at the
cursor position (you can use |cursor()| to set it).
@@ -6222,6 +6223,15 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
The value must not be negative. A zero value is like not
giving the argument.
+ If the {skip} expression is given it is evaluated with the
+ cursor positioned on the start of a match. If it evaluates to
+ non-zero this match is skipped. This can be used, for
+ example, to skip a match in a comment or a string.
+ {skip} can be a string, which is evaluated as an expression, a
+ function reference or a lambda.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
*search()-sub-match*
With the 'p' flag the returned value is one more than the
first sub-match in \(\). One if none of them matched but the
@@ -6505,7 +6515,8 @@ searchpairpos({start}, {middle}, {end} [, {flags} [, {skip}
<
See |match-parens| for a bigger and more useful example.
-searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()*
+ *searchpos()*
+searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Same as |search()|, but returns a |List| with the line and
column position of the match. The first element of the |List|
is the line number and the second element is the byte index of
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index c197754685..2d8d1694d1 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3381,6 +3381,67 @@ static int eval_func(char_u **const arg, char_u *const name, const int name_len,
return ret;
}
+/// Process a function argument that can be a string expression or a function
+/// reference.
+/// "tv" must remain valid until calling evalarg_clean()!
+/// @return false when the argument is invalid.
+bool evalarg_get(typval_T *const tv, evalarg_T *const eva)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tv->v_type == VAR_STRING || tv->v_type == VAR_NUMBER || tv->v_type == VAR_BOOL
+ || tv->v_type == VAR_SPECIAL) {
+ char numbuf[NUMBUFLEN];
+ eva->eva_string = tv_get_string_buf(tv, numbuf);
+ return true;
+ }
+
+ return callback_from_typval(&eva->eva_callback, tv);
+}
+
+/// @return whether "eva" has a valid expression or callback.
+bool evalarg_valid(const evalarg_T *const eva)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST
+{
+ return eva->eva_string != NULL || eva->eva_callback.type != kCallbackNone;
+}
+
+/// Invoke the expression or callback "eva" and return the result in "tv".
+/// @return false if something failed
+bool evalarg_call(evalarg_T *const eva, typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (eva->eva_string != NULL) {
+ return eval0((char_u *)eva->eva_string, tv, NULL, true);
+ }
+
+ typval_T argv[1];
+ argv[0].v_type = VAR_UNKNOWN;
+ return callback_call(&eva->eva_callback, 0, argv, tv);
+}
+
+/// Like evalarg_call(), but just return true or false.
+/// Sets "error" to true if evaluation failed.
+bool evalarg_call_bool(evalarg_T *const eva, bool *const error)
+ FUNC_ATTR_NONNULL_ALL
+{
+ typval_T tv;
+ if (!evalarg_call(eva, &tv)) {
+ *error = true;
+ return false;
+ }
+
+ const bool r = tv_get_number(&tv);
+ tv_clear(&tv);
+ *error = false;
+ return r;
+}
+
+void evalarg_clean(evalarg_T *const eva)
+ FUNC_ATTR_NONNULL_ALL
+{
+ callback_free(&eva->eva_callback);
+}
+
// TODO(ZyX-I): move to eval/expressions
/*
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index a9ec5d47a6..f74f23d084 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -272,6 +272,22 @@ typedef int (*ex_unletlock_callback)(lval_T *, char_u *, exarg_T *, int);
// Used for checking if local variables or arguments used in a lambda.
extern bool *eval_lavars_used;
+/// Function argument that can be a string, funcref or partial.
+/// - declare: evalarg_T name;
+/// - init: name = EVALARG_INIT;
+/// - set: evalarg_get(&argvars[3], &name);
+/// - use: if (evalarg_valid(&name)) res = evalarg_call(&name);
+/// - cleanup: evalarg_clean(&name);
+typedef struct {
+ const char *eva_string;
+ Callback eva_callback;
+} evalarg_T;
+
+#define EVALARG_INIT (evalarg_T) { \
+ .eva_string = NULL, \
+ .eva_callback = CALLBACK_NONE, \
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 1e39854c86..05e91a658f 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -307,12 +307,12 @@ return {
screenpos={args=3, base=1},
screenrow={},
screenstring={args=2, base=1},
- search={args={1, 4}, base=1},
+ search={args={1, 5}, base=1},
searchcount={args={0, 1}, base=1},
searchdecl={args={1, 3}, base=1},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
- searchpos={args={1, 4}, base=1},
+ searchpos={args={1, 5}, base=1},
serverlist={},
serverstart={args={0, 1}},
serverstop={args=1},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index db4fb06a73..8004c1d32e 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -8237,6 +8237,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
int options = SEARCH_KEEP;
int subpatnum;
searchit_arg_T sia;
+ evalarg_T skip = EVALARG_INIT;
const char *const pat = tv_get_string(&argvars[0]);
dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
@@ -8254,7 +8255,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
options |= SEARCH_COL;
}
- // Optional arguments: line number to stop searching and timeout.
+ // Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
lnum_stop = tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
@@ -8265,6 +8266,9 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
if (time_limit < 0) {
goto theend;
}
+ if (argvars[4].v_type != VAR_UNKNOWN && !evalarg_get(&argvars[4], &skip)) {
+ goto theend;
+ }
}
}
@@ -8284,11 +8288,46 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
pos = save_cursor = curwin->w_cursor;
+ pos_T firstpos = { 0 };
memset(&sia, 0, sizeof(sia));
sia.sa_stop_lnum = (linenr_T)lnum_stop;
sia.sa_tm = &tm;
- subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, &sia);
+
+ // Repeat until {skip} returns false.
+ for (;;) {
+ subpatnum
+ = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia);
+ // finding the first match again means there is no match where {skip}
+ // evaluates to zero.
+ if (firstpos.lnum != 0 && equalpos(pos, firstpos)) {
+ subpatnum = FAIL;
+ }
+
+ if (subpatnum == FAIL || !evalarg_valid(&skip)) {
+ // didn't find it or no skip argument
+ break;
+ }
+ firstpos = pos;
+
+ // If the skip pattern matches, ignore this match.
+ {
+ bool err;
+ const pos_T save_pos = curwin->w_cursor;
+
+ curwin->w_cursor = pos;
+ const bool do_skip = evalarg_call_bool(&skip, &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ subpatnum = FAIL;
+ break;
+ }
+ if (!do_skip) {
+ break;
+ }
+ }
+ }
+
if (subpatnum != FAIL) {
if (flags & SP_SUBPAT) {
retval = subpatnum;
@@ -8317,6 +8356,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
}
theend:
p_ws = save_p_ws;
+ evalarg_clean(&skip);
return retval;
}
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 757866f5dc..b047b53b6f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -728,6 +728,56 @@ func Test_syntax_foldlevel()
quit!
endfunc
+func Test_search_syntax_skip()
+ new
+ let lines =<< trim END
+
+ /* This is VIM */
+ Another Text for VIM
+ let a = "VIM"
+ END
+ call setline(1, lines)
+ syntax on
+ syntax match Comment "^/\*.*\*/"
+ syntax match String '".*"'
+
+ " Skip argument using string evaluation.
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"')
+ call assert_equal('Another Text for VIM', getline('.'))
+ 1
+ call search('VIM', 'w', '', 0, 'synIDattr(synID(line("."), col("."), 1), "name") !~? "string"')
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using Lambda.
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"})
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, { -> synIDattr(synID(line("."), col("."), 1), "name") !~? "string"})
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ " Skip argument using funcref.
+ func InComment()
+ return synIDattr(synID(line("."), col("."), 1), "name") =~? "comment"
+ endfunc
+ func InString()
+ return synIDattr(synID(line("."), col("."), 1), "name") !~? "string"
+ endfunc
+ 1
+ call search('VIM', 'w', '', 0, function('InComment'))
+ call assert_equal('Another Text for VIM', getline('.'))
+
+ 1
+ call search('VIM', 'w', '', 0, function('InString'))
+ call assert_equal(' let a = "VIM"', getline('.'))
+
+ delfunc InComment
+ delfunc InString
+ bwipe!
+endfunc
+
func Test_syn_include_contains_TOP()
let l:case = "TOP in included syntax means its group list name"
new