diff options
-rw-r--r-- | runtime/doc/api.txt | 133 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 200 | ||||
-rw-r--r-- | src/nvim/api/vimscript.c | 229 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 122 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 122 |
5 files changed, 483 insertions, 323 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 7fc94ff76b..8e2bc1f036 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1361,69 +1361,6 @@ nvim_out_write({str}) *nvim_out_write()* Parameters: ~ {str} Message -nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* - Parse command line. - - Doesn't check the validity of command arguments. - - Attributes: ~ - |api-fast| - - Parameters: ~ - {str} Command line string to parse. Cannot contain "\n". - {opts} Optional parameters. Reserved for future use. - - Return: ~ - Dictionary containing command information, with these - keys: - • cmd: (string) Command name. - • line1: (number) Starting line of command range. Only - applicable if command can take a range. - • line2: (number) Final line of command range. Only - applicable if command can take a range. - • bang: (boolean) Whether command contains a bang (!) - 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|. - • 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|. - nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Pastes at cursor, in any mode. @@ -1822,6 +1759,76 @@ nvim_exec({src}, {output}) *nvim_exec()* |execute()| |nvim_command()| +nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()* + Parse command line. + + Doesn't check the validity of command arguments. + + Attributes: ~ + |api-fast| + + Parameters: ~ + {str} Command line string to parse. Cannot contain "\n". + {opts} Optional parameters. Reserved for future use. + + Return: ~ + Dictionary containing command information, with these + keys: + • cmd: (string) Command name. + • range: (number) Number of items in the command + |<range>|. Can be 0, 1 or 2. + • line1: (number) Starting line of command |<range>|. -1 + if command cannot take a range. |<line1>| + • line2: (number) Final line of command |<range>|. -1 if + command cannot take a range. |<line2>| + • count: (number) Any |<count>| that was supplied to the + command. -1 if command cannot take a count. + • reg: (number) The optional command |<register>|, if + specified. Empty string if not specified or if command + cannot take a register. + • bang: (boolean) Whether command contains a |<bang>| (!) + 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|. + • 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|. + *nvim_parse_expression()* nvim_parse_expression({expr}, {flags}, {highlight}) Parse a VimL expression. diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d9ab097316..3f0cfb82a6 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2461,203 +2461,3 @@ void nvim_del_user_command(String name, Error *err) { nvim_buf_del_user_command(-1, name, err); } - -/// 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. -/// - line1: (number) Starting line of command range. Only applicable if command can take a -/// range. -/// - line2: (number) Final line of command range. Only applicable if command can take a -/// range. -/// - bang: (boolean) Whether command contains a bang (!) 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|. -/// - 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); - } - - if (ea.cmdidx == CMD_USER) { - PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD(ea.useridx)->uc_name)); - } else if (ea.cmdidx == CMD_USER_BUF) { - PUT(result, "cmd", CSTR_TO_OBJ((char *)USER_CMD_GA(&curbuf->b_ucmds, ea.useridx)->uc_name)); - } else { - PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); - } - PUT(result, "line1", INTEGER_OBJ(ea.line1)); - PUT(result, "line2", INTEGER_OBJ(ea.line2)); - 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; -} 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 |<range>|. Can be 0, 1 or 2. +/// - line1: (number) Starting line of command |<range>|. -1 if command cannot take a range. +/// |<line1>| +/// - line2: (number) Final line of command |<range>|. -1 if command cannot take a range. +/// |<line2>| +/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot +/// take a count. +/// - reg: (number) The optional command |<register>|, if specified. Empty string if not +/// specified or if command cannot take a register. +/// - bang: (boolean) Whether command contains a |<bang>| (!) 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; +} diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4aa2ef6ae0..e06dd7e59c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -1240,7 +1240,7 @@ static void set_cmd_addr_type(exarg_T *eap, char_u *p) } } -/// Set default command range based on the addr type of the command +/// Set default command range for -range=% based on the addr type of the command static void set_cmd_default_range(exarg_T *eap) { buf_T *buf; @@ -1298,6 +1298,66 @@ static void set_cmd_default_range(exarg_T *eap) } } +static void parse_register(exarg_T *eap) +{ + // Accept numbered register only when no count allowed (:put) + if ((eap->argt & EX_REGSTR) + && *eap->arg != NUL + // Do not allow register = for user commands + && (!IS_USER_CMDIDX(eap->cmdidx) || *eap->arg != '=') + && !((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg))) { + if (valid_yank_reg(*eap->arg, (eap->cmdidx != CMD_put + && !IS_USER_CMDIDX(eap->cmdidx)))) { + eap->regname = *eap->arg++; + // for '=' register: accept the rest of the line as an expression + if (eap->arg[-1] == '=' && eap->arg[0] != NUL) { + if (!eap->skip) { + set_expr_line(vim_strsave(eap->arg)); + } + eap->arg += STRLEN(eap->arg); + } + eap->arg = skipwhite(eap->arg); + } + } +} + +static int parse_count(exarg_T *eap, char **errormsg) +{ + // Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a + // count, it's a buffer name. + char *p; + long n; + + if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg) + && (!(eap->argt & EX_BUFNAME) || *(p = (char *)skipdigits(eap->arg + 1)) == NUL + || ascii_iswhite(*p))) { + n = getdigits_long(&eap->arg, false, -1); + eap->arg = skipwhite(eap->arg); + if (n <= 0 && (eap->argt & EX_ZEROR) == 0) { + if (errormsg != NULL) { + *errormsg = _(e_zerocount); + } + 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 (eap->line2 > curbuf->b_ml.ml_line_count) { + eap->line2 = curbuf->b_ml.ml_line_count; + } + } + } + + return OK; +} + /// Parse command line and return information about the first command. /// /// @return Success or failure @@ -1345,6 +1405,8 @@ bool parse_cmdline(char_u *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo) if (eap->verbose_save != -1) { cmdinfo->verbose = p_verbose; p_verbose = eap->verbose_save; + } else { + cmdinfo->verbose = -1; } cmdinfo->cmdmod = cmdmod; cmdmod = save_cmdmod; @@ -1422,6 +1484,12 @@ bool parse_cmdline(char_u *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo) set_cmd_default_range(eap); } + // Parse register and count + parse_register(eap); + if (parse_count(eap, NULL) == FAIL) { + return false; + } + // Remove leading whitespace and colon from next command if (eap->nextcmd) { eap->nextcmd = (char_u *)skip_colon_white((char *)eap->nextcmd, true); @@ -1460,7 +1528,6 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter { char *p; linenr_T lnum; - long n; char *errormsg = NULL; // error message char *after_modifier = NULL; exarg_T ea; @@ -1899,53 +1966,10 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter set_cmd_default_range(&ea); } - // accept numbered register only when no count allowed (:put) - if ((ea.argt & EX_REGSTR) - && *ea.arg != NUL - // Do not allow register = for user commands - && (!IS_USER_CMDIDX(ea.cmdidx) || *ea.arg != '=') - && !((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg))) { - if (valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put - && !IS_USER_CMDIDX(ea.cmdidx)))) { - ea.regname = *ea.arg++; - // for '=' register: accept the rest of the line as an expression - if (ea.arg[-1] == '=' && ea.arg[0] != NUL) { - if (!ea.skip) { - set_expr_line(vim_strsave(ea.arg)); - } - ea.arg += STRLEN(ea.arg); - } - ea.arg = skipwhite(ea.arg); - } - } - - // - // Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a - // count, it's a buffer name. - /// - if ((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg) - && (!(ea.argt & EX_BUFNAME) || *(p = (char *)skipdigits(ea.arg + 1)) == NUL - || ascii_iswhite(*p))) { - n = getdigits_long(&ea.arg, false, -1); - ea.arg = skipwhite(ea.arg); - if (n <= 0 && !ni && (ea.argt & EX_ZEROR) == 0) { - errormsg = _(e_zerocount); - goto doend; - } - if (ea.addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3 - ea.line2 = n; - if (ea.addr_count == 0) { - ea.addr_count = 1; - } - } else { - ea.line1 = ea.line2; - ea.line2 += n - 1; - ++ea.addr_count; - // Be vi compatible: no error message for out of range. - if (ea.line2 > curbuf->b_ml.ml_line_count) { - ea.line2 = curbuf->b_ml.ml_line_count; - } - } + // Parse register and count + parse_register(&ea); + if (parse_count(&ea, &errormsg) == FAIL) { + goto doend; } /* diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index e138e2cc38..7e54ae0248 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -3104,8 +3104,11 @@ describe('API', function() cmd = 'echo', args = { 'foo' }, bang = false, - line1 = 1, - line2 = 1, + line1 = -1, + line2 = -1, + range = 0, + count = -1, + reg = '', addr = 'none', magic = { file = false, @@ -3130,7 +3133,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('echo foo', {})) end) @@ -3141,6 +3144,9 @@ describe('API', function() bang = false, line1 = 4, line2 = 6, + range = 2, + count = -1, + reg = '', addr = 'line', magic = { file = false, @@ -3165,10 +3171,86 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('4,6s/math.random/math.max/', {})) end) + it('works with count', function() + eq({ + cmd = 'buffer', + args = {}, + bang = false, + line1 = 1, + line2 = 1, + range = 1, + count = 1, + reg = '', + addr = 'buf', + magic = { + file = false, + bar = true + }, + nargs = '*', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + vertical = false, + split = "", + tab = 0, + verbose = -1 + } + }, meths.parse_cmd('buffer 1', {})) + end) + it('works with register', function() + eq({ + cmd = 'put', + args = {}, + bang = false, + line1 = 1, + line2 = 1, + range = 0, + count = -1, + reg = '+', + addr = 'line', + magic = { + file = false, + bar = true + }, + nargs = '0', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + vertical = false, + split = "", + tab = 0, + verbose = -1 + } + }, meths.parse_cmd('put +', {})) + end) it('works with bang', function() eq({ cmd = 'write', @@ -3176,6 +3258,9 @@ describe('API', function() bang = true, line1 = 1, line2 = 1, + range = 0, + count = -1, + reg = '', addr = 'line', magic = { file = true, @@ -3200,7 +3285,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 }, }, meths.parse_cmd('w!', {})) end) @@ -3211,6 +3296,9 @@ describe('API', function() bang = false, line1 = 1, line2 = 1, + range = 0, + count = -1, + reg = '', addr = '?', magic = { file = true, @@ -3247,6 +3335,9 @@ describe('API', function() bang = true, line1 = 4, line2 = 6, + range = 2, + count = -1, + reg = '', addr = 'line', magic = { file = false, @@ -3271,7 +3362,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('4,6MyCommand! test it', {})) end) @@ -3282,6 +3373,9 @@ describe('API', function() bang = false, line1 = 0, line2 = 0, + range = 0, + count = -1, + reg = '', addr = 'arg', magic = { file = true, @@ -3306,7 +3400,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('argadd a.txt | argadd b.txt', {})) end) @@ -3316,8 +3410,11 @@ describe('API', function() cmd = 'MyCommand', args = { 'test it' }, bang = false, - line1 = 1, - line2 = 1, + line1 = -1, + line2 = -1, + range = 0, + count = -1, + reg = '', addr = 'none', magic = { file = false, @@ -3342,7 +3439,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('MyCommand test it', {})) end) @@ -3355,6 +3452,9 @@ describe('API', function() bang = false, line1 = 1, line2 = 2, + range = 0, + count = -1, + reg = '', addr = 'buf', magic = { file = false, @@ -3379,7 +3479,7 @@ describe('API', function() vertical = false, split = "", tab = 0, - verbose = 0 + verbose = -1 } }, meths.parse_cmd('MyCommand', {})) end) |