Merge pull request #18231 from famiu/feat/api/parse_cmdline

feat(api): `nvim_parse_cmd`
This commit is contained in:
bfredl 2022-04-30 18:41:17 +02:00 committed by GitHub
commit cc27540560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 793 additions and 71 deletions

View File

@ -1350,6 +1350,69 @@ 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: ~
{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.

View File

@ -1421,7 +1421,7 @@ which by default correspond to the current line, last line and the whole
buffer, relate to arguments, (loaded) buffers, windows or tab pages.
Possible values are (second column is the short name used in listing):
-addr=lines Range of lines (this is the default)
-addr=lines line Range of lines (this is the default)
-addr=arguments arg Range for arguments
-addr=buffers buf Range for buffers (also not loaded buffers)
-addr=loaded_buffers load Range for loaded buffers

View File

@ -28,6 +28,7 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
@ -2460,3 +2461,203 @@ 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 = vim_strsave((char_u *)str.data);
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(cmdmod.tab));
PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.verbose));
PUT(mods, "browse", BOOLEAN_OBJ(cmdmod.browse));
PUT(mods, "confirm", BOOLEAN_OBJ(cmdmod.confirm));
PUT(mods, "hide", BOOLEAN_OBJ(cmdmod.hide));
PUT(mods, "keepalt", BOOLEAN_OBJ(cmdmod.keepalt));
PUT(mods, "keepjumps", BOOLEAN_OBJ(cmdmod.keepjumps));
PUT(mods, "keepmarks", BOOLEAN_OBJ(cmdmod.keepmarks));
PUT(mods, "keeppatterns", BOOLEAN_OBJ(cmdmod.keeppatterns));
PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdmod.lockmarks));
PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdmod.noswapfile));
PUT(mods, "vertical", BOOLEAN_OBJ(cmdmod.split & WSP_VERT));
const char *split;
if (cmdmod.split & WSP_BOT) {
split = "botright";
} else if (cmdmod.split & WSP_TOP) {
split = "topleft";
} else if (cmdmod.split & WSP_BELOW) {
split = "belowright";
} else if (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;
}

View File

@ -261,4 +261,18 @@ typedef struct {
bool filter_force; ///< set for :filter!
} cmdmod_T;
/// Stores command modifier info used by `nvim_parse_cmd`
typedef struct {
bool silent;
bool emsg_silent;
bool sandbox;
bool noautocmd;
long verbose;
cmdmod_T cmdmod;
struct {
bool file;
bool bar;
} magic;
} CmdParseInfo;
#endif // NVIM_EX_CMDS_DEFS_H

View File

