neovim

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

usercmd.c (49467B)


      1 // usercmd.c: User defined command support
      2 
      3 #include <assert.h>
      4 #include <inttypes.h>
      5 #include <lauxlib.h>
      6 #include <stdbool.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 
     10 #include "auto/config.h"
     11 #include "nvim/api/private/helpers.h"
     12 #include "nvim/ascii_defs.h"
     13 #include "nvim/buffer_defs.h"
     14 #include "nvim/charset.h"
     15 #include "nvim/cmdexpand_defs.h"
     16 #include "nvim/eval.h"
     17 #include "nvim/ex_docmd.h"
     18 #include "nvim/garray.h"
     19 #include "nvim/gettext_defs.h"
     20 #include "nvim/globals.h"
     21 #include "nvim/highlight_defs.h"
     22 #include "nvim/keycodes.h"
     23 #include "nvim/lua/executor.h"
     24 #include "nvim/macros_defs.h"
     25 #include "nvim/mapping.h"
     26 #include "nvim/mbyte.h"
     27 #include "nvim/memory.h"
     28 #include "nvim/memory_defs.h"
     29 #include "nvim/menu.h"
     30 #include "nvim/message.h"
     31 #include "nvim/option_vars.h"
     32 #include "nvim/os/input.h"
     33 #include "nvim/runtime.h"
     34 #include "nvim/strings.h"
     35 #include "nvim/usercmd.h"
     36 #include "nvim/vim_defs.h"
     37 #include "nvim/window.h"
     38 
     39 #include "usercmd.c.generated.h"
     40 
     41 garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
     42 
     43 static const char e_argument_required_for_str[]
     44  = N_("E179: Argument required for %s");
     45 static const char e_no_such_user_defined_command_str[]
     46  = N_("E184: No such user-defined command: %s");
     47 static const char e_complete_used_without_allowing_arguments[]
     48  = N_("E1208: -complete used without allowing arguments");
     49 static const char e_no_such_user_defined_command_in_current_buffer_str[]
     50  = N_("E1237: No such user-defined command in current buffer: %s");
     51 
     52 /// List of names for completion for ":command" with the EXPAND_ flag.
     53 /// Must be alphabetical for completion.
     54 static const char *command_complete[] = {
     55  [EXPAND_ARGLIST] = "arglist",
     56  [EXPAND_AUGROUP] = "augroup",
     57  [EXPAND_BUFFERS] = "buffer",
     58  [EXPAND_CHECKHEALTH] = "checkhealth",
     59  [EXPAND_COLORS] = "color",
     60  [EXPAND_COMMANDS] = "command",
     61  [EXPAND_COMPILER] = "compiler",
     62  [EXPAND_USER_DEFINED] = "custom",
     63  [EXPAND_USER_LIST] = "customlist",
     64  [EXPAND_USER_LUA] = "<Lua function>",
     65  [EXPAND_DIFF_BUFFERS] = "diff_buffer",
     66  [EXPAND_DIRECTORIES] = "dir",
     67  [EXPAND_ENV_VARS] = "environment",
     68  [EXPAND_EVENTS] = "event",
     69  [EXPAND_EXPRESSION] = "expression",
     70  [EXPAND_FILES] = "file",
     71  [EXPAND_FILES_IN_PATH] = "file_in_path",
     72  [EXPAND_FILETYPE] = "filetype",
     73  [EXPAND_FILETYPECMD] = "filetypecmd",
     74  [EXPAND_FUNCTIONS] = "function",
     75  [EXPAND_HELP] = "help",
     76  [EXPAND_HIGHLIGHT] = "highlight",
     77  [EXPAND_HISTORY] = "history",
     78  [EXPAND_KEYMAP] = "keymap",
     79 #ifdef HAVE_WORKING_LIBINTL
     80  [EXPAND_LOCALES] = "locale",
     81 #endif
     82  [EXPAND_LUA] = "lua",
     83  [EXPAND_MAPCLEAR] = "mapclear",
     84  [EXPAND_MAPPINGS] = "mapping",
     85  [EXPAND_MENUS] = "menu",
     86  [EXPAND_MESSAGES] = "messages",
     87  [EXPAND_OWNSYNTAX] = "syntax",
     88  [EXPAND_SYNTIME] = "syntime",
     89  [EXPAND_SETTINGS] = "option",
     90  [EXPAND_PACKADD] = "packadd",
     91  [EXPAND_RETAB] = "retab",
     92  [EXPAND_RUNTIME] = "runtime",
     93  [EXPAND_SHELLCMD] = "shellcmd",
     94  [EXPAND_SHELLCMDLINE] = "shellcmdline",
     95  [EXPAND_SIGN] = "sign",
     96  [EXPAND_TAGS] = "tag",
     97  [EXPAND_TAGS_LISTFILES] = "tag_listfiles",
     98  [EXPAND_USER] = "user",
     99  [EXPAND_USER_VARS] = "var",
    100  [EXPAND_BREAKPOINT] = "breakpoint",
    101  [EXPAND_SCRIPTNAMES] = "scriptnames",
    102  [EXPAND_DIRS_IN_CDPATH] = "dir_in_path",
    103 };
    104 
    105 /// List of names of address types.  Must be alphabetical for completion.
    106 static struct {
    107  cmd_addr_T expand;
    108  char *name;
    109  char *shortname;
    110 } addr_type_complete[] = {
    111  { ADDR_ARGUMENTS, "arguments", "arg" },
    112  { ADDR_LINES, "lines", "line" },
    113  { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" },
    114  { ADDR_TABS, "tabs", "tab" },
    115  { ADDR_BUFFERS, "buffers", "buf" },
    116  { ADDR_WINDOWS, "windows", "win" },
    117  { ADDR_QUICKFIX, "quickfix", "qf" },
    118  { ADDR_OTHER, "other", "?" },
    119  { ADDR_NONE, NULL, NULL }
    120 };
    121 
    122 /// Search for a user command that matches "eap->cmd".
    123 /// Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
    124 /// Return a pointer to just after the command.
    125 /// Return NULL if there is no matching command.
    126 ///
    127 /// @param *p      end of the command (possibly including count)
    128 /// @param full    set to true for a full match
    129 /// @param xp      used for completion, NULL otherwise
    130 /// @param complp  completion flags or NULL
    131 char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp)
    132 {
    133  int len = (int)(p - eap->cmd);
    134  int matchlen = 0;
    135  bool found = false;
    136  bool possible = false;
    137  bool amb_local = false;            // Found ambiguous buffer-local command,
    138                                     // only full match global is accepted.
    139 
    140  // Look for buffer-local user commands first, then global ones.
    141  garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
    142  while (true) {
    143    int j;
    144    for (j = 0; j < gap->ga_len; j++) {
    145      ucmd_T *uc = USER_CMD_GA(gap, j);
    146      char *cp = eap->cmd;
    147      char *np = uc->uc_name;
    148      int k = 0;
    149      while (k < len && *np != NUL && *cp++ == *np++) {
    150        k++;
    151      }
    152      if (k == len || (*np == NUL && ascii_isdigit(eap->cmd[k]))) {
    153        // If finding a second match, the command is ambiguous.  But
    154        // not if a buffer-local command wasn't a full match and a
    155        // global command is a full match.
    156        if (k == len && found && *np != NUL) {
    157          if (gap == &ucmds) {
    158            return NULL;
    159          }
    160          amb_local = true;
    161        }
    162 
    163        if (!found || (k == len && *np == NUL)) {
    164          // If we matched up to a digit, then there could
    165          // be another command including the digit that we
    166          // should use instead.
    167          if (k == len) {
    168            found = true;
    169          } else {
    170            possible = true;
    171          }
    172 
    173          if (gap == &ucmds) {
    174            eap->cmdidx = CMD_USER;
    175          } else {
    176            eap->cmdidx = CMD_USER_BUF;
    177          }
    178          eap->argt = uc->uc_argt;
    179          eap->useridx = j;
    180          eap->addr_type = uc->uc_addr_type;
    181 
    182          if (complp != NULL) {
    183            *complp = uc->uc_compl;
    184          }
    185          if (xp != NULL) {
    186            xp->xp_luaref = uc->uc_compl_luaref;
    187            xp->xp_arg = uc->uc_compl_arg;
    188            xp->xp_script_ctx = uc->uc_script_ctx;
    189            xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
    190          }
    191          // Do not search for further abbreviations
    192          // if this is an exact match.
    193          matchlen = k;
    194          if (k == len && *np == NUL) {
    195            if (full != NULL) {
    196              *full = true;
    197            }
    198            amb_local = false;
    199            break;
    200          }
    201        }
    202      }
    203    }
    204 
    205    // Stop if we found a full match or searched all.
    206    if (j < gap->ga_len || gap == &ucmds) {
    207      break;
    208    }
    209    gap = &ucmds;
    210  }
    211 
    212  // Only found ambiguous matches.
    213  if (amb_local) {
    214    if (xp != NULL) {
    215      xp->xp_context = EXPAND_UNSUCCESSFUL;
    216    }
    217    return NULL;
    218  }
    219 
    220  // The match we found may be followed immediately by a number.  Move "p"
    221  // back to point to it.
    222  if (found || possible) {
    223    return p + (matchlen - len);
    224  }
    225  return p;
    226 }
    227 
    228 /// Set completion context for :command
    229 const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
    230 {
    231  const char *arg = arg_in;
    232  const char *p;
    233 
    234  // Check for attributes
    235  while (*arg == '-') {
    236    arg++;  // Skip "-".
    237    p = skiptowhite(arg);
    238    if (*p == NUL) {
    239      // Cursor is still in the attribute.
    240      p = strchr(arg, '=');
    241      if (p == NULL) {
    242        // No "=", so complete attribute names.
    243        xp->xp_context = EXPAND_USER_CMD_FLAGS;
    244        xp->xp_pattern = (char *)arg;
    245        return NULL;
    246      }
    247 
    248      // For the -complete, -nargs and -addr attributes, we complete
    249      // their arguments as well.
    250      if (STRNICMP(arg, "complete", p - arg) == 0) {
    251        xp->xp_context = EXPAND_USER_COMPLETE;
    252        xp->xp_pattern = (char *)p + 1;
    253        return NULL;
    254      } else if (STRNICMP(arg, "nargs", p - arg) == 0) {
    255        xp->xp_context = EXPAND_USER_NARGS;
    256        xp->xp_pattern = (char *)p + 1;
    257        return NULL;
    258      } else if (STRNICMP(arg, "addr", p - arg) == 0) {
    259        xp->xp_context = EXPAND_USER_ADDR_TYPE;
    260        xp->xp_pattern = (char *)p + 1;
    261        return NULL;
    262      }
    263      return NULL;
    264    }
    265    arg = skipwhite(p);
    266  }
    267 
    268  // After the attributes comes the new command name.
    269  p = skiptowhite(arg);
    270  if (*p == NUL) {
    271    xp->xp_context = EXPAND_USER_COMMANDS;
    272    xp->xp_pattern = (char *)arg;
    273    return NULL;
    274  }
    275 
    276  // And finally comes a normal command.
    277  return skipwhite(p);
    278 }
    279 
    280 /// Set the completion context for the argument of a user defined command.
    281 const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const char *arg,
    282                                       uint32_t argt, int context, expand_T *xp, bool forceit)
    283 {
    284  if (context == EXPAND_NOTHING) {
    285    return NULL;
    286  }
    287 
    288  if (argt & EX_XFILE) {
    289    // EX_XFILE: file names are handled before this call.
    290    return NULL;
    291  }
    292 
    293  if (context == EXPAND_MENUS) {
    294    return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
    295  }
    296  if (context == EXPAND_COMMANDS) {
    297    return arg;
    298  }
    299  if (context == EXPAND_MAPPINGS) {
    300    return set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false,
    301                                  CMD_map);
    302  }
    303  // Find start of last argument.
    304  const char *p = arg;
    305  while (*p) {
    306    if (*p == ' ') {
    307      // argument starts after a space
    308      arg = p + 1;
    309    } else if (*p == '\\' && *(p + 1) != NUL) {
    310      p++;  // skip over escaped character
    311    }
    312    MB_PTR_ADV(p);
    313  }
    314  xp->xp_pattern = (char *)arg;
    315  xp->xp_context = context;
    316 
    317  return NULL;
    318 }
    319 
    320 char *expand_user_command_name(int idx)
    321 {
    322  return get_user_commands(NULL, idx - CMD_SIZE);
    323 }
    324 
    325 /// Function given to ExpandGeneric() to obtain the list of user command names.
    326 char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
    327  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    328 {
    329  // In cmdwin, the alternative buffer should be used.
    330  const buf_T *const buf = prevwin_curwin()->w_buffer;
    331 
    332  if (idx < buf->b_ucmds.ga_len) {
    333    return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
    334  }
    335 
    336  idx -= buf->b_ucmds.ga_len;
    337  if (idx < ucmds.ga_len) {
    338    char *name = USER_CMD(idx)->uc_name;
    339 
    340    for (int i = 0; i < buf->b_ucmds.ga_len; i++) {
    341      if (strcmp(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) {
    342        // global command is overruled by buffer-local one
    343        return "";
    344      }
    345    }
    346    return name;
    347  }
    348  return NULL;
    349 }
    350 
    351 /// Get the name of user command "idx".  "cmdidx" can be CMD_USER or
    352 /// CMD_USER_BUF.
    353 ///
    354 /// @return  NULL if the command is not found.
    355 char *get_user_command_name(int idx, int cmdidx)
    356 {
    357  if (cmdidx == CMD_USER && idx < ucmds.ga_len) {
    358    return USER_CMD(idx)->uc_name;
    359  }
    360  if (cmdidx == CMD_USER_BUF) {
    361    // In cmdwin, the alternative buffer should be used.
    362    const buf_T *const buf = prevwin_curwin()->w_buffer;
    363 
    364    if (idx < buf->b_ucmds.ga_len) {
    365      return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
    366    }
    367  }
    368  return NULL;
    369 }
    370 
    371 /// Function given to ExpandGeneric() to obtain the list of user address type names.
    372 char *get_user_cmd_addr_type(expand_T *xp, int idx)
    373 {
    374  return addr_type_complete[idx].name;
    375 }
    376 
    377 /// Function given to ExpandGeneric() to obtain the list of user command
    378 /// attributes.
    379 char *get_user_cmd_flags(expand_T *xp, int idx)
    380 {
    381  static char *user_cmd_flags[] = { "addr",   "bang",     "bar",
    382                                    "buffer", "complete", "count",
    383                                    "nargs",  "range",    "register",
    384                                    "keepscript" };
    385 
    386  if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) {
    387    return NULL;
    388  }
    389  return user_cmd_flags[idx];
    390 }
    391 
    392 /// Function given to ExpandGeneric() to obtain the list of values for -nargs.
    393 char *get_user_cmd_nargs(expand_T *xp, int idx)
    394 {
    395  static char *user_cmd_nargs[] = { "0", "1", "*", "?", "+" };
    396 
    397  if (idx >= (int)ARRAY_SIZE(user_cmd_nargs)) {
    398    return NULL;
    399  }
    400  return user_cmd_nargs[idx];
    401 }
    402 
    403 static char *get_command_complete(int arg)
    404 {
    405  if (arg < 0 || arg >= (int)(ARRAY_SIZE(command_complete))) {
    406    return NULL;
    407  }
    408  return (char *)command_complete[arg];
    409 }
    410 
    411 /// Function given to ExpandGeneric() to obtain the list of values for -complete.
    412 char *get_user_cmd_complete(expand_T *xp, int idx)
    413 {
    414  if (idx >= (int)ARRAY_SIZE(command_complete)) {
    415    return NULL;
    416  }
    417  char *cmd_compl = get_command_complete(idx);
    418  if (cmd_compl == NULL || idx == EXPAND_USER_LUA) {
    419    return "";
    420  }
    421  return cmd_compl;
    422 }
    423 
    424 /// Get the name of completion type "expand" as an allocated string.
    425 /// "compl_arg" is the function name for "custom" and "customlist" types.
    426 /// Returns NULL if no completion is available.
    427 char *cmdcomplete_type_to_str(int expand, const char *compl_arg)
    428 {
    429  char *cmd_compl = get_command_complete(expand);
    430  if (cmd_compl == NULL || expand == EXPAND_USER_LUA) {
    431    return NULL;
    432  }
    433 
    434  if (expand == EXPAND_USER_LIST || expand == EXPAND_USER_DEFINED) {
    435    size_t buflen = strlen(cmd_compl) + strlen(compl_arg) + 2;
    436    char *buffer = xmalloc(buflen);
    437    snprintf(buffer, buflen, "%s,%s", cmd_compl, compl_arg);
    438    return buffer;
    439  }
    440 
    441  return xstrdup(cmd_compl);
    442 }
    443 
    444 int cmdcomplete_str_to_type(const char *complete_str)
    445 {
    446  if (strncmp(complete_str, "custom,", 7) == 0) {
    447    return EXPAND_USER_DEFINED;
    448  }
    449  if (strncmp(complete_str, "customlist,", 11) == 0) {
    450    return EXPAND_USER_LIST;
    451  }
    452 
    453  for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) {
    454    char *cmd_compl = get_command_complete(i);
    455    if (cmd_compl == NULL) {
    456      continue;
    457    }
    458    if (strcmp(complete_str, command_complete[i]) == 0) {
    459      return i;
    460    }
    461  }
    462 
    463  return EXPAND_NOTHING;
    464 }
    465 
    466 static void uc_list(char *name, size_t name_len)
    467 {
    468  bool found = false;
    469 
    470  msg_ext_set_kind("list_cmd");
    471  // In cmdwin, the alternative buffer should be used.
    472  const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
    473  while (true) {
    474    int i;
    475    for (i = 0; i < gap->ga_len; i++) {
    476      ucmd_T *cmd = USER_CMD_GA(gap, i);
    477      uint32_t a = cmd->uc_argt;
    478 
    479      // Skip commands which don't match the requested prefix and
    480      // commands filtered out.
    481      if (strncmp(name, cmd->uc_name, name_len) != 0
    482          || message_filtered(cmd->uc_name)) {
    483        continue;
    484      }
    485 
    486      // Put out the title first time
    487      if (!found) {
    488        msg_puts_title(_("\n    Name              Args Address Complete    Definition"));
    489      }
    490      found = true;
    491      msg_putchar('\n');
    492      if (got_int) {
    493        break;
    494      }
    495 
    496      // Special cases
    497      size_t len = 4;
    498      if (a & EX_BANG) {
    499        msg_putchar('!');
    500        len--;
    501      }
    502      if (a & EX_REGSTR) {
    503        msg_putchar('"');
    504        len--;
    505      }
    506      if (gap != &ucmds) {
    507        msg_putchar('b');
    508        len--;
    509      }
    510      if (a & EX_TRLBAR) {
    511        msg_putchar('|');
    512        len--;
    513      }
    514      if (len != 0) {
    515        msg_puts(&"    "[4 - len]);
    516      }
    517 
    518      msg_outtrans(cmd->uc_name, HLF_D, false);
    519      len = strlen(cmd->uc_name) + 4;
    520 
    521      if (len < 21) {
    522        // Field padding spaces   12345678901234567
    523        static char spaces[18] = "                 ";
    524        msg_puts(&spaces[len - 4]);
    525        len = 21;
    526      }
    527      msg_putchar(' ');
    528      len++;
    529 
    530      // "over" is how much longer the name is than the column width for
    531      // the name, we'll try to align what comes after.
    532      const int64_t over = (int64_t)len - 22;
    533      len = 0;
    534 
    535      // Arguments
    536      switch (a & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
    537      case 0:
    538        IObuff[len++] = '0';
    539        break;
    540      case (EX_EXTRA):
    541        IObuff[len++] = '*';
    542        break;
    543      case (EX_EXTRA | EX_NOSPC):
    544        IObuff[len++] = '?';
    545        break;
    546      case (EX_EXTRA | EX_NEEDARG):
    547        IObuff[len++] = '+';
    548        break;
    549      case (EX_EXTRA | EX_NOSPC | EX_NEEDARG):
    550        IObuff[len++] = '1';
    551        break;
    552      }
    553 
    554      do {
    555        IObuff[len++] = ' ';
    556      } while ((int64_t)len < 5 - over);
    557 
    558      // Address / Range
    559      if (a & (EX_RANGE | EX_COUNT)) {
    560        if (a & EX_COUNT) {
    561          // -count=N
    562          int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "c", cmd->uc_def);
    563          assert(rc > 0);
    564          len += (size_t)rc;
    565        } else if (a & EX_DFLALL) {
    566          IObuff[len++] = '%';
    567        } else if (cmd->uc_def >= 0) {
    568          // -range=N
    569          int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "", cmd->uc_def);
    570          assert(rc > 0);
    571          len += (size_t)rc;
    572        } else {
    573          IObuff[len++] = '.';
    574        }
    575      }
    576 
    577      do {
    578        IObuff[len++] = ' ';
    579      } while ((int64_t)len < 8 - over);
    580 
    581      // Address Type
    582      for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
    583        if (addr_type_complete[j].expand != ADDR_LINES
    584            && addr_type_complete[j].expand == cmd->uc_addr_type) {
    585          int rc = snprintf(IObuff + len, IOSIZE - len, "%s", addr_type_complete[j].shortname);
    586          assert(rc > 0);
    587          len += (size_t)rc;
    588          break;
    589        }
    590      }
    591 
    592      do {
    593        IObuff[len++] = ' ';
    594      } while ((int64_t)len < 13 - over);
    595 
    596      // Completion
    597      char *cmd_compl = get_command_complete(cmd->uc_compl);
    598      if (cmd_compl != NULL) {
    599        int rc = snprintf(IObuff + len, IOSIZE - len, "%s", get_command_complete(cmd->uc_compl));
    600        assert(rc > 0);
    601        len += (size_t)rc;
    602      }
    603 
    604      do {
    605        IObuff[len++] = ' ';
    606      } while ((int64_t)len < 25 - over);
    607 
    608      IObuff[len] = NUL;
    609      msg_outtrans(IObuff, 0, false);
    610 
    611      if (cmd->uc_luaref != LUA_NOREF) {
    612        char *fn = nlua_funcref_str(cmd->uc_luaref, NULL);
    613        msg_puts_hl(fn, HLF_8, false);
    614        xfree(fn);
    615        // put the description on a new line
    616        if (*cmd->uc_rep != NUL) {
    617          msg_puts("\n                                               ");
    618        }
    619      }
    620 
    621      msg_outtrans_special(cmd->uc_rep, false,
    622                           name_len == 0 ? Columns - 47 : 0);
    623      if (p_verbose > 0) {
    624        last_set_msg(cmd->uc_script_ctx);
    625      }
    626      line_breakcheck();
    627      if (got_int) {
    628        break;
    629      }
    630    }
    631    if (gap == &ucmds || i < gap->ga_len) {
    632      break;
    633    }
    634    gap = &ucmds;
    635  }
    636 
    637  if (!found) {
    638    msg(_("No user-defined commands found"), 0);
    639  }
    640 }
    641 
    642 /// Parse address type argument
    643 int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg)
    644  FUNC_ATTR_NONNULL_ALL
    645 {
    646  int i;
    647 
    648  for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) {
    649    int a = (int)strlen(addr_type_complete[i].name) == vallen;
    650    int b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0;
    651    if (a && b) {
    652      *addr_type_arg = addr_type_complete[i].expand;
    653      break;
    654    }
    655  }
    656 
    657  if (addr_type_complete[i].expand == ADDR_NONE) {
    658    char *err = value;
    659 
    660    for (i = 0; err[i] != NUL && !ascii_iswhite(err[i]); i++) {}
    661    err[i] = NUL;
    662    semsg(_("E180: Invalid address type value: %s"), err);
    663    return FAIL;
    664  }
    665 
    666  return OK;
    667 }
    668 
    669 /// Parse a completion argument "value[vallen]".
    670 /// The detected completion goes in "*complp", argument type in "*argt".
    671 /// When there is an argument, for function and user defined completion, it's
    672 /// copied to allocated memory and stored in "*compl_arg".
    673 ///
    674 /// @return  FAIL if something is wrong.
    675 int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, char **compl_arg)
    676  FUNC_ATTR_NONNULL_ALL
    677 {
    678  const char *arg = NULL;
    679  size_t arglen = 0;
    680  int valend = vallen;
    681 
    682  // Look for any argument part - which is the part after any ','
    683  for (int i = 0; i < vallen; i++) {
    684    if (value[i] == ',') {
    685      arg = (char *)&value[i + 1];
    686      arglen = (size_t)(vallen - i - 1);
    687      valend = i;
    688      break;
    689    }
    690  }
    691 
    692  int i;
    693  for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) {
    694    if (get_command_complete(i) == NULL) {
    695      continue;
    696    }
    697    if ((int)strlen(command_complete[i]) == valend
    698        && strncmp(value, command_complete[i], (size_t)valend) == 0) {
    699      *complp = i;
    700      if (i == EXPAND_BUFFERS) {
    701        *argt |= EX_BUFNAME;
    702      } else if (i == EXPAND_DIRECTORIES || i == EXPAND_FILES
    703                 || i == EXPAND_SHELLCMDLINE) {
    704        *argt |= EX_XFILE;
    705      }
    706      break;
    707    }
    708  }
    709 
    710  if (i == (int)ARRAY_SIZE(command_complete)) {
    711    semsg(_("E180: Invalid complete value: %s"), value);
    712    return FAIL;
    713  }
    714 
    715  if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
    716      && arg != NULL) {
    717    emsg(_("E468: Completion argument only allowed for custom completion"));
    718    return FAIL;
    719  }
    720 
    721  if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
    722      && arg == NULL) {
    723    emsg(_("E467: Custom completion requires a function argument"));
    724    return FAIL;
    725  }
    726 
    727  if (arg != NULL) {
    728    *compl_arg = xstrnsave(arg, arglen);
    729  }
    730  return OK;
    731 }
    732 
    733 static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, int *def, int *flags, int *complp,
    734                        char **compl_arg, cmd_addr_T *addr_type_arg)
    735  FUNC_ATTR_NONNULL_ALL
    736 {
    737  if (len == 0) {
    738    emsg(_("E175: No attribute specified"));
    739    return FAIL;
    740  }
    741 
    742  // First, try the simple attributes (no arguments)
    743  if (STRNICMP(attr, "bang", len) == 0) {
    744    *argt |= EX_BANG;
    745  } else if (STRNICMP(attr, "buffer", len) == 0) {
    746    *flags |= UC_BUFFER;
    747  } else if (STRNICMP(attr, "register", len) == 0) {
    748    *argt |= EX_REGSTR;
    749  } else if (STRNICMP(attr, "keepscript", len) == 0) {
    750    *argt |= EX_KEEPSCRIPT;
    751  } else if (STRNICMP(attr, "bar", len) == 0) {
    752    *argt |= EX_TRLBAR;
    753  } else {
    754    char *val = NULL;
    755    size_t vallen = 0;
    756    size_t attrlen = len;
    757 
    758    // Look for the attribute name - which is the part before any '='
    759    for (int i = 0; i < (int)len; i++) {
    760      if (attr[i] == '=') {
    761        val = &attr[i + 1];
    762        vallen = len - (size_t)i - 1;
    763        attrlen = (size_t)i;
    764        break;
    765      }
    766    }
    767 
    768    if (STRNICMP(attr, "nargs", attrlen) == 0) {
    769      if (vallen == 1) {
    770        if (*val == '0') {
    771          // Do nothing - this is the default;
    772        } else if (*val == '1') {
    773          *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
    774        } else if (*val == '*') {
    775          *argt |= EX_EXTRA;
    776        } else if (*val == '?') {
    777          *argt |= (EX_EXTRA | EX_NOSPC);
    778        } else if (*val == '+') {
    779          *argt |= (EX_EXTRA | EX_NEEDARG);
    780        } else {
    781          goto wrong_nargs;
    782        }
    783      } else {
    784 wrong_nargs:
    785        emsg(_("E176: Invalid number of arguments"));
    786        return FAIL;
    787      }
    788    } else if (STRNICMP(attr, "range", attrlen) == 0) {
    789      *argt |= EX_RANGE;
    790      if (vallen == 1 && *val == '%') {
    791        *argt |= EX_DFLALL;
    792      } else if (val != NULL) {
    793        char *p = val;
    794        if (*def >= 0) {
    795 two_count:
    796          emsg(_("E177: Count cannot be specified twice"));
    797          return FAIL;
    798        }
    799 
    800        *def = getdigits_int(&p, true, 0);
    801        *argt |= EX_ZEROR;
    802 
    803        if (p != val + vallen || vallen == 0) {
    804 invalid_count:
    805          emsg(_("E178: Invalid default value for count"));
    806          return FAIL;
    807        }
    808      }
    809      // default for -range is using buffer lines
    810      if (*addr_type_arg == ADDR_NONE) {
    811        *addr_type_arg = ADDR_LINES;
    812      }
    813    } else if (STRNICMP(attr, "count", attrlen) == 0) {
    814      *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
    815      // default for -count is using any number
    816      if (*addr_type_arg == ADDR_NONE) {
    817        *addr_type_arg = ADDR_OTHER;
    818      }
    819 
    820      if (val != NULL) {
    821        char *p = val;
    822        if (*def >= 0) {
    823          goto two_count;
    824        }
    825 
    826        *def = getdigits_int(&p, true, 0);
    827 
    828        if (p != val + vallen) {
    829          goto invalid_count;
    830        }
    831      }
    832 
    833      *def = MAX(*def, 0);
    834    } else if (STRNICMP(attr, "complete", attrlen) == 0) {
    835      if (val == NULL) {
    836        semsg(_(e_argument_required_for_str), "-complete");
    837        return FAIL;
    838      }
    839 
    840      if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
    841          == FAIL) {
    842        return FAIL;
    843      }
    844    } else if (STRNICMP(attr, "addr", attrlen) == 0) {
    845      *argt |= EX_RANGE;
    846      if (val == NULL) {
    847        semsg(_(e_argument_required_for_str), "-addr");
    848        return FAIL;
    849      }
    850      if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) {
    851        return FAIL;
    852      }
    853      if (*addr_type_arg != ADDR_LINES) {
    854        *argt |= EX_ZEROR;
    855      }
    856    } else {
    857      char ch = attr[len];
    858      attr[len] = NUL;
    859      semsg(_("E181: Invalid attribute: %s"), attr);
    860      attr[len] = ch;
    861      return FAIL;
    862    }
    863  }
    864 
    865  return OK;
    866 }
    867 
    868 /// Check for a valid user command name
    869 ///
    870 /// If the given {name} is valid, then a pointer to the end of the valid name is returned.
    871 /// Otherwise, returns NULL.
    872 char *uc_validate_name(char *name)
    873 {
    874  if (ASCII_ISALPHA(*name)) {
    875    while (ASCII_ISALNUM(*name)) {
    876      name++;
    877    }
    878  }
    879  if (!ends_excmd(*name) && !ascii_iswhite(*name)) {
    880    return NULL;
    881  }
    882 
    883  return name;
    884 }
    885 
    886 /// Create a new user command {name}, if one doesn't already exist.
    887 ///
    888 /// This function takes ownership of compl_arg, compl_luaref, and luaref.
    889 ///
    890 /// @return  OK if the command is created, FAIL otherwise.
    891 int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, int64_t def,
    892                   int flags, int context, char *compl_arg, LuaRef compl_luaref,
    893                   LuaRef preview_luaref, cmd_addr_T addr_type, LuaRef luaref, bool force)
    894  FUNC_ATTR_NONNULL_ARG(1, 3)
    895 {
    896  ucmd_T *cmd = NULL;
    897  int cmp = 1;
    898  char *rep_buf = NULL;
    899  garray_T *gap;
    900 
    901  replace_termcodes(rep, strlen(rep), &rep_buf, 0, 0, NULL, p_cpo);
    902  if (rep_buf == NULL) {
    903    // Can't replace termcodes - try using the string as is
    904    rep_buf = xstrdup(rep);
    905  }
    906 
    907  // get address of growarray: global or in curbuf
    908  if (flags & UC_BUFFER) {
    909    gap = &curbuf->b_ucmds;
    910    if (gap->ga_itemsize == 0) {
    911      ga_init(gap, (int)sizeof(ucmd_T), 4);
    912    }
    913  } else {
    914    gap = &ucmds;
    915  }
    916 
    917  int i;
    918 
    919  // Search for the command in the already defined commands.
    920  for (i = 0; i < gap->ga_len; i++) {
    921    cmd = USER_CMD_GA(gap, i);
    922    size_t len = strlen(cmd->uc_name);
    923    cmp = strncmp(name, cmd->uc_name, name_len);
    924    if (cmp == 0) {
    925      if (name_len < len) {
    926        cmp = -1;
    927      } else if (name_len > len) {
    928        cmp = 1;
    929      }
    930    }
    931 
    932    if (cmp == 0) {
    933      // Command can be replaced with "command!" and when sourcing the
    934      // same script again, but only once.
    935      if (!force
    936          && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
    937              || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) {
    938        semsg(_("E174: Command already exists: add ! to replace it: %s"),
    939              name);
    940        goto fail;
    941      }
    942 
    943      XFREE_CLEAR(cmd->uc_rep);
    944      XFREE_CLEAR(cmd->uc_compl_arg);
    945      NLUA_CLEAR_REF(cmd->uc_luaref);
    946      NLUA_CLEAR_REF(cmd->uc_compl_luaref);
    947      NLUA_CLEAR_REF(cmd->uc_preview_luaref);
    948      break;
    949    }
    950 
    951    // Stop as soon as we pass the name to add
    952    if (cmp < 0) {
    953      break;
    954    }
    955  }
    956 
    957  // Extend the array unless we're replacing an existing command
    958  if (cmp != 0) {
    959    ga_grow(gap, 1);
    960 
    961    char *const p = xstrnsave(name, name_len);
    962 
    963    cmd = USER_CMD_GA(gap, i);
    964    memmove(cmd + 1, cmd, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
    965 
    966    gap->ga_len++;
    967 
    968    cmd->uc_name = p;
    969  }
    970 
    971  cmd->uc_rep = rep_buf;
    972  cmd->uc_argt = argt;
    973  cmd->uc_def = def;
    974  cmd->uc_compl = context;
    975  cmd->uc_script_ctx = current_sctx;
    976  cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
    977  nlua_set_sctx(&cmd->uc_script_ctx);
    978  cmd->uc_compl_arg = compl_arg;
    979  cmd->uc_compl_luaref = compl_luaref;
    980  cmd->uc_preview_luaref = preview_luaref;
    981  cmd->uc_addr_type = addr_type;
    982  cmd->uc_luaref = luaref;
    983 
    984  return OK;
    985 
    986 fail:
    987  xfree(rep_buf);
    988  xfree(compl_arg);
    989  NLUA_CLEAR_REF(luaref);
    990  NLUA_CLEAR_REF(compl_luaref);
    991  NLUA_CLEAR_REF(preview_luaref);
    992  return FAIL;
    993 }
    994 
    995 /// ":command ..."
    996 void ex_command(exarg_T *eap)
    997 {
    998  char *end;
    999  uint32_t argt = 0;
   1000  int def = -1;
   1001  int flags = 0;
   1002  int context = EXPAND_NOTHING;
   1003  char *compl_arg = NULL;
   1004  cmd_addr_T addr_type_arg = ADDR_NONE;
   1005  int has_attr = (eap->arg[0] == '-');
   1006 
   1007  char *p = eap->arg;
   1008 
   1009  // Check for attributes
   1010  while (*p == '-') {
   1011    p++;
   1012    end = skiptowhite(p);
   1013    if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &context, &compl_arg,
   1014                     &addr_type_arg) == FAIL) {
   1015      goto theend;
   1016    }
   1017    p = skipwhite(end);
   1018  }
   1019 
   1020  // Get the name (if any) and skip to the following argument.
   1021  char *name = p;
   1022  end = uc_validate_name(name);
   1023  if (!end) {
   1024    emsg(_("E182: Invalid command name"));
   1025    goto theend;
   1026  }
   1027  size_t name_len = (size_t)(end - name);
   1028 
   1029  // If there is nothing after the name, and no attributes were specified,
   1030  // we are listing commands
   1031  p = skipwhite(end);
   1032  if (!has_attr && ends_excmd(*p)) {
   1033    uc_list(name, name_len);
   1034  } else if (!ASCII_ISUPPER(*name)) {
   1035    emsg(_("E183: User defined commands must start with an uppercase letter"));
   1036  } else if (name_len <= 4 && strncmp(name, "Next", name_len) == 0) {
   1037    emsg(_("E841: Reserved name, cannot be used for user defined command"));
   1038  } else if (context > 0 && (argt & EX_EXTRA) == 0) {
   1039    emsg(_(e_complete_used_without_allowing_arguments));
   1040  } else {
   1041    uc_add_command(name, name_len, p, argt, def, flags, context, compl_arg, LUA_NOREF, LUA_NOREF,
   1042                   addr_type_arg, LUA_NOREF, eap->forceit);
   1043 
   1044    return;  // success
   1045  }
   1046 
   1047 theend:
   1048  xfree(compl_arg);
   1049 }
   1050 
   1051 /// ":comclear"
   1052 /// Clear all user commands, global and for current buffer.
   1053 void ex_comclear(exarg_T *eap)
   1054 {
   1055  uc_clear(&ucmds);
   1056  if (curbuf != NULL) {
   1057    uc_clear(&curbuf->b_ucmds);
   1058  }
   1059 }
   1060 
   1061 void free_ucmd(ucmd_T *cmd)
   1062 {
   1063  xfree(cmd->uc_name);
   1064  xfree(cmd->uc_rep);
   1065  xfree(cmd->uc_compl_arg);
   1066  NLUA_CLEAR_REF(cmd->uc_compl_luaref);
   1067  NLUA_CLEAR_REF(cmd->uc_luaref);
   1068  NLUA_CLEAR_REF(cmd->uc_preview_luaref);
   1069 }
   1070 
   1071 /// Clear all user commands for "gap".
   1072 void uc_clear(garray_T *gap)
   1073 {
   1074  GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd);
   1075 }
   1076 
   1077 void ex_delcommand(exarg_T *eap)
   1078 {
   1079  int i = 0;
   1080  ucmd_T *cmd = NULL;
   1081  int res = -1;
   1082  const char *arg = eap->arg;
   1083  bool buffer_only = false;
   1084 
   1085  if (strncmp(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) {
   1086    buffer_only = true;
   1087    arg = skipwhite(arg + 7);
   1088  }
   1089 
   1090  garray_T *gap = &curbuf->b_ucmds;
   1091  while (true) {
   1092    for (i = 0; i < gap->ga_len; i++) {
   1093      cmd = USER_CMD_GA(gap, i);
   1094      res = strcmp(arg, cmd->uc_name);
   1095      if (res <= 0) {
   1096        break;
   1097      }
   1098    }
   1099    if (gap == &ucmds || res == 0 || buffer_only) {
   1100      break;
   1101    }
   1102    gap = &ucmds;
   1103  }
   1104 
   1105  if (res != 0) {
   1106    semsg(_(buffer_only
   1107            ? e_no_such_user_defined_command_in_current_buffer_str
   1108            : e_no_such_user_defined_command_str),
   1109          arg);
   1110    return;
   1111  }
   1112 
   1113  free_ucmd(cmd);
   1114 
   1115  gap->ga_len--;
   1116 
   1117  if (i < gap->ga_len) {
   1118    memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T));
   1119  }
   1120 }
   1121 
   1122 /// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback.
   1123 /// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator.
   1124 ///
   1125 /// @param[in]  arg String to split
   1126 /// @param[in]  arglen Length of {arg}
   1127 /// @param[inout] end Index of last character of previous iteration
   1128 /// @param[out] buf Buffer to copy string into
   1129 /// @param[out] len Length of string in {buf}
   1130 ///
   1131 /// @return true if iteration is complete, else false
   1132 bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, size_t *len)
   1133 {
   1134  if (!arglen) {
   1135    return true;
   1136  }
   1137 
   1138  size_t pos = *end;
   1139  while (pos < arglen && ascii_iswhite(arg[pos])) {
   1140    pos++;
   1141  }
   1142 
   1143  size_t l = 0;
   1144  for (; pos < arglen - 1; pos++) {
   1145    if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) {
   1146      buf[l++] = arg[++pos];
   1147    } else {
   1148      buf[l++] = arg[pos];
   1149    }
   1150    if (ascii_iswhite(arg[pos + 1])) {
   1151      *end = pos + 1;
   1152      *len = l;
   1153      return false;
   1154    }
   1155  }
   1156 
   1157  if (pos < arglen && !ascii_iswhite(arg[pos])) {
   1158    buf[l++] = arg[pos];
   1159  }
   1160 
   1161  *len = l;
   1162  return true;
   1163 }
   1164 
   1165 size_t uc_nargs_upper_bound(const char *arg, size_t arglen)
   1166 {
   1167  bool was_white = true;  // space before first arg
   1168  size_t nargs = 0;
   1169  for (size_t i = 0; i < arglen; i++) {
   1170    bool is_white = ascii_iswhite(arg[i]);
   1171    if (was_white && !is_white) {
   1172      nargs++;
   1173    }
   1174    was_white = is_white;
   1175  }
   1176  return nargs;
   1177 }
   1178 
   1179 /// split and quote args for <f-args>
   1180 static char *uc_split_args(const char *arg, char **args, const size_t *arglens, size_t argc,
   1181                           size_t *lenp)
   1182 {
   1183  // Precalculate length
   1184  int len = 2;   // Initial and final quotes
   1185  if (args == NULL) {
   1186    const char *p = arg;
   1187 
   1188    while (*p) {
   1189      if (p[0] == '\\' && p[1] == '\\') {
   1190        len += 2;
   1191        p += 2;
   1192      } else if (p[0] == '\\' && ascii_iswhite(p[1])) {
   1193        len += 1;
   1194        p += 2;
   1195      } else if (*p == '\\' || *p == '"') {
   1196        len += 2;
   1197        p += 1;
   1198      } else if (ascii_iswhite(*p)) {
   1199        p = skipwhite(p);
   1200        if (*p == NUL) {
   1201          break;
   1202        }
   1203        len += 4;  // ", "
   1204      } else {
   1205        const int charlen = utfc_ptr2len(p);
   1206 
   1207        len += charlen;
   1208        p += charlen;
   1209      }
   1210    }
   1211  } else {
   1212    for (size_t i = 0; i < argc; i++) {
   1213      const char *p = args[i];
   1214      const char *arg_end = args[i] + arglens[i];
   1215 
   1216      while (p < arg_end) {
   1217        if (*p == '\\' || *p == '"') {
   1218          len += 2;
   1219          p += 1;
   1220        } else {
   1221          const int charlen = utfc_ptr2len(p);
   1222 
   1223          len += charlen;
   1224          p += charlen;
   1225        }
   1226      }
   1227 
   1228      if (i != argc - 1) {
   1229        len += 4;  // ", "
   1230      }
   1231    }
   1232  }
   1233 
   1234  char *buf = xmalloc((size_t)len + 1);
   1235 
   1236  char *q = buf;
   1237  *q++ = '"';
   1238 
   1239  if (args == NULL) {
   1240    const char *p = arg;
   1241    while (*p) {
   1242      if (p[0] == '\\' && p[1] == '\\') {
   1243        *q++ = '\\';
   1244        *q++ = '\\';
   1245        p += 2;
   1246      } else if (p[0] == '\\' && ascii_iswhite(p[1])) {
   1247        *q++ = p[1];
   1248        p += 2;
   1249      } else if (*p == '\\' || *p == '"') {
   1250        *q++ = '\\';
   1251        *q++ = *p++;
   1252      } else if (ascii_iswhite(*p)) {
   1253        p = skipwhite(p);
   1254        if (*p == NUL) {
   1255          break;
   1256        }
   1257        *q++ = '"';
   1258        *q++ = ',';
   1259        *q++ = ' ';
   1260        *q++ = '"';
   1261      } else {
   1262        mb_copy_char(&p, &q);
   1263      }
   1264    }
   1265  } else {
   1266    for (size_t i = 0; i < argc; i++) {
   1267      const char *p = args[i];
   1268      const char *arg_end = args[i] + arglens[i];
   1269 
   1270      while (p < arg_end) {
   1271        if (*p == '\\' || *p == '"') {
   1272          *q++ = '\\';
   1273          *q++ = *p++;
   1274        } else {
   1275          mb_copy_char(&p, &q);
   1276        }
   1277      }
   1278      if (i != argc - 1) {
   1279        *q++ = '"';
   1280        *q++ = ',';
   1281        *q++ = ' ';
   1282        *q++ = '"';
   1283      }
   1284    }
   1285  }
   1286 
   1287  *q++ = '"';
   1288  *q = 0;
   1289 
   1290  *lenp = (size_t)len;
   1291  return buf;
   1292 }
   1293 
   1294 static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods)
   1295 {
   1296  size_t result = strlen(mod_str);
   1297  if (*multi_mods) {
   1298    result++;
   1299  }
   1300 
   1301  if (buf != NULL) {
   1302    if (*multi_mods) {
   1303      strcat(buf, " ");
   1304    }
   1305    strcat(buf, mod_str);
   1306  }
   1307 
   1308  *multi_mods = true;
   1309  return result;
   1310 }
   1311 
   1312 /// Add modifiers from "cmod->cmod_split" to "buf".  Set "multi_mods" when one
   1313 /// was added.
   1314 ///
   1315 /// @return the number of bytes added
   1316 size_t add_win_cmd_modifiers(char *buf, const cmdmod_T *cmod, bool *multi_mods)
   1317 {
   1318  size_t result = 0;
   1319 
   1320  // :aboveleft and :leftabove
   1321  if (cmod->cmod_split & WSP_ABOVE) {
   1322    result += add_cmd_modifier(buf, "aboveleft", multi_mods);
   1323  }
   1324  // :belowright and :rightbelow
   1325  if (cmod->cmod_split & WSP_BELOW) {
   1326    result += add_cmd_modifier(buf, "belowright", multi_mods);
   1327  }
   1328  // :botright
   1329  if (cmod->cmod_split & WSP_BOT) {
   1330    result += add_cmd_modifier(buf, "botright", multi_mods);
   1331  }
   1332 
   1333  // :tab
   1334  if (cmod->cmod_tab > 0) {
   1335    int tabnr = cmod->cmod_tab - 1;
   1336    if (tabnr == tabpage_index(curtab)) {
   1337      // For compatibility, don't add a tabpage number if it is the same
   1338      // as the default number for :tab.
   1339      result += add_cmd_modifier(buf, "tab", multi_mods);
   1340    } else {
   1341      char tab_buf[NUMBUFLEN + 3];
   1342      snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
   1343      result += add_cmd_modifier(buf, tab_buf, multi_mods);
   1344    }
   1345  }
   1346 
   1347  // :topleft
   1348  if (cmod->cmod_split & WSP_TOP) {
   1349    result += add_cmd_modifier(buf, "topleft", multi_mods);
   1350  }
   1351  // :vertical
   1352  if (cmod->cmod_split & WSP_VERT) {
   1353    result += add_cmd_modifier(buf, "vertical", multi_mods);
   1354  }
   1355  // :horizontal
   1356  if (cmod->cmod_split & WSP_HOR) {
   1357    result += add_cmd_modifier(buf, "horizontal", multi_mods);
   1358  }
   1359  return result;
   1360 }
   1361 
   1362 /// Generate text for the "cmod" command modifiers.
   1363 /// If "buf" is NULL just return the length.
   1364 size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote)
   1365 {
   1366  size_t result = 0;
   1367  bool multi_mods = false;
   1368 
   1369  typedef struct {
   1370    int flag;
   1371    char *name;
   1372  } mod_entry_T;
   1373  static mod_entry_T mod_entries[] = {
   1374    { CMOD_BROWSE, "browse" },
   1375    { CMOD_CONFIRM, "confirm" },
   1376    { CMOD_HIDE, "hide" },
   1377    { CMOD_KEEPALT, "keepalt" },
   1378    { CMOD_KEEPJUMPS, "keepjumps" },
   1379    { CMOD_KEEPMARKS, "keepmarks" },
   1380    { CMOD_KEEPPATTERNS, "keeppatterns" },
   1381    { CMOD_LOCKMARKS, "lockmarks" },
   1382    { CMOD_NOSWAPFILE, "noswapfile" },
   1383    { CMOD_UNSILENT, "unsilent" },
   1384    { CMOD_NOAUTOCMD, "noautocmd" },
   1385    { CMOD_SANDBOX, "sandbox" },
   1386  };
   1387 
   1388  result = quote ? 2 : 0;
   1389  if (buf != NULL) {
   1390    if (quote) {
   1391      *buf++ = '"';
   1392    }
   1393    *buf = NUL;
   1394  }
   1395 
   1396  // the modifiers that are simple flags
   1397  for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
   1398    if (cmod->cmod_flags & mod_entries[i].flag) {
   1399      result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
   1400    }
   1401  }
   1402 
   1403  // :silent
   1404  if (cmod->cmod_flags & CMOD_SILENT) {
   1405    result += add_cmd_modifier(buf,
   1406                               (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!" : "silent",
   1407                               &multi_mods);
   1408  }
   1409  // :verbose
   1410  if (cmod->cmod_verbose > 0) {
   1411    int verbose_value = cmod->cmod_verbose - 1;
   1412    if (verbose_value == 1) {
   1413      result += add_cmd_modifier(buf, "verbose", &multi_mods);
   1414    } else {
   1415      char verbose_buf[NUMBUFLEN];
   1416      snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
   1417      result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
   1418    }
   1419  }
   1420  // flags from cmod->cmod_split
   1421  result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
   1422 
   1423  if (quote && buf != NULL) {
   1424    buf += result - 2;
   1425    *buf = '"';
   1426  }
   1427  return result;
   1428 }
   1429 
   1430 /// Check for a <> code in a user command.
   1431 ///
   1432 /// @param code       points to the '<'.  "len" the length of the <> (inclusive).
   1433 /// @param buf        is where the result is to be added.
   1434 /// @param cmd        the user command we're expanding
   1435 /// @param eap        ex arguments
   1436 /// @param split_buf  points to a buffer used for splitting, caller should free it.
   1437 /// @param split_len  is the length of what "split_buf" contains.
   1438 ///
   1439 /// @return           the length of the replacement, which has been added to "buf".
   1440 ///                   Return -1 if there was no match, and only the "<" has been copied.
   1441 static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exarg_T *eap,
   1442                            char **split_buf, size_t *split_len)
   1443 {
   1444  size_t result = 0;
   1445  char *p = code + 1;
   1446  size_t l = len - 2;
   1447  int quote = 0;
   1448  enum {
   1449    ct_ARGS,
   1450    ct_BANG,
   1451    ct_COUNT,
   1452    ct_LINE1,
   1453    ct_LINE2,
   1454    ct_RANGE,
   1455    ct_MODS,
   1456    ct_REGISTER,
   1457    ct_LT,
   1458    ct_NONE,
   1459  } type = ct_NONE;
   1460 
   1461  if ((vim_strchr("qQfF", (uint8_t)(*p)) != NULL) && p[1] == '-') {
   1462    quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
   1463    p += 2;
   1464    l -= 2;
   1465  }
   1466 
   1467  l++;
   1468  if (l <= 1) {
   1469    // type = ct_NONE;
   1470  } else if (STRNICMP(p, "args>", l) == 0) {
   1471    type = ct_ARGS;
   1472  } else if (STRNICMP(p, "bang>", l) == 0) {
   1473    type = ct_BANG;
   1474  } else if (STRNICMP(p, "count>", l) == 0) {
   1475    type = ct_COUNT;
   1476  } else if (STRNICMP(p, "line1>", l) == 0) {
   1477    type = ct_LINE1;
   1478  } else if (STRNICMP(p, "line2>", l) == 0) {
   1479    type = ct_LINE2;
   1480  } else if (STRNICMP(p, "range>", l) == 0) {
   1481    type = ct_RANGE;
   1482  } else if (STRNICMP(p, "lt>", l) == 0) {
   1483    type = ct_LT;
   1484  } else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0) {
   1485    type = ct_REGISTER;
   1486  } else if (STRNICMP(p, "mods>", l) == 0) {
   1487    type = ct_MODS;
   1488  }
   1489 
   1490  switch (type) {
   1491  case ct_ARGS:
   1492    // Simple case first
   1493    if (*eap->arg == NUL) {
   1494      if (quote == 1) {
   1495        result = 2;
   1496        if (buf != NULL) {
   1497          STRCPY(buf, "''");
   1498        }
   1499      } else {
   1500        result = 0;
   1501      }
   1502      break;
   1503    }
   1504 
   1505    // When specified there is a single argument don't split it.
   1506    // Works for ":Cmd %" when % is "a b c".
   1507    if ((eap->argt & EX_NOSPC) && quote == 2) {
   1508      quote = 1;
   1509    }
   1510 
   1511    switch (quote) {
   1512    case 0:     // No quoting, no splitting
   1513      result = strlen(eap->arg);
   1514      if (buf != NULL) {
   1515        STRCPY(buf, eap->arg);
   1516      }
   1517      break;
   1518    case 1:     // Quote, but don't split
   1519      result = strlen(eap->arg) + 2;
   1520      for (p = eap->arg; *p; p++) {
   1521        if (*p == '\\' || *p == '"') {
   1522          result++;
   1523        }
   1524      }
   1525 
   1526      if (buf != NULL) {
   1527        *buf++ = '"';
   1528        for (p = eap->arg; *p; p++) {
   1529          if (*p == '\\' || *p == '"') {
   1530            *buf++ = '\\';
   1531          }
   1532          *buf++ = *p;
   1533        }
   1534        *buf = '"';
   1535      }
   1536 
   1537      break;
   1538    case 2:     // Quote and split (<f-args>)
   1539      // This is hard, so only do it once, and cache the result
   1540      if (*split_buf == NULL) {
   1541        *split_buf = uc_split_args(eap->arg, eap->args, eap->arglens, eap->argc, split_len);
   1542      }
   1543 
   1544      result = *split_len;
   1545      if (buf != NULL && result != 0) {
   1546        STRCPY(buf, *split_buf);
   1547      }
   1548 
   1549      break;
   1550    }
   1551    break;
   1552 
   1553  case ct_BANG:
   1554    result = eap->forceit ? 1 : 0;
   1555    if (quote) {
   1556      result += 2;
   1557    }
   1558    if (buf != NULL) {
   1559      if (quote) {
   1560        *buf++ = '"';
   1561      }
   1562      if (eap->forceit) {
   1563        *buf++ = '!';
   1564      }
   1565      if (quote) {
   1566        *buf = '"';
   1567      }
   1568    }
   1569    break;
   1570 
   1571  case ct_LINE1:
   1572  case ct_LINE2:
   1573  case ct_RANGE:
   1574  case ct_COUNT: {
   1575    char num_buf[20];
   1576    int64_t num = type == ct_LINE1
   1577                  ? eap->line1
   1578                  : (type == ct_LINE2
   1579                     ? eap->line2
   1580                     : (type == ct_RANGE
   1581                        ? eap->addr_count
   1582                        : (eap->addr_count > 0 ? eap->line2 : cmd->uc_def)));
   1583    size_t num_len;
   1584 
   1585    snprintf(num_buf, sizeof(num_buf), "%" PRId64, num);
   1586    num_len = strlen(num_buf);
   1587    result = num_len;
   1588 
   1589    if (quote) {
   1590      result += 2;
   1591    }
   1592 
   1593    if (buf != NULL) {
   1594      if (quote) {
   1595        *buf++ = '"';
   1596      }
   1597      STRCPY(buf, num_buf);
   1598      buf += num_len;
   1599      if (quote) {
   1600        *buf = '"';
   1601      }
   1602    }
   1603 
   1604    break;
   1605  }
   1606 
   1607  case ct_MODS:
   1608    result = uc_mods(buf, &cmdmod, quote);
   1609    break;
   1610 
   1611  case ct_REGISTER:
   1612    result = eap->regname ? 1 : 0;
   1613    if (quote) {
   1614      result += 2;
   1615    }
   1616    if (buf != NULL) {
   1617      if (quote) {
   1618        *buf++ = '\'';
   1619      }
   1620      if (eap->regname) {
   1621        *buf++ = (char)eap->regname;
   1622      }
   1623      if (quote) {
   1624        *buf = '\'';
   1625      }
   1626    }
   1627    break;
   1628 
   1629  case ct_LT:
   1630    result = 1;
   1631    if (buf != NULL) {
   1632      *buf = '<';
   1633    }
   1634    break;
   1635 
   1636  default:
   1637    // Not recognized: just copy the '<' and return -1.
   1638    result = (size_t)-1;
   1639    if (buf != NULL) {
   1640      *buf = '<';
   1641    }
   1642    break;
   1643  }
   1644 
   1645  return result;
   1646 }
   1647 
   1648 int do_ucmd(exarg_T *eap, bool preview)
   1649 {
   1650  char *end = NULL;
   1651 
   1652  size_t split_len = 0;
   1653  char *split_buf = NULL;
   1654  ucmd_T *cmd;
   1655 
   1656  if (eap->cmdidx == CMD_USER) {
   1657    cmd = USER_CMD(eap->useridx);
   1658  } else {
   1659    cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
   1660  }
   1661 
   1662  if (preview) {
   1663    assert(cmd->uc_preview_luaref > 0);
   1664    return nlua_do_ucmd(cmd, eap, true);
   1665  }
   1666 
   1667  if (cmd->uc_luaref > 0) {
   1668    nlua_do_ucmd(cmd, eap, false);
   1669    return 0;
   1670  }
   1671 
   1672  // Replace <> in the command by the arguments.
   1673  // First round: "buf" is NULL, compute length, allocate "buf".
   1674  // Second round: copy result into "buf".
   1675  char *buf = NULL;
   1676  while (true) {
   1677    char *p = cmd->uc_rep;        // source
   1678    char *q = buf;                // destination
   1679    size_t totlen = 0;
   1680 
   1681    while (true) {
   1682      char *start = vim_strchr(p, '<');
   1683      if (start != NULL) {
   1684        end = vim_strchr(start + 1, '>');
   1685      }
   1686      if (buf != NULL) {
   1687        char *ksp;
   1688        for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {}
   1689        if ((uint8_t)(*ksp) == K_SPECIAL
   1690            && (start == NULL || ksp < start || end == NULL)
   1691            && ((uint8_t)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) {
   1692          // K_SPECIAL has been put in the buffer as K_SPECIAL
   1693          // KS_SPECIAL KE_FILLER, like for mappings, but
   1694          // do_cmdline() doesn't handle that, so convert it back.
   1695          size_t len = (size_t)(ksp - p);
   1696          if (len > 0) {
   1697            memmove(q, p, len);
   1698            q += len;
   1699          }
   1700          *q++ = (char)K_SPECIAL;
   1701          p = ksp + 3;
   1702          continue;
   1703        }
   1704      }
   1705 
   1706      // break if no <item> is found
   1707      if (start == NULL || end == NULL) {
   1708        break;
   1709      }
   1710 
   1711      // Include the '>'
   1712      end++;
   1713 
   1714      // Take everything up to the '<'
   1715      size_t len = (size_t)(start - p);
   1716      if (buf == NULL) {
   1717        totlen += len;
   1718      } else {
   1719        memmove(q, p, len);
   1720        q += len;
   1721      }
   1722 
   1723      len = uc_check_code(start, (size_t)(end - start), q, cmd, eap, &split_buf, &split_len);
   1724      if (len == (size_t)-1) {
   1725        // no match, continue after '<'
   1726        p = start + 1;
   1727        len = 1;
   1728      } else {
   1729        p = end;
   1730      }
   1731      if (buf == NULL) {
   1732        totlen += len;
   1733      } else {
   1734        q += len;
   1735      }
   1736    }
   1737    if (buf != NULL) {              // second time here, finished
   1738      STRCPY(q, p);
   1739      break;
   1740    }
   1741 
   1742    totlen += strlen(p);            // Add on the trailing characters
   1743    buf = xmalloc(totlen + 1);
   1744  }
   1745 
   1746  sctx_T save_current_sctx;
   1747  bool restore_current_sctx = false;
   1748  if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) {
   1749    restore_current_sctx = true;
   1750    save_current_sctx = current_sctx;
   1751    current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
   1752  }
   1753  do_cmdline(buf, eap->ea_getline, eap->cookie,
   1754             DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
   1755 
   1756  // Careful: Do not use "cmd" here, it may have become invalid if a user
   1757  // command was added.
   1758  if (restore_current_sctx) {
   1759    current_sctx = save_current_sctx;
   1760  }
   1761  xfree(buf);
   1762  xfree(split_buf);
   1763 
   1764  return 0;
   1765 }
   1766 
   1767 /// Gets a map of maps describing user-commands defined for buffer `buf` or
   1768 /// defined globally if `buf` is NULL.
   1769 ///
   1770 /// @param buf  Buffer to inspect, or NULL to get global commands.
   1771 ///
   1772 /// @return Map of maps describing commands
   1773 Dict commands_array(buf_T *buf, Arena *arena)
   1774 {
   1775  garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds;
   1776 
   1777  Dict rv = arena_dict(arena, (size_t)gap->ga_len);
   1778  for (int i = 0; i < gap->ga_len; i++) {
   1779    char arg[2] = { 0, 0 };
   1780    Dict d = arena_dict(arena, 16);
   1781    ucmd_T *cmd = USER_CMD_GA(gap, i);
   1782 
   1783    PUT_C(d, "name", CSTR_AS_OBJ(cmd->uc_name));
   1784    PUT_C(d, "definition", CSTR_AS_OBJ(cmd->uc_rep));
   1785    PUT_C(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid));
   1786    PUT_C(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG)));
   1787    PUT_C(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
   1788    PUT_C(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
   1789    PUT_C(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
   1790 
   1791    if (cmd->uc_preview_luaref != LUA_NOREF) {
   1792      PUT_C(d, "preview", LUAREF_OBJ(api_new_luaref(cmd->uc_preview_luaref)));
   1793    }
   1794 
   1795    if (cmd->uc_luaref != LUA_NOREF) {
   1796      PUT_C(d, "callback", LUAREF_OBJ(api_new_luaref(cmd->uc_luaref)));
   1797    }
   1798 
   1799    switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
   1800    case 0:
   1801      arg[0] = '0'; break;
   1802    case (EX_EXTRA):
   1803      arg[0] = '*'; break;
   1804    case (EX_EXTRA | EX_NOSPC):
   1805      arg[0] = '?'; break;
   1806    case (EX_EXTRA | EX_NEEDARG):
   1807      arg[0] = '+'; break;
   1808    case (EX_EXTRA | EX_NOSPC | EX_NEEDARG):
   1809      arg[0] = '1'; break;
   1810    }
   1811    PUT_C(d, "nargs", CSTR_TO_ARENA_OBJ(arena, arg));
   1812 
   1813    if (cmd->uc_compl_luaref != LUA_NOREF) {
   1814      PUT_C(d, "complete", LUAREF_OBJ(api_new_luaref(cmd->uc_compl_luaref)));
   1815    } else {
   1816      char *cmd_compl = get_command_complete(cmd->uc_compl);
   1817      PUT_C(d, "complete", (cmd_compl == NULL ? NIL : CSTR_AS_OBJ(cmd_compl)));
   1818    }
   1819    PUT_C(d, "complete_arg", cmd->uc_compl_arg == NULL
   1820          ? NIL : CSTR_AS_OBJ(cmd->uc_compl_arg));
   1821 
   1822    Object obj = NIL;
   1823    if (cmd->uc_argt & EX_COUNT) {
   1824      if (cmd->uc_def >= 0) {
   1825        obj = STRING_OBJ(arena_printf(arena, "%" PRId64, cmd->uc_def));    // -count=N
   1826      } else {
   1827        obj = CSTR_AS_OBJ("0");    // -count
   1828      }
   1829    }
   1830    PUT_C(d, "count", obj);
   1831 
   1832    obj = NIL;
   1833    if (cmd->uc_argt & EX_RANGE) {
   1834      if (cmd->uc_argt & EX_DFLALL) {
   1835        obj = STATIC_CSTR_AS_OBJ("%");    // -range=%
   1836      } else if (cmd->uc_def >= 0) {
   1837        obj = STRING_OBJ(arena_printf(arena, "%" PRId64, cmd->uc_def));    // -range=N
   1838      } else {
   1839        obj = STATIC_CSTR_AS_OBJ(".");    // -range
   1840      }
   1841    }
   1842    PUT_C(d, "range", obj);
   1843 
   1844    obj = NIL;
   1845    for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
   1846      if (addr_type_complete[j].expand != ADDR_LINES
   1847          && addr_type_complete[j].expand == cmd->uc_addr_type) {
   1848        obj = CSTR_AS_OBJ(addr_type_complete[j].name);
   1849        break;
   1850      }
   1851    }
   1852    PUT_C(d, "addr", obj);
   1853 
   1854    PUT_C(rv, cmd->uc_name, DICT_OBJ(d));
   1855  }
   1856  return rv;
   1857 }