aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api/command.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
commit1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch)
treecd08258054db80bb9a11b1061bb091c70b76926a /src/nvim/api/command.c
parenteaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.gz
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.bz2
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.zip
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
Diffstat (limited to 'src/nvim/api/command.c')
-rw-r--r--src/nvim/api/command.c567
1 files changed, 267 insertions, 300 deletions
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index abd265f2cf..2a57ce9a19 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1,36 +1,37 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/command.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -99,16 +100,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
{
Dictionary result = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return result;
- }
+ });
// Parse command line
exarg_T ea;
CmdParseInfo cmdinfo;
char *cmdline = string_to_cstr(str);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
if (errormsg != NULL) {
@@ -127,7 +127,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
// 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)));
+ ADD(args, STRING_OBJ(cstrn_to_string(ea.arg, length)));
}
} else {
size_t end = 0;
@@ -153,9 +153,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
}
if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ PUT(result, "cmd", CSTR_TO_OBJ(cmd->uc_name));
} else {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ PUT(result, "cmd", CSTR_TO_OBJ(get_command_name(NULL, ea.cmdidx)));
}
if (ea.argt & EX_RANGE) {
@@ -237,14 +237,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
break;
}
PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+ PUT(result, "nextcmd", CSTR_TO_OBJ(ea.nextcmd));
Dictionary mods = ARRAY_DICT_INIT;
Dictionary filter = ARRAY_DICT_INIT;
PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat
? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat)
- : STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ : STATIC_CSTR_TO_OBJ(""));
PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
PUT(mods, "filter", DICTIONARY_OBJ(filter));
@@ -305,9 +305,9 @@ end:
/// 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.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @see |nvim_exec()|
+/// @see |nvim_exec2()|
/// @see |nvim_command()|
///
/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
@@ -340,32 +340,22 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
} \
} while (0)
-#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \
+#define VALIDATE_MOD(cond, mod_, name_) \
do { \
- if (api_object_to_bool(value, varname, default, err)) { \
- cmdinfo.cmdmod.cmod_flags |= (flag); \
- } \
- if (ERROR_SET(err)) { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \
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");
- }
+ VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", {
+ goto end;
+ });
+ VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, {
+ goto end;
+ });
- cmdname = string_to_cstr(cmd->cmd.data.string);
+ cmdname = string_to_cstr(cmd->cmd);
ea.cmd = cmdname;
char *p = find_ex_command(&ea, NULL);
@@ -382,12 +372,18 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
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);
- }
+ VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, {
+ goto end;
+ });
+ VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, {
+ goto end;
+ });
+ const char *fullname = IS_USER_CMDIDX(ea.cmdidx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
+ : get_command_name(NULL, ea.cmdidx);
+ VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, {
+ goto end;
+ });
// 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.
@@ -396,15 +392,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
// 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");
- }
-
+ if (HAS_KEY(cmd, cmd, args)) {
// 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];
+ for (size_t i = 0; i < cmd->args.size; i++) {
+ Object elem = cmd->args.items[i];
char *data_str;
switch (elem.type) {
@@ -421,18 +413,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
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);
+ VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
+ goto end;
+ });
+ data_str = string_to_cstr(elem.data.string);
break;
default:
- VALIDATION_ERROR("Invalid type for command argument");
+ VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
+ goto end;
+ });
break;
}
- ADD(args, STRING_OBJ(cstr_as_string(data_str)));
+ ADD(args, CSTR_AS_OBJ(data_str));
}
bool argc_valid;
@@ -456,32 +449,30 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
break;
}
- if (!argc_valid) {
- VALIDATION_ERROR("Incorrect number of arguments supplied");
- }
+ VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
+ goto end;
+ });
}
// 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, args.size > 0 ? args.items[0].data.string.data : 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");
- }
+ if (HAS_KEY(cmd, cmd, range)) {
+ VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data);
+ VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, {
+ goto end;
+ });
- Array range = cmd->range.data.array;
+ Array range = cmd->range;
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");
- }
+ VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0),
+ "range element", "non-negative Integer", NULL, {
+ goto end;
+ });
}
if (range.size > 0) {
@@ -489,9 +480,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
}
- if (invalid_range(&ea) != NULL) {
- VALIDATION_ERROR("Invalid range provided");
- }
+ VALIDATE_S((invalid_range(&ea) == NULL), "range", "", {
+ goto end;
+ });
}
if (ea.addr_count == 0) {
if (ea.argt & EX_DFLALL) {
@@ -506,48 +497,42 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
}
- 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, cmd, count)) {
+ VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
+ VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
+ goto end;
+ });
+ set_cmd_count(&ea, (linenr_T)cmd->count, 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);
- }
+ if (HAS_KEY(cmd, cmd, reg)) {
+ VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
+ VALIDATE_EXP((cmd->reg.size == 1),
+ "reg", "single character", cmd->reg.data, {
+ goto end;
+ });
+ char regname = cmd->reg.data[0];
+ VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
+ goto end;
+ });
+ VALIDATE(valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx)),
+ "Invalid register: \"%c", regname, {
+ goto end;
+ });
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");
- }
+ ea.forceit = cmd->bang;
+ VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data);
- Dict(cmd_magic) magic = { 0 };
- if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
- cmd->magic.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, magic)) {
+ Dict(cmd_magic) magic[1] = { 0 };
+ if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, 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'");
+ cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE);
+ cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR);
if (cmdinfo.magic.file) {
ea.argt |= EX_XFILE;
} else {
@@ -558,107 +543,90 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
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)) {
+ if (HAS_KEY(cmd, cmd, mods)) {
+ Dict(cmd_mods) mods[1] = { 0 };
+ if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) {
goto end;
}
- if (HAS_KEY(mods.filter)) {
- if (mods.filter.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods.filter' must be a Dictionary");
- }
-
- Dict(cmd_mods_filter) filter = { 0 };
+ if (HAS_KEY(mods, cmd_mods, filter)) {
+ Dict(cmd_mods_filter) filter[1] = { 0 };
if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
- mods.filter.data.dictionary, err)) {
+ mods->filter, err)) {
goto end;
}
- if (HAS_KEY(filter.pattern)) {
- if (filter.pattern.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.filter.pattern' must be a String");
- }
-
- OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'");
+ if (HAS_KEY(filter, cmd_mods_filter, pattern)) {
+ cmdinfo.cmdmod.cmod_filter_force = filter->force;
// "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter
// is inverted.
- if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
- cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string);
+ if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
+ cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern);
cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat,
RE_MAGIC);
}
}
}
- if (HAS_KEY(mods.tab)) {
- if (mods.tab.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.tab' must be an Integer");
- } else if ((int)mods.tab.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, tab)) {
+ if ((int)mods->tab >= 0) {
// Silently ignore negative integers to allow mods.tab to be set to -1.
- cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1;
+ cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1;
}
}
- if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.verbose' must be an Integer");
- } else if ((int)mods.verbose.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, verbose)) {
+ if ((int)mods->verbose >= 0) {
// Silently ignore negative integers to allow mods.verbose to be set to -1.
- cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1;
+ cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1;
}
}
- bool vertical;
- OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
- cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0);
- bool horizontal;
- OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'");
- cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0);
- if (HAS_KEY(mods.split)) {
- if (mods.split.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.split' must be a String");
- }
-
- if (*mods.split.data.string.data == NUL) {
+ if (HAS_KEY(mods, cmd_mods, split)) {
+ if (*mods->split.data == NUL) {
// Empty string, do nothing.
- } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0
- || strcmp(mods.split.data.string.data, "leftabove") == 0) {
+ } else if (strcmp(mods->split.data, "aboveleft") == 0
+ || strcmp(mods->split.data, "leftabove") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_ABOVE;
- } else if (strcmp(mods.split.data.string.data, "belowright") == 0
- || strcmp(mods.split.data.string.data, "rightbelow") == 0) {
+ } else if (strcmp(mods->split.data, "belowright") == 0
+ || strcmp(mods->split.data, "rightbelow") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
- } else if (strcmp(mods.split.data.string.data, "topleft") == 0) {
+ } else if (strcmp(mods->split.data, "topleft") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_TOP;
- } else if (strcmp(mods.split.data.string.data, "botright") == 0) {
+ } else if (strcmp(mods->split.data, "botright") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BOT;
} else {
- VALIDATION_ERROR("Invalid value for 'mods.split'");
+ VALIDATE_S(false, "mods.split", "", {
+ goto end;
+ });
}
}
- OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'");
- OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'");
- OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.unsilent, false, "'mods.unsilent'");
- OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'");
- OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'");
- OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'");
- OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods.confirm, false, "'mods.confirm'");
- OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods.hide, false, "'mods.hide'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods.keepalt, false, "'mods.keepalt'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods.keepjumps, false, "'mods.keepjumps'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods.keepmarks, false, "'mods.keepmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods.keeppatterns, false, "'mods.keeppatterns'");
- OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'");
+#define OBJ_TO_CMOD_FLAG(flag, value) \
+ if (value) { \
+ cmdinfo.cmdmod.cmod_flags |= (flag); \
+ }
+
+ OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent);
+ OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent);
+ OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent);
+ OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox);
+ OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd);
+ OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse);
+ OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm);
+ OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns);
+ OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile);
if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) {
// CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't
@@ -666,9 +634,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT;
}
- if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) {
- VALIDATION_ERROR("Command cannot be run in sandbox");
- }
+ VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)),
+ "%s", "Command cannot be run in sandbox", {
+ goto end;
+ });
}
// Finally, build the command line string that will be stored inside ea.cmdlinep.
@@ -681,14 +650,13 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
garray_T * const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
- TRY_WRAP({
- try_start();
- if (output) {
+ TRY_WRAP(err, {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
@@ -697,21 +665,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
execute_cmd(&ea, &cmdinfo, false);
});
- if (output) {
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}
-
- try_end(err);
});
if (ERROR_SET(err)) {
goto clear_ga;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
retv = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -725,7 +691,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
goto end;
}
clear_ga:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
end:
@@ -739,7 +705,7 @@ end:
#undef OBJ_TO_BOOL
#undef OBJ_TO_CMOD_FLAG
-#undef VALIDATION_ERROR
+#undef VALIDATE_MOD
}
/// Check if a string contains only whitespace characters.
@@ -870,8 +836,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
offset += eap->arglens[i];
}
// If there isn't an argument, make eap->arg point to end of cmdline.
- eap->arg = argc > 0 ? eap->args[0] :
- cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
+ eap->arg = argc > 0 ? eap->args[0]
+ : cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
// Finally, make cmdlinep point to the cmdline string.
*cmdlinep = cmdline.items;
@@ -888,18 +854,17 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
}
}
-/// Create a new user command |user-commands|
+/// Creates a global |user-commands| command.
///
-/// {name} is the name of the new command. The name must begin with an uppercase letter.
-///
-/// {command} is the replacement text or Lua function to execute.
+/// For Lua usage see |lua-guide-commands-create|.
///
/// Example:
-/// <pre>vim
-/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
+///
+/// ```vim
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+/// :SayHello
+/// Hello world!
+/// ```
///
/// @param name Name of the new user command. Must begin with an uppercase letter.
/// @param command Replacement command to execute when this user command is executed. When called
@@ -909,6 +874,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - args: (string) The args passed to the command, if any |<args>|
/// - fargs: (table) The args split by unescaped whitespace (when more than one
/// argument is allowed), if any |<f-args>|
+/// - nargs: (string) Number of arguments |:command-nargs|
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
/// - line1: (number) The starting line of the command range |<line1>|
/// - line2: (number) The final line of the command range |<line2>|
@@ -918,20 +884,22 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - mods: (string) Command modifiers, if any |<mods>|
/// - smods: (table) Command modifiers in a structured format. Has the same
/// structure as the "mods" key of |nvim_parse_cmd()|.
-/// @param opts Optional command attributes. See |command-attributes| for more details. To use
-/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". In addition to the string options listed in |:command-complete|, the
-/// "complete" key also accepts a Lua function which works like the "customlist"
-/// completion mode |:command-completion-customlist|. Additional parameters:
-/// - desc: (string) Used for listing the command when a Lua function is used for
-/// {command}.
-/// - force: (boolean, default true) Override any previous definition.
-/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
+/// @param opts Optional |command-attributes|.
+/// - Set boolean attributes such as |:command-bang| or |:command-bar| to true (but
+/// not |:command-buffer|, use |nvim_buf_create_user_command()| instead).
+/// - "complete" |:command-complete| also accepts a Lua function which works like
+/// |:command-completion-customlist|.
+/// - Other parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
-void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+void nvim_create_user_command(uint64_t channel_id, String name, Object command,
+ Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
- create_user_command(name, command, opts, 0, err);
+ create_user_command(channel_id, name, command, opts, 0, err);
}
/// Delete a user-defined command.
@@ -944,12 +912,12 @@ void nvim_del_user_command(String name, Error *err)
nvim_buf_del_user_command(-1, name, err);
}
-/// Create a new user command |user-commands| in the given buffer.
+/// Creates a buffer-local command |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer.
/// @param[out] err Error details, if any.
/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
+void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command,
Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
@@ -960,7 +928,7 @@ void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
buf_T *save_curbuf = curbuf;
curbuf = target_buf;
- create_user_command(name, command, opts, UC_BUFFER, err);
+ create_user_command(channel_id, name, command, opts, UC_BUFFER, err);
curbuf = save_curbuf;
}
@@ -998,36 +966,33 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
}
}
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+ api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data);
}
-void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
- Error *err)
+void create_user_command(uint64_t channel_id, String name, Object command, Dict(user_command) *opts,
+ int flags, Error *err)
{
uint32_t argt = 0;
- long def = -1;
+ int64_t def = -1;
cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
const char *rep = NULL;
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
LuaRef preview_luaref = LUA_NOREF;
- if (!uc_validate_name(name.data)) {
- api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
goto err;
- }
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ });
+ VALIDATE_S(!mb_islower(name.data[0]), "command name (must start with uppercase)",
+ name.data, {
goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ });
+ VALIDATE((!HAS_KEY(opts, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s",
+ "Cannot use both 'range' and 'count'", {
goto err;
- }
+ });
if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) {
@@ -1038,14 +1003,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
+ goto err;
+ });
}
} else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
goto err;
- }
+ });
switch (opts->nargs.data.string.data[0]) {
case '*':
@@ -1058,18 +1023,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
+ goto err;
+ });
}
- } else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, nargs)) {
+ VALIDATE_S(false, "nargs", "", {
+ goto err;
+ });
}
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ VALIDATE((!HAS_KEY(opts, user_command, complete) || argt),
+ "%s", "'complete' used without 'nargs'", {
goto err;
- }
+ });
if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) {
@@ -1077,20 +1044,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_LINES;
}
} else if (opts->range.type == kObjectTypeString) {
- if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
- argt |= EX_RANGE | EX_DFLALL;
- addr_type_arg = ADDR_LINES;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
+ "range", "", {
goto err;
- }
+ });
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
} else if (opts->range.type == kObjectTypeInteger) {
argt |= EX_RANGE | EX_ZEROR;
def = opts->range.data.integer;
addr_type_arg = ADDR_LINES;
- } else if (HAS_KEY(opts->range)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, range)) {
+ VALIDATE_S(false, "range", "", {
+ goto err;
+ });
}
if (opts->count.type == kObjectTypeBoolean) {
@@ -1103,76 +1070,72 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
addr_type_arg = ADDR_OTHER;
def = opts->count.data.integer;
- } else if (HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, count)) {
+ VALIDATE_S(false, "count", "", {
+ goto err;
+ });
}
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ if (HAS_KEY(opts, user_command, addr)) {
+ VALIDATE_T("addr", kObjectTypeString, opts->addr.type, {
goto err;
- }
+ });
+
+ VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data,
+ (int)opts->addr.data.string.size, &addr_type_arg), "addr",
+ opts->addr.data.string.data, {
+ goto err;
+ });
if (addr_type_arg != ADDR_LINES) {
argt |= EX_ZEROR;
}
- } else if (HAS_KEY(opts->addr)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
}
- if (api_object_to_bool(opts->bang, "bang", false, err)) {
+ if (opts->bang) {
argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ if (opts->bar) {
argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->register_, "register", false, err)) {
+ if (opts->register_) {
argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ if (opts->keepscript) {
argt |= EX_KEEPSCRIPT;
- } else if (ERROR_SET(err)) {
- goto err;
}
- bool force = api_object_to_bool(opts->force, "force", true, err);
+ bool force = GET_BOOL_OR_TRUE(opts, user_command, force);
if (ERROR_SET(err)) {
goto err;
}
if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
+ context = EXPAND_USER_LUA;
compl_luaref = api_new_luaref(opts->complete.data.luaref);
} else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg(opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- &compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &context, &argt,
+ &compl_arg),
+ "complete", opts->complete.data.string.data, {
goto err;
- }
- } else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
+ });
+ } else if (HAS_KEY(opts, user_command, complete)) {
+ VALIDATE_EXP(false, "complete", "Function or String", NULL, {
+ goto err;
+ });
}
- if (opts->preview.type == kObjectTypeLuaRef) {
+ if (HAS_KEY(opts, user_command, preview)) {
+ VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, {
+ goto err;
+ });
+
argt |= EX_PREVIEW;
preview_luaref = api_new_luaref(opts->preview.data.luaref);
- } else if (HAS_KEY(opts->preview)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
- goto err;
}
switch (command.type) {
@@ -1188,15 +1151,18 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
rep = command.data.string.data;
break;
default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
+ VALIDATE_EXP(false, "command", "Function or String", NULL, {
+ goto err;
+ });
}
- if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- preview_luaref, addr_type_arg, luaref, force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
- }
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
+ compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ }
+ });
return;
@@ -1209,6 +1175,8 @@ err:
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
///
+/// @see |nvim_get_all_options_info()|
+///
/// @param opts Optional parameters. Currently only supports
/// {"builtin":false}
/// @param[out] err Error details, if any.
@@ -1231,13 +1199,12 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
FUNC_API_SINCE(4)
{
bool global = (buffer == -1);
- bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
if (ERROR_SET(err)) {
return (Dictionary)ARRAY_DICT_INIT;
}
if (global) {
- if (builtin) {
+ if (opts->builtin) {
api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
return (Dictionary)ARRAY_DICT_INIT;
}
@@ -1245,7 +1212,7 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
}
buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
+ if (opts->builtin || !buf) {
return (Dictionary)ARRAY_DICT_INIT;
}
return commands_array(buf);