diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/eval.c | 61 | ||||
-rw-r--r-- | src/nvim/eval.h | 16 | ||||
-rw-r--r-- | src/nvim/eval.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 46 | ||||
-rw-r--r-- | src/nvim/testdir/test_syntax.vim | 50 |
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 |