To: vim_dev@googlegroups.com Subject: Patch 8.2.0915 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0915 Problem: Search() cannot skip over matches like searchpair() can. Solution: Add an optional "skip" argument. (Christian Brabandt, closes #861) Files: runtime/doc/eval.txt, src/evalfunc.c, src/testdir/test_syntax.vim, src/structs.h, src/evalvars.c, src/proto/evalvars.pro *** ../vim-8.2.0914/runtime/doc/eval.txt 2020-06-04 15:21:45.482189580 +0200 --- runtime/doc/eval.txt 2020-06-06 16:33:29.485004934 +0200 *************** *** 2710,2716 **** 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}]]]) Number search for {pattern} searchcount([{options}]) Dict get or update search stats searchdecl({name} [, {global} [, {thisblock}]]) --- 2716,2722 ---- 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} [, {skip}]]]]) Number search for {pattern} searchcount([{options}]) Dict get or update search stats searchdecl({name} [, {global} [, {thisblock}]]) *************** *** 2719,2725 **** 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}]]]) List search for {pattern} server2client({clientid}, {string}) Number send reply string --- 2725,2731 ---- 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} [, {skip}]]]]) List search for {pattern} server2client({clientid}, {string}) Number send reply string *************** *** 8349,8356 **** Can also be used as a |method|: > GetRow()->screenstring(col) ! ! search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()* Search for regexp pattern {pattern}. The search starts at the cursor position (you can use |cursor()| to set it). --- 8364,8372 ---- Can also be used as a |method|: > GetRow()->screenstring(col) ! < ! *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). *************** *** 8398,8403 **** --- 8414,8428 ---- giving the argument. {only available when compiled with the |+reltime| feature} + 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 *************** *** 8679,8685 **** < See |match-parens| for a bigger and more useful example. ! searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()* 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 --- 8706,8713 ---- < See |match-parens| for a bigger and more useful example. ! *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 *** ../vim-8.2.0914/src/evalfunc.c 2020-06-03 19:55:31.115092047 +0200 --- src/evalfunc.c 2020-06-06 17:52:15.022737648 +0200 *************** *** 801,812 **** {"screenpos", 3, 3, FEARG_1, ret_dict_number, f_screenpos}, {"screenrow", 0, 0, 0, ret_number, f_screenrow}, {"screenstring", 2, 2, FEARG_1, ret_string, f_screenstring}, ! {"search", 1, 4, FEARG_1, ret_number, f_search}, {"searchcount", 0, 1, FEARG_1, ret_dict_any, f_searchcount}, {"searchdecl", 1, 3, FEARG_1, ret_number, f_searchdecl}, {"searchpair", 3, 7, 0, ret_number, f_searchpair}, {"searchpairpos", 3, 7, 0, ret_list_number, f_searchpairpos}, ! {"searchpos", 1, 4, FEARG_1, ret_list_number, f_searchpos}, {"server2client", 2, 2, FEARG_1, ret_number, f_server2client}, {"serverlist", 0, 0, 0, ret_string, f_serverlist}, {"setbufline", 3, 3, FEARG_3, ret_number, f_setbufline}, --- 801,812 ---- {"screenpos", 3, 3, FEARG_1, ret_dict_number, f_screenpos}, {"screenrow", 0, 0, 0, ret_number, f_screenrow}, {"screenstring", 2, 2, FEARG_1, ret_string, f_screenstring}, ! {"search", 1, 5, FEARG_1, ret_number, f_search}, {"searchcount", 0, 1, FEARG_1, ret_dict_any, f_searchcount}, {"searchdecl", 1, 3, FEARG_1, ret_number, f_searchdecl}, {"searchpair", 3, 7, 0, ret_number, f_searchpair}, {"searchpairpos", 3, 7, 0, ret_list_number, f_searchpairpos}, ! {"searchpos", 1, 5, FEARG_1, ret_list_number, f_searchpos}, {"server2client", 2, 2, FEARG_1, ret_number, f_server2client}, {"serverlist", 0, 0, 0, ret_string, f_serverlist}, {"setbufline", 3, 3, FEARG_3, ret_number, f_setbufline}, *************** *** 6399,6404 **** --- 6399,6408 ---- int options = SEARCH_KEEP; int subpatnum; searchit_arg_T sia; + evalarg_T skip; + pos_T firstpos; + + CLEAR_FIELD(skip); pat = tv_get_string(&argvars[0]); dir = get_search_arg(&argvars[1], flagsp); // may set p_ws *************** *** 6412,6431 **** if (flags & SP_COLUMN) options |= SEARCH_COL; ! // Optional arguments: line number to stop searching and timeout. if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { lnum_stop = (long)tv_get_number_chk(&argvars[2], NULL); if (lnum_stop < 0) goto theend; - #ifdef FEAT_RELTIME if (argvars[3].v_type != VAR_UNKNOWN) { time_limit = (long)tv_get_number_chk(&argvars[3], NULL); if (time_limit < 0) goto theend; - } #endif } #ifdef FEAT_RELTIME --- 6416,6438 ---- if (flags & SP_COLUMN) options |= SEARCH_COL; ! // 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 = (long)tv_get_number_chk(&argvars[2], NULL); if (lnum_stop < 0) goto theend; if (argvars[3].v_type != VAR_UNKNOWN) { + #ifdef FEAT_RELTIME time_limit = (long)tv_get_number_chk(&argvars[3], NULL); if (time_limit < 0) goto theend; #endif + if (argvars[4].v_type != VAR_UNKNOWN + && evalarg_get(&argvars[4], &skip) == FAIL) + goto theend; + } } #ifdef FEAT_RELTIME *************** *** 6447,6459 **** } pos = save_cursor = curwin->w_cursor; CLEAR_FIELD(sia); sia.sa_stop_lnum = (linenr_T)lnum_stop; #ifdef FEAT_RELTIME sia.sa_tm = &tm; #endif ! subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, options, RE_SEARCH, &sia); if (subpatnum != FAIL) { if (flags & SP_SUBPAT) --- 6454,6501 ---- } pos = save_cursor = curwin->w_cursor; + CLEAR_FIELD(firstpos); CLEAR_FIELD(sia); sia.sa_stop_lnum = (linenr_T)lnum_stop; #ifdef FEAT_RELTIME sia.sa_tm = &tm; #endif ! ! // Repeat until {skip} returns FALSE. ! for (;;) ! { ! subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, options, RE_SEARCH, &sia); + // finding the first match again means there is no match where {skip} + // evaluates to zero. + if (firstpos.lnum != 0 && EQUAL_POS(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. + { + int do_skip; + int err; + pos_T save_pos = curwin->w_cursor; + + curwin->w_cursor = pos; + 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) *************** *** 6481,6486 **** --- 6523,6529 ---- curwin->w_set_curswant = TRUE; theend: p_ws = save_p_ws; + evalarg_clean(&skip); return retval; } *** ../vim-8.2.0914/src/testdir/test_syntax.vim 2020-05-31 21:27:58.335221898 +0200 --- src/testdir/test_syntax.vim 2020-06-06 18:25:34.289960182 +0200 *************** *** 772,775 **** --- 772,825 ---- 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 + " vim: shiftwidth=2 sts=2 expandtab *** ../vim-8.2.0914/src/structs.h 2020-06-05 22:33:38.409658754 +0200 --- src/structs.h 2020-06-06 16:58:04.528247639 +0200 *************** *** 4130,4135 **** --- 4130,4150 ---- int sa_wrapped; // search wrapped around } searchit_arg_T; + /* + * Function argument that can be a string, funcref or partial. + * - declare: evalarg_T name; + * - init: CLEAR_FIELD(name); + * - set: evalarg_get(&argvars[3], &name); + * - use: if (evalarg_valid(&name)) res = evalarg_call(&name); + * - cleanup: evalarg_clean(&name); + */ + typedef struct + { + char_u eva_buf[NUMBUFLEN]; // buffer for get_tv_string_buf() + char_u *eva_string; + callback_T eva_callback; + } evalarg_T; + #define WRITEBUFSIZE 8192 // size of normal write buffer #define FIO_LATIN1 0x01 // convert Latin1 *** ../vim-8.2.0914/src/evalvars.c 2020-06-06 15:57:59.083088326 +0200 --- src/evalvars.c 2020-06-06 18:21:11.850516448 +0200 *************** *** 3811,3814 **** --- 3811,3889 ---- callback->cb_name = NULL; } + /* + * Process a function argument that can be a string expression or a function + * reference. + * "tv" must remain valid until calling evalarg_clean()! + * Returns FAIL when the argument is invalid. + */ + int + evalarg_get(typval_T *tv, evalarg_T *eva) + { + if (tv->v_type == VAR_STRING + || tv->v_type == VAR_NUMBER + || tv->v_type == VAR_BOOL + || tv->v_type == VAR_SPECIAL) + { + eva->eva_string = tv_get_string_buf(tv, eva->eva_buf); + return OK; + } + + eva->eva_callback = get_callback(tv); + return eva->eva_callback.cb_name == NULL ? FAIL : OK; + } + + /* + * Return whether "eva" has a valid expression or callback. + */ + int + evalarg_valid(evalarg_T *eva) + { + return eva->eva_string != NULL || eva->eva_callback.cb_name != NULL; + } + + /* + * Invoke the expression or callback "eva" and return the result in "tv". + * Returns FAIL if something failed + */ + int + evalarg_call(evalarg_T *eva, typval_T *tv) + { + typval_T argv[1]; + + if (eva->eva_string != NULL) + return eval0(eva->eva_string, tv, NULL, EVAL_EVALUATE); + + argv[0].v_type = VAR_UNKNOWN; + return call_callback(&eva->eva_callback, -1, tv, 0, argv); + } + + /* + * Like evalarg_call(), but just return TRUE of FALSE. + * Sets "error" to TRUE if evaluation failed. + */ + int + evalarg_call_bool(evalarg_T *eva, int *error) + { + typval_T tv; + int r; + + if (evalarg_call(eva, &tv) == FAIL) + { + *error = TRUE; + return FALSE; + } + r = tv_get_number(&tv); + clear_tv(&tv); + *error = FALSE; + return r; + } + + void + evalarg_clean(evalarg_T *eva) + { + if (eva->eva_string == NULL) + free_callback(&eva->eva_callback); + } + #endif // FEAT_EVAL *** ../vim-8.2.0914/src/proto/evalvars.pro 2020-06-06 15:57:59.083088326 +0200 --- src/proto/evalvars.pro 2020-06-06 17:50:49.151016107 +0200 *************** *** 88,91 **** --- 88,96 ---- void put_callback(callback_T *cb, typval_T *tv); void set_callback(callback_T *dest, callback_T *src); void free_callback(callback_T *callback); + int evalarg_get(typval_T *tv, evalarg_T *eva); + int evalarg_valid(evalarg_T *eva); + int evalarg_call(evalarg_T *eva, typval_T *tv); + int evalarg_call_bool(evalarg_T *eva, int *error); + void evalarg_clean(evalarg_T *eva); /* vim: set ft=c : */ *** ../vim-8.2.0914/src/version.c 2020-06-06 16:18:41.787823124 +0200 --- src/version.c 2020-06-06 18:36:30.464340676 +0200 *************** *** 756,757 **** --- 756,759 ---- { /* Add new patch number below this line */ + /**/ + 915, /**/ -- Facepalm statement #9: "Did you see, there is now even a hobbit book" /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///