feat(nvim_cmd): allow using first argument as count

Allows `nvim_cmd` to use the first argument as count for applicable
commands. Also adds support for non-String arguments to `nvim_cmd`.
This commit is contained in:
Famiu Haque
2022-09-28 17:43:18 +06:00
parent d9f5940997
commit e46eef75ac
4 changed files with 117 additions and 30 deletions

View File

@@ -283,7 +283,11 @@ end:
/// 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.
/// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String.
///
/// The first argument may also be used instead of count for commands that support it in order to
/// make their usage simpler with |vim.cmd()|. For example, instead of
/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
///
/// On execution error: fails with VimL error, updates v:errmsg.
///
@@ -308,8 +312,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
char *cmdline = NULL;
char *cmdname = NULL;
ArrayOf(String) args;
size_t argc = 0;
ArrayOf(String) args = ARRAY_DICT_INIT;
String retv = (String)STRING_INIT;
@@ -381,48 +384,70 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
if (cmd->args.type != kObjectTypeArray) {
VALIDATION_ERROR("'args' must be an Array");
}
// Check if every argument is valid
// Process all arguments. Convert non-String arguments to String and check if String arguments
// have non-whitespace characters.
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");
char *data_str;
switch (elem.type) {
case kObjectTypeBoolean:
data_str = xcalloc(2, sizeof(char));
data_str[0] = elem.data.boolean ? '1' : '0';
data_str[1] = '\0';
break;
case kObjectTypeBuffer:
case kObjectTypeWindow:
case kObjectTypeTabpage:
case kObjectTypeInteger:
data_str = xcalloc(NUMBUFLEN, sizeof(char));
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
break;
case kObjectTypeString:
if (string_iswhite(elem.data.string)) {
VALIDATION_ERROR("String command argument must have at least one non-whitespace "
"character");
}
data_str = xstrndup(elem.data.string.data, elem.data.string.size);
break;
default:
VALIDATION_ERROR("Invalid type for command argument");
break;
}
ADD(args, STRING_OBJ(cstr_as_string(data_str)));
}
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;
argc_valid = args.size == 1;
break;
case EX_EXTRA | EX_NOSPC:
argc_valid = argc <= 1;
argc_valid = args.size <= 1;
break;
case EX_EXTRA | EX_NEEDARG:
argc_valid = argc >= 1;
argc_valid = args.size >= 1;
break;
case EX_EXTRA:
argc_valid = true;
break;
default:
argc_valid = argc == 0;
argc_valid = args.size == 0;
break;
}
if (!argc_valid) {
VALIDATION_ERROR("Incorrect number of arguments supplied");
}
args = cmd->args.data.array;
}
// 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 ? args.items[0].data.string.data : NULL);
set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL);
if (HAS_KEY(cmd->range)) {
if (!(ea.argt & EX_RANGE)) {
@@ -626,7 +651,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
// Finally, build the command line string that will be stored inside ea.cmdlinep.
// This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc);
build_cmdline_str(&cmdline, &ea, &cmdinfo, args);
ea.cmdlinep = &cmdline;
garray_T capture_local;
@@ -682,6 +707,7 @@ clear_ga:
ga_clear(&capture_local);
}
end:
api_free_array(args);
xfree(cmdline);
xfree(cmdname);
xfree(ea.args);
@@ -711,8 +737,9 @@ static bool string_iswhite(String str)
/// Build cmdline string for command, used by `nvim_cmd()`.
static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo,
ArrayOf(String) args, size_t argc)
ArrayOf(String) args)
{
size_t argc = args.size;
StringBuilder cmdline = KV_INITIAL_VALUE;
kv_resize(cmdline, 32); // Make it big enough to handle most typical commands
@@ -798,7 +825,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
}
eap->argc = argc;
eap->arglens = xcalloc(argc, sizeof(size_t));
eap->arglens = eap->argc > 0 ? xcalloc(argc, sizeof(size_t)) : NULL;
size_t argstart_idx = cmdline.size;
for (size_t i = 0; i < argc; i++) {
String s = args.items[i].data.string;
@@ -813,7 +840,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
// Now that all the arguments are appended, use the command index and argument indices to set the
// values of eap->cmd, eap->arg and eap->args.
eap->cmd = cmdline.items + cmdname_idx;
eap->args = xcalloc(argc, sizeof(char *));
eap->args = eap->argc > 0 ? xcalloc(argc, sizeof(char *)) : NULL;
size_t offset = argstart_idx;
for (size_t i = 0; i < argc; i++) {
offset++; // Account for space