mirror of
https://github.com/neovim/neovim.git
synced 2024-12-31 17:13:26 -07:00
Merge pull request #18231 from famiu/feat/api/parse_cmdline
feat(api): `nvim_parse_cmd`
This commit is contained in:
commit
cc27540560
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user