mirror of
https://github.com/neovim/neovim.git
synced 2025-10-05 01:16:31 +00:00
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.f93b1c881a
vim-patch:eb3475df0d92 runtime(doc): Replace non-breaking space with normal space (vim/vim#13868)eb3475df0d
Co-authored-by: Yegappan Lakshmanan <4298407+yegappan@users.noreply.github.com>
This commit is contained in:
@@ -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',
|
||||
|
@@ -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, ®match, 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, ®match, 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)
|
||||
{
|
||||
|
@@ -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
|
||||
|
@@ -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"));
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user