aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/keysets.lua39
-rw-r--r--src/nvim/api/private/helpers.c172
-rw-r--r--src/nvim/api/vimscript.c385
3 files changed, 592 insertions, 4 deletions
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index 5baffaf505..78f8ed3cca 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -155,5 +155,44 @@ return {
create_augroup = {
"clear";
};
+ cmd = {
+ "cmd";
+ "range";
+ "count";
+ "reg";
+ "bang";
+ "args";
+ "magic";
+ "mods";
+ "nargs";
+ "addr";
+ "nextcmd";
+ };
+ cmd_magic = {
+ "file";
+ "bar";
+ };
+ cmd_mods = {
+ "silent";
+ "emsg_silent";
+ "sandbox";
+ "noautocmd";
+ "browse";
+ "confirm";
+ "hide";
+ "keepalt";
+ "keepjumps";
+ "keepmarks";
+ "keeppatterns";
+ "lockmarks";
+ "noswapfile";
+ "tab";
+ "verbose";
+ "vertical";
+ "split";
+ };
+ cmd_opts = {
+ "output";
+ };
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a2ee4f91bc..725033a4c5 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -19,6 +19,7 @@
#include "nvim/decoration.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
@@ -1691,3 +1692,174 @@ int init_sign_text(char **sign_text, char *text)
return OK;
}
+
+/// Check if a string contains only whitespace characters.
+bool string_iswhite(String str)
+{
+ for (size_t i = 0; i < str.size; i++) {
+ if (!ascii_iswhite(str.data[i])) {
+ // Found a non-whitespace character
+ return false;
+ } else if (str.data[i] == NUL) {
+ // Terminate at first occurence of a NUL character
+ break;
+ }
+ }
+ return true;
+}
+
+// Add modifier string for command into the command line. Includes trailing whitespace if non-empty.
+// @return OK or FAIL.
+static int add_cmd_modifier_str(char *cmdline, size_t *pos, const size_t bufsize,
+ CmdParseInfo *cmdinfo)
+{
+#define APPEND_MODIFIER(...) \
+ do { \
+ if (*pos < bufsize) { \
+ *pos += (size_t)snprintf(cmdline + *pos, bufsize - *pos, __VA_ARGS__); \
+ } \
+ if (*pos < bufsize) { \
+ cmdline[*pos] = ' '; \
+ *pos += 1; \
+ } else { \
+ goto err; \
+ } \
+ } while (0)
+
+#define APPEND_MODIFIER_IF(cond, mod) \
+ do { \
+ if (cond) { \
+ APPEND_MODIFIER(mod); \
+ } \
+ } while (0)
+
+ if (cmdinfo->cmdmod.tab != 0) {
+ APPEND_MODIFIER("%dtab", cmdinfo->cmdmod.tab - 1);
+ }
+ if (cmdinfo->verbose != -1) {
+ APPEND_MODIFIER("%ldverbose", cmdinfo->verbose);
+ }
+
+ switch (cmdinfo->cmdmod.split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
+ case WSP_ABOVE:
+ APPEND_MODIFIER("aboveleft");
+ break;
+ case WSP_BELOW:
+ APPEND_MODIFIER("belowright");
+ break;
+ case WSP_TOP:
+ APPEND_MODIFIER("topleft");
+ break;
+ case WSP_BOT:
+ APPEND_MODIFIER("botright");
+ break;
+ default:
+ break;
+ }
+
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.split & WSP_VERT, "vertical");
+
+ if (cmdinfo->emsg_silent) {
+ APPEND_MODIFIER("silent!");
+ } else if (cmdinfo->silent) {
+ APPEND_MODIFIER("silent");
+ }
+
+ APPEND_MODIFIER_IF(cmdinfo->sandbox, "sandbox");
+ APPEND_MODIFIER_IF(cmdinfo->noautocmd, "noautocmd");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.browse, "browse");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.confirm, "confirm");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.hide, "hide");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepalt, "keepalt");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepjumps, "keepjumps");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keepmarks, "keepmarks");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.keeppatterns, "keeppatterns");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.lockmarks, "lockmarks");
+ APPEND_MODIFIER_IF(cmdinfo->cmdmod.noswapfile, "noswapfile");
+
+ return OK;
+err:
+ return FAIL;
+
+#undef APPEND_MODIFIER
+#undef APPEND_MODIFIER_IF
+}
+
+/// Build cmdline string for command, used by `nvim_cmd()`.
+///
+/// @return OK or FAIL.
+int build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, char **args,
+ size_t argc)
+{
+ const size_t bufsize = IOSIZE;
+ size_t pos = 0;
+ char *cmdline = xcalloc(bufsize, sizeof(char));
+
+#define CMDLINE_APPEND(...) \
+ do { \
+ if (pos < bufsize) { \
+ pos += (size_t)snprintf(cmdline + pos, bufsize - pos, __VA_ARGS__); \
+ } else { \
+ goto err; \
+ } \
+ } while (0)
+
+ // Command modifiers.
+ if (add_cmd_modifier_str(cmdline, &pos, bufsize, cmdinfo) == FAIL) {
+ goto err;
+ }
+
+ // Command range / count.
+ if (eap->argt & EX_RANGE) {
+ if (eap->addr_count == 1) {
+ CMDLINE_APPEND("%ld", eap->line2);
+ } else if (eap->addr_count > 1) {
+ CMDLINE_APPEND("%ld,%ld", eap->line1, eap->line2);
+ eap->addr_count = 2; // Make sure address count is not greater than 2
+ }
+ }
+
+ // Keep the index of the position where command name starts, so eap->cmd can point to it.
+ size_t cmdname_idx = pos;
+ CMDLINE_APPEND("%s", eap->cmd);
+ eap->cmd = cmdline + cmdname_idx;
+
+ // Command bang.
+ if (eap->argt & EX_BANG && eap->forceit) {
+ CMDLINE_APPEND("!");
+ }
+
+ // Command register.
+ if (eap->argt & EX_REGSTR && eap->regname) {
+ CMDLINE_APPEND(" %c", eap->regname);
+ }
+
+ // Iterate through each argument and store the starting position and length of each argument in
+ // the cmdline string in `eap->args` and `eap->arglens`, respectively.
+ eap->args = xcalloc(argc, sizeof(char *));
+ eap->arglens = xcalloc(argc, sizeof(size_t));
+ for (size_t i = 0; i < argc; i++) {
+ eap->args[i] = cmdline + pos + 1; // add 1 to skip the leading space.
+ eap->arglens[i] = STRLEN(args[i]);
+ CMDLINE_APPEND(" %s", args[i]);
+ }
+ eap->argc = argc;
+ eap->arg = argc > 0 ? eap->args[0] : NULL;
+
+ // Replace, :make and :grep with 'makeprg' and 'grepprg'.
+ char *p = replace_makeprg(eap, eap->arg, cmdlinep);
+ if (p != eap->arg) {
+ // If replace_makeprg modified the cmdline string, correct the argument pointers.
+ assert(argc == 1);
+ eap->arg = p;
+ eap->args[0] = p;
+ }
+
+ *cmdlinep = cmdline;
+ return OK;
+err:
+ xfree(cmdline);
+ return FAIL;
+
+#undef CMDLINE_APPEND
+}
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index c4a301839f..02f406d686 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -10,10 +10,14 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
+#include "nvim/ops.h"
+#include "nvim/strings.h"
+#include "nvim/vim.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
#include "nvim/window.h"
@@ -22,7 +26,9 @@
# include "api/vimscript.c.generated.h"
#endif
-/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
+
+/// Executes Vimscript (multiline block of Ex commands), like anonymous
/// |:source|.
///
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
@@ -32,6 +38,7 @@
///
/// @see |execute()|
/// @see |nvim_command()|
+/// @see |nvim_cmd()|
///
/// @param src Vimscript code
/// @param output Capture and return all (non-error, non-shell |:!|) output
@@ -89,13 +96,16 @@ theend:
return (String)STRING_INIT;
}
-/// Executes an ex-command.
+/// Executes an Ex command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
-/// @see |nvim_exec()|
+/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
+/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
///
-/// @param command Ex-command string
+/// @param command Ex command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
FUNC_API_SINCE(1)
@@ -978,3 +988,370 @@ end:
xfree(cmdline);
return result;
}
+
+/// Executes an Ex command.
+///
+/// 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.
+///
+/// @see |nvim_exec()|
+/// @see |nvim_command()|
+///
+/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
+/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
+/// which are ignored if provided. All values except for "cmd" are optional.
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to return command output.
+/// @param[out] err Error details, if any.
+/// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
+String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error *err)
+ FUNC_API_SINCE(10)
+{
+ exarg_T ea;
+ memset(&ea, 0, sizeof(ea));
+ ea.verbose_save = -1;
+ ea.save_msg_silent = -1;
+
+ CmdParseInfo cmdinfo;
+ memset(&cmdinfo, 0, sizeof(cmdinfo));
+ cmdinfo.verbose = -1;
+
+ char *cmdline = NULL;
+ char *cmdname = NULL;
+ char **args = NULL;
+ size_t argc = 0;
+
+ String retv = (String)STRING_INIT;
+
+#define OBJ_TO_BOOL(var, value, default, varname) \
+ do { \
+ var = api_object_to_bool(value, varname, default, err); \
+ if (ERROR_SET(err)) { \
+ 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");
+ }
+
+ cmdname = string_to_cstr(cmd->cmd.data.string);
+ ea.cmd = cmdname;
+
+ char *p = find_ex_command(&ea, NULL);
+
+ // If this looks like an undefined user command and there are CmdUndefined
+ // autocommands defined, trigger the matching autocommands.
+ if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
+ && has_event(EVENT_CMDUNDEFINED)) {
+ p = xstrdup(cmdname);
+ int ret = apply_autocmds(EVENT_CMDUNDEFINED, (char_u *)p, (char_u *)p, true, NULL);
+ xfree(p);
+ // If the autocommands did something and didn't cause an error, try
+ // finding the command again.
+ 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);
+ }
+
+ // 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.
+ if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ ea.argt = get_cmd_argt(ea.cmdidx);
+ }
+
+ // 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");
+ }
+ // Check if every argument is valid
+ 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");
+ }
+ }
+
+ 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;
+ break;
+ case EX_EXTRA | EX_NOSPC:
+ argc_valid = argc <= 1;
+ break;
+ case EX_EXTRA | EX_NEEDARG:
+ argc_valid = argc >= 1;
+ break;
+ case EX_EXTRA:
+ argc_valid = true;
+ break;
+ default:
+ argc_valid = argc == 0;
+ break;
+ }
+
+ if (!argc_valid) {
+ argc = 0; // Ensure that args array isn't erroneously freed at the end.
+ VALIDATION_ERROR("Incorrect number of arguments supplied");
+ }
+
+ if (argc != 0) {
+ args = xcalloc(argc, sizeof(char *));
+
+ for (size_t i = 0; i < argc; i++) {
+ args[i] = string_to_cstr(cmd->args.data.array.items[i].data.string);
+ }
+ }
+ }
+
+ // 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 ? (char_u *)args[0] : 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");
+ }
+
+ Array range = cmd->range.data.array;
+ 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");
+ }
+ }
+
+ if (range.size > 0) {
+ ea.line1 = range.items[0].data.integer;
+ ea.line2 = range.items[range.size - 1].data.integer;
+ }
+
+ if (invalid_range(&ea) != NULL) {
+ VALIDATION_ERROR("Invalid range provided");
+ }
+ }
+ if (ea.addr_count == 0) {
+ if (ea.argt & EX_DFLALL) {
+ set_cmd_dflall_range(&ea); // Default range for range=%
+ } else {
+ ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range.
+
+ if (ea.addr_type == ADDR_OTHER) {
+ // Default is 1, not cursor.
+ ea.line2 = 1;
+ }
+ }
+ }
+
+ 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->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);
+ }
+ 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");
+ }
+
+ Dict(cmd_magic) magic = { 0 };
+ if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
+ cmd->magic.data.dictionary, 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'");
+ } else {
+ cmdinfo.magic.file = ea.argt & EX_XFILE;
+ 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)) {
+ goto end;
+ }
+
+ if (HAS_KEY(mods.tab)) {
+ if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) {
+ VALIDATION_ERROR("'mods.tab' must be a non-negative Integer");
+ }
+ cmdinfo.cmdmod.tab = (int)mods.tab.data.integer + 1;
+ }
+
+ if (HAS_KEY(mods.verbose)) {
+ if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) {
+ VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer");
+ }
+ cmdinfo.verbose = mods.verbose.data.integer;
+ }
+
+ bool vertical;
+ OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
+ cmdinfo.cmdmod.split |= (vertical ? WSP_VERT : 0);
+
+ if (HAS_KEY(mods.split)) {
+ if (mods.split.type != kObjectTypeString) {
+ VALIDATION_ERROR("'mods.split' must be a String");
+ }
+
+ if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+ || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+ cmdinfo.cmdmod.split |= WSP_ABOVE;
+ } else if (STRCMP(mods.split.data.string.data, "belowright") == 0
+ || STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BELOW;
+ } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) {
+ cmdinfo.cmdmod.split |= WSP_TOP;
+ } else if (STRCMP(mods.split.data.string.data, "botright") == 0) {
+ cmdinfo.cmdmod.split |= WSP_BOT;
+ } else {
+ VALIDATION_ERROR("Invalid value for 'mods.split'");
+ }
+ }
+
+ OBJ_TO_BOOL(cmdinfo.silent, mods.silent, false, "'mods.silent'");
+ OBJ_TO_BOOL(cmdinfo.emsg_silent, mods.emsg_silent, false, "'mods.emsg_silent'");
+ OBJ_TO_BOOL(cmdinfo.sandbox, mods.sandbox, false, "'mods.sandbox'");
+ OBJ_TO_BOOL(cmdinfo.noautocmd, mods.noautocmd, false, "'mods.noautocmd'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.browse, mods.browse, false, "'mods.browse'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.confirm, mods.confirm, false, "'mods.confirm'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.hide, mods.hide, false, "'mods.hide'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepalt, mods.keepalt, false, "'mods.keepalt'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepjumps, mods.keepjumps, false, "'mods.keepjumps'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keepmarks, mods.keepmarks, false, "'mods.keepmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.keeppatterns, mods.keeppatterns, false, "'mods.keeppatterns'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.lockmarks, mods.lockmarks, false, "'mods.lockmarks'");
+ OBJ_TO_BOOL(cmdinfo.cmdmod.noswapfile, mods.noswapfile, false, "'mods.noswapfile'");
+
+ if (cmdinfo.sandbox && !(ea.argt & EX_SBOXOK)) {
+ VALIDATION_ERROR("Command cannot be run in sandbox");
+ }
+ }
+
+ // 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.
+ if (build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc) == FAIL) {
+ goto end;
+ }
+ ea.cmdlinep = &cmdline;
+
+ garray_T capture_local;
+ const int save_msg_silent = msg_silent;
+ garray_T * const save_capture_ga = capture_ga;
+
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ if (output) {
+ msg_silent++;
+ }
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ execute_cmd(&ea, &cmdinfo);
+ });
+
+ if (output) {
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ }
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto clear_ga;
+ }
+
+ if (output && capture_local.ga_len > 1) {
+ retv = (String){
+ .data = capture_local.ga_data,
+ .size = (size_t)capture_local.ga_len,
+ };
+ // redir usually (except :echon) prepends a newline.
+ if (retv.data[0] == '\n') {
+ memmove(retv.data, retv.data + 1, retv.size - 1);
+ retv.data[retv.size - 1] = '\0';
+ retv.size = retv.size - 1;
+ }
+ goto end;
+ }
+clear_ga:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+end:
+ xfree(cmdline);
+ xfree(cmdname);
+ xfree(ea.args);
+ xfree(ea.arglens);
+ for (size_t i = 0; i < argc; i++) {
+ xfree(args[i]);
+ }
+ xfree(args);
+
+ return retv;
+
+#undef OBJ_TO_BOOL
+#undef VALIDATION_ERROR
+}