@ -1216,6 +1216,226 @@ static char *skip_colon_white(const char *p, bool skipleadingwhite)
return (char *)p;
}
/// 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)
{
// ea.addr_type for user commands is set by find_ucmd
if (IS_USER_CMDIDX(eap->cmdidx)) {
return;
}
if (eap->cmdidx != CMD_SIZE) {
eap->addr_type = cmdnames[(int)eap->cmdidx].cmd_addr_type;
} else {
eap->addr_type = ADDR_LINES;
}
// :wincmd range depends on the argument
if (eap->cmdidx == CMD_wincmd && p != NULL) {
get_wincmd_addr_type((char *)skipwhite((char_u *)p), eap);
}
// :.cc in quickfix window uses line number
if ((eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) && bt_quickfix(curbuf)) {
eap->addr_type = ADDR_OTHER;
}
}
/// Set default command range based on the addr type of the command
static void set_cmd_default_range(exarg_T *eap)
{
buf_T *buf;
eap->line1 = 1;
switch (eap->addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
eap->line2 = curbuf->b_ml.ml_line_count;
break;
case ADDR_LOADED_BUFFERS:
buf = firstbuf;
while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
buf = buf->b_next;
}
eap->line1 = buf->b_fnum;
buf = lastbuf;
while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
buf = buf->b_prev;
}
eap->line2 = buf->b_fnum;
break;
case ADDR_BUFFERS:
eap->line1 = firstbuf->b_fnum;
eap->line2 = lastbuf->b_fnum;
break;
case ADDR_WINDOWS:
eap->line2 = LAST_WIN_NR;
break;
case ADDR_TABS:
eap->line2 = LAST_TAB_NR;
break;
case ADDR_TABS_RELATIVE:
eap->line2 = 1;
break;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0) {
eap->line1 = eap->line2 = 0;
} else {
eap->line2 = ARGCOUNT;
}
break;
case ADDR_QUICKFIX_VALID:
eap->line2 = (linenr_T)qf_get_valid_size(eap);
if (eap->line2 == 0) {
eap->line2 = 1;
}
break;
case ADDR_NONE:
case ADDR_UNSIGNED:
case ADDR_QUICKFIX:
iemsg(_("INTERNAL: Cannot use EX_DFLALL "
"with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"));
break;
}
}
/// Parse command line and return information about the first command.
///
/// @return Success or failure
bool parse_cmdline(char_u *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo)
{
char *errormsg = NULL;
char *cmd;
char *p;
// Initialize cmdinfo
memset(cmdinfo, 0, sizeof(*cmdinfo));
// Initialize eap
memset(eap, 0, sizeof(*eap));
eap->line1 = 1;
eap->line2 = 1;
eap->cmd = (char *)cmdline;
eap->cmdlinep = &cmdline;
eap->getline = NULL;
eap->cookie = NULL;
// Parse command modifiers
if (parse_command_modifiers(eap, &errormsg, false) == FAIL) {
return false;
}
// Revert the side-effects of `parse_command_modifiers`
if (eap->save_msg_silent != -1) {
cmdinfo->silent = !!msg_silent;
msg_silent = eap->save_msg_silent;
}
if (eap->did_esilent) {
cmdinfo->emsg_silent = true;
emsg_silent--;
}
if (eap->did_sandbox) {
cmdinfo->sandbox = true;
sandbox--;
}
if (cmdmod.save_ei != NULL) {
cmdinfo->noautocmd = true;
set_string_option_direct("ei", -1, cmdmod.save_ei, OPT_FREE, SID_NONE);
free_string_option(cmdmod.save_ei);
}
if (eap->verbose_save != -1) {
cmdinfo->verbose = p_verbose;
p_verbose = eap->verbose_save;
}
cmdinfo->cmdmod = cmdmod;
// Save location after command modifiers
cmd = eap->cmd;
// Skip ranges to find command name since we need the command to know what kind of range it uses
eap->cmd = skip_range(eap->cmd, NULL);
if (*eap->cmd == '*') {
eap->cmd = (char *)skipwhite((char_u *)eap->cmd + 1);
}
p = find_command(eap, NULL);
// Set command attribute type and parse command range
set_cmd_addr_type(eap, (char_u *)p);
eap->cmd = cmd;
if (parse_cmd_address(eap, &errormsg, false) == FAIL) {
return false;
}
// Skip colon and whitespace
eap->cmd = skip_colon_white(eap->cmd, true);
// Fail if command is a comment or if command doesn't exist
if (*eap->cmd == NUL || *eap->cmd == '"') {
return false;
}
// Fail if command is invalid
if (eap->cmdidx == CMD_SIZE) {
return false;
}
// Correctly set 'forceit' for commands
if (*p == '!' && eap->cmdidx != CMD_substitute
&& eap->cmdidx != CMD_smagic && eap->cmdidx != CMD_snomagic) {
p++;
eap->forceit = true;
} else {
eap->forceit = false;
}
// Parse arguments.
if (!IS_USER_CMDIDX(eap->cmdidx)) {
eap->argt = cmdnames[(int)eap->cmdidx].cmd_argt;
}
// Skip to start of argument.
// Don't do this for the ":!" command, because ":!! -l" needs the space.
if (eap->cmdidx == CMD_bang) {
eap->arg = (char_u *)p;
} else {
eap->arg = skipwhite((char_u *)p);
}
// Don't treat ":r! filter" like a bang
if (eap->cmdidx == CMD_read) {
if (eap->forceit) {
eap->forceit = false; // :r! filter
}
}
// Check for '|' to separate commands and '"' to start comments.
// Don't do this for ":read !cmd" and ":write !cmd".
if ((eap->argt & EX_TRLBAR)) {
separate_nextcmd(eap);
}
// Fail if command doesn't support bang but is used with a bang
if (!(eap->argt & EX_BANG) && eap->forceit) {
return false;
}
// Fail if command doesn't support a range but it is given a range
if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) {
return false;
}
// Set default range for command if required
if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) {
set_cmd_default_range(eap);
}
// Remove leading whitespace and colon from next command
if (eap->nextcmd) {
eap->nextcmd = (char_u *)skip_colon_white((char *)eap->nextcmd, true);
}
// Set the "magic" values (characters that get treated specially)
if (eap->argt & EX_XFILE) {
cmdinfo->magic.file = true;
}
if (eap->argt & EX_TRLBAR) {
cmdinfo->magic.bar = true;
}
return true;
}
/// Execute one Ex command.
///
/// If 'sourcing' is TRUE, the command will be included in the error message.
@ -1361,23 +1581,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// The ea.cmd pointer is updated to point to the first character following the
// range spec. If an initial address is found, but no second, the upper bound
// is equal to the lower.
// ea.addr_type for user commands is set by find_ucmd
if (!IS_USER_CMDIDX(ea.cmdidx)) {
if (ea.cmdidx != CMD_SIZE) {
ea.addr_type = cmdnames[(int)ea.cmdidx].cmd_addr_type;
} else {
ea.addr_type = ADDR_LINES;
}
// :wincmd range depends on the argument
if (ea.cmdidx == CMD_wincmd && p != NULL) {
get_wincmd_addr_type((char *)skipwhite((char_u *)p), &ea);
}
// :.cc in quickfix window uses line number
if ((ea.cmdidx == CMD_cc || ea.cmdidx == CMD_ll) && bt_quickfix(curbuf)) {
ea.addr_type = ADDR_OTHER;
}
}
set_cmd_addr_type(&ea, (char_u *)p);
ea.cmd = cmd;
if (parse_cmd_address(&ea, &errormsg, false) == FAIL) {
@ -1690,59 +1894,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
}
if ((ea.argt & EX_DFLALL) && ea.addr_count == 0) {
buf_T *buf;
ea.line1 = 1;
switch (ea.addr_type) {
case ADDR_LINES:
case ADDR_OTHER:
ea.line2 = curbuf->b_ml.ml_line_count;
break;
case ADDR_LOADED_BUFFERS:
buf = firstbuf;
while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) {
buf = buf->b_next;
}
ea.line1 = buf->b_fnum;
buf = lastbuf;
while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) {
buf = buf->b_prev;
}
ea.line2 = buf->b_fnum;
break;
case ADDR_BUFFERS:
ea.line1 = firstbuf->b_fnum;
ea.line2 = lastbuf->b_fnum;
break;
case ADDR_WINDOWS:
ea.line2 = LAST_WIN_NR;
break;
case ADDR_TABS:
ea.line2 = LAST_TAB_NR;
break;
case ADDR_TABS_RELATIVE:
ea.line2 = 1;
break;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0) {
ea.line1 = ea.line2 = 0;
} else {
ea.line2 = ARGCOUNT;
}
break;
case ADDR_QUICKFIX_VALID:
ea.line2 = (linenr_T)qf_get_valid_size(&ea);
if (ea.line2 == 0) {
ea.line2 = 1;
}
break;
case ADDR_NONE:
case ADDR_UNSIGNED:
case ADDR_QUICKFIX:
iemsg(_("INTERNAL: Cannot use EX_DFLALL "
"with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"));
break;
}
set_cmd_default_range(&ea);
}
// accept numbered register only when no count allowed (:put)

