neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

command.c (44625B)


      1 #include <inttypes.h>
      2 #include <lauxlib.h>
      3 #include <stdbool.h>
      4 #include <stdio.h>
      5 #include <string.h>
      6 
      7 #include "klib/kvec.h"
      8 #include "nvim/api/command.h"
      9 #include "nvim/api/keysets_defs.h"
     10 #include "nvim/api/private/defs.h"
     11 #include "nvim/api/private/dispatch.h"
     12 #include "nvim/api/private/helpers.h"
     13 #include "nvim/api/private/validate.h"
     14 #include "nvim/ascii_defs.h"
     15 #include "nvim/autocmd.h"
     16 #include "nvim/autocmd_defs.h"
     17 #include "nvim/buffer_defs.h"
     18 #include "nvim/charset.h"
     19 #include "nvim/cmdexpand_defs.h"
     20 #include "nvim/ex_cmds_defs.h"
     21 #include "nvim/ex_docmd.h"
     22 #include "nvim/ex_eval.h"
     23 #include "nvim/garray.h"
     24 #include "nvim/garray_defs.h"
     25 #include "nvim/globals.h"
     26 #include "nvim/lua/executor.h"
     27 #include "nvim/macros_defs.h"
     28 #include "nvim/mbyte.h"
     29 #include "nvim/memory.h"
     30 #include "nvim/memory_defs.h"
     31 #include "nvim/pos_defs.h"
     32 #include "nvim/regexp.h"
     33 #include "nvim/register.h"
     34 #include "nvim/strings.h"
     35 #include "nvim/types_defs.h"
     36 #include "nvim/usercmd.h"
     37 #include "nvim/vim_defs.h"
     38 #include "nvim/window.h"
     39 
     40 #include "api/command.c.generated.h"
     41 
     42 /// Parse arguments for :map/:abbrev commands, preserving whitespace in RHS.
     43 /// @param arg_str  The argument string to parse
     44 /// @param arena    Arena allocator
     45 /// @return Array with at most 2 elements: [lhs, rhs]
     46 static Array parse_map_cmd(const char *arg_str, Arena *arena)
     47 {
     48  Array args = arena_array(arena, 2);
     49 
     50  char *lhs_start = (char *)arg_str;
     51  char *lhs_end = skiptowhite(lhs_start);
     52  size_t lhs_len = (size_t)(lhs_end - lhs_start);
     53 
     54  // Add the LHS (first argument)
     55  ADD_C(args, STRING_OBJ(cstrn_as_string(lhs_start, lhs_len)));
     56 
     57  // Add the RHS (second argument) if it exists, preserving all whitespace
     58  char *rhs_start = skipwhite(lhs_end);
     59  if (*rhs_start != NUL) {
     60    size_t rhs_len = strlen(rhs_start);
     61    ADD_C(args, STRING_OBJ(cstrn_as_string(rhs_start, rhs_len)));
     62  }
     63 
     64  return args;
     65 }
     66 
     67 /// Parse command line.
     68 ///
     69 /// Doesn't check the validity of command arguments.
     70 ///
     71 /// @param str       Command line string to parse. Cannot contain "\n".
     72 /// @param opts      Optional parameters. Reserved for future use.
     73 /// @param[out] err  Error details, if any.
     74 /// @return Dict containing command information, with these keys:
     75 ///         - cmd: (string) Command name.
     76 ///         - range: (array) (optional) Command range ([<line1>] [<line2>]).
     77 ///                          Omitted if command doesn't accept a range.
     78 ///                          Otherwise, has no elements if no range was specified, one element if
     79 ///                          only a single range item was specified, or two elements if both range
     80 ///                          items were specified.
     81 ///         - count: (number) (optional) Command [<count>].
     82 ///                           Omitted if command cannot take a count.
     83 ///         - reg: (string) (optional) Command [<register>].
     84 ///                         Omitted if command cannot take a register.
     85 ///         - bang: (boolean) Whether command contains a [<bang>] (!) modifier.
     86 ///         - args: (array) Command arguments.
     87 ///         - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines.
     88 ///         - nargs: (string) Value of |:command-nargs|.
     89 ///         - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
     90 ///                             Empty if there isn't a next command.
     91 ///         - magic: (dict) Which characters have special meaning in the command arguments.
     92 ///             - file: (boolean) The command expands filenames. Which means characters such as "%",
     93 ///                               "#" and wildcards are expanded.
     94 ///             - bar: (boolean) The "|" character is treated as a command separator and the double
     95 ///                              quote character (") is treated as the start of a comment.
     96 ///         - mods: (dict) |:command-modifiers|.
     97 ///             - filter: (dict) |:filter|.
     98 ///                 - pattern: (string) Filter pattern. Empty string if there is no filter.
     99 ///                 - force: (boolean) Whether filter is inverted or not.
    100 ///             - silent: (boolean) |:silent|.
    101 ///             - emsg_silent: (boolean) |:silent!|.
    102 ///             - unsilent: (boolean) |:unsilent|.
    103 ///             - sandbox: (boolean) |:sandbox|.
    104 ///             - noautocmd: (boolean) |:noautocmd|.
    105 ///             - browse: (boolean) |:browse|.
    106 ///             - confirm: (boolean) |:confirm|.
    107 ///             - hide: (boolean) |:hide|.
    108 ///             - horizontal: (boolean) |:horizontal|.
    109 ///             - keepalt: (boolean) |:keepalt|.
    110 ///             - keepjumps: (boolean) |:keepjumps|.
    111 ///             - keepmarks: (boolean) |:keepmarks|.
    112 ///             - keeppatterns: (boolean) |:keeppatterns|.
    113 ///             - lockmarks: (boolean) |:lockmarks|.
    114 ///             - noswapfile: (boolean) |:noswapfile|.
    115 ///             - tab: (integer) |:tab|. -1 when omitted.
    116 ///             - verbose: (integer) |:verbose|. -1 when omitted.
    117 ///             - vertical: (boolean) |:vertical|.
    118 ///             - split: (string) Split modifier string, is an empty string when there's no split
    119 ///                               modifier. If there is a split modifier it can be one of:
    120 ///               - "aboveleft": |:aboveleft|.
    121 ///               - "belowright": |:belowright|.
    122 ///               - "topleft": |:topleft|.
    123 ///               - "botright": |:botright|.
    124 Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err)
    125  FUNC_API_SINCE(10) FUNC_API_FAST
    126 {
    127  Dict(cmd) result = KEYDICT_INIT;
    128 
    129  // Parse command line
    130  exarg_T ea;
    131  CmdParseInfo cmdinfo;
    132  char *cmdline = arena_memdupz(arena, str.data, str.size);
    133  const char *errormsg = NULL;
    134 
    135  if (!parse_cmdline(&cmdline, &ea, &cmdinfo, &errormsg)) {
    136    if (errormsg != NULL) {
    137      api_set_error(err, kErrorTypeException, "Parsing command-line: %s", errormsg);
    138    } else {
    139      api_set_error(err, kErrorTypeException, "Parsing command-line");
    140    }
    141    goto end;
    142  }
    143 
    144  // Parse arguments
    145  Array args = ARRAY_DICT_INIT;
    146  size_t length = strlen(ea.arg);
    147 
    148  // Check if this is a mapping command that needs special handling
    149  // like mapping commands need special argument parsing to preserve whitespace in RHS:
    150  // "map a b  c" => { args=["a", "b  c"], ... }
    151  if (is_map_cmd(ea.cmdidx) && *ea.arg != NUL) {
    152    // For mapping commands, split differently to preserve whitespace
    153    args = parse_map_cmd(ea.arg, arena);
    154  } else if (ea.argt & EX_NOSPC) {
    155    // For nargs = 1 or '?', pass the entire argument list as a single argument,
    156    // otherwise split arguments by whitespace.
    157    if (*ea.arg != NUL) {
    158      args = arena_array(arena, 1);
    159      ADD_C(args, STRING_OBJ(cstrn_as_string(ea.arg, length)));
    160    }
    161  } else {
    162    size_t end = 0;
    163    size_t len = 0;
    164    char *buf = arena_alloc(arena, length + 1, false);
    165    bool done = false;
    166    args = arena_array(arena, uc_nargs_upper_bound(ea.arg, length));
    167 
    168    while (!done) {
    169      done = uc_split_args_iter(ea.arg, length, &end, buf, &len);
    170      if (len > 0) {
    171        ADD_C(args, STRING_OBJ(cstrn_as_string(buf, len)));
    172        buf += len + 1;
    173      }
    174    }
    175  }
    176 
    177  ucmd_T *cmd = NULL;
    178  if (ea.cmdidx == CMD_USER) {
    179    cmd = USER_CMD(ea.useridx);
    180  } else if (ea.cmdidx == CMD_USER_BUF) {
    181    cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx);
    182  }
    183 
    184  char *name = (cmd != NULL ? cmd->uc_name : get_command_name(NULL, ea.cmdidx));
    185  PUT_KEY(result, cmd, cmd, cstr_as_string(name));
    186 
    187  if ((ea.argt & EX_RANGE) && ea.addr_count > 0) {
    188    Array range = arena_array(arena, 2);
    189    if (ea.addr_count > 1) {
    190      ADD_C(range, INTEGER_OBJ(ea.line1));
    191    }
    192    ADD_C(range, INTEGER_OBJ(ea.line2));
    193    PUT_KEY(result, cmd, range, range);
    194  }
    195 
    196  if (ea.argt & EX_COUNT) {
    197    Integer count = ea.addr_count > 0 ? ea.line2 : (cmd != NULL ? cmd->uc_def : 0);
    198    // For built-in commands, if count is not explicitly provided and the default value is 0,
    199    // do not include the count field in the result, so the command uses its built-in default
    200    // behavior.
    201    if (ea.addr_count > 0 || (cmd != NULL && cmd->uc_def != 0) || count != 0) {
    202      PUT_KEY(result, cmd, count, count);
    203    }
    204  }
    205 
    206  if (ea.argt & EX_REGSTR) {
    207    char reg[2] = { (char)ea.regname, NUL };
    208    PUT_KEY(result, cmd, reg, CSTR_TO_ARENA_STR(arena, reg));
    209  }
    210 
    211  PUT_KEY(result, cmd, bang, ea.forceit);
    212  PUT_KEY(result, cmd, args, args);
    213 
    214  char nargs[2];
    215  if (ea.argt & EX_EXTRA) {
    216    if (ea.argt & EX_NOSPC) {
    217      if (ea.argt & EX_NEEDARG) {
    218        nargs[0] = '1';
    219      } else {
    220        nargs[0] = '?';
    221      }
    222    } else if (ea.argt & EX_NEEDARG) {
    223      nargs[0] = '+';
    224    } else {
    225      nargs[0] = '*';
    226    }
    227  } else {
    228    nargs[0] = '0';
    229  }
    230  nargs[1] = NUL;
    231  PUT_KEY(result, cmd, nargs, CSTR_TO_ARENA_OBJ(arena, nargs));
    232 
    233  char *addr;
    234  switch (ea.addr_type) {
    235  case ADDR_LINES:
    236    addr = "line";
    237    break;
    238  case ADDR_ARGUMENTS:
    239    addr = "arg";
    240    break;
    241  case ADDR_BUFFERS:
    242    addr = "buf";
    243    break;
    244  case ADDR_LOADED_BUFFERS:
    245    addr = "load";
    246    break;
    247  case ADDR_WINDOWS:
    248    addr = "win";
    249    break;
    250  case ADDR_TABS:
    251    addr = "tab";
    252    break;
    253  case ADDR_QUICKFIX:
    254    addr = "qf";
    255    break;
    256  case ADDR_NONE:
    257    addr = "none";
    258    break;
    259  default:
    260    addr = "?";
    261    break;
    262  }
    263  PUT_KEY(result, cmd, addr, cstr_as_string(addr));
    264  PUT_KEY(result, cmd, nextcmd, cstr_as_string(ea.nextcmd));
    265 
    266  // TODO(bfredl): nested keydict would be nice..
    267  Dict mods = arena_dict(arena, 20);
    268 
    269  Dict filter = arena_dict(arena, 2);
    270  PUT_C(filter, "pattern", CSTR_TO_ARENA_OBJ(arena, cmdinfo.cmdmod.cmod_filter_pat));
    271  PUT_C(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
    272  PUT_C(mods, "filter", DICT_OBJ(filter));
    273 
    274  PUT_C(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT));
    275  PUT_C(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT));
    276  PUT_C(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT));
    277  PUT_C(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX));
    278  PUT_C(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD));
    279  PUT_C(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1));
    280  PUT_C(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1));
    281  PUT_C(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE));
    282  PUT_C(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM));
    283  PUT_C(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE));
    284  PUT_C(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT));
    285  PUT_C(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS));
    286  PUT_C(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS));
    287  PUT_C(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS));
    288  PUT_C(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS));
    289  PUT_C(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE));
    290  PUT_C(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT));
    291  PUT_C(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR));
    292 
    293  char *split;
    294  if (cmdinfo.cmdmod.cmod_split & WSP_BOT) {
    295    split = "botright";
    296  } else if (cmdinfo.cmdmod.cmod_split & WSP_TOP) {
    297    split = "topleft";
    298  } else if (cmdinfo.cmdmod.cmod_split & WSP_BELOW) {
    299    split = "belowright";
    300  } else if (cmdinfo.cmdmod.cmod_split & WSP_ABOVE) {
    301    split = "aboveleft";
    302  } else {
    303    split = "";
    304  }
    305  PUT_C(mods, "split", CSTR_AS_OBJ(split));
    306 
    307  PUT_KEY(result, cmd, mods, mods);
    308 
    309  Dict magic = arena_dict(arena, 2);
    310  PUT_C(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
    311  PUT_C(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
    312  PUT_KEY(result, cmd, magic, magic);
    313 
    314  undo_cmdmod(&cmdinfo.cmdmod);
    315 end:
    316  return result;
    317 }
    318 
    319 /// Executes an Ex command.
    320 ///
    321 /// Unlike |nvim_command()| this command takes a structured Dict instead of a String. This
    322 /// allows for easier construction and manipulation of an Ex command. This also allows for things
    323 /// such as having spaces inside a command argument, expanding filenames in a command that otherwise
    324 /// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String.
    325 ///
    326 /// The first argument may also be used instead of count for commands that support it in order to
    327 /// make their usage simpler with |vim.cmd()|. For example, instead of
    328 /// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
    329 ///
    330 /// On execution error: fails with Vimscript error, updates v:errmsg.
    331 ///
    332 /// @see |nvim_exec2()|
    333 /// @see |nvim_command()|
    334 ///
    335 /// @param cmd       Command to execute. Must be a Dict that can contain the same values as
    336 ///                  the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
    337 ///                  which are ignored if provided. All values except for "cmd" are optional.
    338 /// @param opts      Optional parameters.
    339 ///                  - output: (boolean, default false) Whether to return command output.
    340 /// @param[out] err  Error details, if any.
    341 /// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string.
    342 String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena *arena, Error *err)
    343  FUNC_API_SINCE(10)
    344 {
    345  exarg_T ea;
    346  CLEAR_FIELD(ea);
    347 
    348  CmdParseInfo cmdinfo;
    349  CLEAR_FIELD(cmdinfo);
    350 
    351  char *cmdline = NULL;
    352  char *cmdname = NULL;
    353  ArrayOf(String) args = ARRAY_DICT_INIT;
    354 
    355  String retv = (String)STRING_INIT;
    356 
    357 #define OBJ_TO_BOOL(var, value, default, varname) \
    358  do { \
    359    (var) = api_object_to_bool(value, varname, default, err); \
    360    if (ERROR_SET(err)) { \
    361      goto end; \
    362    } \
    363  } while (0)
    364 
    365 #define VALIDATE_MOD(cond, mod_, name_) \
    366  do { \
    367    if (!(cond)) { \
    368      api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \
    369      goto end; \
    370    } \
    371  } while (0)
    372 
    373  VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", {
    374    goto end;
    375  });
    376  VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, {
    377    goto end;
    378  });
    379 
    380  cmdname = arena_string(arena, cmd->cmd).data;
    381  ea.cmd = cmdname;
    382 
    383  char *p = find_ex_command(&ea, NULL);
    384 
    385  // If this looks like an undefined user command and there are CmdUndefined
    386  // autocommands defined, trigger the matching autocommands.
    387  if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd)
    388      && has_event(EVENT_CMDUNDEFINED)) {
    389    p = arena_string(arena, cmd->cmd).data;
    390    int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
    391    // If the autocommands did something and didn't cause an error, try
    392    // finding the command again.
    393    p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
    394  }
    395 
    396  VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, {
    397    goto end;
    398  });
    399  VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, {
    400    goto end;
    401  });
    402  const char *fullname = IS_USER_CMDIDX(ea.cmdidx)
    403                         ? get_user_command_name(ea.useridx, ea.cmdidx)
    404                         : get_command_name(NULL, ea.cmdidx);
    405  VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, {
    406    goto end;
    407  });
    408 
    409  // Get the command flags so that we can know what type of arguments the command uses.
    410  // Not required for a user command since `find_ex_command` already deals with it in that case.
    411  if (!IS_USER_CMDIDX(ea.cmdidx)) {
    412    ea.argt = get_cmd_argt(ea.cmdidx);
    413  }
    414 
    415  // Track whether the first argument was interpreted as count to avoid conflicts
    416  bool count_from_first_arg = false;
    417  // Parse command arguments since it's needed to get the command address type.
    418  if (HAS_KEY(cmd, cmd, args)) {
    419    // Special handling: for commands that support count but not regular arguments,
    420    // if a single numeric argument is provided, interpret it as count
    421    if (cmd->args.size == 1 && (ea.argt & EX_COUNT) && !(ea.argt & EX_EXTRA)) {
    422      Object first_arg = cmd->args.items[0];
    423      bool is_numeric = false;
    424      int64_t count_value = 0;
    425 
    426      if (first_arg.type == kObjectTypeInteger) {
    427        is_numeric = true;
    428        count_value = first_arg.data.integer;
    429      } else if (first_arg.type == kObjectTypeString) {
    430        // Try to parse string as a number Example: vim.api.nvim_cmd({cmd = 'copen', args = {'10'}}, {})
    431        char *endptr;
    432        long val = strtol(first_arg.data.string.data, &endptr, 10);
    433        // Check if entire string was consumed (valid number) and string is not empty
    434        if (*endptr == '\0' && first_arg.data.string.size > 0) {
    435          is_numeric = true;
    436          count_value = val;
    437        }
    438      }
    439 
    440      if (is_numeric && count_value >= 0) {
    441        // Interpret the argument as count
    442        count_from_first_arg = true;
    443        ea.addr_count = 1;
    444        ea.line1 = ea.line2 = (linenr_T)count_value;
    445        args = arena_array(arena, 0);
    446      }
    447    }
    448 
    449    if (!count_from_first_arg) {
    450      // Process all arguments. Convert non-String arguments to String and check if String arguments
    451      // have non-whitespace characters.
    452      args = arena_array(arena, cmd->args.size);
    453      for (size_t i = 0; i < cmd->args.size; i++) {
    454        Object elem = cmd->args.items[i];
    455        char *data_str;
    456 
    457        switch (elem.type) {
    458        case kObjectTypeBoolean:
    459          data_str = arena_alloc(arena, 2, false);
    460          data_str[0] = elem.data.boolean ? '1' : '0';
    461          data_str[1] = NUL;
    462          ADD_C(args, CSTR_AS_OBJ(data_str));
    463          break;
    464        case kObjectTypeBuffer:
    465        case kObjectTypeWindow:
    466        case kObjectTypeTabpage:
    467        case kObjectTypeInteger:
    468          data_str = arena_alloc(arena, NUMBUFLEN, false);
    469          snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
    470          ADD_C(args, CSTR_AS_OBJ(data_str));
    471          break;
    472        case kObjectTypeString:
    473          VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
    474            goto end;
    475          });
    476          ADD_C(args, elem);
    477          break;
    478        default:
    479          VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
    480            goto end;
    481          });
    482          break;
    483        }
    484      }
    485 
    486      bool argc_valid;
    487 
    488      // Check if correct number of arguments is used.
    489      switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
    490      case EX_EXTRA | EX_NOSPC | EX_NEEDARG:
    491        argc_valid = args.size == 1;
    492        break;
    493      case EX_EXTRA | EX_NOSPC:
    494        argc_valid = args.size <= 1;
    495        break;
    496      case EX_EXTRA | EX_NEEDARG:
    497        argc_valid = args.size >= 1;
    498        break;
    499      case EX_EXTRA:
    500        argc_valid = true;
    501        break;
    502      default:
    503        argc_valid = args.size == 0;
    504        break;
    505      }
    506 
    507      VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
    508        goto end;
    509      });
    510    }
    511  }
    512 
    513  // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
    514  // since it only ever checks the first argument.
    515  set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL);
    516 
    517  if (HAS_KEY(cmd, cmd, range)) {
    518    VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data);
    519    VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, {
    520      goto end;
    521    });
    522 
    523    Array range = cmd->range;
    524    ea.addr_count = (int)range.size;
    525 
    526    for (size_t i = 0; i < range.size; i++) {
    527      Object elem = range.items[i];
    528      VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0),
    529                   "range element", "non-negative Integer", NULL, {
    530        goto end;
    531      });
    532    }
    533 
    534    if (range.size > 0) {
    535      ea.line1 = (linenr_T)range.items[0].data.integer;
    536      ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
    537    }
    538 
    539    VALIDATE_S((invalid_range(&ea) == NULL), "range", "", {
    540      goto end;
    541    });
    542  }
    543  if (ea.addr_count == 0) {
    544    if (ea.argt & EX_DFLALL) {
    545      set_cmd_dflall_range(&ea);  // Default range for range=%
    546    } else {
    547      ea.line1 = ea.line2 = get_cmd_default_range(&ea);  // Default range.
    548 
    549      if (ea.addr_type == ADDR_OTHER) {
    550        // Default is 1, not cursor.
    551        ea.line2 = 1;
    552      }
    553    }
    554  }
    555 
    556  if (HAS_KEY(cmd, cmd, count)) {
    557    VALIDATE(!count_from_first_arg, "%s", "Cannot specify both 'count' and numeric argument", {
    558      goto end;
    559    });
    560    VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
    561    VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
    562      goto end;
    563    });
    564    set_cmd_count(&ea, (linenr_T)cmd->count, true);
    565  }
    566 
    567  if (HAS_KEY(cmd, cmd, reg)) {
    568    VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
    569    VALIDATE_EXP((cmd->reg.size == 1),
    570                 "reg", "single character", cmd->reg.data, {
    571      goto end;
    572    });
    573    char regname = cmd->reg.data[0];
    574    VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
    575      goto end;
    576    });
    577    VALIDATE(valid_yank_reg(regname,
    578                            (!IS_USER_CMDIDX(ea.cmdidx)
    579                             && ea.cmdidx != CMD_put && ea.cmdidx != CMD_iput)),
    580             "Invalid register: \"%c", regname, {
    581      goto end;
    582    });
    583    ea.regname = (uint8_t)regname;
    584  }
    585 
    586  ea.forceit = cmd->bang;
    587  VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data);
    588 
    589  if (HAS_KEY(cmd, cmd, magic)) {
    590    Dict(cmd_magic) magic[1] = KEYDICT_INIT;
    591    if (!api_dict_to_keydict(magic, DictHash(cmd_magic), cmd->magic, err)) {
    592      goto end;
    593    }
    594 
    595    cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE);
    596    cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR);
    597    if (cmdinfo.magic.file) {
    598      ea.argt |= EX_XFILE;
    599    } else {
    600      ea.argt &= ~EX_XFILE;
    601    }
    602  } else {
    603    cmdinfo.magic.file = ea.argt & EX_XFILE;
    604    cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
    605  }
    606 
    607  if (HAS_KEY(cmd, cmd, mods)) {
    608    Dict(cmd_mods) mods[1] = KEYDICT_INIT;
    609    if (!api_dict_to_keydict(mods, DictHash(cmd_mods), cmd->mods, err)) {
    610      goto end;
    611    }
    612 
    613    if (HAS_KEY(mods, cmd_mods, filter)) {
    614      Dict(cmd_mods_filter) filter[1] = KEYDICT_INIT;
    615 
    616      if (!api_dict_to_keydict(&filter, DictHash(cmd_mods_filter),
    617                               mods->filter, err)) {
    618        goto end;
    619      }
    620 
    621      if (HAS_KEY(filter, cmd_mods_filter, pattern)) {
    622        cmdinfo.cmdmod.cmod_filter_force = filter->force;
    623 
    624        // "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter
    625        // is inverted.
    626        if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
    627          cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern);
    628          cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat,
    629                                                                    RE_MAGIC);
    630        }
    631      }
    632    }
    633 
    634    if (HAS_KEY(mods, cmd_mods, tab)) {
    635      if ((int)mods->tab >= 0) {
    636        // Silently ignore negative integers to allow mods.tab to be set to -1.
    637        cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1;
    638      }
    639    }
    640 
    641    if (HAS_KEY(mods, cmd_mods, verbose)) {
    642      if ((int)mods->verbose >= 0) {
    643        // Silently ignore negative integers to allow mods.verbose to be set to -1.
    644        cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1;
    645      }
    646    }
    647 
    648    cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0);
    649 
    650    cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0);
    651 
    652    if (HAS_KEY(mods, cmd_mods, split)) {
    653      if (*mods->split.data == NUL) {
    654        // Empty string, do nothing.
    655      } else if (strcmp(mods->split.data, "aboveleft") == 0
    656                 || strcmp(mods->split.data, "leftabove") == 0) {
    657        cmdinfo.cmdmod.cmod_split |= WSP_ABOVE;
    658      } else if (strcmp(mods->split.data, "belowright") == 0
    659                 || strcmp(mods->split.data, "rightbelow") == 0) {
    660        cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
    661      } else if (strcmp(mods->split.data, "topleft") == 0) {
    662        cmdinfo.cmdmod.cmod_split |= WSP_TOP;
    663      } else if (strcmp(mods->split.data, "botright") == 0) {
    664        cmdinfo.cmdmod.cmod_split |= WSP_BOT;
    665      } else {
    666        VALIDATE_S(false, "mods.split", "", {
    667          goto end;
    668        });
    669      }
    670    }
    671 
    672 #define OBJ_TO_CMOD_FLAG(flag, value) \
    673  if (value) { \
    674    cmdinfo.cmdmod.cmod_flags |= (flag); \
    675  }
    676 
    677    OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent);
    678    OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent);
    679    OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent);
    680    OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox);
    681    OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd);
    682    OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse);
    683    OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm);
    684    OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide);
    685    OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt);
    686    OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps);
    687    OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks);
    688    OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns);
    689    OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks);
    690    OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile);
    691 
    692    if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) {
    693      // CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't
    694      // work properly.
    695      cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT;
    696    }
    697 
    698    VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)),
    699             "%s", "Command cannot be run in sandbox", {
    700      goto end;
    701    });
    702  }
    703 
    704  // Finally, build the command line string that will be stored inside ea.cmdlinep.
    705  // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens.
    706  build_cmdline_str(&cmdline, &ea, &cmdinfo, args);
    707  ea.cmdlinep = &cmdline;
    708 
    709  // Check for "++opt=val" argument.
    710  if (ea.argt & EX_ARGOPT) {
    711    while (ea.arg[0] == '+' && ea.arg[1] == '+') {
    712      char *orig_arg = ea.arg;
    713      int result = getargopt(&ea);
    714      VALIDATE_S(result != FAIL || is_cmd_ni(ea.cmdidx), "argument ", orig_arg, {
    715        goto end;
    716      });
    717    }
    718  }
    719 
    720  // Check for "+command" argument.
    721  if ((ea.argt & EX_CMDARG) && !ea.usefilter) {
    722    ea.do_ecmd_cmd = getargcmd(&ea.arg);
    723  }
    724 
    725  garray_T capture_local;
    726  const int save_msg_silent = msg_silent;
    727  const bool save_redir_off = redir_off;
    728  garray_T * const save_capture_ga = capture_ga;
    729  const int save_msg_col = msg_col;
    730 
    731  if (opts->output) {
    732    ga_init(&capture_local, 1, 80);
    733    capture_ga = &capture_local;
    734  }
    735 
    736  TRY_WRAP(err, {
    737    if (opts->output) {
    738      msg_silent++;
    739      redir_off = false;
    740      msg_col = 0;  // prevent leading spaces
    741    }
    742 
    743    WITH_SCRIPT_CONTEXT(channel_id, {
    744      execute_cmd(&ea, &cmdinfo, false);
    745    });
    746 
    747    if (opts->output) {
    748      capture_ga = save_capture_ga;
    749      msg_silent = save_msg_silent;
    750      redir_off = save_redir_off;
    751      // Put msg_col back where it was, since nothing should have been written.
    752      msg_col = save_msg_col;
    753    }
    754  });
    755 
    756  if (ERROR_SET(err)) {
    757    goto clear_ga;
    758  }
    759 
    760  if (opts->output && capture_local.ga_len > 1) {
    761    // TODO(bfredl): if there are more cases like this we might want custom xfree-list in arena
    762    retv = CBUF_TO_ARENA_STR(arena, capture_local.ga_data, (size_t)capture_local.ga_len);
    763    // redir usually (except :echon) prepends a newline.
    764    if (retv.data[0] == '\n') {
    765      retv.data++;
    766      retv.size--;
    767    }
    768  }
    769 clear_ga:
    770  if (opts->output) {
    771    ga_clear(&capture_local);
    772  }
    773 end:
    774  xfree(cmdline);
    775  xfree(ea.args);
    776  xfree(ea.arglens);
    777 
    778  return retv;
    779 
    780 #undef OBJ_TO_BOOL
    781 #undef OBJ_TO_CMOD_FLAG
    782 #undef VALIDATE_MOD
    783 }
    784 
    785 /// Check if a string contains only whitespace characters.
    786 static bool string_iswhite(String str)
    787 {
    788  for (size_t i = 0; i < str.size; i++) {
    789    if (!ascii_iswhite(str.data[i])) {
    790      // Found a non-whitespace character
    791      return false;
    792    } else if (str.data[i] == NUL) {
    793      // Terminate at first occurrence of a NUL character
    794      break;
    795    }
    796  }
    797  return true;
    798 }
    799 
    800 /// Build cmdline string for command, used by `nvim_cmd()`.
    801 static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo,
    802                              ArrayOf(String) args)
    803 {
    804  size_t argc = args.size;
    805  StringBuilder cmdline = KV_INITIAL_VALUE;
    806  kv_resize(cmdline, 32);  // Make it big enough to handle most typical commands
    807 
    808  // Add command modifiers
    809  if (cmdinfo->cmdmod.cmod_tab != 0) {
    810    kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.cmod_tab - 1);
    811  }
    812  if (cmdinfo->cmdmod.cmod_verbose > 0) {
    813    kv_printf(cmdline, "%dverbose ", cmdinfo->cmdmod.cmod_verbose - 1);
    814  }
    815 
    816  if (cmdinfo->cmdmod.cmod_flags & CMOD_ERRSILENT) {
    817    kv_concat(cmdline, "silent! ");
    818  } else if (cmdinfo->cmdmod.cmod_flags & CMOD_SILENT) {
    819    kv_concat(cmdline, "silent ");
    820  }
    821 
    822  if (cmdinfo->cmdmod.cmod_flags & CMOD_UNSILENT) {
    823    kv_concat(cmdline, "unsilent ");
    824  }
    825 
    826  switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) {
    827  case WSP_ABOVE:
    828    kv_concat(cmdline, "aboveleft ");
    829    break;
    830  case WSP_BELOW:
    831    kv_concat(cmdline, "belowright ");
    832    break;
    833  case WSP_TOP:
    834    kv_concat(cmdline, "topleft ");
    835    break;
    836  case WSP_BOT:
    837    kv_concat(cmdline, "botright ");
    838    break;
    839  default:
    840    break;
    841  }
    842 
    843 #define CMDLINE_APPEND_IF(cond, str) \
    844  do { \
    845    if (cond) { \
    846      kv_concat(cmdline, str); \
    847    } \
    848  } while (0)
    849 
    850  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical ");
    851  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_HOR, "horizontal ");
    852  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox ");
    853  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd ");
    854  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse ");
    855  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_CONFIRM, "confirm ");
    856  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_HIDE, "hide ");
    857  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPALT, "keepalt ");
    858  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPJUMPS, "keepjumps ");
    859  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPMARKS, "keepmarks ");
    860  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPPATTERNS, "keeppatterns ");
    861  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_LOCKMARKS, "lockmarks ");
    862  CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOSWAPFILE, "noswapfile ");
    863 #undef CMDLINE_APPEND_IF
    864 
    865  // Command range / count.
    866  if (eap->argt & EX_RANGE) {
    867    if (eap->addr_count == 1) {
    868      kv_printf(cmdline, "%" PRIdLINENR, eap->line2);
    869    } else if (eap->addr_count > 1) {
    870      kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2);
    871      eap->addr_count = 2;  // Make sure address count is not greater than 2
    872    }
    873  }
    874 
    875  // Keep the index of the position where command name starts, so eap->cmd can point to it.
    876  size_t cmdname_idx = cmdline.size;
    877  kv_concat(cmdline, eap->cmd);
    878 
    879  // Command bang.
    880  if (eap->argt & EX_BANG && eap->forceit) {
    881    kv_concat(cmdline, "!");
    882  }
    883 
    884  // Command register.
    885  if (eap->argt & EX_REGSTR && eap->regname) {
    886    kv_printf(cmdline, " %c", eap->regname);
    887  }
    888 
    889  eap->argc = argc;
    890  eap->arglens = eap->argc > 0 ? xcalloc(argc, sizeof(size_t)) : NULL;
    891  size_t argstart_idx = cmdline.size;
    892  for (size_t i = 0; i < argc; i++) {
    893    String s = args.items[i].data.string;
    894    eap->arglens[i] = s.size;
    895    kv_concat(cmdline, " ");
    896    kv_concat_len(cmdline, s.data, s.size);
    897  }
    898 
    899  // Done appending to cmdline, ensure it is NUL terminated
    900  kv_push(cmdline, NUL);
    901 
    902  // Now that all the arguments are appended, use the command index and argument indices to set the
    903  // values of eap->cmd, eap->arg and eap->args.
    904  eap->cmd = cmdline.items + cmdname_idx;
    905  eap->args = eap->argc > 0 ? xcalloc(argc, sizeof(char *)) : NULL;
    906  size_t offset = argstart_idx;
    907  for (size_t i = 0; i < argc; i++) {
    908    offset++;  // Account for space
    909    eap->args[i] = cmdline.items + offset;
    910    offset += eap->arglens[i];
    911  }
    912  // If there isn't an argument, make eap->arg point to end of cmdline.
    913  eap->arg = argc > 0 ? eap->args[0]
    914                      : cmdline.items + cmdline.size - 1;  // Subtract 1 to account for NUL
    915 
    916  // Finally, make cmdlinep point to the cmdline string.
    917  *cmdlinep = cmdline.items;
    918 
    919  // Replace, :make and :grep with 'makeprg' and 'grepprg'.
    920  char *p = replace_makeprg(eap, eap->arg, cmdlinep);
    921  if (p != eap->arg) {
    922    // If replace_makeprg() modified the cmdline string, correct the eap->arg pointer.
    923    eap->arg = p;
    924    // This cannot be a user command, so eap->args will not be used.
    925    XFREE_CLEAR(eap->args);
    926    XFREE_CLEAR(eap->arglens);
    927    eap->argc = 0;
    928  }
    929 }
    930 
    931 // uncrustify:off
    932 
    933 /// Creates a global |user-commands| command.
    934 ///
    935 /// For Lua usage see |lua-guide-commands-create|.
    936 ///
    937 /// Example:
    938 ///
    939 /// ```vim
    940 /// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
    941 /// :SayHello
    942 /// Hello world!
    943 /// ```
    944 ///
    945 /// @param  name    Name of the new user command. Must begin with an uppercase letter.
    946 /// @param  command Replacement command to execute when this user command is executed. When called
    947 ///                 from Lua, the command can also be a Lua function. The function is called with a
    948 ///                 single table argument that contains the following keys:
    949 ///                 - name: (string) Command name
    950 ///                 - args: (string) The args passed to the command, if any [<args>]
    951 ///                 - fargs: (table) The args split by unescaped whitespace (when more than one
    952 ///                 argument is allowed), if any [<f-args>]
    953 ///                 - nargs: (string) Number of arguments |:command-nargs|
    954 ///                 - bang: (boolean) "true" if the command was executed with a ! modifier [<bang>]
    955 ///                 - line1: (number) The starting line of the command range [<line1>]
    956 ///                 - line2: (number) The final line of the command range [<line2>]
    957 ///                 - range: (number) The number of items in the command range: 0, 1, or 2 [<range>]
    958 ///                 - count: (number) Any count supplied [<count>]
    959 ///                 - reg: (string) The optional register, if specified [<reg>]
    960 ///                 - mods: (string) Command modifiers, if any [<mods>]
    961 ///                 - smods: (table) Command modifiers in a structured format. Has the same
    962 ///                 structure as the "mods" key of |nvim_parse_cmd()|.
    963 /// @param  opts    Optional flags
    964 ///                 - `desc` (string) Command description.
    965 ///                 - `force` (boolean, default true) Override any previous definition.
    966 ///                 - `complete` |:command-complete| command or function like |:command-completion-customlist|.
    967 ///                 - `preview` (function) Preview handler for 'inccommand' |:command-preview|
    968 ///                 - Set boolean |command-attributes| such as |:command-bang| or |:command-bar| to
    969 ///                   true (but not |:command-buffer|, use |nvim_buf_create_user_command()| instead).
    970 /// @param[out] err Error details, if any.
    971 void nvim_create_user_command(uint64_t channel_id,
    972                              String name,
    973                              Union(String, LuaRefOf((DictAs(create_user_command__command_args) args))) command,
    974                              Dict(user_command) *opts,
    975                              Error *err)
    976  FUNC_API_SINCE(9)
    977 {
    978  create_user_command(channel_id, name, command, opts, 0, err);
    979 }
    980 
    981 // uncrustify:on
    982 
    983 /// Delete a user-defined command.
    984 ///
    985 /// @param  name    Name of the command to delete.
    986 /// @param[out] err Error details, if any.
    987 void nvim_del_user_command(String name, Error *err)
    988  FUNC_API_SINCE(9)
    989 {
    990  nvim_buf_del_user_command(-1, name, err);
    991 }
    992 
    993 /// Creates a buffer-local command |user-commands|.
    994 ///
    995 /// @param  buffer  Buffer id, or 0 for current buffer.
    996 /// @param[out] err Error details, if any.
    997 /// @see nvim_create_user_command
    998 void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command,
    999                                  Dict(user_command) *opts, Error *err)
   1000  FUNC_API_SINCE(9)
   1001 {
   1002  buf_T *target_buf = find_buffer_by_handle(buffer, err);
   1003  if (ERROR_SET(err)) {
   1004    return;
   1005  }
   1006 
   1007  buf_T *save_curbuf = curbuf;
   1008  curbuf = target_buf;
   1009  create_user_command(channel_id, name, command, opts, UC_BUFFER, err);
   1010  curbuf = save_curbuf;
   1011 }
   1012 
   1013 /// Delete a buffer-local user-defined command.
   1014 ///
   1015 /// Only commands created with |:command-buffer| or
   1016 /// |nvim_buf_create_user_command()| can be deleted with this function.
   1017 ///
   1018 /// @param  buffer  Buffer id, or 0 for current buffer.
   1019 /// @param  name    Name of the command to delete.
   1020 /// @param[out] err Error details, if any.
   1021 void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
   1022  FUNC_API_SINCE(9)
   1023 {
   1024  garray_T *gap;
   1025  if (buffer == -1) {
   1026    gap = &ucmds;
   1027  } else {
   1028    buf_T *buf = find_buffer_by_handle(buffer, err);
   1029    if (ERROR_SET(err)) {
   1030      return;
   1031    }
   1032    gap = &buf->b_ucmds;
   1033  }
   1034 
   1035  for (int i = 0; i < gap->ga_len; i++) {
   1036    ucmd_T *cmd = USER_CMD_GA(gap, i);
   1037    if (!strcmp(name.data, cmd->uc_name)) {
   1038      free_ucmd(cmd);
   1039 
   1040      gap->ga_len -= 1;
   1041 
   1042      if (i < gap->ga_len) {
   1043        memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
   1044      }
   1045 
   1046      return;
   1047    }
   1048  }
   1049 
   1050  api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data);
   1051 }
   1052 
   1053 void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef) command,
   1054                         Dict(user_command) *opts, int flags, Error *err)
   1055 {
   1056  uint32_t argt = 0;
   1057  int64_t def = -1;
   1058  cmd_addr_T addr_type_arg = ADDR_NONE;
   1059  int context = EXPAND_NOTHING;
   1060  char *compl_arg = NULL;
   1061  const char *rep = NULL;
   1062  LuaRef luaref = LUA_NOREF;
   1063  LuaRef compl_luaref = LUA_NOREF;
   1064  LuaRef preview_luaref = LUA_NOREF;
   1065 
   1066  VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
   1067    goto err;
   1068  });
   1069  VALIDATE_S(!mb_islower(name.data[0]), "command name (must start with uppercase)",
   1070             name.data, {
   1071    goto err;
   1072  });
   1073  VALIDATE((!HAS_KEY(opts, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s",
   1074           "Cannot use both 'range' and 'count'", {
   1075    goto err;
   1076  });
   1077 
   1078  if (opts->nargs.type == kObjectTypeInteger) {
   1079    switch (opts->nargs.data.integer) {
   1080    case 0:
   1081      // Default value, nothing to do
   1082      break;
   1083    case 1:
   1084      argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
   1085      break;
   1086    default:
   1087      VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
   1088        goto err;
   1089      });
   1090    }
   1091  } else if (opts->nargs.type == kObjectTypeString) {
   1092    VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
   1093      goto err;
   1094    });
   1095 
   1096    switch (opts->nargs.data.string.data[0]) {
   1097    case '*':
   1098      argt |= EX_EXTRA;
   1099      break;
   1100    case '?':
   1101      argt |= EX_EXTRA | EX_NOSPC;
   1102      break;
   1103    case '+':
   1104      argt |= EX_EXTRA | EX_NEEDARG;
   1105      break;
   1106    default:
   1107      VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
   1108        goto err;
   1109      });
   1110    }
   1111  } else if (HAS_KEY(opts, user_command, nargs)) {
   1112    VALIDATE_S(false, "nargs", "", {
   1113      goto err;
   1114    });
   1115  }
   1116 
   1117  VALIDATE((!HAS_KEY(opts, user_command, complete) || argt),
   1118           "%s", "'complete' used without 'nargs'", {
   1119    goto err;
   1120  });
   1121 
   1122  if (opts->range.type == kObjectTypeBoolean) {
   1123    if (opts->range.data.boolean) {
   1124      argt |= EX_RANGE;
   1125      addr_type_arg = ADDR_LINES;
   1126    }
   1127  } else if (opts->range.type == kObjectTypeString) {
   1128    VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
   1129               "range", "", {
   1130      goto err;
   1131    });
   1132    argt |= EX_RANGE | EX_DFLALL;
   1133    addr_type_arg = ADDR_LINES;
   1134  } else if (opts->range.type == kObjectTypeInteger) {
   1135    argt |= EX_RANGE | EX_ZEROR;
   1136    def = opts->range.data.integer;
   1137    addr_type_arg = ADDR_LINES;
   1138  } else if (HAS_KEY(opts, user_command, range)) {
   1139    VALIDATE_S(false, "range", "", {
   1140      goto err;
   1141    });
   1142  }
   1143 
   1144  if (opts->count.type == kObjectTypeBoolean) {
   1145    if (opts->count.data.boolean) {
   1146      argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
   1147      addr_type_arg = ADDR_OTHER;
   1148      def = 0;
   1149    }
   1150  } else if (opts->count.type == kObjectTypeInteger) {
   1151    argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
   1152    addr_type_arg = ADDR_OTHER;
   1153    def = opts->count.data.integer;
   1154  } else if (HAS_KEY(opts, user_command, count)) {
   1155    VALIDATE_S(false, "count", "", {
   1156      goto err;
   1157    });
   1158  }
   1159 
   1160  if (HAS_KEY(opts, user_command, addr)) {
   1161    VALIDATE_T("addr", kObjectTypeString, opts->addr.type, {
   1162      goto err;
   1163    });
   1164 
   1165    VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data,
   1166                                         (int)opts->addr.data.string.size, &addr_type_arg), "addr",
   1167               opts->addr.data.string.data, {
   1168      goto err;
   1169    });
   1170 
   1171    argt |= EX_RANGE;
   1172    if (addr_type_arg != ADDR_LINES) {
   1173      argt |= EX_ZEROR;
   1174    }
   1175  }
   1176 
   1177  if (opts->bang) {
   1178    argt |= EX_BANG;
   1179  }
   1180 
   1181  if (opts->bar) {
   1182    argt |= EX_TRLBAR;
   1183  }
   1184 
   1185  if (opts->register_) {
   1186    argt |= EX_REGSTR;
   1187  }
   1188 
   1189  if (opts->keepscript) {
   1190    argt |= EX_KEEPSCRIPT;
   1191  }
   1192 
   1193  bool force = GET_BOOL_OR_TRUE(opts, user_command, force);
   1194  if (ERROR_SET(err)) {
   1195    goto err;
   1196  }
   1197 
   1198  if (opts->complete.type == kObjectTypeLuaRef) {
   1199    context = EXPAND_USER_LUA;
   1200    compl_luaref = opts->complete.data.luaref;
   1201    opts->complete.data.luaref = LUA_NOREF;
   1202  } else if (opts->complete.type == kObjectTypeString) {
   1203    VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data,
   1204                                     (int)opts->complete.data.string.size, &context, &argt,
   1205                                     &compl_arg),
   1206               "complete", opts->complete.data.string.data, {
   1207      goto err;
   1208    });
   1209  } else if (HAS_KEY(opts, user_command, complete)) {
   1210    VALIDATE_EXP(false, "complete", "Function or String", NULL, {
   1211      goto err;
   1212    });
   1213  }
   1214 
   1215  if (HAS_KEY(opts, user_command, preview)) {
   1216    VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, {
   1217      goto err;
   1218    });
   1219 
   1220    argt |= EX_PREVIEW;
   1221    preview_luaref = opts->preview.data.luaref;
   1222    opts->preview.data.luaref = LUA_NOREF;
   1223  }
   1224 
   1225  switch (command.type) {
   1226  case kObjectTypeLuaRef:
   1227    luaref = api_new_luaref(command.data.luaref);
   1228    if (opts->desc.type == kObjectTypeString) {
   1229      rep = opts->desc.data.string.data;
   1230    } else {
   1231      rep = "";
   1232    }
   1233    break;
   1234  case kObjectTypeString:
   1235    rep = command.data.string.data;
   1236    break;
   1237  default:
   1238    VALIDATE_EXP(false, "command", "Function or String", NULL, {
   1239      goto err;
   1240    });
   1241  }
   1242 
   1243  WITH_SCRIPT_CONTEXT(channel_id, {
   1244    if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
   1245                       compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
   1246      api_set_error(err, kErrorTypeException, "Failed to create user command");
   1247      // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
   1248    }
   1249  });
   1250 
   1251  return;
   1252 
   1253 err:
   1254  NLUA_CLEAR_REF(luaref);
   1255  NLUA_CLEAR_REF(compl_luaref);
   1256  xfree(compl_arg);
   1257 }
   1258 /// Gets a map of global (non-buffer-local) Ex commands.
   1259 ///
   1260 /// Currently only |user-commands| are supported, not builtin Ex commands.
   1261 ///
   1262 /// @see |nvim_get_all_options_info()|
   1263 ///
   1264 /// @param  opts  Optional parameters. Currently only supports
   1265 ///               {"builtin":false}
   1266 /// @param[out]  err   Error details, if any.
   1267 ///
   1268 /// @returns Map of maps describing commands.
   1269 DictOf(DictAs(command_info)) nvim_get_commands(Dict(get_commands) *opts, Arena *arena, Error *err)
   1270  FUNC_API_SINCE(4)
   1271 {
   1272  return nvim_buf_get_commands(-1, opts, arena, err);
   1273 }
   1274 
   1275 /// Gets a map of buffer-local |user-commands|.
   1276 ///
   1277 /// @param  buffer  Buffer id, or 0 for current buffer
   1278 /// @param  opts  Optional parameters. Currently not used.
   1279 /// @param[out]  err   Error details, if any.
   1280 ///
   1281 /// @returns Map of maps describing commands.
   1282 DictAs(command_info) nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Arena *arena,
   1283                                           Error *err)
   1284  FUNC_API_SINCE(4)
   1285 {
   1286  bool global = (buffer == -1);
   1287  if (ERROR_SET(err)) {
   1288    return (Dict)ARRAY_DICT_INIT;
   1289  }
   1290 
   1291  if (global) {
   1292    if (opts->builtin) {
   1293      api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
   1294      return (Dict)ARRAY_DICT_INIT;
   1295    }
   1296    return commands_array(NULL, arena);
   1297  }
   1298 
   1299  buf_T *buf = find_buffer_by_handle(buffer, err);
   1300  if (opts->builtin || !buf) {
   1301    return (Dict)ARRAY_DICT_INIT;
   1302  }
   1303  return commands_array(buf, arena);
   1304 }