diff options
-rw-r--r-- | runtime/autoload/freebasic.vim | 3 | ||||
-rw-r--r-- | runtime/doc/builtin.txt | 21 | ||||
-rw-r--r-- | src/nvim/eval.c | 9 | ||||
-rw-r--r-- | src/nvim/eval.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 56 | ||||
-rw-r--r-- | src/nvim/testdir/test_search.vim | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_syntax.vim | 50 |
7 files changed, 121 insertions, 24 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..4b83bda804 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -766,6 +766,15 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) return ret; } +/// @return whether a typval is a valid expression to pass to eval_expr_typval() +/// or eval_expr_to_bool(). An empty string returns false; +bool eval_expr_valid_arg(const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_CONST +{ + return tv->v_type != VAR_UNKNOWN + && (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL)); +} + int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { 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..8beacc9988 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; + bool use_skip = false; 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,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) if (time_limit < 0) { goto theend; } + use_skip = eval_expr_valid_arg(&argvars[4]); } } @@ -8284,11 +8286,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 || !use_skip) { + // didn't find it or no skip argument + break; + } + firstpos = pos; + + // If the skip expression matches, ignore this match. + { + const pos_T save_pos = curwin->w_cursor; + + curwin->w_cursor = pos; + bool err = false; + const bool do_skip = eval_expr_to_bool(&argvars[4], &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; @@ -8748,13 +8785,9 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) || argvars[4].v_type == VAR_UNKNOWN) { skip = NULL; } else { + // Type is checked later. skip = &argvars[4]; - if (skip->v_type != VAR_FUNC - && skip->v_type != VAR_PARTIAL - && skip->v_type != VAR_STRING) { - semsg(_(e_invarg2), tv_get_string(&argvars[4])); - goto theend; // Type error. - } + if (argvars[5].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { @@ -8865,10 +8898,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir } if (skip != NULL) { - // Empty string means to not use the skip expression. - if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { - use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; - } + use_skip = eval_expr_valid_arg(skip); } save_cursor = curwin->w_cursor; diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 2a1a93b5e3..a3d5ca96a1 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -378,7 +378,6 @@ func Test_searchpair_errors() call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') - call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') call assert_fails("call searchpair('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e') @@ -390,7 +389,6 @@ func Test_searchpairpos_errors() call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') - call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0') call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') call assert_fails("call searchpairpos('start', 'middle', 'end', 'e')", 'E475: Invalid argument: e') 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 |