aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-01-16 08:00:08 +0800
committerGitHub <noreply@github.com>2024-01-16 08:00:08 +0800
commit73e1942abe7a96d63ce3749af4187f2cdff87e69 (patch)
treebd508b354771b2c01bbcc41dc83fb82a3d0d38b7
parentae48d965d70cc721a3165c40ba0c34d95408e229 (diff)
downloadrneovim-73e1942abe7a96d63ce3749af4187f2cdff87e69.tar.gz
rneovim-73e1942abe7a96d63ce3749af4187f2cdff87e69.tar.bz2
rneovim-73e1942abe7a96d63ce3749af4187f2cdff87e69.zip
vim-patch:9.1.0009: Cannot easily get the list of matches (#27028)
Problem: Cannot easily get the list of matches Solution: Add the matchstrlist() and matchbufline() Vim script functions (Yegappan Lakshmanan) closes: vim/vim#13766 Omit CHECK_LIST_MATERIALIZE(): it populates a List with numbers only, and there is a check for strings below. https://github.com/vim/vim/commit/f93b1c881a99fa847a1bafa71877d7e16f18e6ef vim-patch:eb3475df0d92 runtime(doc): Replace non-breaking space with normal space (vim/vim#13868) https://github.com/vim/vim/commit/eb3475df0d927a178789cf8e7fc4983932e1cdbe Co-authored-by: Yegappan Lakshmanan <4298407+yegappan@users.noreply.github.com>
-rw-r--r--runtime/doc/builtin.txt73
-rw-r--r--runtime/doc/usr_41.txt3
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua85
-rw-r--r--src/nvim/eval.lua93
-rw-r--r--src/nvim/eval/funcs.c212
-rw-r--r--src/nvim/eval/typval.c16
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/quickfix.c2
-rw-r--r--src/nvim/sign.c2
-rw-r--r--test/old/testdir/test_functions.vim188
-rw-r--r--test/old/testdir/vim9.vim43
11 files changed, 713 insertions, 6 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index d85735a008..416a10460a 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -4480,6 +4480,47 @@ matcharg({nr}) *matcharg()*
Highlighting matches using the |:match| commands are limited
to three matches. |matchadd()| does not have this limitation.
+matchbufline({buf}, {pat}, {lnum}, {end}, [, {dict}]) *matchbufline()*
+ 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: >vim
+ " 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: >vim
+ " 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.
+
matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803*
Deletes a match with ID {id} previously defined by |matchadd()|
or one of the |:match| commands. Returns 0 if successful,
@@ -4617,6 +4658,36 @@ matchstr({expr}, {pat} [, {start} [, {count}]]) *matchstr()*
When {expr} is a |List| then the matching item is returned.
The type isn't changed, it's not necessarily a String.
+matchstrlist({list}, {pat} [, {dict}]) *matchstrlist()*
+ 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: >vim
+ :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: >vim
+ :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.
+
matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()*
Same as |matchstr()|, but return the matched string, the start
position and the end position of the match. Example: >vim
@@ -4643,7 +4714,7 @@ max({expr}) *max()*
it returns the maximum of all values in the Dictionary.
If {expr} is neither a List nor a Dictionary, or one of the
items in {expr} cannot be used as a Number this results in
- an error. An empty |List| or |Dictionary| results in zero.
+ an error. An empty |List| or |Dictionary| results in zero.
menu_get({path} [, {modes}]) *menu_get()*
Returns a |List| of |Dictionaries| describing |menus| (defined
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index e206a804f4..acb957d0c1 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -610,10 +610,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
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index fd9b68a6f3..01b4ef920b 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -5412,6 +5412,54 @@ function vim.fn.matchaddpos(group, pos, priority, id, dict) end
--- @return any
function vim.fn.matcharg(nr) end
+--- 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: >vim
+--- " 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: >vim
+--- " 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.
+---
+--- @param buf string|integer
+--- @param pat string
+--- @param lnum string|integer
+--- @param end_ string|integer
+--- @param dict? table
+--- @return any
+function vim.fn.matchbufline(buf, pat, lnum, end_, dict) end
+
--- Deletes a match with ID {id} previously defined by |matchadd()|
--- or one of the |:match| commands. Returns 0 if successful,
--- otherwise -1. See example for |matchadd()|. All matches can
@@ -5581,6 +5629,41 @@ function vim.fn.matchlist(expr, pat, start, count) end
--- @return any
function vim.fn.matchstr(expr, pat, start, count) end
+--- 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: >vim
+--- :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: >vim
+--- :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.
+---
+--- @param list string[]
+--- @param pat string
+--- @param dict? table
+--- @return any
+function vim.fn.matchstrlist(list, pat, dict) end
+
--- Same as |matchstr()|, but return the matched string, the start
--- position and the end position of the match. Example: >vim
--- echo matchstrpos("testing", "ing")
@@ -5612,7 +5695,7 @@ function vim.fn.matchstrpos(expr, pat, start, count) end
--- it returns the maximum of all values in the Dictionary.
--- If {expr} is neither a List nor a Dictionary, or one of the
--- items in {expr} cannot be used as a Number this results in
---- an error. An empty |List| or |Dictionary| results in zero.
+--- an error. An empty |List| or |Dictionary| results in zero.
---
--- @param expr any
--- @return any
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 6bd6087a4e..9545cb9823 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -6615,6 +6615,60 @@ M.funcs = {
params = { { 'nr', 'integer' } },
signature = 'matcharg({nr})',
},
+ matchbufline = {
+ args = { 4, 5 },
+ base = 1,
+ desc = [=[
+ 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: >vim
+ " 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: >vim
+ " 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.
+ ]=],
+ name = 'matchbufline',
+ params = {
+ { 'buf', 'string|integer' },
+ { 'pat', 'string' },
+ { 'lnum', 'string|integer' },
+ { 'end', 'string|integer' },
+ { 'dict', 'table' },
+ },
+ signature = 'matchbufline({buf}, {pat}, {lnum}, {end}, [, {dict}])',
+ },
matchdelete = {
args = { 1, 2 },
base = 1,
@@ -6799,6 +6853,43 @@ M.funcs = {
params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
signature = 'matchstr({expr}, {pat} [, {start} [, {count}]])',
},
+ matchstrlist = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ 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: >vim
+ :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: >vim
+ :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.
+ ]=],
+ name = 'matchstrlist',
+ params = { { 'list', 'string[]' }, { 'pat', 'string' }, { 'dict', 'table' } },
+ signature = 'matchstrlist({list}, {pat} [, {dict}])',
+ },
matchstrpos = {
args = { 2, 4 },
base = 1,
@@ -6836,7 +6927,7 @@ M.funcs = {
it returns the maximum of all values in the Dictionary.
If {expr} is neither a List nor a Dictionary, or one of the
items in {expr} cannot be used as a Number this results in
- an error. An empty |List| or |Dictionary| results in zero.
+ an error. An empty |List| or |Dictionary| results in zero.
]=],
name = 'max',
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 4d60cd3660..26e4e24124 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -4731,6 +4731,151 @@ 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 void get_matches_in_str(const char *str, regmatch_T *rmp, list_T *mlist, int idx,
+ bool submatches, bool matchbuf)
+{
+ size_t len = strlen(str);
+ int match = 0;
+ colnr_T startidx = 0;
+
+ while (true) {
+ match = vim_regexec_nl(rmp, str, startidx);
+ if (!match) {
+ break;
+ }
+
+ dict_T *d = tv_dict_alloc();
+ tv_list_append_dict(mlist, d);
+
+ if (matchbuf) {
+ tv_dict_add_nr(d, S_LEN("lnum"), idx);
+ } else {
+ tv_dict_add_nr(d, S_LEN("idx"), idx);
+ }
+
+ tv_dict_add_nr(d, S_LEN("byteidx"),
+ (colnr_T)(rmp->startp[0] - str));
+
+ tv_dict_add_str_len(d, S_LEN("text"), rmp->startp[0],
+ (int)(rmp->endp[0] - rmp->startp[0]));
+
+ if (submatches) {
+ list_T *sml = tv_list_alloc(NSUBEXP - 1);
+
+ tv_dict_add_list(d, S_LEN("submatches"), sml);
+
+ // return a list with the submatches
+ for (int i = 1; i < NSUBEXP; i++) {
+ if (rmp->endp[i] == NULL) {
+ tv_list_append_string(sml, "", 0);
+ } else {
+ tv_list_append_string(sml, rmp->startp[i], rmp->endp[i] - rmp->startp[i]);
+ }
+ }
+ }
+ startidx = (colnr_T)(rmp->endp[0] - str);
+ if (startidx >= (colnr_T)len || str + startidx <= rmp->startp[0]) {
+ break;
+ }
+ }
+}
+
+/// "matchbufline()" function
+static void f_matchbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ list_T *retlist = rettv->vval.v_list;
+
+ if (tv_check_for_buffer_arg(argvars, 0) == FAIL
+ || tv_check_for_string_arg(argvars, 1) == FAIL
+ || tv_check_for_lnum_arg(argvars, 2) == FAIL
+ || tv_check_for_lnum_arg(argvars, 3) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 4) == FAIL) {
+ return;
+ }
+
+ const 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 patbuf[NUMBUFLEN];
+ const char *pat = tv_get_string_buf(&argvars[1], patbuf);
+
+ const 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_invargval), "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_invargval), "end_lnum");
+ return;
+ }
+
+ if (elnum > buf->b_ml.ml_line_count) {
+ elnum = buf->b_ml.ml_line_count;
+ }
+
+ bool submatches = false;
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ dict_T *d = argvars[4].vval.v_dict;
+ if (d != NULL) {
+ dictitem_T *di = tv_dict_find(d, S_LEN("submatches"));
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_BOOL) {
+ semsg(_(e_invargval), "submatches");
+ return;
+ }
+ submatches = tv_get_bool(&di->di_tv);
+ }
+ }
+ }
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ char *const save_cpo = p_cpo;
+ p_cpo = empty_string_option;
+
+ regmatch_T regmatch;
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ if (regmatch.regprog == NULL) {
+ goto theend;
+ }
+ regmatch.rm_ic = p_ic;
+
+ while (slnum <= elnum) {
+ const char *str = ml_get_buf(buf, slnum);
+ get_matches_in_str(str, &regmatch, retlist, slnum, submatches, true);
+ slnum++;
+ }
+
+ vim_regfree(regmatch.regprog);
+
+theend:
+ p_cpo = save_cpo;
+}
+
/// "match()" function
static void f_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -4755,6 +4900,73 @@ static void f_matchstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
find_some_match(argvars, rettv, kSomeMatchStr);
}
+/// "matchstrlist()" function
+static void f_matchstrlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ list_T *retlist = rettv->vval.v_list;
+
+ if (tv_check_for_list_arg(argvars, 0) == FAIL
+ || tv_check_for_string_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) {
+ return;
+ }
+
+ list_T *l = NULL;
+ if ((l = argvars[0].vval.v_list) == NULL) {
+ return;
+ }
+
+ char patbuf[NUMBUFLEN];
+ const char *pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ if (pat == NULL) {
+ return;
+ }
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ char *const save_cpo = p_cpo;
+ p_cpo = empty_string_option;
+
+ regmatch_T regmatch;
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ if (regmatch.regprog == NULL) {
+ goto theend;
+ }
+ regmatch.rm_ic = p_ic;
+
+ bool submatches = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ dict_T *d = argvars[2].vval.v_dict;
+ if (d != NULL) {
+ dictitem_T *di = tv_dict_find(d, S_LEN("submatches"));
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_BOOL) {
+ semsg(_(e_invargval), "submatches");
+ goto cleanup;
+ }
+ submatches = tv_get_bool(&di->di_tv);
+ }
+ }
+ }
+
+ int idx = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ const typval_T *const li_tv = TV_LIST_ITEM_TV(li);
+ if (li_tv->v_type == VAR_STRING && li_tv->vval.v_string != NULL) {
+ const char *str = li_tv->vval.v_string;
+ get_matches_in_str(str, &regmatch, retlist, idx, submatches, false);
+ }
+ idx++;
+ });
+
+cleanup:
+ vim_regfree(regmatch.regprog);
+
+theend:
+ p_cpo = save_cpo;
+}
+
/// "matchstrpos()" function
static void f_matchstrpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 2892cb60f9..d510cf2a3d 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -4346,6 +4346,22 @@ int tv_check_for_string_or_number_arg(const typval_T *const args, const int idx)
return OK;
}
+/// Give an error and return FAIL unless "args[idx]" is a buffer number.
+/// Buffer number can be a number or a string.
+int tv_check_for_buffer_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return tv_check_for_string_or_number_arg(args, idx);
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a line number.
+/// Line number can be a number or a string.
+int tv_check_for_lnum_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return tv_check_for_string_or_number_arg(args, idx);
+}
+
/// Give an error and return FAIL unless "args[idx]" is a string or a list.
int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 0ace7f8e0e..f0cc9dba74 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -804,7 +804,9 @@ EXTERN const char e_argreq[] INIT(= N_("E471: Argument required"));
EXTERN const char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
EXTERN const char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
EXTERN const char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
+EXTERN const char e_invalid_buffer_name_str[] INIT(= N_("E158: Invalid buffer name: %s"));
EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
+EXTERN const char e_buffer_is_not_loaded[] INIT(= N_("E681: Buffer is not loaded"));
EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 3ae3807937..801c00933a 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -6916,7 +6916,7 @@ static int cbuffer_process_args(exarg_T *eap, buf_T **bufp, linenr_T *line1, lin
}
if (buf->b_ml.ml_mfp == NULL) {
- emsg(_("E681: Buffer is not loaded"));
+ emsg(_(e_buffer_is_not_loaded));
return FAIL;
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index e8d4f4b163..0cf35c4921 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -807,7 +807,7 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **name, int *id, char **
}
if (filename != NULL && *buf == NULL) {
- semsg(_("E158: Invalid buffer name: %s"), filename);
+ semsg(_(e_invalid_buffer_name_str), filename);
return FAIL;
}
diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim
index d277ff72df..938928cd0b 100644
--- a/test/old/testdir/test_functions.vim
+++ b/test/old/testdir/test_functions.vim
@@ -1044,6 +1044,192 @@ func Test_matchstrpos()
call assert_equal(['', -1, -1], matchstrpos(v:_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(v:_null_list, 'abc'))
+ call assert_equal([], matchstrlist([v:_null_string], 'abc'))
+ call assert_equal([{'idx': 0, 'byteidx': 0, 'text': ''}],
+ \ matchstrlist(['abc'], v:_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\+', v:_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 CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ vim9script
+ # non string items
+ matchstrlist([0z10, {'a': 'x'}], 'x')
+ END
+ call CheckSourceSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ def Foo()
+ # non string items
+ assert_equal([], matchstrlist([0z10, {'a': 'x'}], 'x'))
+ enddef
+ Foo()
+ END
+ call 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, v:_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\+', '$', '$', v:_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 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 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 CheckSourceFailure(lines, 'E1030: Using a String as a Number: "abc"', 1)
+endfunc
+
func Test_nextnonblank_prevnonblank()
new
insert
@@ -2104,7 +2290,7 @@ func Test_trim()
call assert_equal(" \tabcd\t xxxx tail", trim(" \tabcd\t xxxx tail", "abx"))
call assert_equal("RESERVE", trim("你RESERVE好", "你好"))
call assert_equal("您R E SER V E早", trim("你好您R E SER V E早好你你", "你好"))
- call assert_equal("你好您R E SER V E早好你你", trim(" \n\r\r 你好您R E SER V E早好你你 \t \x0B"))
+ call assert_equal("你好您R E SER V E早好你你", trim(" \n\r\r 你好您R E SER V E早好你你 \t \x0B", ))
call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" 你好您R E SER V E早好你你 \t \x0B", " 你好"))
call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你好tes"))
call assert_equal("您R E SER V E早好你你 \t \x0B", trim(" tteesstttt你好您R E SER V E早好你你 \t \x0B ttestt", " 你你你好好好tttsses"))
diff --git a/test/old/testdir/vim9.vim b/test/old/testdir/vim9.vim
index 825f01c9d6..f8a90c1e97 100644
--- a/test/old/testdir/vim9.vim
+++ b/test/old/testdir/vim9.vim
@@ -42,6 +42,49 @@ func CheckScriptSuccess(lines)
endtry
endfunc
+" :source a list of "lines" and check whether it fails with "error"
+func CheckSourceFailure(lines, error, lnum = -3)
+ if get(a:lines, 0, '') ==# 'vim9script'
+ return
+ endif
+ new
+ call setline(1, a:lines)
+ try
+ call assert_fails('source', a:error, a:lines, a:lnum)
+ finally
+ bw!
+ endtry
+endfunc
+
+" :source a list of "lines" and check whether it fails with the list of
+" "errors"
+func CheckSourceFailureList(lines, errors, lnum = -3)
+ if get(a:lines, 0, '') ==# 'vim9script'
+ return
+ endif
+ new
+ call setline(1, a:lines)
+ try
+ call assert_fails('source', a:errors, a:lines, a:lnum)
+ finally
+ bw!
+ endtry
+endfunc
+
+" :source a list of "lines" and check whether it succeeds
+func CheckSourceSuccess(lines)
+ if get(a:lines, 0, '') ==# 'vim9script'
+ return
+ endif
+ new
+ call setline(1, a:lines)
+ try
+ :source
+ finally
+ bw!
+ endtry
+endfunc
+
func CheckDefAndScriptSuccess(lines)
return
endfunc