diff options
author | Famiu Haque <famiuhaque@protonmail.com> | 2022-06-28 16:22:29 +0600 |
---|---|---|
committer | Famiu Haque <famiuhaque@protonmail.com> | 2022-06-28 17:31:04 +0600 |
commit | 606ec8b70874095a62289f1df3934eca78625742 (patch) | |
tree | d5501227f7b270fec8d01ee5915da4175c6e7d22 | |
parent | ee6b21e8430ea810ba2e3e9163b941386a2e1d65 (diff) | |
download | rneovim-606ec8b70874095a62289f1df3934eca78625742.tar.gz rneovim-606ec8b70874095a62289f1df3934eca78625742.tar.bz2 rneovim-606ec8b70874095a62289f1df3934eca78625742.zip |
feat(api): make `nvim_parse_cmd` and `nvim_cmd` support :filter
Also fixes a memory leak in `parse_cmdline`.
Closes #18954.
-rw-r--r-- | runtime/doc/api.txt | 5 | ||||
-rw-r--r-- | src/nvim/api/command.c | 43 | ||||
-rw-r--r-- | src/nvim/api/keysets.lua | 5 | ||||
-rw-r--r-- | src/nvim/ex_cmds_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 30 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 2 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 87 |
7 files changed, 157 insertions, 16 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 1d7a783bf1..46f4bc63c9 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1944,6 +1944,11 @@ nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* is treated as the start of a comment. • mods: (dictionary) |:command-modifiers|. + • filter: (dictionary) |:filter|. + • pattern: (string) Filter pattern. Empty string if + there is no filter. + • force: (boolean) Whether filter is inverted or not. + • silent: (boolean) |:silent|. • emsg_silent: (boolean) |:silent!|. • sandbox: (boolean) |:sandbox|. diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 80a5449d29..e6a055995e 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -49,6 +49,9 @@ /// - bar: (boolean) The "|" character is treated as a command separator and the double /// quote character (\") is treated as the start of a comment. /// - mods: (dictionary) |:command-modifiers|. +/// - filter: (dictionary) |:filter|. +/// - pattern: (string) Filter pattern. Empty string if there is no filter. +/// - force: (boolean) Whether filter is inverted or not. /// - silent: (boolean) |:silent|. /// - emsg_silent: (boolean) |:silent!|. /// - sandbox: (boolean) |:sandbox|. @@ -95,7 +98,6 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) } goto end; } - vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog); // Parse arguments Array args = ARRAY_DICT_INIT; @@ -220,6 +222,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); Dictionary mods = ARRAY_DICT_INIT; + + Dictionary filter = ARRAY_DICT_INIT; + PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat + ? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat) + : STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force)); + PUT(mods, "filter", DICTIONARY_OBJ(filter)); + PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); @@ -257,6 +267,8 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); PUT(result, "magic", DICTIONARY_OBJ(magic)); + + undo_cmdmod(&cmdinfo.cmdmod); end: xfree(cmdline); return result; @@ -513,6 +525,35 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error goto end; } + if (HAS_KEY(mods.filter)) { + if (mods.filter.type != kObjectTypeDictionary) { + VALIDATION_ERROR("'mods.filter' must be a Dictionary"); + } + + Dict(cmd_mods_filter) filter = { 0 }; + + if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field, + mods.filter.data.dictionary, err)) { + goto end; + } + + if (HAS_KEY(filter.pattern)) { + if (filter.pattern.type != kObjectTypeString) { + VALIDATION_ERROR("'mods.filter.pattern' must be a String"); + } + + OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'"); + + // "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter + // is inverted. + if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) { + cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string); + cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat, + RE_MAGIC); + } + } + } + if (HAS_KEY(mods.tab)) { if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 70e91dd844..1c071eaf48 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -181,6 +181,7 @@ return { cmd_mods = { "silent"; "emsg_silent"; + "filter"; "sandbox"; "noautocmd"; "browse"; @@ -197,6 +198,10 @@ return { "vertical"; "split"; }; + cmd_mods_filter = { + "pattern"; + "force"; + }; cmd_opts = { "output"; }; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 9350a9f60b..6798f7693b 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -268,6 +268,7 @@ typedef struct { int cmod_split; ///< flags for win_split() int cmod_tab; ///< > 0 when ":tab" was used + char *cmod_filter_pat; regmatch_T cmod_filter_regmatch; ///< set by :filter /pat/ bool cmod_filter_force; ///< set for :filter! diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 48e6dad720..fee98a18dc 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1405,8 +1405,8 @@ bool is_cmd_ni(cmdidx_T cmdidx) } /// Parse command line and return information about the first command. -/// If parsing is done successfully, need to free cmod_filter_regmatch.regprog after calling, -/// usually done using undo_cmdmod() or execute_cmd(). +/// If parsing is done successfully, need to free cmod_filter_pat and cmod_filter_regmatch.regprog +/// after calling, usually done using undo_cmdmod() or execute_cmd(). /// /// @param cmdline Command line string /// @param[out] eap Ex command arguments @@ -1434,8 +1434,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse command modifiers if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { - vim_regfree(cmdinfo->cmdmod.cmod_filter_regmatch.regprog); - return false; + goto err; } after_modifier = eap->cmd; @@ -1449,21 +1448,21 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er p = find_ex_command(eap, NULL); if (p == NULL) { *errormsg = _(e_ambiguous_use_of_user_defined_command); - return false; + goto err; } // Set command address type and parse command range set_cmd_addr_type(eap, p); eap->cmd = cmd; if (parse_cmd_address(eap, errormsg, false) == FAIL) { - return false; + goto err; } // Skip colon and whitespace eap->cmd = skip_colon_white(eap->cmd, true); // Fail if command is a comment or if command doesn't exist if (*eap->cmd == NUL || *eap->cmd == '"') { - return false; + goto err; } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { @@ -1472,7 +1471,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); *errormsg = (char *)IObuff; - return false; + goto err; } // Correctly set 'forceit' for commands @@ -1511,12 +1510,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Fail if command doesn't support bang but is used with a bang if (!(eap->argt & EX_BANG) && eap->forceit) { *errormsg = _(e_nobang); - return false; + goto err; } // Fail if command doesn't support a range but it is given a range if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { *errormsg = _(e_norange); - return false; + goto err; } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { @@ -1526,7 +1525,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse register and count parse_register(eap); if (parse_count(eap, errormsg, false) == FAIL) { - return false; + goto err; } // Remove leading whitespace and colon from next command @@ -1543,6 +1542,9 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er } return true; +err: + undo_cmdmod(&cmdinfo->cmdmod); + return false; } /// Execute an Ex command using parsed command line information. @@ -2393,7 +2395,7 @@ char *ex_errmsg(const char *const msg, const char *const arg) /// - Set ex_pressedreturn for an empty command line. /// /// @param skip_only if false, undo_cmdmod() must be called later to free -/// any cmod_filter_regmatch.regprog. +/// any cmod_filter_pat and cmod_filter_regmatch.regprog. /// @param[out] errormsg potential error message. /// /// Call apply_cmdmod() to get the side effects of the modifiers: @@ -2512,6 +2514,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool break; } if (!skip_only) { + cmod->cmod_filter_pat = xstrdup(reg_pat); cmod->cmod_filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); if (cmod->cmod_filter_regmatch.regprog == NULL) { break; @@ -2673,7 +2676,7 @@ static void apply_cmdmod(cmdmod_T *cmod) } /// Undo and free contents of "cmod". -static void undo_cmdmod(cmdmod_T *cmod) +void undo_cmdmod(cmdmod_T *cmod) FUNC_ATTR_NONNULL_ALL { if (cmod->cmod_verbose_save > 0) { @@ -2693,6 +2696,7 @@ static void undo_cmdmod(cmdmod_T *cmod) cmod->cmod_save_ei = NULL; } + xfree(cmod->cmod_filter_pat); vim_regfree(cmod->cmod_filter_regmatch.regprog); if (cmod->cmod_save_msg_silent > 0) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 93758b9ec9..f7c650cf3e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2404,7 +2404,7 @@ static bool cmdpreview_may_show(CommandLineState *s) // Check if command is previewable, if not, don't attempt to show preview if (!(ea.argt & EX_PREVIEW)) { - vim_regfree(cmdinfo.cmdmod.cmod_filter_regmatch.regprog); + undo_cmdmod(&cmdinfo.cmdmod); goto end; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index c05345dd4c..858463efbd 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3167,6 +3167,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3203,6 +3207,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3239,6 +3247,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3275,6 +3287,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3311,6 +3327,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3347,6 +3367,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3383,6 +3407,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = true, + filter = { + pattern = "foo", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3398,7 +3426,45 @@ describe('API', function() tab = 2, verbose = 15 }, - }, meths.parse_cmd('15verbose silent! aboveleft topleft tab split foo.txt', {})) + }, meths.parse_cmd('15verbose silent! aboveleft topleft tab filter /foo/ split foo.txt', {})) + eq({ + cmd = 'split', + args = { 'foo.txt' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = '?', + magic = { + file = true, + bar = true + }, + nargs = '?', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "foo", + force = true + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + vertical = false, + split = "", + tab = 0, + verbose = -1 + }, + }, meths.parse_cmd('filter! /foo/ split foo.txt', {})) end) it('works with user commands', function() command('command -bang -nargs=+ -range -addr=lines MyCommand echo foo') @@ -3420,6 +3486,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3456,6 +3526,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3493,6 +3567,10 @@ describe('API', function() browse = false, confirm = false, emsg_silent = false, + filter = { + pattern = "", + force = false + }, hide = false, keepalt = false, keepjumps = false, @@ -3604,6 +3682,13 @@ describe('API', function() meths.create_user_command("Foo", 'set verbose', {}) eq(" verbose=1", meths.cmd({ cmd = "Foo", mods = { verbose = 1 } }, { output = true })) eq(0, meths.get_option_value("verbose", {})) + command('edit foo.txt | edit bar.txt') + eq(' 1 #h "foo.txt" line 1', + meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = false } } }, + { output = true })) + eq(' 2 %a "bar.txt" line 1', + meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } }, + { output = true })) end) it('works with magic.file', function() exec_lua([[ |