View File

@ -3098,4 +3098,296 @@ describe('API', function()
end)
end)
end)
describe('nvim_parse_cmd', function()
it('works', function()
eq({
cmd = 'echo',
args = { 'foo' },
bang = false,
line1 = 1,
line2 = 1,
addr = 'none',
magic = {
file = false,
bar = false
},
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 = 0
}
}, meths.parse_cmd('echo foo', {}))
end)
it('works with ranges', function()
eq({
cmd = 'substitute',
args = { '/math.random/math.max/' },
bang = false,
line1 = 4,
line2 = 6,
addr = 'line',
magic = {
file = false,
bar = false
},
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 = 0
}
}, meths.parse_cmd('4,6s/math.random/math.max/', {}))
end)
it('works with bang', function()
eq({
cmd = 'write',
args = {},
bang = true,
line1 = 1,
line2 = 1,
addr = 'line',
magic = {
file = true,
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 = 0
},
}, meths.parse_cmd('w!', {}))
end)
it('works with modifiers', function()
eq({
cmd = 'split',
args = { 'foo.txt' },
bang = false,
line1 = 1,
line2 = 1,
addr = '?',
magic = {
file = true,
bar = true
},
nargs = '?',
nextcmd = '',
mods = {
browse = false,
confirm = false,
emsg_silent = true,
hide = false,
keepalt = false,
keepjumps = false,
keepmarks = false,
keeppatterns = false,
lockmarks = false,
noautocmd = false,
noswapfile = false,
sandbox = false,
silent = true,
vertical = false,
split = "topleft",
tab = 2,
verbose = 15
},
}, meths.parse_cmd('15verbose silent! aboveleft topleft tab split foo.txt', {}))
end)
it('works with user commands', function()
command('command -bang -nargs=+ -range -addr=lines MyCommand echo foo')
eq({
cmd = 'MyCommand',
args = { 'test', 'it' },
bang = true,
line1 = 4,
line2 = 6,
addr = 'line',
magic = {
file = false,
bar = false
},
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 = 0
}
}, meths.parse_cmd('4,6MyCommand! test it', {}))
end)
it('works for commands separated by bar', function()
eq({
cmd = 'argadd',
args = { 'a.txt' },
bang = false,
line1 = 0,
line2 = 0,
addr = 'arg',
magic = {
file = true,
bar = true
},
nargs = '*',
nextcmd = 'argadd b.txt',
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 = 0
}
}, meths.parse_cmd('argadd a.txt | argadd b.txt', {}))
end)
it('works for nargs=1', function()
command('command -nargs=1 MyCommand echo <q-args>')
eq({
cmd = 'MyCommand',
args = { 'test it' },
bang = false,
line1 = 1,
line2 = 1,
addr = 'none',
magic = {
file = false,
bar = false
},
nargs = '1',
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 = 0
}
}, meths.parse_cmd('MyCommand test it', {}))
end)
it('sets correct default range', function()
command('command -range=% -addr=buffers MyCommand echo foo')
command('new')
eq({
cmd = 'MyCommand',
args = {},
bang = false,
line1 = 1,
line2 = 2,
addr = 'buf',
magic = {
file = false,
bar = false
},
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 = 0
}
}, meths.parse_cmd('MyCommand', {}))
end)
it('errors for invalid command', function()
eq('Error while parsing command line', pcall_err(meths.parse_cmd, 'Fubar', {}))
command('command! Fubar echo foo')
eq('Error while parsing command line', pcall_err(meths.parse_cmd, 'Fubar!', {}))
eq('Error while parsing command line', pcall_err(meths.parse_cmd, '4,6Fubar', {}))
end)
end)
end)