]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0009: Cannot easily get the list of matches v9.1.0009
authorYegappan Lakshmanan <yegappan@yahoo.com>
Thu, 4 Jan 2024 21:28:46 +0000 (22:28 +0100)
committerChristian Brabandt <cb@256bit.org>
Thu, 4 Jan 2024 21:28:46 +0000 (22:28 +0100)
Problem:  Cannot easily get the list of matches
Solution: Add the matchstrlist() and matchbufline() Vim script
          functions (Yegappan Lakshmanan)

closes: #13766

Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/builtin.txt
runtime/doc/tags
runtime/doc/usr_41.txt
src/errors.h
src/evalfunc.c
src/testdir/test_functions.vim
src/version.c

index 084c76edfb3c8721d5879934f25cba6cb3fb0822..8f79d2001dfbc872f72c764cc346f5ce3cff97fe 100644 (file)
@@ -370,6 +370,8 @@ matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
 matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])
                                Number  highlight positions with {group}
 matcharg({nr})                 List    arguments of |:match|
+matchbufline({buf}, {pat}, {lnum}, {end}, [, {dict})
+                               List    all the {pat} matches in buffer {buf}
 matchdelete({id} [, {win}])    Number  delete match identified by {id}
 matchend({expr}, {pat} [, {start} [, {count}]])
                                Number  position where {pat} ends in {expr}
@@ -381,6 +383,8 @@ matchlist({expr}, {pat} [, {start} [, {count}]])
                                List    match and submatches of {pat} in {expr}
 matchstr({expr}, {pat} [, {start} [, {count}]])
                                String  {count}'th match of {pat} in {expr}
+matchstrlist({list}, {pat} [, {dict})
+                               List    all the {pat} matches in {list}
 matchstrpos({expr}, {pat} [, {start} [, {count}]])
                                List    {count}'th match of {pat} in {expr}
 max({expr})                    Number  maximum value of items in {expr}
@@ -6054,6 +6058,51 @@ matcharg({nr})                                                   *matcharg()*
 
                Can also be used as a |method|: >
                        GetMatch()->matcharg()
+<
+                                                       *matchbufline()*
+matchbufline({buf}, {pat}, {lnum}, {end}, [, {dict}])
+               Returns the |List| of matches in lines from {lnum} to {end} in
+               buffer {buf} where {pat} matches.
+
+               {lnum} and {end} can either be a line number or the string "$"
+               to refer to the last line in {buf}.
+
+               The {dict} argument supports following items:
+                   submatches  include submatch information (|/\(|)
+
+               For each match, a |Dict| with the following items is returned:
+                   byteidx     starting byte index of the match
+                   lnum      line number where there is a match
+                   text      matched string
+               Note that there can be multiple matches in a single line.
+
+               This function works only for loaded buffers. First call
+               |bufload()| if needed.
+
+               When {buf} is not a valid buffer, the buffer is not loaded or
+               {lnum} or {end} is not valid then an error is given and an
+               empty |List| is returned.
+
+               Examples: >
+                   " Assuming line 3 in buffer 5 contains "a"
+                   :echo matchbufline(5, '\<\k\+\>', 3, 3)
+                   [{'lnum': 3, 'byteidx': 0, 'text': 'a'}]
+                   " Assuming line 4 in buffer 10 contains "tik tok"
+                   :echo matchbufline(10, '\<\k\+\>', 1, 4)
+                   [{'lnum': 4, 'byteidx': 0, 'text': 'tik'}, {'lnum': 4, 'byteidx': 4, 'text': 'tok'}]
+<
+               If {submatch} is present and is v:true, then submatches like
+               "\1", "\2", etc. are also returned.  Example: >
+                   " Assuming line 2 in buffer 2 contains "acd"
+                   :echo matchbufline(2, '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2, 2
+                                               \ {'submatches': v:true})
+                   [{'lnum': 2, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
+<              The "submatches" List always contains 9 items.  If a submatch
+               is not found, then an empty string is returned for that
+               submatch.
+
+               Can also be used as a |method|: >
+                       GetBuffer()->matchbufline('mypat', 1, '$')
 
 matchdelete({id} [, {win})                    *matchdelete()* *E802* *E803*
                Deletes a match with ID {id} previously defined by |matchadd()|
@@ -6187,6 +6236,40 @@ matchlist({expr}, {pat} [, {start} [, {count}]])         *matchlist()*
 
                Can also be used as a |method|: >
                        GetText()->matchlist('word')
+<
+                                               *matchstrlist()*
+matchstrlist({list}, {pat} [, {dict}])
+               Returns the |List| of matches in {list} where {pat} matches.
+               {list} is a |List| of strings.  {pat} is matched against each
+               string in {list}.
+
+               The {dict} argument supports following items:
+                   submatches  include submatch information (|/\(|)
+
+               For each match, a |Dict| with the following items is returned:
+                   byteidx     starting byte index of the match.
+                   idx         index in {list} of the match.
+                   text        matched string
+                   submatches  a List of submatches.  Present only if
+                               "submatches" is set to v:true in {dict}.
+
+               Example: >
+                   :echo matchstrlist(['tik tok'], '\<\k\+\>')
+                   [{'idx': 0, 'byteidx': 0, 'text': 'tik'}, {'idx': 0, 'byteidx': 4, 'text': 'tok'}]
+                   :echo matchstrlist(['a', 'b'], '\<\k\+\>')
+                   [{'idx': 0, 'byteidx': 0, 'text': 'a'}, {'idx': 1, 'byteidx': 0, 'text': 'b'}]
+<
+               If "submatches" is present and is v:true, then submatches like
+               "\1", "\2", etc. are also returned.  Example: >
+                   :echo matchstrlist(['acd'], '\(a\)\?\(b\)\?\(c\)\?\(.*\)',
+                                               \ #{submatches: v:true})
+                   [{'idx': 0, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
+<              The "submatches" List always contains 9 items.  If a submatch
+               is not found, then an empty string is returned for that
+               submatch.
+
+               Can also be used as a |method|: >
+                       GetListOfStrings()->matchstrlist('mypat')
 
 matchstr({expr}, {pat} [, {start} [, {count}]])                        *matchstr()*
                Same as |match()|, but return the matched string.  Example: >
index e8ac2bc191296c0ed12c0a75c94a3399ca87f10f..9af1ac0f64c13f5c22b81ad910a29b209b8a3f58 100644 (file)
@@ -8594,6 +8594,7 @@ match-parens      tips.txt        /*match-parens*
 matchadd()     builtin.txt     /*matchadd()*
 matchaddpos()  builtin.txt     /*matchaddpos()*
 matcharg()     builtin.txt     /*matcharg()*
+matchbufline() builtin.txt     /*matchbufline()*
 matchdelete()  builtin.txt     /*matchdelete()*
 matchend()     builtin.txt     /*matchend()*
 matchfuzzy()   builtin.txt     /*matchfuzzy()*
@@ -8602,6 +8603,7 @@ matchit-install   usr_05.txt      /*matchit-install*
 matchlist()    builtin.txt     /*matchlist()*
 matchparen     pi_paren.txt    /*matchparen*
 matchstr()     builtin.txt     /*matchstr()*
+matchstrlist() builtin.txt     /*matchstrlist()*
 matchstrpos()  builtin.txt     /*matchstrpos()*
 matlab-indent  indent.txt      /*matlab-indent*
 matlab-indenting       indent.txt      /*matlab-indenting*
index c4f2a8c4b8f9c1b48e4c36a502b72c59e70bedc3..2286d48516a05e7408c774a8f073ba9f3225065e 100644 (file)
@@ -743,10 +743,13 @@ String manipulation:                                      *string-functions*
        toupper()               turn a string to uppercase
        charclass()             class of a character
        match()                 position where a pattern matches in a string
+       matchbufline()          all the matches of a pattern in a buffer
        matchend()              position where a pattern match ends in a string
        matchfuzzy()            fuzzy matches a string in a list of strings
        matchfuzzypos()         fuzzy matches a string in a list of strings
        matchstr()              match of a pattern in a string
+       matchstrlist()          all the matches of a pattern in a List of
+                               strings
        matchstrpos()           match and positions of a pattern in a string
        matchlist()             like matchstr() and also return submatches
        stridx()                first index of a short string in a long string
index 828e8de2493663aec77ba11423bfc70f266d2b28..dfb6ce644d76bb6baed845b3806137c8d91850ca 100644 (file)
@@ -1749,9 +1749,11 @@ EXTERN char e_recursive_loop_loading_syncolor_vim[]
 #endif
 EXTERN char e_buffer_nr_invalid_buffer_number[]
        INIT(= N_("E680: <buffer=%d>: invalid buffer number"));
-#ifdef FEAT_QUICKFIX
+#if defined(FEAT_QUICKFIX) || defined(FEAT_EVAL)
 EXTERN char e_buffer_is_not_loaded[]
        INIT(= N_("E681: Buffer is not loaded"));
+#endif
+#ifdef FEAT_QUICKFIX
 EXTERN char e_invalid_search_pattern_or_delimiter[]
        INIT(= N_("E682: Invalid search pattern or delimiter"));
 EXTERN char e_file_name_missing_or_invalid_pattern[]
index 513ddccdbe7f8ffd944b641e127b3af862bc06b1..e37b3a412a8a56d3c34cd9b8965bf1bf019b9ecc 100644 (file)
@@ -100,9 +100,11 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv);
 static void f_luaeval(typval_T *argvars, typval_T *rettv);
 #endif
 static void f_match(typval_T *argvars, typval_T *rettv);
+static void f_matchbufline(typval_T *argvars, typval_T *rettv);
 static void f_matchend(typval_T *argvars, typval_T *rettv);
 static void f_matchlist(typval_T *argvars, typval_T *rettv);
 static void f_matchstr(typval_T *argvars, typval_T *rettv);
+static void f_matchstrlist(typval_T *argvars, typval_T *rettv);
 static void f_matchstrpos(typval_T *argvars, typval_T *rettv);
 static void f_max(typval_T *argvars, typval_T *rettv);
 static void f_min(typval_T *argvars, typval_T *rettv);
@@ -1176,6 +1178,8 @@ static argcheck_T arg2_map[] = {arg_list_or_dict_or_blob_or_string_mod, arg_map_
 static argcheck_T arg2_mapnew[] = {arg_list_or_dict_or_blob_or_string, arg_any};
 static argcheck_T arg25_matchadd[] = {arg_string, arg_string, arg_number, arg_number, arg_dict_any};
 static argcheck_T arg25_matchaddpos[] = {arg_string, arg_list_any, arg_number, arg_number, arg_dict_any};
+static argcheck_T arg23_matchstrlist[] = {arg_list_string, arg_string, arg_dict_any};
+static argcheck_T arg45_matchbufline[] = {arg_buffer, arg_string, arg_lnum, arg_lnum, arg_dict_any};
 static argcheck_T arg119_printf[] = {arg_string_or_nr, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any, arg_any};
 static argcheck_T arg23_reduce[] = {arg_string_list_or_blob, arg_any, arg_any};
 static argcheck_T arg24_remote_expr[] = {arg_string, arg_string, arg_string, arg_number};
@@ -2285,6 +2289,8 @@ static funcentry_T global_functions[] =
                        ret_number,         f_matchaddpos},
     {"matcharg",       1, 1, FEARG_1,      arg1_number,
                        ret_list_string,    f_matcharg},
+    {"matchbufline",   4, 5, FEARG_1,      arg45_matchbufline,
+                       ret_list_any,       f_matchbufline},
     {"matchdelete",    1, 2, FEARG_1,      arg2_number,
                        ret_number_bool,    f_matchdelete},
     {"matchend",       2, 4, FEARG_1,      arg24_match_func,
@@ -2297,6 +2303,8 @@ static funcentry_T global_functions[] =
                        ret_list_string,    f_matchlist},
     {"matchstr",       2, 4, FEARG_1,      arg24_match_func,
                        ret_string,         f_matchstr},
+    {"matchstrlist",   2, 3, FEARG_1,      arg23_matchstrlist,
+                       ret_list_any,       f_matchstrlist},
     {"matchstrpos",    2, 4, FEARG_1,      arg24_match_func,
                        ret_list_any,       f_matchstrpos},
     {"max",            1, 1, FEARG_1,      arg1_list_or_dict,
@@ -8023,6 +8031,183 @@ theend:
     p_cpo = save_cpo;
 }
 
+/*
+ * Return all the matches in string "str" for pattern "rmp".
+ * The matches are returned in the List "mlist".
+ * If "submatches" is TRUE, then submatch information is also returned.
+ * "matchbuf" is TRUE when called for matchbufline().
+ */
+    static int
+get_matches_in_str(
+    char_u     *str,
+    regmatch_T *rmp,
+    list_T     *mlist,
+    int                idx,
+    int                submatches,
+    int                matchbuf)
+{
+    long       len = (long)STRLEN(str);
+    int                match = 0;
+    colnr_T    startidx = 0;
+
+    for (;;)
+    {
+       match = vim_regexec_nl(rmp, str, startidx);
+       if (!match)
+           break;
+
+       dict_T *d = dict_alloc();
+       if (d == NULL)
+           return FAIL;
+       if (list_append_dict(mlist, d) == FAIL)
+           return FAIL;;
+
+       if (dict_add_number(d, matchbuf ? "lnum" : "idx", idx) == FAIL)
+           return FAIL;
+
+       if (dict_add_number(d, "byteidx",
+                   (colnr_T)(rmp->startp[0] - str)) == FAIL)
+           return FAIL;
+
+       if (dict_add_string_len(d, "text", rmp->startp[0],
+                   (int)(rmp->endp[0] - rmp->startp[0])) == FAIL)
+           return FAIL;
+
+       if (submatches)
+       {
+           list_T *sml = list_alloc();
+           if (sml == NULL)
+               return FAIL;
+
+           if (dict_add_list(d, "submatches", sml) == FAIL)
+               return FAIL;
+
+           // return a list with the submatches
+           for (int i = 1; i < NSUBEXP; ++i)
+           {
+               if (rmp->endp[i] == NULL)
+               {
+                   if (list_append_string(sml, (char_u *)"", 0) == FAIL)
+                       return FAIL;
+               }
+               else if (list_append_string(sml, rmp->startp[i],
+                           (int)(rmp->endp[i] - rmp->startp[i])) == FAIL)
+                   return FAIL;
+           }
+       }
+       startidx = (colnr_T)(rmp->endp[0] - str);
+       if (startidx >= (colnr_T)len || str + startidx <= rmp->startp[0])
+           break;
+    }
+
+    return OK;
+}
+
+/*
+ * "matchbufline()" function
+ */
+    static void
+f_matchbufline(typval_T *argvars, typval_T *rettv)
+{
+    list_T     *retlist = NULL;
+    char_u     *save_cpo;
+    char_u     patbuf[NUMBUFLEN];
+    regmatch_T regmatch;
+
+    rettv->vval.v_number = -1;
+    if (rettv_list_alloc(rettv) != OK)
+       return;
+    retlist = rettv->vval.v_list;
+
+    if (check_for_buffer_arg(argvars, 0) == FAIL
+           || check_for_string_arg(argvars, 1) == FAIL
+           || check_for_lnum_arg(argvars, 2) == FAIL
+           || check_for_lnum_arg(argvars, 3) == FAIL
+           || check_for_opt_dict_arg(argvars, 4) == FAIL)
+       return;
+
+    int prev_did_emsg = did_emsg;
+    buf_T *buf = tv_get_buf(&argvars[0], FALSE);
+    if (buf == NULL)
+    {
+       if (did_emsg == prev_did_emsg)
+           semsg(_(e_invalid_buffer_name_str), tv_get_string(&argvars[0]));
+       return;
+    }
+    if (buf->b_ml.ml_mfp == NULL)
+    {
+       emsg(_(e_buffer_is_not_loaded));
+       return;
+    }
+
+    char_u *pat = tv_get_string_buf(&argvars[1], patbuf);
+
+    int                did_emsg_before = did_emsg;
+    linenr_T slnum = tv_get_lnum_buf(&argvars[2], buf);
+    if (did_emsg > did_emsg_before)
+       return;
+    if (slnum < 1)
+    {
+       semsg(_(e_invalid_value_for_argument_str), "lnum");
+       return;
+    }
+
+    linenr_T elnum = tv_get_lnum_buf(&argvars[3], buf);
+    if (did_emsg > did_emsg_before)
+       return;
+    if (elnum < 1 || elnum < slnum)
+    {
+       semsg(_(e_invalid_value_for_argument_str), "end_lnum");
+       return;
+    }
+
+    if (elnum > buf->b_ml.ml_line_count)
+       elnum = buf->b_ml.ml_line_count;
+
+    int                submatches = FALSE;
+    if (argvars[4].v_type != VAR_UNKNOWN)
+    {
+       dict_T *d = argvars[4].vval.v_dict;
+       if (d != NULL)
+       {
+           dictitem_T *di = dict_find(d, (char_u *)"submatches", -1);
+           if (di != NULL)
+           {
+               if (di->di_tv.v_type != VAR_BOOL)
+               {
+                   semsg(_(e_invalid_value_for_argument_str), "submatches");
+                   return;
+               }
+               submatches = tv_get_bool(&di->di_tv);
+           }
+       }
+    }
+
+    // Make 'cpoptions' empty, the 'l' flag should not be used here.
+    save_cpo = p_cpo;
+    p_cpo = empty_option;
+
+    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+    if (regmatch.regprog == NULL)
+       goto theend;
+    regmatch.rm_ic = p_ic;
+
+    while (slnum <= elnum)
+    {
+       char_u *str = ml_get_buf(buf, slnum, FALSE);
+       if (get_matches_in_str(str, &regmatch, retlist, slnum, submatches,
+                                                               TRUE) == FAIL)
+           goto cleanup;
+       slnum++;
+    }
+
+cleanup:
+    vim_regfree(regmatch.regprog);
+
+theend:
+    p_cpo = save_cpo;
+}
+
 /*
  * "match()" function
  */
@@ -8059,6 +8244,85 @@ f_matchstr(typval_T *argvars, typval_T *rettv)
     find_some_match(argvars, rettv, MATCH_STR);
 }
 
+/*
+ * "matchstrlist()" function
+ */
+    static void
+f_matchstrlist(typval_T *argvars, typval_T *rettv)
+{
+    list_T     *retlist = NULL;
+    char_u     *save_cpo;
+    list_T     *l = NULL;
+    listitem_T *li = NULL;
+    char_u     patbuf[NUMBUFLEN];
+    regmatch_T regmatch;
+
+    rettv->vval.v_number = -1;
+    if (rettv_list_alloc(rettv) != OK)
+       return;
+    retlist = rettv->vval.v_list;
+
+    if (check_for_list_arg(argvars, 0) == FAIL
+           || check_for_string_arg(argvars, 1) == FAIL
+           || check_for_opt_dict_arg(argvars, 2) == FAIL)
+       return;
+
+    if ((l = argvars[0].vval.v_list) == NULL)
+       return;
+
+    char_u *pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+    if (pat == NULL)
+       return;
+
+    // Make 'cpoptions' empty, the 'l' flag should not be used here.
+    save_cpo = p_cpo;
+    p_cpo = empty_option;
+
+    regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+    if (regmatch.regprog == NULL)
+       goto theend;
+    regmatch.rm_ic = p_ic;
+
+    int                submatches = FALSE;
+    if (argvars[2].v_type != VAR_UNKNOWN)
+    {
+       dict_T *d = argvars[2].vval.v_dict;
+       if (d != NULL)
+       {
+           dictitem_T *di = dict_find(d, (char_u *)"submatches", -1);
+           if (di != NULL)
+           {
+               if (di->di_tv.v_type != VAR_BOOL)
+               {
+                   semsg(_(e_invalid_value_for_argument_str), "submatches");
+                   goto cleanup;
+               }
+               submatches = tv_get_bool(&di->di_tv);
+           }
+       }
+    }
+
+    int idx = 0;
+    CHECK_LIST_MATERIALIZE(l);
+    FOR_ALL_LIST_ITEMS(l, li)
+    {
+       if (li->li_tv.v_type == VAR_STRING && li->li_tv.vval.v_string != NULL)
+       {
+           char_u *str = li->li_tv.vval.v_string;
+           if (get_matches_in_str(str, &regmatch, retlist, idx, submatches,
+                                                               FALSE) == FAIL)
+               goto cleanup;
+       }
+       idx++;
+    }
+
+cleanup:
+    vim_regfree(regmatch.regprog);
+
+theend:
+    p_cpo = save_cpo;
+}
+
 /*
  * "matchstrpos()" function
  */
index 7bfe7fa7506e1c9e759a0fadfc44294454600dbf..85ccc78b8f64490fc1377f5c1c6fa4dc63d40e4f 100644 (file)
@@ -1172,6 +1172,192 @@ func Test_matchstrpos()
   call assert_equal(['', -1, -1], matchstrpos(test_null_list(), '\a'))
 endfunc
 
+" Test for matchstrlist()
+func Test_matchstrlist()
+  let lines =<< trim END
+    #" Basic match
+    call assert_equal([{'idx': 0, 'byteidx': 1, 'text': 'bout'},
+          \ {'idx': 1, 'byteidx': 1, 'text': 'bove'}],
+          \ matchstrlist(['about', 'above'], 'bo.*'))
+    #" no match
+    call assert_equal([], matchstrlist(['about', 'above'], 'xy.*'))
+    #" empty string
+    call assert_equal([], matchstrlist([''], '.'))
+    #" empty pattern
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': ''}], matchstrlist(['abc'], ''))
+    #" method call
+    call assert_equal([{'idx': 0, 'byteidx': 2, 'text': 'it'}], ['editor']->matchstrlist('ed\zsit\zeor'))
+    #" single character matches
+    call assert_equal([{'idx': 0, 'byteidx': 5, 'text': 'r'}],
+          \ ['editor']->matchstrlist('r'))
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': 'a'}], ['a']->matchstrlist('a'))
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': ''}],
+          \ matchstrlist(['foobar'], '\zs'))
+    #" string with tabs
+    call assert_equal([{'idx': 0, 'byteidx': 1, 'text': 'foo'}],
+          \ matchstrlist(["\tfoobar"], 'foo'))
+    #" string with multibyte characters
+    call assert_equal([{'idx': 0, 'byteidx': 2, 'text': '😊😊'}],
+          \ matchstrlist(["\t\t😊😊"], '\k\+'))
+
+    #" null string
+    call assert_equal([], matchstrlist(test_null_list(), 'abc'))
+    call assert_equal([], matchstrlist([test_null_string()], 'abc'))
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': ''}],
+          \ matchstrlist(['abc'], test_null_string()))
+
+    #" sub matches
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}], matchstrlist(['acd'], '\(a\)\?\(b\)\?\(c\)\?\(.*\)', {'submatches': v:true}))
+
+    #" null dict argument
+    call assert_equal([{'idx': 0, 'byteidx': 0, 'text': 'vim'}],
+          \ matchstrlist(['vim'], '\w\+', test_null_dict()))
+
+    #" Error cases
+    call assert_fails("echo matchstrlist('abc', 'a')", 'E1211: List required for argument 1')
+    call assert_fails("echo matchstrlist(['abc'], {})", 'E1174: String required for argument 2')
+    call assert_fails("echo matchstrlist(['abc'], '.', [])", 'E1206: Dictionary required for argument 3')
+    call assert_fails("echo matchstrlist(['abc'], 'a', {'submatches': []})", 'E475: Invalid value for argument submatches')
+    call assert_fails("echo matchstrlist(['abc'], '\\@=')", 'E866: (NFA regexp) Misplaced @')
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  let lines =<< trim END
+    vim9script
+    # non string items
+    matchstrlist([0z10, {'a': 'x'}], 'x')
+  END
+  call v9.CheckSourceSuccess(lines)
+
+  let lines =<< trim END
+    vim9script
+    def Foo()
+      # non string items
+      assert_equal([], matchstrlist([0z10, {'a': 'x'}], 'x'))
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceFailure(lines, 'E1013: Argument 1: type mismatch, expected list<string> but got list<any>', 2)
+endfunc
+
+" Test for matchbufline()
+func Test_matchbufline()
+  let lines =<< trim END
+    #" Basic match
+    new
+    call setline(1, ['about', 'above', 'below'])
+    VAR bnr = bufnr()
+    wincmd w
+    call assert_equal([{'lnum': 1, 'byteidx': 1, 'text': 'bout'},
+          \ {'lnum': 2, 'byteidx': 1, 'text': 'bove'}],
+          \ matchbufline(bnr, 'bo.*', 1, '$'))
+    #" multiple matches in a line
+    call setbufline(bnr, 1, ['about about', 'above above', 'below'])
+    call assert_equal([{'lnum': 1, 'byteidx': 1, 'text': 'bout'},
+          \ {'lnum': 1, 'byteidx': 7, 'text': 'bout'},
+          \ {'lnum': 2, 'byteidx': 1, 'text': 'bove'},
+          \ {'lnum': 2, 'byteidx': 7, 'text': 'bove'}],
+          \ matchbufline(bnr, 'bo\k\+', 1, '$'))
+    #" no match
+    call assert_equal([], matchbufline(bnr, 'xy.*', 1, '$'))
+    #" match on a particular line
+    call assert_equal([{'lnum': 2, 'byteidx': 7, 'text': 'bove'}],
+          \ matchbufline(bnr, 'bo\k\+$', 2, 2))
+    #" match on a particular line
+    call assert_equal([], matchbufline(bnr, 'bo.*', 3, 3))
+    #" empty string
+    call deletebufline(bnr, 1, '$')
+    call assert_equal([], matchbufline(bnr, '.', 1, '$'))
+    #" empty pattern
+    call setbufline(bnr, 1, 'abc')
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': ''}],
+          \ matchbufline(bnr, '', 1, '$'))
+    #" method call
+    call setbufline(bnr, 1, 'editor')
+    call assert_equal([{'lnum': 1, 'byteidx': 2, 'text': 'it'}],
+          \ bnr->matchbufline('ed\zsit\zeor', 1, 1))
+    #" single character matches
+    call assert_equal([{'lnum': 1, 'byteidx': 5, 'text': 'r'}],
+          \ matchbufline(bnr, 'r', 1, '$'))
+    call setbufline(bnr, 1, 'a')
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': 'a'}],
+          \ matchbufline(bnr, 'a', 1, '$'))
+    #" zero-width match
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': ''}],
+          \ matchbufline(bnr, '\zs', 1, '$'))
+    #" string with tabs
+    call setbufline(bnr, 1, "\tfoobar")
+    call assert_equal([{'lnum': 1, 'byteidx': 1, 'text': 'foo'}],
+          \ matchbufline(bnr, 'foo', 1, '$'))
+    #" string with multibyte characters
+    call setbufline(bnr, 1, "\t\t😊😊")
+    call assert_equal([{'lnum': 1, 'byteidx': 2, 'text': '😊😊'}],
+          \ matchbufline(bnr, '\k\+', 1, '$'))
+    #" empty buffer
+    call deletebufline(bnr, 1, '$')
+    call assert_equal([], matchbufline(bnr, 'abc', 1, '$'))
+
+    #" Non existing buffer
+    call setbufline(bnr, 1, 'abc')
+    call assert_fails("echo matchbufline(5000, 'abc', 1, 1)", 'E158: Invalid buffer name: 5000')
+    #" null string
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': ''}],
+          \ matchbufline(bnr, test_null_string(), 1, 1))
+    #" invalid starting line number
+    call assert_equal([], matchbufline(bnr, 'abc', 100, 100))
+    #" ending line number greater than the last line
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': 'abc'}],
+          \ matchbufline(bnr, 'abc', 1, 100))
+    #" ending line number greater than the starting line number
+    call setbufline(bnr, 1, ['one', 'two'])
+    call assert_fails($"echo matchbufline({bnr}, 'abc', 2, 1)", 'E475: Invalid value for argument end_lnum')
+
+    #" sub matches
+    call deletebufline(bnr, 1, '$')
+    call setbufline(bnr, 1, 'acd')
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}],
+          \ matchbufline(bnr, '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 1, '$', {'submatches': v:true}))
+
+    #" null dict argument
+    call assert_equal([{'lnum': 1, 'byteidx': 0, 'text': 'acd'}],
+          \ matchbufline(bnr, '\w\+', '$', '$', test_null_dict()))
+
+    #" Error cases
+    call assert_fails("echo matchbufline([1], 'abc', 1, 1)", 'E1220: String or Number required for argument 1')
+    call assert_fails("echo matchbufline(1, {}, 1, 1)", 'E1174: String required for argument 2')
+    call assert_fails("echo matchbufline(1, 'abc', {}, 1)", 'E1220: String or Number required for argument 3')
+    call assert_fails("echo matchbufline(1, 'abc', 1, {})", 'E1220: String or Number required for argument 4')
+    call assert_fails($"echo matchbufline({bnr}, 'abc', -1, '$')", 'E475: Invalid value for argument lnum')
+    call assert_fails($"echo matchbufline({bnr}, 'abc', 1, -1)", 'E475: Invalid value for argument end_lnum')
+    call assert_fails($"echo matchbufline({bnr}, '\\@=', 1, 1)", 'E866: (NFA regexp) Misplaced @')
+    call assert_fails($"echo matchbufline({bnr}, 'abc', 1, 1, {{'submatches': []}})", 'E475: Invalid value for argument submatches')
+    :%bdelete!
+    call assert_fails($"echo matchbufline({bnr}, 'abc', 1, '$'))", 'E681: Buffer is not loaded')
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  call assert_fails($"echo matchbufline('', 'abc', 'abc', 1)", 'E475: Invalid value for argument lnum')
+  call assert_fails($"echo matchbufline('', 'abc', 1, 'abc')", 'E475: Invalid value for argument end_lnum')
+
+  let lines =<< trim END
+    vim9script
+    def Foo()
+      echo matchbufline('', 'abc', 'abc', 1)
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "abc"', 1)
+
+  let lines =<< trim END
+    vim9script
+    def Foo()
+      echo matchbufline('', 'abc', 1, 'abc')
+    enddef
+    Foo()
+  END
+  call v9.CheckSourceFailure(lines, 'E1030: Using a String as a Number: "abc"', 1)
+endfunc
+
 func Test_nextnonblank_prevnonblank()
   new
 insert
index 3839d9c35991a707857a6769541b8111a8395996..2e7f2871ea5cc5c64d5628d1ba6344ecbb6d7964 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    9,
 /**/
     8,
 /**/