From ebfe083337701534887ac3ea3d8e7ad47f7a206a Mon Sep 17 00:00:00 2001 From: shadmansaleh <13149513+shadmansaleh@users.noreply.github.com> Date: Sat, 8 Jan 2022 00:39:44 +0600 Subject: feat(lua): show proper verbose output for lua configuration `:verbose` didn't work properly with lua configs (For example: options or keymaps are set from lua, just say that they were set from lua, doesn't say where they were set at. This fixes that issue. Now `:verbose` will provide filename and line no when option/keymap is set from lua. Changes: - compiles lua/vim/keymap.lua as vim/keymap.lua - When souring a lua file current_sctx.sc_sid is set to SID_LUA - Moved finding scripts SID out of `do_source()` to `get_current_script_id()`. So it can be reused for lua files. - Added new function `nlua_get_sctx` that extracts current lua scripts name and line no with debug library. And creates a sctx for it. NOTE: This function ignores C functions and blacklist which currently contains only vim/_meta.lua so vim.o/opt wrappers aren't targeted. - Added function `nlua_set_sctx` that changes provided sctx to current lua scripts sctx if a lua file is being executed. - Added tests in tests/functional/lua/verbose_spec.lua - add primary support for additional types (:autocmd, :function, :syntax) to lua verbose Note: These can't yet be directly set from lua but once that's possible :verbose should work for them hopefully :D - add :verbose support for nvim_exec & nvim_command within lua Currently auto commands/commands/functions ... can only be defined by nvim_exec/nvim_command this adds support for them. Means if those Are defined within lua with vim.cmd/nvim_exec :verbose will show their location . Though note it'll show the line no on which nvim_exec call was made. --- src/nvim/api/vimscript.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 640144b234..4123674fe7 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -37,7 +37,7 @@ /// @param[out] err Error details (Vim error), if any /// @return Output (non-error, non-shell |:!|) if `output` is true, /// else empty string. -String nvim_exec(String src, Boolean output, Error *err) +String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) FUNC_API_SINCE(7) { const int save_msg_silent = msg_silent; @@ -52,11 +52,16 @@ String nvim_exec(String src, Boolean output, Error *err) if (output) { msg_silent++; } + + const sctx_T save_current_sctx = api_set_sctx(channel_id); + do_source_str(src.data, "nvim_exec()"); if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; } + + current_sctx = save_current_sctx; try_end(err); if (ERROR_SET(err)) { -- cgit From b9bdd0f61e5e7365c07aadbc3f796556b6d85fdf Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Sun, 1 May 2022 11:18:17 +0200 Subject: refactor: replace char_u variables and functions with char Work on https://github.com/neovim/neovim/issues/459 --- src/nvim/api/vimscript.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 4123674fe7..40ac0b8b64 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -131,7 +131,7 @@ Object nvim_eval(String expr, Error *err) try_start(); typval_T rettv; - int ok = eval0((char_u *)expr.data, &rettv, NULL, true); + int ok = eval0(expr.data, &rettv, NULL, true); if (!try_end(err)) { if (ok == FAIL) { @@ -246,7 +246,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) switch (dict.type) { case kObjectTypeString: try_start(); - if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { + if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to evaluate dict expression"); } -- cgit From 3ec93ca92cb08faed342586e86a6f21b35264376 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Wed, 4 May 2022 18:04:01 +0600 Subject: feat(nvim_parse_cmd): add range, count, reg #18383 Adds range, count and reg to the return values of nvim_parse_cmd. Also makes line1 and line2 be -1 if the command does not take a range. Also moves nvim_parse_cmd to vimscript.c because it fits better there. --- src/nvim/api/vimscript.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 40ac0b8b64..99d1406cfb 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -16,6 +16,7 @@ #include "nvim/ex_cmds2.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" +#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vimscript.c.generated.h" @@ -736,3 +737,231 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E viml_parser_destroy(&pstate); return ret; } + +/// Parse command line. +/// +/// Doesn't check the validity of command arguments. +/// +/// @param str Command line string to parse. Cannot contain "\n". +/// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any. +/// @return Dictionary containing command information, with these keys: +/// - cmd: (string) Command name. +/// - range: (number) Number of items in the command ||. Can be 0, 1 or 2. +/// - line1: (number) Starting line of command ||. -1 if command cannot take a range. +/// || +/// - line2: (number) Final line of command ||. -1 if command cannot take a range. +/// || +/// - count: (number) Any || that was supplied to the command. -1 if command cannot +/// take a count. +/// - reg: (number) The optional command ||, if specified. Empty string if not +/// specified or if command cannot take a register. +/// - bang: (boolean) Whether command contains a || (!) modifier. +/// - args: (array) Command arguments. +/// - addr: (string) Value of |:command-addr|. Uses short name. +/// - nargs: (string) Value of |:command-nargs|. +/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. +/// Empty if there isn't a next command. +/// - magic: (dictionary) Which characters have special meaning in the command arguments. +/// - file: (boolean) The command expands filenames. Which means characters such as "%", +/// "#" and wildcards are expanded. +/// - 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|. +/// - silent: (boolean) |:silent|. +/// - emsg_silent: (boolean) |:silent!|. +/// - sandbox: (boolean) |:sandbox|. +/// - noautocmd: (boolean) |:noautocmd|. +/// - browse: (boolean) |:browse|. +/// - confirm: (boolean) |:confirm|. +/// - hide: (boolean) |:hide|. +/// - keepalt: (boolean) |:keepalt|. +/// - keepjumps: (boolean) |:keepjumps|. +/// - keepmarks: (boolean) |:keepmarks|. +/// - keeppatterns: (boolean) |:keeppatterns|. +/// - lockmarks: (boolean) |:lockmarks|. +/// - noswapfile: (boolean) |:noswapfile|. +/// - tab: (integer) |:tab|. +/// - verbose: (integer) |:verbose|. -1 when omitted. +/// - vertical: (boolean) |:vertical|. +/// - split: (string) Split modifier string, is an empty string when there's no split +/// modifier. If there is a split modifier it can be one of: +/// - "aboveleft": |:aboveleft|. +/// - "belowright": |:belowright|. +/// - "topleft": |:topleft|. +/// - "botright": |:botright|. +Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) + FUNC_API_SINCE(10) FUNC_API_FAST +{ + Dictionary result = ARRAY_DICT_INIT; + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return result; + } + + // Parse command line + exarg_T ea; + CmdParseInfo cmdinfo; + char_u *cmdline = (char_u *)string_to_cstr(str); + + if (!parse_cmdline(cmdline, &ea, &cmdinfo)) { + api_set_error(err, kErrorTypeException, "Error while parsing command line"); + goto end; + } + + // Parse arguments + Array args = ARRAY_DICT_INIT; + size_t length = STRLEN(ea.arg); + + // For nargs = 1 or '?', pass the entire argument list as a single argument, + // otherwise split arguments by whitespace. + if (ea.argt & EX_NOSPC) { + if (*ea.arg != NUL) { + ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length))); + } + } else { + size_t end = 0; + size_t len = 0; + char *buf = xcalloc(length, sizeof(char)); + bool done = false; + + while (!done) { + done = uc_split_args_iter(ea.arg, length, &end, buf, &len); + if (len > 0) { + ADD(args, STRING_OBJ(cstrn_to_string(buf, len))); + } + } + + xfree(buf); + } + + ucmd_T *cmd = NULL; + if (ea.cmdidx == CMD_USER) { + cmd = USER_CMD(ea.useridx); + } else if (ea.cmdidx == CMD_USER_BUF) { + cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx); + } + + if (cmd != NULL) { + PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name)); + } else { + PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); + } + + PUT(result, "range", INTEGER_OBJ(ea.addr_count)); + PUT(result, "line1", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line1 : -1)); + PUT(result, "line2", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line2 : -1)); + + if (ea.argt & EX_COUNT) { + if (ea.addr_count > 0 || cmd == NULL) { + PUT(result, "count", INTEGER_OBJ(ea.line2)); + } else { + PUT(result, "count", INTEGER_OBJ(cmd->uc_def)); + } + } else { + PUT(result, "count", INTEGER_OBJ(-1)); + } + + char reg[2]; + reg[0] = (char)ea.regname; + reg[1] = '\0'; + PUT(result, "reg", CSTR_TO_OBJ(reg)); + + PUT(result, "bang", BOOLEAN_OBJ(ea.forceit)); + PUT(result, "args", ARRAY_OBJ(args)); + + char nargs[2]; + if (ea.argt & EX_EXTRA) { + if (ea.argt & EX_NOSPC) { + if (ea.argt & EX_NEEDARG) { + nargs[0] = '1'; + } else { + nargs[0] = '?'; + } + } else if (ea.argt & EX_NEEDARG) { + nargs[0] = '+'; + } else { + nargs[0] = '*'; + } + } else { + nargs[0] = '0'; + } + nargs[1] = '\0'; + PUT(result, "nargs", CSTR_TO_OBJ(nargs)); + + const char *addr; + switch (ea.addr_type) { + case ADDR_LINES: + addr = "line"; + break; + case ADDR_ARGUMENTS: + addr = "arg"; + break; + case ADDR_BUFFERS: + addr = "buf"; + break; + case ADDR_LOADED_BUFFERS: + addr = "load"; + break; + case ADDR_WINDOWS: + addr = "win"; + break; + case ADDR_TABS: + addr = "tab"; + break; + case ADDR_QUICKFIX: + addr = "qf"; + break; + case ADDR_NONE: + addr = "none"; + break; + default: + addr = "?"; + break; + } + PUT(result, "addr", CSTR_TO_OBJ(addr)); + PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); + + Dictionary mods = ARRAY_DICT_INIT; + PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent)); + PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent)); + PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox)); + PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd)); + PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab)); + PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); + PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse)); + PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm)); + PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide)); + PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt)); + PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps)); + PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks)); + PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns)); + PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks)); + PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile)); + PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT)); + + const char *split; + if (cmdinfo.cmdmod.split & WSP_BOT) { + split = "botright"; + } else if (cmdinfo.cmdmod.split & WSP_TOP) { + split = "topleft"; + } else if (cmdinfo.cmdmod.split & WSP_BELOW) { + split = "belowright"; + } else if (cmdinfo.cmdmod.split & WSP_ABOVE) { + split = "aboveleft"; + } else { + split = ""; + } + PUT(mods, "split", CSTR_TO_OBJ(split)); + + PUT(result, "mods", DICTIONARY_OBJ(mods)); + + Dictionary magic = ARRAY_DICT_INIT; + PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); + PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); + PUT(result, "magic", DICTIONARY_OBJ(magic)); +end: + xfree(cmdline); + return result; +} -- cgit From 5576d30e89153c817fb1a8d23c30cfc0432bc7c6 Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Tue, 3 May 2022 11:06:27 +0200 Subject: refactor: replace char_u variables and functions with char Work on https://github.com/neovim/neovim/issues/459 --- src/nvim/api/vimscript.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 99d1406cfb..acd89119f9 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -827,7 +827,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) bool done = false; while (!done) { - done = uc_split_args_iter(ea.arg, length, &end, buf, &len); + done = uc_split_args_iter((char_u *)ea.arg, length, &end, buf, &len); if (len > 0) { ADD(args, STRING_OBJ(cstrn_to_string(buf, len))); } -- cgit From 7aedcd8febaf74403851f8482529302e3ab30922 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 5 May 2022 01:49:29 +0600 Subject: refactor(api): make `range` in `nvim_parse_cmd` an array Changes the `range` value in `nvim_parse_cmd` into an array to describe range information more concisely. Also makes `range` and `count` be mutually exclusive by making count `-1` when command takes a range instead of a count. Additionally corrects the behavior of `count` for built-in commands by making the default count `0`. --- src/nvim/api/vimscript.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index acd89119f9..698b2d06fb 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -747,13 +747,12 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E /// @param[out] err Error details, if any. /// @return Dictionary containing command information, with these keys: /// - cmd: (string) Command name. -/// - range: (number) Number of items in the command ||. Can be 0, 1 or 2. -/// - line1: (number) Starting line of command ||. -1 if command cannot take a range. -/// || -/// - line2: (number) Final line of command ||. -1 if command cannot take a range. -/// || +/// - range: (array) Command . Can have 0-2 elements depending on how many items the +/// range contains. Has no elements if command doesn't accept a range or if +/// no range was specified, one element if only a single range item was +/// specified and two elements if both range items were specified. /// - count: (number) Any || that was supplied to the command. -1 if command cannot -/// take a count. +/// take a count. Mutually exclusive with "range". /// - reg: (number) The optional command ||, if specified. Empty string if not /// specified or if command cannot take a register. /// - bang: (boolean) Whether command contains a || (!) modifier. @@ -849,15 +848,24 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); } - PUT(result, "range", INTEGER_OBJ(ea.addr_count)); - PUT(result, "line1", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line1 : -1)); - PUT(result, "line2", INTEGER_OBJ((ea.argt & EX_RANGE) ? ea.line2 : -1)); + if ((ea.argt & EX_RANGE) && !(ea.argt & EX_COUNT) && ea.addr_count > 0) { + Array range = ARRAY_DICT_INIT; + if (ea.addr_count > 1) { + ADD(range, INTEGER_OBJ(ea.line1)); + } + ADD(range, INTEGER_OBJ(ea.line2)); + PUT(result, "range", ARRAY_OBJ(range));; + } else { + PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); + } if (ea.argt & EX_COUNT) { - if (ea.addr_count > 0 || cmd == NULL) { + if (ea.addr_count > 0) { PUT(result, "count", INTEGER_OBJ(ea.line2)); - } else { + } else if (cmd != NULL) { PUT(result, "count", INTEGER_OBJ(cmd->uc_def)); + } else { + PUT(result, "count", INTEGER_OBJ(0)); } } else { PUT(result, "count", INTEGER_OBJ(-1)); -- cgit From 511f06a56e77f612f7bc8ca563ebecb7239a9914 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 5 May 2022 23:11:57 +0600 Subject: fix(api): make `nvim_parse_cmd` propagate errors Makes `nvim_parse_cmd` propagate any errors that occur while parsing to give the user a better idea of what's wrong with the command. --- src/nvim/api/vimscript.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 698b2d06fb..3346b5a237 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -802,10 +802,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) // Parse command line exarg_T ea; CmdParseInfo cmdinfo; - char_u *cmdline = (char_u *)string_to_cstr(str); + char *cmdline = string_to_cstr(str); + char *errormsg = NULL; - if (!parse_cmdline(cmdline, &ea, &cmdinfo)) { - api_set_error(err, kErrorTypeException, "Error while parsing command line"); + if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { + if (errormsg != NULL) { + api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg); + } else { + api_set_error(err, kErrorTypeException, "Error while parsing command line"); + } goto end; } -- cgit From 544ef994df72c3cbe0dca6b856ce2dcbc5169767 Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Fri, 6 May 2022 00:46:30 +0200 Subject: refactor: upgrade uncrustify configuration to version 0.75 --- src/nvim/api/vimscript.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 698b2d06fb..f5fdabdc0c 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -854,7 +854,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) ADD(range, INTEGER_OBJ(ea.line1)); } ADD(range, INTEGER_OBJ(ea.line2)); - PUT(result, "range", ARRAY_OBJ(range));; + PUT(result, "range", ARRAY_OBJ(range)); } else { PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); } -- cgit From 14f3383c0da1413a5ae82feb19ac89f01d4b9aad Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 7 May 2022 08:57:21 +0600 Subject: fix(api): make `nvim_parse_cmd` work correctly with both range and count It seems range and count can be used together in commands. This PR fixes the behavior of `nvim_parse_cmd` for those cases by removing the mutual exclusivity of "range" and "count". It also removes range line number validation for `nvim_parse_cmd` as it's not its job to validate the command. --- src/nvim/api/vimscript.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index d3675a3c40..9396435466 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -752,7 +752,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E /// no range was specified, one element if only a single range item was /// specified and two elements if both range items were specified. /// - count: (number) Any || that was supplied to the command. -1 if command cannot -/// take a count. Mutually exclusive with "range". +/// take a count. /// - reg: (number) The optional command ||, if specified. Empty string if not /// specified or if command cannot take a register. /// - bang: (boolean) Whether command contains a || (!) modifier. @@ -853,7 +853,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); } - if ((ea.argt & EX_RANGE) && !(ea.argt & EX_COUNT) && ea.addr_count > 0) { + if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { Array range = ARRAY_DICT_INIT; if (ea.addr_count > 1) { ADD(range, INTEGER_OBJ(ea.line1)); -- cgit From e31b32a293f6ba8708499a176234f8be1df6a145 Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Thu, 5 May 2022 13:36:14 +0200 Subject: refactor: replace char_u variables and functions with char Work on https://github.com/neovim/neovim/issues/459 --- src/nvim/api/vimscript.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 9396435466..c4a301839f 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -197,7 +197,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) funcexe.selfdict = self; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (see above) to capture abort-causing non-exception errors. - (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, vim_args, &funcexe); if (!try_end(err)) { rv = vim_to_object(&rettv); @@ -290,7 +290,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) goto end; } fn = (String) { - .data = (char *)di->di_tv.vval.v_string, + .data = di->di_tv.vval.v_string, .size = STRLEN(di->di_tv.vval.v_string), }; } @@ -831,7 +831,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) bool done = false; while (!done) { - done = uc_split_args_iter((char_u *)ea.arg, length, &end, buf, &len); + done = uc_split_args_iter(ea.arg, length, &end, buf, &len); if (len > 0) { ADD(args, STRING_OBJ(cstrn_to_string(buf, len))); } -- cgit From dfcc58466505f7fb5f62d636a67facaeea143285 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sun, 8 May 2022 17:39:45 +0600 Subject: feat(api): add `nvim_cmd` Adds the API function `nvim_cmd` which allows executing an Ex-command through a Dictionary which can have the same values as the return value of `nvim_parse_cmd()`. This makes it much easier to do things like passing arguments with a space to commands that otherwise may not allow it, or to make commands interpret certain characters literally when they otherwise would not. --- src/nvim/api/vimscript.c | 385 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 381 insertions(+), 4 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index c4a301839f..02f406d686 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -10,10 +10,14 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vimscript.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" +#include "nvim/ops.h" +#include "nvim/strings.h" +#include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" #include "nvim/window.h" @@ -22,7 +26,9 @@ # include "api/vimscript.c.generated.h" #endif -/// Executes Vimscript (multiline block of Ex-commands), like anonymous +#define IS_USER_CMDIDX(idx) ((int)(idx) < 0) + +/// Executes Vimscript (multiline block of Ex commands), like anonymous /// |:source|. /// /// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), @@ -32,6 +38,7 @@ /// /// @see |execute()| /// @see |nvim_command()| +/// @see |nvim_cmd()| /// /// @param src Vimscript code /// @param output Capture and return all (non-error, non-shell |:!|) output @@ -89,13 +96,16 @@ theend: return (String)STRING_INIT; } -/// Executes an ex-command. +/// Executes an Ex command. /// /// On execution error: fails with VimL error, does not update v:errmsg. /// -/// @see |nvim_exec()| +/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script +/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured +/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use +/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|. /// -/// @param command Ex-command string +/// @param command Ex command string /// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) @@ -978,3 +988,370 @@ end: xfree(cmdline); return result; } + +/// Executes an Ex command. +/// +/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This +/// allows for easier construction and manipulation of an Ex command. This also allows for things +/// such as having spaces inside a command argument, expanding filenames in a command that otherwise +/// doesn't expand filenames, etc. +/// +/// @see |nvim_exec()| +/// @see |nvim_command()| +/// +/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as +/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd" +/// which are ignored if provided. All values except for "cmd" are optional. +/// @param opts Optional parameters. +/// - output: (boolean, default false) Whether to return command output. +/// @param[out] err Error details, if any. +/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string. +String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err) + FUNC_API_SINCE(10) +{ + exarg_T ea; + memset(&ea, 0, sizeof(ea)); + ea.verbose_save = -1; + ea.save_msg_silent = -1; + + CmdParseInfo cmdinfo; + memset(&cmdinfo, 0, sizeof(cmdinfo)); + cmdinfo.verbose = -1; + + char *cmdline = NULL; + char *cmdname = NULL; + char **args = NULL; + size_t argc = 0; + + String retv = (String)STRING_INIT; + +#define OBJ_TO_BOOL(var, value, default, varname) \ + do { \ + var = api_object_to_bool(value, varname, default, err); \ + if (ERROR_SET(err)) { \ + goto end; \ + } \ + } while (0) + +#define VALIDATION_ERROR(...) \ + do { \ + api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \ + goto end; \ + } while (0) + + bool output; + OBJ_TO_BOOL(output, opts->output, false, "'output'"); + + // First, parse the command name and check if it exists and is valid. + if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString + || cmd->cmd.data.string.data[0] == NUL) { + VALIDATION_ERROR("'cmd' must be a non-empty String"); + } + + cmdname = string_to_cstr(cmd->cmd.data.string); + ea.cmd = cmdname; + + char *p = find_ex_command(&ea, NULL); + + // If this looks like an undefined user command and there are CmdUndefined + // autocommands defined, trigger the matching autocommands. + if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd) + && has_event(EVENT_CMDUNDEFINED)) { + p = xstrdup(cmdname); + int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL); + xfree(p); + // If the autocommands did something and didn't cause an error, try + // finding the command again. + p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; + } + + if (p == NULL || ea.cmdidx == CMD_SIZE) { + VALIDATION_ERROR("Command not found: %s", cmdname); + } + if (is_cmd_ni(ea.cmdidx)) { + VALIDATION_ERROR("Command not implemented: %s", cmdname); + } + + // Get the command flags so that we can know what type of arguments the command uses. + // Not required for a user command since `find_ex_command` already deals with it in that case. + if (!IS_USER_CMDIDX(ea.cmdidx)) { + ea.argt = get_cmd_argt(ea.cmdidx); + } + + // Parse command arguments since it's needed to get the command address type. + if (HAS_KEY(cmd->args)) { + if (cmd->args.type != kObjectTypeArray) { + VALIDATION_ERROR("'args' must be an Array"); + } + // Check if every argument is valid + for (size_t i = 0; i < cmd->args.data.array.size; i++) { + Object elem = cmd->args.data.array.items[i]; + if (elem.type != kObjectTypeString) { + VALIDATION_ERROR("Command argument must be a String"); + } else if (string_iswhite(elem.data.string)) { + VALIDATION_ERROR("Command argument must have non-whitespace characters"); + } + } + + argc = cmd->args.data.array.size; + bool argc_valid; + + // Check if correct number of arguments is used. + switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { + case EX_EXTRA | EX_NOSPC | EX_NEEDARG: + argc_valid = argc == 1; + break; + case EX_EXTRA | EX_NOSPC: + argc_valid = argc <= 1; + break; + case EX_EXTRA | EX_NEEDARG: + argc_valid = argc >= 1; + break; + case EX_EXTRA: + argc_valid = true; + break; + default: + argc_valid = argc == 0; + break; + } + + if (!argc_valid) { + argc = 0; // Ensure that args array isn't erroneously freed at the end. + VALIDATION_ERROR("Incorrect number of arguments supplied"); + } + + if (argc != 0) { + args = xcalloc(argc, sizeof(char *)); + + for (size_t i = 0; i < argc; i++) { + args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string); + } + } + } + + // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` + // since it only ever checks the first argument. + set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL); + + if (HAS_KEY(cmd->range)) { + if (!(ea.argt & EX_RANGE)) { + VALIDATION_ERROR("Command cannot accept a range"); + } else if (cmd->range.type != kObjectTypeArray) { + VALIDATION_ERROR("'range' must be an Array"); + } else if (cmd->range.data.array.size > 2) { + VALIDATION_ERROR("'range' cannot contain more than two elements"); + } + + Array range = cmd->range.data.array; + ea.addr_count = (int)range.size; + + for (size_t i = 0; i < range.size; i++) { + Object elem = range.items[i]; + if (elem.type != kObjectTypeInteger || elem.data.integer < 0) { + VALIDATION_ERROR("'range' element must be a non-negative Integer"); + } + } + + if (range.size > 0) { + ea.line1 = range.items[0].data.integer; + ea.line2 = range.items[range.size - 1].data.integer; + } + + if (invalid_range(&ea) != NULL) { + VALIDATION_ERROR("Invalid range provided"); + } + } + if (ea.addr_count == 0) { + if (ea.argt & EX_DFLALL) { + set_cmd_dflall_range(&ea); // Default range for range=% + } else { + ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range. + + if (ea.addr_type == ADDR_OTHER) { + // Default is 1, not cursor. + ea.line2 = 1; + } + } + } + + if (HAS_KEY(cmd->count)) { + if (!(ea.argt & EX_COUNT)) { + VALIDATION_ERROR("Command cannot accept a count"); + } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) { + VALIDATION_ERROR("'count' must be a non-negative Integer"); + } + set_cmd_count(&ea, cmd->count.data.integer, true); + } + + if (HAS_KEY(cmd->reg)) { + if (!(ea.argt & EX_REGSTR)) { + VALIDATION_ERROR("Command cannot accept a register"); + } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) { + VALIDATION_ERROR("'reg' must be a single character"); + } + char regname = cmd->reg.data.string.data[0]; + if (regname == '=') { + VALIDATION_ERROR("Cannot use register \"="); + } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) { + VALIDATION_ERROR("Invalid register: \"%c", regname); + } + ea.regname = (uint8_t)regname; + } + + OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'"); + if (ea.forceit && !(ea.argt & EX_BANG)) { + VALIDATION_ERROR("Command cannot accept a bang"); + } + + if (HAS_KEY(cmd->magic)) { + if (cmd->magic.type != kObjectTypeDictionary) { + VALIDATION_ERROR("'magic' must be a Dictionary"); + } + + Dict(cmd_magic) magic = { 0 }; + if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field, + cmd->magic.data.dictionary, err)) { + goto end; + } + + OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'"); + OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'"); + } else { + cmdinfo.magic.file = ea.argt & EX_XFILE; + cmdinfo.magic.bar = ea.argt & EX_TRLBAR; + } + + if (HAS_KEY(cmd->mods)) { + if (cmd->mods.type != kObjectTypeDictionary) { + VALIDATION_ERROR("'mods' must be a Dictionary"); + } + + Dict(cmd_mods) mods = { 0 }; + if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) { + goto end; + } + + 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"); + } + cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1; + } + + if (HAS_KEY(mods.verbose)) { + if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) { + VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer"); + } + cmdinfo.verbose = mods.verbose.data.integer; + } + + bool vertical; + OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); + cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0); + + if (HAS_KEY(mods.split)) { + if (mods.split.type != kObjectTypeString) { + VALIDATION_ERROR("'mods.split' must be a String"); + } + + if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 + || STRCMP(mods.split.data.string.data, "leftabove") == 0) { + cmdinfo.cmdmod.split |= WSP_ABOVE; + } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 + || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { + cmdinfo.cmdmod.split |= WSP_BELOW; + } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { + cmdinfo.cmdmod.split |= WSP_TOP; + } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { + cmdinfo.cmdmod.split |= WSP_BOT; + } else { + VALIDATION_ERROR("Invalid value for 'mods.split'"); + } + } + + OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'"); + OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'"); + OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'"); + OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'"); + OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'"); + + if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) { + VALIDATION_ERROR("Command cannot be run in sandbox"); + } + } + + // Finally, build the command line string that will be stored inside ea.cmdlinep. + // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. + if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) { + goto end; + } + ea.cmdlinep = &cmdline; + + garray_T capture_local; + const int save_msg_silent = msg_silent; + garray_T * const save_capture_ga = capture_ga; + + if (output) { + ga_init(&capture_local, 1, 80); + capture_ga = &capture_local; + } + + try_start(); + if (output) { + msg_silent++; + } + + WITH_SCRIPT_CONTEXT(channel_id, { + execute_cmd(&ea, &cmdinfo); + }); + + if (output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + } + try_end(err); + + if (ERROR_SET(err)) { + goto clear_ga; + } + + if (output && capture_local.ga_len > 1) { + retv = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (retv.data[0] == '\n') { + memmove(retv.data, retv.data + 1, retv.size - 1); + retv.data[retv.size - 1] = '\0'; + retv.size = retv.size - 1; + } + goto end; + } +clear_ga: + if (output) { + ga_clear(&capture_local); + } +end: + xfree(cmdline); + xfree(cmdname); + xfree(ea.args); + xfree(ea.arglens); + for (size_t i = 0; i < argc; i++) { + xfree(args[i]); + } + xfree(args); + + return retv; + +#undef OBJ_TO_BOOL +#undef VALIDATION_ERROR +} -- cgit From 566f8f80d6cb7ef2df8366e5f092b0841ee757ce Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 12 May 2022 10:57:43 +0200 Subject: refactor(api/nvim_cmd): use `kvec_t` for constructing cmdline string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Björn Linse --- src/nvim/api/vimscript.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 02f406d686..506b3e7f10 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1290,9 +1290,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error // Finally, build the command line string that will be stored inside ea.cmdlinep. // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. - if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) { - goto end; - } + build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc); ea.cmdlinep = &cmdline; garray_T capture_local; -- cgit From f0148de7907ec297647816d51c79745be729439e Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Mon, 9 May 2022 11:49:32 +0200 Subject: refactor: replace char_u variables and functions with char Work on https://github.com/neovim/neovim/issues/459 --- src/nvim/api/vimscript.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 506b3e7f10..42101af7f0 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1058,7 +1058,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd) && has_event(EVENT_CMDUNDEFINED)) { p = xstrdup(cmdname); - int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL); + int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); xfree(p); // If the autocommands did something and didn't cause an error, try // finding the command again. -- cgit From 54b5222fbbaed6d283c6160f5d18713718a16eb2 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Tue, 17 May 2022 18:27:33 +0600 Subject: docs(api): update v:errmsg behavior #18593 --- src/nvim/api/vimscript.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 42101af7f0..b8f7b33cd5 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -34,7 +34,7 @@ /// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), /// etc. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @see |execute()| /// @see |nvim_command()| @@ -98,7 +98,7 @@ theend: /// Executes an Ex command. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script /// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured @@ -118,7 +118,7 @@ void nvim_command(String command, Error *err) /// Evaluates a VimL |expression|. /// Dictionaries and Lists are recursively expanded. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param expr VimL expression string /// @param[out] err Error details, if any @@ -226,7 +226,7 @@ free_vim_args: /// Calls a VimL function with the given arguments. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param fn Function to call /// @param args Function arguments packed in an Array @@ -240,7 +240,7 @@ Object nvim_call_function(String fn, Array args, Error *err) /// Calls a VimL |Dictionary-function| with the given arguments. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param dict Dictionary, or String evaluating to a VimL |self| dict /// @param fn Name of the function defined on the VimL dict @@ -996,6 +996,8 @@ end: /// such as having spaces inside a command argument, expanding filenames in a command that otherwise /// doesn't expand filenames, etc. /// +/// On execution error: fails with VimL error, updates v:errmsg. +/// /// @see |nvim_exec()| /// @see |nvim_command()| /// -- cgit From fb8fa004d8c91b7b591509539a228e97ebc57d9d Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Thu, 19 May 2022 21:49:12 +0600 Subject: fix: make `nvim_cmd` not suppress errors inside key mapping Closes #18632 --- src/nvim/api/vimscript.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index b8f7b33cd5..e71f1a11ec 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1304,20 +1304,23 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error capture_ga = &capture_local; } - try_start(); - if (output) { - msg_silent++; - } + TRY_WRAP({ + try_start(); + if (output) { + msg_silent++; + } - WITH_SCRIPT_CONTEXT(channel_id, { - execute_cmd(&ea, &cmdinfo); - }); + WITH_SCRIPT_CONTEXT(channel_id, { + execute_cmd(&ea, &cmdinfo); + }); - if (output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - } - try_end(err); + if (output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + } + + try_end(err); + }); if (ERROR_SET(err)) { goto clear_ga; -- cgit From 46536f53e82967dcac8d030ee3394cdb156f9603 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Wed, 20 Apr 2022 17:02:18 +0600 Subject: feat: add preview functionality to user commands Adds a Lua-only `preview` flag to user commands which allows the command to be incrementally previewed like `:substitute` when 'inccommand' is set. --- src/nvim/api/vimscript.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index e71f1a11ec..99ab247c2a 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1311,7 +1311,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } WITH_SCRIPT_CONTEXT(channel_id, { - execute_cmd(&ea, &cmdinfo); + execute_cmd(&ea, &cmdinfo, false); }); if (output) { -- cgit From c84bd9e21fb1e5c55c9c5370b07271a6ae96f19c Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Wed, 8 Jun 2022 09:06:36 +0600 Subject: fix(nvim_create_user_command): make `smods` work with `nvim_cmd` Closes #18876. --- src/nvim/api/vimscript.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 99ab247c2a..4b4404ea09 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1241,10 +1241,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } if (HAS_KEY(mods.verbose)) { - if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) { - VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer"); + if (mods.verbose.type != kObjectTypeInteger) { + VALIDATION_ERROR("'mods.verbose' must be a Integer"); + } else if (mods.verbose.data.integer >= 0) { + // Silently ignore negative integers to allow mods.verbose to be set to -1. + cmdinfo.verbose = mods.verbose.data.integer; } - cmdinfo.verbose = mods.verbose.data.integer; } bool vertical; @@ -1256,8 +1258,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error VALIDATION_ERROR("'mods.split' must be a String"); } - if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 - || STRCMP(mods.split.data.string.data, "leftabove") == 0) { + if (*mods.split.data.string.data == NUL) { + // Empty string, do nothing. + } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 + || STRCMP(mods.split.data.string.data, "leftabove") == 0) { cmdinfo.cmdmod.split |= WSP_ABOVE; } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { -- cgit From a732c253b71f89702285d5ec6fd7803045f6add9 Mon Sep 17 00:00:00 2001 From: Dundar Goc Date: Sat, 7 May 2022 12:53:37 +0200 Subject: refactor: change type of linenr_T from long to int32_t The size of long varies depending on architecture, in contrast to the MAXLNUM constant which sets the maximum allowable number of lines to 2^32-1. This discrepancy may lead to hard to detect bugs, for example https://github.com/neovim/neovim/issues/18454. Setting linenr_T to a fix maximum size of 2^32-1 will prevent this type of errors in the future. Also change the variables `amount` and `amount_after` to be linenr_T since they're referring to "the line number difference" between two texts. --- src/nvim/api/vimscript.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 4b4404ea09..6d0b9c7d99 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -1155,8 +1155,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } if (range.size > 0) { - ea.line1 = range.items[0].data.integer; - ea.line2 = range.items[range.size - 1].data.integer; + ea.line1 = (linenr_T)range.items[0].data.integer; + ea.line2 = (linenr_T)range.items[range.size - 1].data.integer; } if (invalid_range(&ea) != NULL) { -- cgit From 0d63fafcda3847a6ec8a9da42db7bf10ac917d14 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 12 Jun 2022 16:38:31 +0200 Subject: refactor(api): move command related API to separate file --- src/nvim/api/vimscript.c | 617 ----------------------------------------------- 1 file changed, 617 deletions(-) (limited to 'src/nvim/api/vimscript.c') diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 6d0b9c7d99..478e146781 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -26,8 +26,6 @@ # include "api/vimscript.c.generated.h" #endif -#define IS_USER_CMDIDX(idx) ((int)(idx) < 0) - /// Executes Vimscript (multiline block of Ex commands), like anonymous /// |:source|. /// @@ -747,618 +745,3 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E viml_parser_destroy(&pstate); return ret; } - -/// Parse command line. -/// -/// Doesn't check the validity of command arguments. -/// -/// @param str Command line string to parse. Cannot contain "\n". -/// @param opts Optional parameters. Reserved for future use. -/// @param[out] err Error details, if any. -/// @return Dictionary containing command information, with these keys: -/// - cmd: (string) Command name. -/// - range: (array) Command . Can have 0-2 elements depending on how many items the -/// range contains. Has no elements if command doesn't accept a range or if -/// no range was specified, one element if only a single range item was -/// specified and two elements if both range items were specified. -/// - count: (number) Any || that was supplied to the command. -1 if command cannot -/// take a count. -/// - reg: (number) The optional command ||, if specified. Empty string if not -/// specified or if command cannot take a register. -/// - bang: (boolean) Whether command contains a || (!) modifier. -/// - args: (array) Command arguments. -/// - addr: (string) Value of |:command-addr|. Uses short name. -/// - nargs: (string) Value of |:command-nargs|. -/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. -/// Empty if there isn't a next command. -/// - magic: (dictionary) Which characters have special meaning in the command arguments. -/// - file: (boolean) The command expands filenames. Which means characters such as "%", -/// "#" and wildcards are expanded. -/// - 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|. -/// - silent: (boolean) |:silent|. -/// - emsg_silent: (boolean) |:silent!|. -/// - sandbox: (boolean) |:sandbox|. -/// - noautocmd: (boolean) |:noautocmd|. -/// - browse: (boolean) |:browse|. -/// - confirm: (boolean) |:confirm|. -/// - hide: (boolean) |:hide|. -/// - keepalt: (boolean) |:keepalt|. -/// - keepjumps: (boolean) |:keepjumps|. -/// - keepmarks: (boolean) |:keepmarks|. -/// - keeppatterns: (boolean) |:keeppatterns|. -/// - lockmarks: (boolean) |:lockmarks|. -/// - noswapfile: (boolean) |:noswapfile|. -/// - tab: (integer) |:tab|. -/// - verbose: (integer) |:verbose|. -1 when omitted. -/// - vertical: (boolean) |:vertical|. -/// - split: (string) Split modifier string, is an empty string when there's no split -/// modifier. If there is a split modifier it can be one of: -/// - "aboveleft": |:aboveleft|. -/// - "belowright": |:belowright|. -/// - "topleft": |:topleft|. -/// - "botright": |:botright|. -Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) - FUNC_API_SINCE(10) FUNC_API_FAST -{ - Dictionary result = ARRAY_DICT_INIT; - - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - return result; - } - - // Parse command line - exarg_T ea; - CmdParseInfo cmdinfo; - char *cmdline = string_to_cstr(str); - char *errormsg = NULL; - - if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { - if (errormsg != NULL) { - api_set_error(err, kErrorTypeException, "Error while parsing command line: %s", errormsg); - } else { - api_set_error(err, kErrorTypeException, "Error while parsing command line"); - } - goto end; - } - - // Parse arguments - Array args = ARRAY_DICT_INIT; - size_t length = STRLEN(ea.arg); - - // For nargs = 1 or '?', pass the entire argument list as a single argument, - // otherwise split arguments by whitespace. - if (ea.argt & EX_NOSPC) { - if (*ea.arg != NUL) { - ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length))); - } - } else { - size_t end = 0; - size_t len = 0; - char *buf = xcalloc(length, sizeof(char)); - bool done = false; - - while (!done) { - done = uc_split_args_iter(ea.arg, length, &end, buf, &len); - if (len > 0) { - ADD(args, STRING_OBJ(cstrn_to_string(buf, len))); - } - } - - xfree(buf); - } - - ucmd_T *cmd = NULL; - if (ea.cmdidx == CMD_USER) { - cmd = USER_CMD(ea.useridx); - } else if (ea.cmdidx == CMD_USER_BUF) { - cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx); - } - - if (cmd != NULL) { - PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name)); - } else { - PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); - } - - if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { - Array range = ARRAY_DICT_INIT; - if (ea.addr_count > 1) { - ADD(range, INTEGER_OBJ(ea.line1)); - } - ADD(range, INTEGER_OBJ(ea.line2)); - PUT(result, "range", ARRAY_OBJ(range)); - } else { - PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); - } - - if (ea.argt & EX_COUNT) { - if (ea.addr_count > 0) { - PUT(result, "count", INTEGER_OBJ(ea.line2)); - } else if (cmd != NULL) { - PUT(result, "count", INTEGER_OBJ(cmd->uc_def)); - } else { - PUT(result, "count", INTEGER_OBJ(0)); - } - } else { - PUT(result, "count", INTEGER_OBJ(-1)); - } - - char reg[2]; - reg[0] = (char)ea.regname; - reg[1] = '\0'; - PUT(result, "reg", CSTR_TO_OBJ(reg)); - - PUT(result, "bang", BOOLEAN_OBJ(ea.forceit)); - PUT(result, "args", ARRAY_OBJ(args)); - - char nargs[2]; - if (ea.argt & EX_EXTRA) { - if (ea.argt & EX_NOSPC) { - if (ea.argt & EX_NEEDARG) { - nargs[0] = '1'; - } else { - nargs[0] = '?'; - } - } else if (ea.argt & EX_NEEDARG) { - nargs[0] = '+'; - } else { - nargs[0] = '*'; - } - } else { - nargs[0] = '0'; - } - nargs[1] = '\0'; - PUT(result, "nargs", CSTR_TO_OBJ(nargs)); - - const char *addr; - switch (ea.addr_type) { - case ADDR_LINES: - addr = "line"; - break; - case ADDR_ARGUMENTS: - addr = "arg"; - break; - case ADDR_BUFFERS: - addr = "buf"; - break; - case ADDR_LOADED_BUFFERS: - addr = "load"; - break; - case ADDR_WINDOWS: - addr = "win"; - break; - case ADDR_TABS: - addr = "tab"; - break; - case ADDR_QUICKFIX: - addr = "qf"; - break; - case ADDR_NONE: - addr = "none"; - break; - default: - addr = "?"; - break; - } - PUT(result, "addr", CSTR_TO_OBJ(addr)); - PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd)); - - Dictionary mods = ARRAY_DICT_INIT; - PUT(mods, "silent", BOOLEAN_OBJ(cmdinfo.silent)); - PUT(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.emsg_silent)); - PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.sandbox)); - PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.noautocmd)); - PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.tab)); - PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose)); - PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.browse)); - PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.confirm)); - PUT(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.hide)); - PUT(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.keepalt)); - PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.keepjumps)); - PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.keepmarks)); - PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.keeppatterns)); - PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.lockmarks)); - PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.noswapfile)); - PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.split & WSP_VERT)); - - const char *split; - if (cmdinfo.cmdmod.split & WSP_BOT) { - split = "botright"; - } else if (cmdinfo.cmdmod.split & WSP_TOP) { - split = "topleft"; - } else if (cmdinfo.cmdmod.split & WSP_BELOW) { - split = "belowright"; - } else if (cmdinfo.cmdmod.split & WSP_ABOVE) { - split = "aboveleft"; - } else { - split = ""; - } - PUT(mods, "split", CSTR_TO_OBJ(split)); - - PUT(result, "mods", DICTIONARY_OBJ(mods)); - - Dictionary magic = ARRAY_DICT_INIT; - PUT(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); - PUT(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); - PUT(result, "magic", DICTIONARY_OBJ(magic)); -end: - xfree(cmdline); - return result; -} - -/// Executes an Ex command. -/// -/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This -/// allows for easier construction and manipulation of an Ex command. This also allows for things -/// such as having spaces inside a command argument, expanding filenames in a command that otherwise -/// doesn't expand filenames, etc. -/// -/// On execution error: fails with VimL error, updates v:errmsg. -/// -/// @see |nvim_exec()| -/// @see |nvim_command()| -/// -/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as -/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd" -/// which are ignored if provided. All values except for "cmd" are optional. -/// @param opts Optional parameters. -/// - output: (boolean, default false) Whether to return command output. -/// @param[out] err Error details, if any. -/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string. -String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err) - FUNC_API_SINCE(10) -{ - exarg_T ea; - memset(&ea, 0, sizeof(ea)); - ea.verbose_save = -1; - ea.save_msg_silent = -1; - - CmdParseInfo cmdinfo; - memset(&cmdinfo, 0, sizeof(cmdinfo)); - cmdinfo.verbose = -1; - - char *cmdline = NULL; - char *cmdname = NULL; - char **args = NULL; - size_t argc = 0; - - String retv = (String)STRING_INIT; - -#define OBJ_TO_BOOL(var, value, default, varname) \ - do { \ - var = api_object_to_bool(value, varname, default, err); \ - if (ERROR_SET(err)) { \ - goto end; \ - } \ - } while (0) - -#define VALIDATION_ERROR(...) \ - do { \ - api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \ - goto end; \ - } while (0) - - bool output; - OBJ_TO_BOOL(output, opts->output, false, "'output'"); - - // First, parse the command name and check if it exists and is valid. - if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString - || cmd->cmd.data.string.data[0] == NUL) { - VALIDATION_ERROR("'cmd' must be a non-empty String"); - } - - cmdname = string_to_cstr(cmd->cmd.data.string); - ea.cmd = cmdname; - - char *p = find_ex_command(&ea, NULL); - - // If this looks like an undefined user command and there are CmdUndefined - // autocommands defined, trigger the matching autocommands. - if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd) - && has_event(EVENT_CMDUNDEFINED)) { - p = xstrdup(cmdname); - int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); - xfree(p); - // If the autocommands did something and didn't cause an error, try - // finding the command again. - p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; - } - - if (p == NULL || ea.cmdidx == CMD_SIZE) { - VALIDATION_ERROR("Command not found: %s", cmdname); - } - if (is_cmd_ni(ea.cmdidx)) { - VALIDATION_ERROR("Command not implemented: %s", cmdname); - } - - // Get the command flags so that we can know what type of arguments the command uses. - // Not required for a user command since `find_ex_command` already deals with it in that case. - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = get_cmd_argt(ea.cmdidx); - } - - // Parse command arguments since it's needed to get the command address type. - if (HAS_KEY(cmd->args)) { - if (cmd->args.type != kObjectTypeArray) { - VALIDATION_ERROR("'args' must be an Array"); - } - // Check if every argument is valid - for (size_t i = 0; i < cmd->args.data.array.size; i++) { - Object elem = cmd->args.data.array.items[i]; - if (elem.type != kObjectTypeString) { - VALIDATION_ERROR("Command argument must be a String"); - } else if (string_iswhite(elem.data.string)) { - VALIDATION_ERROR("Command argument must have non-whitespace characters"); - } - } - - argc = cmd->args.data.array.size; - bool argc_valid; - - // Check if correct number of arguments is used. - switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { - case EX_EXTRA | EX_NOSPC | EX_NEEDARG: - argc_valid = argc == 1; - break; - case EX_EXTRA | EX_NOSPC: - argc_valid = argc <= 1; - break; - case EX_EXTRA | EX_NEEDARG: - argc_valid = argc >= 1; - break; - case EX_EXTRA: - argc_valid = true; - break; - default: - argc_valid = argc == 0; - break; - } - - if (!argc_valid) { - argc = 0; // Ensure that args array isn't erroneously freed at the end. - VALIDATION_ERROR("Incorrect number of arguments supplied"); - } - - if (argc != 0) { - args = xcalloc(argc, sizeof(char *)); - - for (size_t i = 0; i < argc; i++) { - args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string); - } - } - } - - // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` - // since it only ever checks the first argument. - set_cmd_addr_type(&ea, argc > 0 ? (char_u *)args[0] : NULL); - - if (HAS_KEY(cmd->range)) { - if (!(ea.argt & EX_RANGE)) { - VALIDATION_ERROR("Command cannot accept a range"); - } else if (cmd->range.type != kObjectTypeArray) { - VALIDATION_ERROR("'range' must be an Array"); - } else if (cmd->range.data.array.size > 2) { - VALIDATION_ERROR("'range' cannot contain more than two elements"); - } - - Array range = cmd->range.data.array; - ea.addr_count = (int)range.size; - - for (size_t i = 0; i < range.size; i++) { - Object elem = range.items[i]; - if (elem.type != kObjectTypeInteger || elem.data.integer < 0) { - VALIDATION_ERROR("'range' element must be a non-negative Integer"); - } - } - - if (range.size > 0) { - ea.line1 = (linenr_T)range.items[0].data.integer; - ea.line2 = (linenr_T)range.items[range.size - 1].data.integer; - } - - if (invalid_range(&ea) != NULL) { - VALIDATION_ERROR("Invalid range provided"); - } - } - if (ea.addr_count == 0) { - if (ea.argt & EX_DFLALL) { - set_cmd_dflall_range(&ea); // Default range for range=% - } else { - ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range. - - if (ea.addr_type == ADDR_OTHER) { - // Default is 1, not cursor. - ea.line2 = 1; - } - } - } - - if (HAS_KEY(cmd->count)) { - if (!(ea.argt & EX_COUNT)) { - VALIDATION_ERROR("Command cannot accept a count"); - } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) { - VALIDATION_ERROR("'count' must be a non-negative Integer"); - } - set_cmd_count(&ea, cmd->count.data.integer, true); - } - - if (HAS_KEY(cmd->reg)) { - if (!(ea.argt & EX_REGSTR)) { - VALIDATION_ERROR("Command cannot accept a register"); - } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) { - VALIDATION_ERROR("'reg' must be a single character"); - } - char regname = cmd->reg.data.string.data[0]; - if (regname == '=') { - VALIDATION_ERROR("Cannot use register \"="); - } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) { - VALIDATION_ERROR("Invalid register: \"%c", regname); - } - ea.regname = (uint8_t)regname; - } - - OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'"); - if (ea.forceit && !(ea.argt & EX_BANG)) { - VALIDATION_ERROR("Command cannot accept a bang"); - } - - if (HAS_KEY(cmd->magic)) { - if (cmd->magic.type != kObjectTypeDictionary) { - VALIDATION_ERROR("'magic' must be a Dictionary"); - } - - Dict(cmd_magic) magic = { 0 }; - if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field, - cmd->magic.data.dictionary, err)) { - goto end; - } - - OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'"); - OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'"); - } else { - cmdinfo.magic.file = ea.argt & EX_XFILE; - cmdinfo.magic.bar = ea.argt & EX_TRLBAR; - } - - if (HAS_KEY(cmd->mods)) { - if (cmd->mods.type != kObjectTypeDictionary) { - VALIDATION_ERROR("'mods' must be a Dictionary"); - } - - Dict(cmd_mods) mods = { 0 }; - if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) { - goto end; - } - - 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"); - } - cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1; - } - - if (HAS_KEY(mods.verbose)) { - if (mods.verbose.type != kObjectTypeInteger) { - VALIDATION_ERROR("'mods.verbose' must be a Integer"); - } else if (mods.verbose.data.integer >= 0) { - // Silently ignore negative integers to allow mods.verbose to be set to -1. - cmdinfo.verbose = mods.verbose.data.integer; - } - } - - bool vertical; - OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); - cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0); - - if (HAS_KEY(mods.split)) { - if (mods.split.type != kObjectTypeString) { - VALIDATION_ERROR("'mods.split' must be a String"); - } - - if (*mods.split.data.string.data == NUL) { - // Empty string, do nothing. - } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 - || STRCMP(mods.split.data.string.data, "leftabove") == 0) { - cmdinfo.cmdmod.split |= WSP_ABOVE; - } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 - || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { - cmdinfo.cmdmod.split |= WSP_BELOW; - } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { - cmdinfo.cmdmod.split |= WSP_TOP; - } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { - cmdinfo.cmdmod.split |= WSP_BOT; - } else { - VALIDATION_ERROR("Invalid value for 'mods.split'"); - } - } - - OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'"); - OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'"); - OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'"); - OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'"); - OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'"); - - if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) { - VALIDATION_ERROR("Command cannot be run in sandbox"); - } - } - - // Finally, build the command line string that will be stored inside ea.cmdlinep. - // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. - build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc); - ea.cmdlinep = &cmdline; - - garray_T capture_local; - const int save_msg_silent = msg_silent; - garray_T * const save_capture_ga = capture_ga; - - if (output) { - ga_init(&capture_local, 1, 80); - capture_ga = &capture_local; - } - - TRY_WRAP({ - try_start(); - if (output) { - msg_silent++; - } - - WITH_SCRIPT_CONTEXT(channel_id, { - execute_cmd(&ea, &cmdinfo, false); - }); - - if (output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - } - - try_end(err); - }); - - if (ERROR_SET(err)) { - goto clear_ga; - } - - if (output && capture_local.ga_len > 1) { - retv = (String){ - .data = capture_local.ga_data, - .size = (size_t)capture_local.ga_len, - }; - // redir usually (except :echon) prepends a newline. - if (retv.data[0] == '\n') { - memmove(retv.data, retv.data + 1, retv.size - 1); - retv.data[retv.size - 1] = '\0'; - retv.size = retv.size - 1; - } - goto end; - } -clear_ga: - if (output) { - ga_clear(&capture_local); - } -end: - xfree(cmdline); - xfree(cmdname); - xfree(ea.args); - xfree(ea.arglens); - for (size_t i = 0; i < argc; i++) { - xfree(args[i]); - } - xfree(args); - - return retv; - -#undef OBJ_TO_BOOL -#undef VALIDATION_ERROR -} -- cgit