aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
5 files changed, 172 insertions, 5 deletions
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