diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/keysets.lua | 39 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 172 | ||||
-rw-r--r-- | src/nvim/api/vimscript.c | 385 | ||||
-rw-r--r-- | src/nvim/ex_cmds_defs.h | 3 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 461 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 3 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 11 |
8 files changed, 949 insertions, 126 deletions
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 5baffaf505..78f8ed3cca 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -155,5 +155,44 @@ return { create_augroup = { "clear"; }; + cmd = { + "cmd"; + "range"; + "count"; + "reg"; + "bang"; + "args"; + "magic"; + "mods"; + "nargs"; + "addr"; + "nextcmd"; + }; + cmd_magic = { + "file"; + "bar"; + }; + cmd_mods = { + "silent"; + "emsg_silent"; + "sandbox"; + "noautocmd"; + "browse"; + "confirm"; + "hide"; + "keepalt"; + "keepjumps"; + "keepmarks"; + "keeppatterns"; + "lockmarks"; + "noswapfile"; + "tab"; + "verbose"; + "vertical"; + "split"; + }; + cmd_opts = { + "output"; + }; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index a2ee4f91bc..725033a4c5 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -19,6 +19,7 @@ #include "nvim/decoration.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/getchar.h" @@ -1691,3 +1692,174 @@ int init_sign_text(char **sign_text, char *text) return OK; } + +/// Check if a string contains only whitespace characters. +bool string_iswhite(String str) +{ + for (size_t i = 0; i < str.size; i++) { + if (!ascii_iswhite(str.data[i])) { + // Found a non-whitespace character + return false; + } else if (str.data[i] == NUL) { + // Terminate at first occurence of a NUL character + break; + } + } + return true; +} + +// Add modifier string for command into the command line. Includes trailing whitespace if non-empty. +// @return OK or FAIL. +static int add_cmd_modifier_str(char *cmdline, size_t *pos, const size_t bufsize, + CmdParseInfo *cmdinfo) +{ +#define APPEND_MODIFIER(...) \ + do { \ + if (*pos < bufsize) { \ + *pos += (size_t)snprintf(cmdline + *pos, bufsize - *pos, __VA_ARGS__); \ + } \ + if (*pos < bufsize) { \ + cmdline[*pos] = ' '; \ + *pos += 1; \ + } else { \ + goto err; \ + } \ + } while (0) + +#define APPEND_MODIFIER_IF(cond, mod) \ + do { \ + if (cond) { \ + APPEND_MODIFIER(mod); \ + } \ + } while (0) + + if (cmdinfo->cmdmod.tab != 0) { + APPEND_MODIFIER("%dtab", cmdinfo->cmdmod.tab - 1); + } + if (cmdinfo->verbose != -1) { + APPEND_MODIFIER("%ldverbose", cmdinfo->verbose); + } + + switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { + case WSP_ABOVE: + APPEND_MODIFIER("aboveleft"); + break; + case WSP_BELOW: + APPEND_MODIFIER("belowright"); + break; + case WSP_TOP: + APPEND_MODIFIER("topleft"); + break; + case WSP_BOT: + APPEND_MODIFIER("botright"); + break; + default: + break; + } + + APPEND_MODIFIER_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical"); + + if (cmdinfo->emsg_silent) { + APPEND_MODIFIER("silent!"); + } else if (cmdinfo->silent) { + APPEND_MODIFIER("silent"); + } + + APPEND_MODIFIER_IF(cmdinfo->sandbox, "sandbox"); + APPEND_MODIFIER_IF(cmdinfo->noautocmd, "noautocmd"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.browse, "browse"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.confirm, "confirm"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.hide, "hide"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepalt, "keepalt"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepjumps, "keepjumps"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepmarks, "keepmarks"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.lockmarks, "lockmarks"); + APPEND_MODIFIER_IF(cmdinfo->cmdmod.noswapfile, "noswapfile"); + + return OK; +err: + return FAIL; + +#undef APPEND_MODIFIER +#undef APPEND_MODIFIER_IF +} + +/// Build cmdline string for command, used by `nvim_cmd()`. +/// +/// @return OK or FAIL. +int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args, + size_t argc) +{ + const size_t bufsize = IOSIZE; + size_t pos = 0; + char *cmdline = xcalloc(bufsize, sizeof(char)); + +#define CMDLINE_APPEND(...) \ + do { \ + if (pos < bufsize) { \ + pos += (size_t)snprintf(cmdline + pos, bufsize - pos, __VA_ARGS__); \ + } else { \ + goto err; \ + } \ + } while (0) + + // Command modifiers. + if (add_cmd_modifier_str(cmdline, &pos, bufsize, cmdinfo) == FAIL) { + goto err; + } + + // Command range / count. + if (eap->argt & EX_RANGE) { + if (eap->addr_count == 1) { + CMDLINE_APPEND("%ld", eap->line2); + } else if (eap->addr_count > 1) { + CMDLINE_APPEND("%ld,%ld", eap->line1, eap->line2); + eap->addr_count = 2; // Make sure address count is not greater than 2 + } + } + + // Keep the index of the position where command name starts, so eap->cmd can point to it. + size_t cmdname_idx = pos; + CMDLINE_APPEND("%s", eap->cmd); + eap->cmd = cmdline + cmdname_idx; + + // Command bang. + if (eap->argt & EX_BANG && eap->forceit) { + CMDLINE_APPEND("!"); + } + + // Command register. + if (eap->argt & EX_REGSTR && eap->regname) { + CMDLINE_APPEND(" %c", eap->regname); + } + + // Iterate through each argument and store the starting position and length of each argument in + // the cmdline string in `eap->args` and `eap->arglens`, respectively. + eap->args = xcalloc(argc, sizeof(char *)); + eap->arglens = xcalloc(argc, sizeof(size_t)); + for (size_t i = 0; i < argc; i++) { + eap->args[i] = cmdline + pos + 1; // add 1 to skip the leading space. + eap->arglens[i] = STRLEN(args[i]); + CMDLINE_APPEND(" %s", args[i]); + } + eap->argc = argc; + eap->arg = argc > 0 ? eap->args[0] : NULL; + + // Replace, :make and :grep with 'makeprg' and 'grepprg'. + char *p = replace_makeprg(eap, eap->arg, cmdlinep); + if (p != eap->arg) { + // If replace_makeprg modified the cmdline string, correct the argument pointers. + assert(argc == 1); + eap->arg = p; + eap->args[0] = p; + } + + *cmdlinep = cmdline; + return OK; +err: + xfree(cmdline); + return FAIL; + +#undef CMDLINE_APPEND +} 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 +} diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index e4c85fbc9b..396e6fa19f 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -175,6 +175,9 @@ enum { /// Arguments used for Ex commands. struct exarg { char *arg; ///< argument of the command + char **args; ///< starting position of command arguments + size_t *arglens; ///< length of command arguments + size_t argc; ///< number of command arguments char *nextcmd; ///< next command (NULL if none) char *cmd; ///< the name of the command (except for :make) char **cmdlinep; ///< pointer to pointer of allocated cmdline diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 316006befd..b27971581c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1218,7 +1218,7 @@ static char *skip_colon_white(const char *p, bool skipleadingwhite) /// Set the addr type for command /// /// @param p pointer to character after command name in cmdline -static void set_cmd_addr_type(exarg_T *eap, char_u *p) +void set_cmd_addr_type(exarg_T *eap, char_u *p) { // ea.addr_type for user commands is set by find_ucmd if (IS_USER_CMDIDX(eap->cmdidx)) { @@ -1239,8 +1239,48 @@ static void set_cmd_addr_type(exarg_T *eap, char_u *p) } } +/// Get default range number for command based on its address type +linenr_T get_cmd_default_range(exarg_T *eap) +{ + switch (eap->addr_type) { + case ADDR_LINES: + case ADDR_OTHER: + // Default is the cursor line number. Avoid using an invalid + // line number though. + return MIN(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count); + break; + case ADDR_WINDOWS: + return CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + return MIN(curwin->w_arg_idx + 1, ARGCOUNT); + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + return curbuf->b_fnum; + break; + case ADDR_TABS: + return CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + case ADDR_UNSIGNED: + return 1; + break; + case ADDR_QUICKFIX: + return (linenr_T)qf_get_cur_idx(eap); + break; + case ADDR_QUICKFIX_VALID: + return qf_get_cur_valid_idx(eap); + break; + default: + return 0; + // Will give an error later if a range is found. + break; + } +} + /// Set default command range for -range=% based on the addr type of the command -static void set_cmd_default_range(exarg_T *eap) +void set_cmd_dflall_range(exarg_T *eap) { buf_T *buf; @@ -1320,6 +1360,25 @@ static void parse_register(exarg_T *eap) } } +// Change line1 and line2 of Ex command to use count +void set_cmd_count(exarg_T *eap, long count, bool validate) +{ + if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3 + eap->line2 = count; + if (eap->addr_count == 0) { + eap->addr_count = 1; + } + } else { + eap->line1 = eap->line2; + eap->line2 += count - 1; + eap->addr_count++; + // Be vi compatible: no error message for out of range. + if (validate && eap->line2 > curbuf->b_ml.ml_line_count) { + eap->line2 = curbuf->b_ml.ml_line_count; + } + } +} + static int parse_count(exarg_T *eap, char **errormsg, bool validate) { // Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a @@ -1338,25 +1397,19 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate) } return FAIL; } - if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3 - eap->line2 = n; - if (eap->addr_count == 0) { - eap->addr_count = 1; - } - } else { - eap->line1 = eap->line2; - eap->line2 += n - 1; - eap->addr_count++; - // Be vi compatible: no error message for out of range. - if (validate && eap->line2 > curbuf->b_ml.ml_line_count) { - eap->line2 = curbuf->b_ml.ml_line_count; - } - } + set_cmd_count(eap, n, validate); } return OK; } +/// Check if command is not implemented +bool is_cmd_ni(cmdidx_T cmdidx) +{ + return !IS_USER_CMDIDX(cmdidx) && (cmdnames[cmdidx].cmd_func == ex_ni + || cmdnames[cmdidx].cmd_func == ex_script_ni); +} + /// Parse command line and return information about the first command. /// /// @param cmdline Command line string @@ -1394,14 +1447,17 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er if (eap->save_msg_silent != -1) { cmdinfo->silent = !!msg_silent; msg_silent = eap->save_msg_silent; + eap->save_msg_silent = -1; } if (eap->did_esilent) { cmdinfo->emsg_silent = true; emsg_silent--; + eap->did_esilent = false; } if (eap->did_sandbox) { cmdinfo->sandbox = true; sandbox--; + eap->did_sandbox = false; } if (cmdmod.save_ei != NULL) { cmdinfo->noautocmd = true; @@ -1411,6 +1467,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er if (eap->verbose_save != -1) { cmdinfo->verbose = p_verbose; p_verbose = eap->verbose_save; + eap->verbose_save = -1; } else { cmdinfo->verbose = -1; } @@ -1424,7 +1481,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er if (*eap->cmd == '*') { eap->cmd = skipwhite(eap->cmd + 1); } - p = find_command(eap, NULL); + p = find_ex_command(eap, NULL); // Set command address type and parse command range set_cmd_addr_type(eap, (char_u *)p); @@ -1494,7 +1551,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { - set_cmd_default_range(eap); + set_cmd_dflall_range(eap); } // Parse register and count @@ -1519,6 +1576,148 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er return true; } +/// Execute an Ex command using parsed command line information. +/// Does not do any validation of the Ex command arguments. +/// +/// @param eap Ex-command arguments +/// @param cmdinfo Command parse information +void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo) +{ +#define ERROR(msg) \ + do { \ + emsg(msg); \ + goto end; \ + } while (0) + + char *errormsg = NULL; + cmdmod_T save_cmdmod = cmdmod; + cmdmod = cmdinfo->cmdmod; + + // Apply command modifiers + if (cmdinfo->silent) { + eap->save_msg_silent = msg_silent; + msg_silent++; + } + if (cmdinfo->emsg_silent) { + eap->did_esilent = true; + emsg_silent++; + } + if (cmdinfo->sandbox) { + eap->did_sandbox = true; + sandbox++; + } + if (cmdinfo->noautocmd) { + cmdmod.save_ei = (char *)vim_strsave(p_ei); + set_string_option_direct("ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + } + if (cmdinfo->verbose != -1) { + eap->verbose_save = p_verbose; + p_verbose = cmdinfo->verbose; + } + + if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY) + // allow :put in terminals + && !(curbuf->terminal && eap->cmdidx == CMD_put)) { + ERROR(_(e_modifiable)); + } + if (text_locked() && !(eap->argt & EX_CMDWIN) + && !IS_USER_CMDIDX(eap->cmdidx)) { + ERROR(_(get_text_locked_msg())); + } + // Disallow editing another buffer when "curbuf->b_ro_locked" is set. + // Do allow ":checktime" (it is postponed). + // Do allow ":edit" (check for an argument later). + // Do allow ":file" with no arguments + if (!(eap->argt & EX_CMDWIN) + && eap->cmdidx != CMD_checktime + && eap->cmdidx != CMD_edit + && !(eap->cmdidx == CMD_file && *eap->arg == NUL) + && !IS_USER_CMDIDX(eap->cmdidx) + && curbuf_locked()) { + ERROR(_(e_cannot_edit_other_buf)); + } + + if (((eap->argt & EX_WHOLEFOLD) || eap->addr_count >= 2) && !global_busy + && eap->addr_type == ADDR_LINES) { + // Put the first line at the start of a closed fold, put the last line + // at the end of a closed fold. + (void)hasFolding(eap->line1, &eap->line1, NULL); + (void)hasFolding(eap->line2, NULL, &eap->line2); + } + + // If filename expansion is enabled, expand filenames + if (cmdinfo->magic.file) { + if (expand_filename(eap, (char_u **)eap->cmdlinep, &errormsg) == FAIL) { + ERROR(errormsg); + } + } + + // Accept buffer name. Cannot be used at the same time with a buffer + // number. Don't do this for a user command. + if ((eap->argt & EX_BUFNAME) && *eap->arg != NUL && eap->addr_count == 0 + && !IS_USER_CMDIDX(eap->cmdidx)) { + if (eap->args == NULL) { + // If argument positions are not specified, search the argument for the buffer name. + // :bdelete, :bwipeout and :bunload take several arguments, separated by spaces: + // find next space (skipping over escaped characters). + // The others take one argument: ignore trailing spaces. + char *p; + + if (eap->cmdidx == CMD_bdelete || eap->cmdidx == CMD_bwipeout + || eap->cmdidx == CMD_bunload) { + p = (char *)skiptowhite_esc((char_u *)eap->arg); + } else { + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && ascii_iswhite(p[-1])) { + p--; + } + } + eap->line2 = buflist_findpat((char_u *)eap->arg, (char_u *)p, (eap->argt & EX_BUFUNL) != 0, + false, false); + eap->addr_count = 1; + eap->arg = skipwhite(p); + } else { + // If argument positions are specified, just use the first argument + eap->line2 = buflist_findpat((char_u *)eap->args[0], + (char_u *)(eap->args[0] + eap->arglens[0]), + (eap->argt & EX_BUFUNL) != 0, false, false); + eap->addr_count = 1; + // Shift each argument by 1 + if (eap->args != NULL) { + for (size_t i = 0; i < eap->argc - 1; i++) { + eap->args[i] = eap->args[i + 1]; + } + // Make the last argument point to the NUL terminator at the end of string + eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; + eap->argc -= 1; + } + eap->arg = eap->args[0]; + } + if (eap->line2 < 0) { // failed + goto end; + } + } + + // Execute the command + if (IS_USER_CMDIDX(eap->cmdidx)) { + // Execute a user-defined command. + do_ucmd(eap); + } else { + // Call the function to execute the command. + eap->errmsg = NULL; + (cmdnames[eap->cmdidx].cmd_func)(eap); + if (eap->errmsg != NULL) { + ERROR(_(eap->errmsg)); + } + } +end: + // Undo command modifiers + undo_cmdmod(eap, msg_scroll); + cmdmod = save_cmdmod; + +#undef ERROR +} + /// Execute one Ex command. /// /// If 'sourcing' is TRUE, the command will be included in the error message. @@ -1606,7 +1805,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_command(&ea, NULL); + p = find_ex_command(&ea, NULL); // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES @@ -1732,7 +1931,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter xfree(p); // If the autocommands did something and didn't cause an error, try // finding the command again. - p = (ret && !aborting()) ? find_command(&ea, NULL) : ea.cmd; + p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; } if (p == NULL) { @@ -1759,10 +1958,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter } // set when Not Implemented - const int ni = !IS_USER_CMDIDX(ea.cmdidx) - && (cmdnames[ea.cmdidx].cmd_func == ex_ni - || cmdnames[ea.cmdidx].cmd_func == ex_script_ni); - + const int ni = is_cmd_ni(ea.cmdidx); // Forced commands. if (*p == '!' && ea.cmdidx != CMD_substitute @@ -1976,7 +2172,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter } if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) { - set_cmd_default_range(&ea); + set_cmd_dflall_range(&ea); } // Parse register and count @@ -2544,6 +2740,7 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) } + /// Parse the address range, if any, in "eap". /// May set the last search pattern, unless "silent" is true. /// @@ -2557,47 +2754,7 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent) // Repeat for all ',' or ';' separated addresses. for (;;) { eap->line1 = eap->line2; - switch (eap->addr_type) { - case ADDR_LINES: - case ADDR_OTHER: - // Default is the cursor line number. Avoid using an invalid - // line number though. - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - eap->line2 = curbuf->b_ml.ml_line_count; - } else { - eap->line2 = curwin->w_cursor.lnum; - } - break; - case ADDR_WINDOWS: - eap->line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - eap->line2 = curwin->w_arg_idx + 1; - if (eap->line2 > ARGCOUNT) { - eap->line2 = ARGCOUNT; - } - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - eap->line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - eap->line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - case ADDR_UNSIGNED: - eap->line2 = 1; - break; - case ADDR_QUICKFIX: - eap->line2 = (linenr_T)qf_get_cur_idx(eap); - break; - case ADDR_QUICKFIX_VALID: - eap->line2 = qf_get_cur_valid_idx(eap); - break; - case ADDR_NONE: - // Will give an error later if a range is found. - break; - } + eap->line2 = get_cmd_default_range(eap); eap->cmd = skipwhite(eap->cmd); lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent, eap->addr_count == 0, address_count++); @@ -2772,7 +2929,7 @@ static void append_command(char *cmd) /// "full" is set to TRUE if the whole command name matched. /// /// @return NULL for an ambiguous user command. -static char *find_command(exarg_T *eap, int *full) +char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { int len; @@ -2859,7 +3016,7 @@ static char *find_command(exarg_T *eap, int *full) for (; (int)eap->cmdidx < CMD_SIZE; eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, (char *)eap->cmd, + if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { @@ -3076,7 +3233,7 @@ int cmd_exists(const char *const name) ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name); ea.cmdidx = (cmdidx_T)0; int full = false; - p = find_command(&ea, &full); + p = find_ex_command(&ea, &full); if (p == NULL) { return 3; } @@ -3108,7 +3265,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; - char *p = find_command(&ea, NULL); + char *p = find_ex_command(&ea, NULL); if (p == NULL || ea.cmdidx == CMD_SIZE) { return; } @@ -3431,7 +3588,7 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) xp->xp_shell = TRUE; #endif // When still after the command name expand executables. - if ((char_u *)xp->xp_pattern == (char_u *)skipwhite(arg)) { + if (xp->xp_pattern == skipwhite(arg)) { xp->xp_context = EXPAND_SHELLCMD; } } @@ -4416,7 +4573,7 @@ static void ex_script_ni(exarg_T *eap) /// Check range in Ex command for validity. /// /// @return NULL when valid, error message when invalid. -static char *invalid_range(exarg_T *eap) +char *invalid_range(exarg_T *eap) { buf_T *buf; if (eap->line1 < 0 || eap->line2 < 0 || eap->line1 > eap->line2) { @@ -4535,7 +4692,7 @@ static char *skip_grep_pat(exarg_T *eap) /// For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option /// in the command line, so that things like % get expanded. -static char *replace_makeprg(exarg_T *eap, char *p, char **cmdlinep) +char *replace_makeprg(exarg_T *eap, char *p, char **cmdlinep) { char *new_cmdline; char *program; @@ -4773,7 +4930,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp) return OK; } -/// Replace part of the command line, keeping eap->cmd, eap->arg and +/// Replace part of the command line, keeping eap->cmd, eap->arg, eap->args and /// eap->nextcmd correct. /// "src" points to the part that is to be replaced, of length "srclen". /// "repl" is the replacement string. @@ -4792,6 +4949,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch i += STRLEN(eap->nextcmd); // add space for next command } char *new_cmdline = xmalloc(i); + size_t offset = (size_t)(src - *cmdlinep); /* * Copy the stuff before the expanded part. @@ -4799,7 +4957,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch * Copy what came after the expanded part. * Copy the next commands, if there are any. */ - i = (size_t)(src - *cmdlinep); // length of part before match + i = offset; // length of part before match memmove(new_cmdline, *cmdlinep, i); memmove(new_cmdline + i, repl, len); @@ -4814,6 +4972,19 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch } eap->cmd = new_cmdline + (eap->cmd - *cmdlinep); eap->arg = new_cmdline + (eap->arg - *cmdlinep); + + for (size_t j = 0; j < eap->argc; j++) { + if (offset >= (size_t)(eap->args[j] - *cmdlinep)) { + // If replaced text is after or in the same position as the argument, + // the argument's position relative to the beginning of the cmdline stays the same. + eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep); + } else { + // Otherwise, argument gets shifted alongside the replaced text. + // The amount of the shift is equal to the difference of the old and new string length. + eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep) + (len - srclen); + } + } + if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) { eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep); } @@ -6002,7 +6173,7 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, } /// split and quote args for <f-args> -static char *uc_split_args(char *arg, size_t *lenp) +static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) { char *buf; char *p; @@ -6010,61 +6181,107 @@ static char *uc_split_args(char *arg, size_t *lenp) int len; // Precalculate length - p = arg; len = 2; // Initial and final quotes + if (args == NULL) { + p = arg; - while (*p) { - if (p[0] == '\\' && p[1] == '\\') { - len += 2; - p += 2; - } else if (p[0] == '\\' && ascii_iswhite(p[1])) { - len += 1; - p += 2; - } else if (*p == '\\' || *p == '"') { - len += 2; - p += 1; - } else if (ascii_iswhite(*p)) { - p = skipwhite(p); - if (*p == NUL) { - break; + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + len += 2; + p += 2; + } else if (p[0] == '\\' && ascii_iswhite(p[1])) { + len += 1; + p += 2; + } else if (*p == '\\' || *p == '"') { + len += 2; + p += 1; + } else if (ascii_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) { + break; + } + len += 3; // "," + } else { + const int charlen = utfc_ptr2len(p); + + len += charlen; + p += charlen; } - len += 3; // "," - } else { - const int charlen = utfc_ptr2len(p); + } + } else { + for (size_t i = 0; i < argc; i++) { + p = args[i]; + const char *arg_end = args[i] + arglens[i]; + + while (p < arg_end) { + if (*p == '\\' || *p == '"') { + len += 2; + p += 1; + } else { + const int charlen = utfc_ptr2len(p); - len += charlen; - p += charlen; + len += charlen; + p += charlen; + } + } + + if (i != argc - 1) { + len += 3; // "," + } } } buf = xmalloc((size_t)len + 1); - p = arg; q = buf; *q++ = '"'; - while (*p) { - if (p[0] == '\\' && p[1] == '\\') { - *q++ = '\\'; - *q++ = '\\'; - p += 2; - } else if (p[0] == '\\' && ascii_iswhite(p[1])) { - *q++ = p[1]; - p += 2; - } else if (*p == '\\' || *p == '"') { - *q++ = '\\'; - *q++ = *p++; - } else if (ascii_iswhite(*p)) { - p = skipwhite(p); - if (*p == NUL) { - break; + + if (args == NULL) { + p = arg; + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + *q++ = '\\'; + *q++ = '\\'; + p += 2; + } else if (p[0] == '\\' && ascii_iswhite(p[1])) { + *q++ = p[1]; + p += 2; + } else if (*p == '\\' || *p == '"') { + *q++ = '\\'; + *q++ = *p++; + } else if (ascii_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) { + break; + } + *q++ = '"'; + *q++ = ','; + *q++ = '"'; + } else { + mb_copy_char((const char_u **)&p, (char_u **)&q); + } + } + } else { + for (size_t i = 0; i < argc; i++) { + p = args[i]; + const char *arg_end = args[i] + arglens[i]; + + while (p < arg_end) { + if (*p == '\\' || *p == '"') { + *q++ = '\\'; + *q++ = *p++; + } else { + mb_copy_char((const char_u **)&p, (char_u **)&q); + } + } + if (i != argc - 1) { + *q++ = '"'; + *q++ = ','; + *q++ = '"'; } - *q++ = '"'; - *q++ = ','; - *q++ = '"'; - } else { - mb_copy_char((const char_u **)&p, (char_u **)&q); } } + *q++ = '"'; *q = 0; @@ -6201,7 +6418,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar case 2: // Quote and split (<f-args>) // This is hard, so only do it once, and cache the result if (*split_buf == NULL) { - *split_buf = uc_split_args(eap->arg, split_len); + *split_buf = uc_split_args(eap->arg, eap->args, eap->arglens, eap->argc, split_len); } result = *split_len; @@ -9887,7 +10104,7 @@ bool cmd_can_preview(char *cmd) if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - char *end = find_command(&ea, NULL); + char *end = find_ex_command(&ea, NULL); switch (ea.cmdidx) { case CMD_substitute: @@ -10278,3 +10495,9 @@ void verify_command(char *cmd) msg("` `.:.`.,:iii;;;;;;;;iii;;;:` `.`` " " `nW"); } + +/// Get argt of command with id +uint32_t get_cmd_argt(cmdidx_T cmdidx) +{ + return cmdnames[(int)cmdidx].cmd_argt; +} diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 8792e9673a..d651c1f014 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -33,6 +33,7 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" @@ -2522,7 +2523,7 @@ char *get_text_locked_msg(void) bool curbuf_locked(void) { if (curbuf->b_ro_locked > 0) { - emsg(_("E788: Not allowed to edit another buffer now")); + emsg(_(e_cannot_edit_other_buf)); return true; } return allbuf_locked(); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 620af9444c..ecf358c4a9 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -997,6 +997,7 @@ EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String")); +EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); EXTERN char e_autocmd_err[] INIT(= N_("E5500: autocmd has thrown an exception: %s")); EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>")); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 1da868c137..7d08481a7b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1863,8 +1863,8 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) if (cmd->uc_argt & EX_NOSPC) { // Commands where nargs = 1 or "?" fargs is the same as args lua_rawseti(lstate, -2, 1); - } else { - // Commands with more than one possible argument we split + } else if (eap->args == NULL) { + // For commands with more than one possible argument, split if argument list isn't available. lua_pop(lstate, 1); // Pop the reference of opts.args size_t length = STRLEN(eap->arg); size_t end = 0; @@ -1881,6 +1881,13 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) } } xfree(buf); + } else { + // If argument list is available, just use it. + lua_pop(lstate, 1); + for (size_t i = 0; i < eap->argc; i++) { + lua_pushlstring(lstate, eap->args[i], eap->arglens[i]); + lua_rawseti(lstate, -2, (int)i + 1); + } } lua_setfield(lstate, -2, "fargs"); |