neovim

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

cmdexpand.c (127257B)


      1 // cmdexpand.c: functions for command-line completion
      2 
      3 #include <assert.h>
      4 #include <stdbool.h>
      5 #include <stdint.h>
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <uv.h>
     10 
     11 #include "nvim/api/private/defs.h"
     12 #include "nvim/api/private/helpers.h"
     13 #include "nvim/arglist.h"
     14 #include "nvim/ascii_defs.h"
     15 #include "nvim/autocmd.h"
     16 #include "nvim/buffer.h"
     17 #include "nvim/buffer_defs.h"
     18 #include "nvim/charset.h"
     19 #include "nvim/cmdexpand.h"
     20 #include "nvim/cmdhist.h"
     21 #include "nvim/drawscreen.h"
     22 #include "nvim/errors.h"
     23 #include "nvim/eval.h"
     24 #include "nvim/eval/funcs.h"
     25 #include "nvim/eval/typval.h"
     26 #include "nvim/eval/typval_defs.h"
     27 #include "nvim/eval/userfunc.h"
     28 #include "nvim/eval/vars.h"
     29 #include "nvim/ex_cmds.h"
     30 #include "nvim/ex_cmds_defs.h"
     31 #include "nvim/ex_docmd.h"
     32 #include "nvim/ex_getln.h"
     33 #include "nvim/fuzzy.h"
     34 #include "nvim/garray.h"
     35 #include "nvim/garray_defs.h"
     36 #include "nvim/getchar.h"
     37 #include "nvim/gettext_defs.h"
     38 #include "nvim/globals.h"
     39 #include "nvim/grid.h"
     40 #include "nvim/hashtab.h"
     41 #include "nvim/hashtab_defs.h"
     42 #include "nvim/help.h"
     43 #include "nvim/highlight.h"
     44 #include "nvim/highlight_defs.h"
     45 #include "nvim/highlight_group.h"
     46 #include "nvim/insexpand.h"
     47 #include "nvim/keycodes.h"
     48 #include "nvim/lua/executor.h"
     49 #include "nvim/macros_defs.h"
     50 #include "nvim/mapping.h"
     51 #include "nvim/mbyte.h"
     52 #include "nvim/mbyte_defs.h"
     53 #include "nvim/memline.h"
     54 #include "nvim/memory.h"
     55 #include "nvim/menu.h"
     56 #include "nvim/message.h"
     57 #include "nvim/option.h"
     58 #include "nvim/option_vars.h"
     59 #include "nvim/os/fs.h"
     60 #include "nvim/os/lang.h"
     61 #include "nvim/os/os.h"
     62 #include "nvim/os/os_defs.h"
     63 #include "nvim/path.h"
     64 #include "nvim/popupmenu.h"
     65 #include "nvim/profile.h"
     66 #include "nvim/regexp.h"
     67 #include "nvim/runtime.h"
     68 #include "nvim/runtime_defs.h"
     69 #include "nvim/search.h"
     70 #include "nvim/sign.h"
     71 #include "nvim/statusline.h"
     72 #include "nvim/strings.h"
     73 #include "nvim/syntax.h"
     74 #include "nvim/tag.h"
     75 #include "nvim/types_defs.h"
     76 #include "nvim/ui.h"
     77 #include "nvim/ui_defs.h"
     78 #include "nvim/usercmd.h"
     79 #include "nvim/vim_defs.h"
     80 #include "nvim/window.h"
     81 
     82 /// Type used by call_user_expand_func
     83 typedef void *(*user_expand_func_T)(const char *, int, typval_T *);
     84 
     85 #include "cmdexpand.c.generated.h"
     86 
     87 static bool cmd_showtail;  ///< Only show path tail in lists ?
     88 static bool may_expand_pattern = false;
     89 static pos_T pre_incsearch_pos;  ///< Cursor position when incsearch started
     90 
     91 /// "compl_match_array" points the currently displayed list of entries in the
     92 /// popup menu.  It is NULL when there is no popup menu.
     93 static pumitem_T *compl_match_array = NULL;
     94 static int compl_match_arraysize;
     95 /// First column in cmdline of the matched item for completion.
     96 static int compl_startcol;
     97 static int compl_selected;
     98 /// cmdline before expansion
     99 static char *cmdline_orig = NULL;
    100 
    101 #define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m])
    102 
    103 /// Returns true if fuzzy completion is supported for a given cmdline completion
    104 /// context.
    105 static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
    106  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
    107 {
    108  switch (xp->xp_context) {
    109  case EXPAND_BOOL_SETTINGS:
    110  case EXPAND_COLORS:
    111  case EXPAND_COMPILER:
    112  case EXPAND_DIRECTORIES:
    113  case EXPAND_DIRS_IN_CDPATH:
    114  case EXPAND_FILES:
    115  case EXPAND_FILES_IN_PATH:
    116  case EXPAND_FILETYPE:
    117  case EXPAND_FILETYPECMD:
    118  case EXPAND_FINDFUNC:
    119  case EXPAND_HELP:
    120  case EXPAND_KEYMAP:
    121  case EXPAND_LUA:
    122  case EXPAND_OLD_SETTING:
    123  case EXPAND_STRING_SETTING:
    124  case EXPAND_SETTING_SUBTRACT:
    125  case EXPAND_OWNSYNTAX:
    126  case EXPAND_PACKADD:
    127  case EXPAND_RUNTIME:
    128  case EXPAND_SHELLCMD:
    129  case EXPAND_SHELLCMDLINE:
    130  case EXPAND_TAGS:
    131  case EXPAND_TAGS_LISTFILES:
    132  case EXPAND_USER_LIST:
    133  case EXPAND_USER_LUA:
    134    return false;
    135 
    136  default:
    137    break;
    138  }
    139 
    140  return wop_flags & kOptWopFlagFuzzy;
    141 }
    142 
    143 /// Returns true if fuzzy completion for cmdline completion is enabled and
    144 /// "fuzzystr" is not empty.  If search pattern is empty, then don't use fuzzy
    145 /// matching.
    146 bool cmdline_fuzzy_complete(const char *const fuzzystr)
    147  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
    148 {
    149  return (wop_flags & kOptWopFlagFuzzy) && *fuzzystr != NUL;
    150 }
    151 
    152 /// Sort function for the completion matches.
    153 /// <SNR> functions should be sorted to the end.
    154 static int sort_func_compare(const void *s1, const void *s2)
    155 {
    156  char *p1 = *(char **)s1;
    157  char *p2 = *(char **)s2;
    158 
    159  if (*p1 != '<' && *p2 == '<') {
    160    return -1;
    161  }
    162  if (*p1 == '<' && *p2 != '<') {
    163    return 1;
    164  }
    165  return strcmp(p1, p2);
    166 }
    167 
    168 /// Escape special characters in the cmdline completion matches.
    169 static void wildescape(expand_T *xp, const char *str, int numfiles, char **files)
    170 {
    171  char *p;
    172  const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE;
    173 
    174  if (xp->xp_context == EXPAND_FILES
    175      || xp->xp_context == EXPAND_FILES_IN_PATH
    176      || xp->xp_context == EXPAND_SHELLCMD
    177      || xp->xp_context == EXPAND_BUFFERS
    178      || xp->xp_context == EXPAND_DIRECTORIES
    179      || xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
    180    // Insert a backslash into a file name before a space, \, %, #
    181    // and wildmatch characters, except '~'.
    182    for (int i = 0; i < numfiles; i++) {
    183      // for ":set path=" we need to escape spaces twice
    184      if (xp->xp_backslash & XP_BS_THREE) {
    185        char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " ";
    186        p = vim_strsave_escaped(files[i], pat);
    187        xfree(files[i]);
    188        files[i] = p;
    189 #if defined(BACKSLASH_IN_FILENAME)
    190        p = vim_strsave_escaped(files[i], " ");
    191        xfree(files[i]);
    192        files[i] = p;
    193 #endif
    194      } else if (xp->xp_backslash & XP_BS_COMMA) {
    195        if (vim_strchr(files[i], ',') != NULL) {
    196          p = vim_strsave_escaped(files[i], ",");
    197          xfree(files[i]);
    198          files[i] = p;
    199        }
    200      }
    201 #ifdef BACKSLASH_IN_FILENAME
    202      p = vim_strsave_fnameescape(files[i], vse_what);
    203 #else
    204      p = vim_strsave_fnameescape(files[i], xp->xp_shell ? VSE_SHELL : vse_what);
    205 #endif
    206      xfree(files[i]);
    207      files[i] = p;
    208 
    209      // If 'str' starts with "\~", replace "~" at start of
    210      // files[i] with "\~".
    211      if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') {
    212        escape_fname(&files[i]);
    213      }
    214    }
    215    xp->xp_backslash = XP_BS_NONE;
    216 
    217    // If the first file starts with a '+' escape it.  Otherwise it
    218    // could be seen as "+cmd".
    219    if (*files[0] == '+') {
    220      escape_fname(&files[0]);
    221    }
    222  } else if (xp->xp_context == EXPAND_TAGS) {
    223    // Insert a backslash before characters in a tag name that
    224    // would terminate the ":tag" command.
    225    for (int i = 0; i < numfiles; i++) {
    226      p = vim_strsave_escaped(files[i], "\\|\"");
    227      xfree(files[i]);
    228      files[i] = p;
    229    }
    230  }
    231 }
    232 
    233 /// Escape special characters in the cmdline completion matches.
    234 static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options)
    235 {
    236  // May change home directory back to "~"
    237  if (options & WILD_HOME_REPLACE) {
    238    tilde_replace(str, numfiles, files);
    239  }
    240 
    241  if (options & WILD_ESCAPE) {
    242    wildescape(xp, str, numfiles, files);
    243  }
    244 }
    245 
    246 /// Return FAIL if this is not an appropriate context in which to do
    247 /// completion of anything, return OK if it is (even if there are no matches).
    248 /// For the caller, this means that the character is just passed through like a
    249 /// normal character (instead of being expanded).  This allows :s/^I^D etc.
    250 ///
    251 /// @param options  extra options for ExpandOne()
    252 /// @param escape  if true, escape the returned matches
    253 int nextwild(expand_T *xp, int type, int options, bool escape)
    254 {
    255  CmdlineInfo *const ccline = get_cmdline_info();
    256  char *p;
    257  bool from_wildtrigger_func = options & WILD_FUNC_TRIGGER;
    258  bool wild_navigate = (type == WILD_NEXT || type == WILD_PREV
    259                        || type == WILD_PAGEUP || type == WILD_PAGEDOWN
    260                        || type == WILD_PUM_WANT);
    261 
    262  if (xp->xp_numfiles == -1) {
    263    pre_incsearch_pos = xp->xp_pre_incsearch_pos;
    264    if (ccline->input_fn && ccline->xp_context == EXPAND_COMMANDS) {
    265      // Expand commands typed in input() function
    266      set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, false);
    267    } else {
    268      may_expand_pattern = options & WILD_MAY_EXPAND_PATTERN;
    269      set_expand_context(xp);
    270      may_expand_pattern = false;
    271    }
    272    if (xp->xp_context == EXPAND_LUA) {
    273      nlua_expand_pat(xp);
    274    }
    275    cmd_showtail = expand_showtail(xp);
    276  }
    277 
    278  if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
    279    beep_flush();
    280    return OK;      // Something illegal on command line
    281  }
    282  if (xp->xp_context == EXPAND_NOTHING) {
    283    // Caller can use the character as a normal char instead
    284    return FAIL;
    285  }
    286 
    287  int i = (int)(xp->xp_pattern - ccline->cmdbuff);
    288  assert(ccline->cmdpos >= i);
    289  xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
    290 
    291  // Skip showing matches if prefix is invalid during wildtrigger()
    292  if (from_wildtrigger_func && xp->xp_context == EXPAND_COMMANDS
    293      && xp->xp_pattern_len == 0) {
    294    return FAIL;
    295  }
    296 
    297  // If cmd_silent is set then don't show the dots, because redrawcmd() below
    298  // won't remove them.
    299  if (!cmd_silent && !from_wildtrigger_func && !wild_navigate
    300      && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) {
    301    msg_puts("...");  // show that we are busy
    302    ui_flush();
    303  }
    304 
    305  if (wild_navigate) {
    306    // Get next/previous match for a previous expanded pattern.
    307    p = ExpandOne(xp, NULL, NULL, 0, type);
    308  } else {
    309    char *tmp;
    310    if (cmdline_fuzzy_completion_supported(xp)
    311        || xp->xp_context == EXPAND_PATTERN_IN_BUF) {
    312      // Don't modify the search string
    313      tmp = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
    314    } else {
    315      tmp = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
    316    }
    317    // Translate string into pattern and expand it.
    318    const int use_options = (options
    319                             | WILD_HOME_REPLACE
    320                             | WILD_ADD_SLASH
    321                             | WILD_SILENT
    322                             | (escape ? WILD_ESCAPE : 0)
    323                             | (p_wic ? WILD_ICASE : 0));
    324    p = ExpandOne(xp, tmp, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len),
    325                  use_options, type);
    326    xfree(tmp);
    327    // Longest match: make sure it is not shorter, happens with :help.
    328    if (p != NULL && type == WILD_LONGEST) {
    329      int j;
    330      for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
    331        char c = ccline->cmdbuff[i + j];
    332        if (c == '*' || c == '?') {
    333          break;
    334        }
    335      }
    336      if ((int)strlen(p) < j) {
    337        XFREE_CLEAR(p);
    338      }
    339    }
    340  }
    341 
    342  // Save cmdline before inserting selected item
    343  if (!wild_navigate && ccline->cmdbuff != NULL) {
    344    xfree(cmdline_orig);
    345    cmdline_orig = xstrnsave(ccline->cmdbuff, (size_t)ccline->cmdlen);
    346  }
    347 
    348  if (p != NULL && !got_int && !(options & WILD_NOSELECT)) {
    349    size_t plen = strlen(p);
    350    int difflen = (int)plen - (int)(xp->xp_pattern_len);
    351    if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) {
    352      realloc_cmdbuff(ccline->cmdlen + difflen + 4);
    353      xp->xp_pattern = ccline->cmdbuff + i;
    354    }
    355    assert(ccline->cmdpos <= ccline->cmdlen);
    356    memmove(&ccline->cmdbuff[ccline->cmdpos + difflen],
    357            &ccline->cmdbuff[ccline->cmdpos],
    358            (size_t)ccline->cmdlen - (size_t)ccline->cmdpos + 1);
    359    memmove(&ccline->cmdbuff[i], p, plen);
    360    ccline->cmdlen += difflen;
    361    ccline->cmdpos += difflen;
    362  }
    363 
    364  redrawcmd();
    365  cursorcmd();
    366 
    367  // When expanding a ":map" command and no matches are found, assume that
    368  // the key is supposed to be inserted literally
    369  if (xp->xp_context == EXPAND_MAPPINGS && p == NULL) {
    370    return FAIL;
    371  }
    372 
    373  if (xp->xp_numfiles <= 0 && p == NULL) {
    374    beep_flush();
    375  } else if (xp->xp_numfiles == 1 && !(options & WILD_NOSELECT) && !wild_navigate) {
    376    // free expanded pattern
    377    ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
    378  }
    379 
    380  xfree(p);
    381 
    382  return OK;
    383 }
    384 
    385 /// Create completion popup menu with items from "matches".
    386 static void cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches,
    387                               bool showtail, bool noselect)
    388 {
    389  assert(numMatches >= 0);
    390  // Add all the completion matches
    391  compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)numMatches);
    392  compl_match_arraysize = numMatches;
    393  for (int i = 0; i < numMatches; i++) {
    394    compl_match_array[i] = (pumitem_T){
    395      .pum_text = SHOW_MATCH(i),
    396      .pum_info = NULL,
    397      .pum_extra = NULL,
    398      .pum_kind = NULL,
    399      .pum_user_abbr_hlattr = -1,
    400      .pum_user_kind_hlattr = -1,
    401    };
    402  }
    403 
    404  // Compute the popup menu starting column
    405  char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, noselect) : xp->xp_pattern;
    406  if (ui_has(kUICmdline) && cmdline_win == NULL) {
    407    compl_startcol = (int)(endpos - ccline->cmdbuff);
    408  } else {
    409    compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff));
    410  }
    411 }
    412 
    413 void cmdline_pum_display(bool changed_array)
    414 {
    415  pum_display(compl_match_array, compl_match_arraysize, compl_selected,
    416              changed_array, compl_startcol);
    417 }
    418 
    419 /// Returns true if the cmdline completion popup menu is being displayed.
    420 bool cmdline_pum_active(void)
    421 {
    422  return pum_visible() && compl_match_array != NULL;
    423 }
    424 
    425 /// Remove the cmdline completion popup menu (if present), free the list of items.
    426 void cmdline_pum_remove(bool defer_redraw)
    427 {
    428  pum_undisplay(!defer_redraw);
    429  XFREE_CLEAR(compl_match_array);
    430  compl_match_arraysize = 0;
    431 }
    432 
    433 void cmdline_pum_cleanup(CmdlineInfo *cclp)
    434 {
    435  cmdline_pum_remove(false);
    436  wildmenu_cleanup(cclp);
    437 }
    438 
    439 /// Returns the current cmdline completion pattern.
    440 char *cmdline_compl_pattern(void)
    441 {
    442  expand_T *xp = get_cmdline_info()->xpc;
    443  return xp == NULL ? NULL : xp->xp_orig;
    444 }
    445 
    446 /// Returns true if fuzzy cmdline completion is active, false otherwise.
    447 bool cmdline_compl_is_fuzzy(void)
    448 {
    449  expand_T *xp = get_cmdline_info()->xpc;
    450  return xp != NULL && cmdline_fuzzy_completion_supported(xp);
    451 }
    452 
    453 /// Checks whether popup menu should be used for cmdline completion wildmenu.
    454 ///
    455 /// @param wildmenu  whether wildmenu is needed by current 'wildmode' part
    456 static bool cmdline_compl_use_pum(bool need_wildmenu)
    457 {
    458  return ((need_wildmenu && (wop_flags & kOptWopFlagPum)
    459           && !(ui_has(kUICmdline) && cmdline_win == NULL))
    460          || ui_has(kUIWildmenu) || (ui_has(kUICmdline) && ui_has(kUIPopupmenu)));
    461 }
    462 
    463 /// Return the number of characters that should be skipped in the wildmenu
    464 /// These are backslashes used for escaping.  Do show backslashes in help tags
    465 /// and in search pattern completion matches.
    466 static int skip_wildmenu_char(expand_T *xp, char *s)
    467 {
    468  if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP
    469       && xp->xp_context != EXPAND_PATTERN_IN_BUF)
    470      || ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES)
    471          && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) {
    472 #ifndef BACKSLASH_IN_FILENAME
    473    // TODO(bfredl): Why in the actual fuck are we special casing the
    474    // shell variety deep in the redraw logic? Shell special snowflakiness
    475    // should already be eliminated multiple layers before reaching the
    476    // screen infracstructure.
    477    if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') {
    478      return 2;
    479    }
    480 #endif
    481    return 1;
    482  }
    483  return 0;
    484 }
    485 
    486 /// Get the length of an item as it will be shown in the status line.
    487 static int wildmenu_match_len(expand_T *xp, char *s)
    488 {
    489  int len = 0;
    490 
    491  int emenu = (xp->xp_context == EXPAND_MENUS
    492               || xp->xp_context == EXPAND_MENUNAMES);
    493 
    494  // Check for menu separators - replace with '|'.
    495  if (emenu && menu_is_separator(s)) {
    496    return 1;
    497  }
    498 
    499  while (*s != NUL) {
    500    s += skip_wildmenu_char(xp, s);
    501    len += ptr2cells(s);
    502    MB_PTR_ADV(s);
    503  }
    504 
    505  return len;
    506 }
    507 
    508 /// Show wildchar matches in the status line.
    509 /// Show at least the "match" item.
    510 /// We start at item "first_match" in the list and show all matches that fit.
    511 ///
    512 /// If inversion is possible we use it. Else '=' characters are used.
    513 ///
    514 /// @param matches  list of matches
    515 static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, bool showtail)
    516 {
    517  bool highlight = true;
    518  char *selstart = NULL;
    519  int selstart_col = 0;
    520  char *selend = NULL;
    521  static int first_match = 0;
    522  bool add_left = false;
    523  int i, l;
    524 
    525  if (matches == NULL) {        // interrupted completion?
    526    return;
    527  }
    528 
    529  char *buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1);
    530 
    531  if (match == -1) {    // don't show match but original text
    532    match = 0;
    533    highlight = false;
    534  }
    535  // count 1 for the ending ">"
    536  int clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3;  // length in screen cells
    537  if (match == 0) {
    538    first_match = 0;
    539  } else if (match < first_match) {
    540    // jumping left, as far as we can go
    541    first_match = match;
    542    add_left = true;
    543  } else {
    544    // check if match fits on the screen
    545    for (i = first_match; i < match; i++) {
    546      clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2;
    547    }
    548    if (first_match > 0) {
    549      clen += 2;
    550    }
    551    // jumping right, put match at the left
    552    if (clen > Columns) {
    553      first_match = match;
    554      // if showing the last match, we can add some on the left
    555      clen = 2;
    556      for (i = match; i < num_matches; i++) {
    557        clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2;
    558        if (clen >= Columns) {
    559          break;
    560        }
    561      }
    562      if (i == num_matches) {
    563        add_left = true;
    564      }
    565    }
    566  }
    567  if (add_left) {
    568    while (first_match > 0) {
    569      clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2;
    570      if (clen >= Columns) {
    571        break;
    572      }
    573      first_match--;
    574    }
    575  }
    576 
    577  int len;
    578  hlf_T group;
    579  schar_T fillchar = fillchar_status(&group, curwin);
    580  int attr = win_hl_attr(curwin, (int)group);
    581 
    582  if (first_match == 0) {
    583    *buf = NUL;
    584    len = 0;
    585  } else {
    586    STRCPY(buf, "< ");
    587    len = 2;
    588  }
    589  clen = len;
    590 
    591  i = first_match;
    592  while (clen + wildmenu_match_len(xp, SHOW_MATCH(i)) + 2 < Columns) {
    593    if (i == match) {
    594      selstart = buf + len;
    595      selstart_col = clen;
    596    }
    597 
    598    char *s = SHOW_MATCH(i);
    599    // Check for menu separators - replace with '|'
    600    int emenu = (xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES);
    601    if (emenu && menu_is_separator(s)) {
    602      STRCPY(buf + len, transchar('|'));
    603      l = (int)strlen(buf + len);
    604      len += l;
    605      clen += l;
    606    } else {
    607      for (; *s != NUL; s++) {
    608        s += skip_wildmenu_char(xp, s);
    609        clen += ptr2cells(s);
    610        if ((l = utfc_ptr2len(s)) > 1) {
    611          strncpy(buf + len, s, (size_t)l);  // NOLINT(runtime/printf)
    612          s += l - 1;
    613          len += l;
    614        } else {
    615          STRCPY(buf + len, transchar_byte((uint8_t)(*s)));
    616          len += (int)strlen(buf + len);
    617        }
    618      }
    619    }
    620    if (i == match) {
    621      selend = buf + len;
    622    }
    623 
    624    *(buf + len++) = ' ';
    625    *(buf + len++) = ' ';
    626    clen += 2;
    627    if (++i == num_matches) {
    628      break;
    629    }
    630  }
    631 
    632  if (i != num_matches) {
    633    *(buf + len++) = '>';
    634    clen++;
    635  }
    636 
    637  buf[len] = NUL;
    638 
    639  int row = cmdline_row - 1;
    640  if (row >= 0) {
    641    if (wild_menu_showing == 0) {
    642      if (msg_scrolled > 0) {
    643        // Put the wildmenu just above the command line.  If there is
    644        // no room, scroll the screen one line up.
    645        if (cmdline_row == Rows - 1) {
    646          msg_scroll_up(false, false);
    647          msg_scrolled++;
    648        } else {
    649          cmdline_row++;
    650          row++;
    651        }
    652        wild_menu_showing = WM_SCROLLED;
    653      } else {
    654        // Create status line if needed by setting 'laststatus' to 2.
    655        // Set 'winminheight' to zero to avoid that the window is
    656        // resized.
    657        if (lastwin->w_status_height == 0 && global_stl_height() == 0) {
    658          save_p_ls = (int)p_ls;
    659          save_p_wmh = (int)p_wmh;
    660          p_ls = 2;
    661          p_wmh = 0;
    662          last_status(false);
    663        }
    664        wild_menu_showing = WM_SHOWN;
    665      }
    666    }
    667 
    668    // Tricky: wildmenu can be drawn either over a status line, or at empty
    669    // scrolled space in the message output
    670    grid_line_start((wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_gridview, row);
    671 
    672    grid_line_puts(0, buf, -1, attr);
    673    if (selstart != NULL && highlight) {
    674      *selend = NUL;
    675      grid_line_puts(selstart_col, selstart, -1, HL_ATTR(HLF_WM));
    676    }
    677 
    678    grid_line_fill(clen, Columns, fillchar, attr);
    679 
    680    grid_line_flush();
    681  }
    682 
    683  win_redraw_last_status(topframe);
    684  xfree(buf);
    685 }
    686 
    687 /// Get the next or prev cmdline completion match. The index of the match is set
    688 /// in "xp->xp_selected"
    689 static char *get_next_or_prev_match(int mode, expand_T *xp)
    690 {
    691  // When no matches found, return NULL
    692  if (xp->xp_numfiles <= 0) {
    693    return NULL;
    694  }
    695 
    696  int findex = xp->xp_selected;
    697 
    698  if (mode == WILD_PREV) {
    699    // Select the last entry if at original text
    700    if (findex == -1) {
    701      findex = xp->xp_numfiles;
    702    }
    703    // Otherwise select the previous entry
    704    findex--;
    705  } else if (mode == WILD_NEXT) {
    706    // Select the next entry
    707    findex++;
    708  } else if (mode == WILD_PAGEUP || mode == WILD_PAGEDOWN) {
    709    // Get the height of popup menu (used for both PAGEUP and PAGEDOWN)
    710    int ht = pum_get_height();
    711    if (ht > 3) {
    712      ht -= 2;
    713    }
    714 
    715    if (mode == WILD_PAGEUP) {
    716      if (findex == 0) {
    717        // at the first entry, don't select any entries
    718        findex = -1;
    719      } else if (findex < 0) {
    720        // no entry is selected. select the last entry
    721        findex = xp->xp_numfiles - 1;
    722      } else {
    723        // go up by the pum height
    724        findex = MAX(findex - ht, 0);
    725      }
    726    } else {  // mode == WILD_PAGEDOWN
    727      if (findex == xp->xp_numfiles - 1) {
    728        // at the last entry, don't select any entries
    729        findex = -1;
    730      } else if (findex < 0) {
    731        // no entry is selected. select the first entry
    732        findex = 0;
    733      } else {
    734        // go down by the pum height
    735        findex = MIN(findex + ht, xp->xp_numfiles - 1);
    736      }
    737    }
    738  } else {  // mode == WILD_PUM_WANT
    739    assert(pum_want.active);
    740    findex = pum_want.item;
    741  }
    742 
    743  // Handle wrapping around
    744  if (findex < 0 || findex >= xp->xp_numfiles) {
    745    // If original text exists, return to it when wrapping around
    746    if (xp->xp_orig != NULL) {
    747      findex = -1;
    748    } else {
    749      // Wrap around to opposite end
    750      findex = (findex < 0) ? xp->xp_numfiles - 1 : 0;
    751    }
    752  }
    753 
    754  // Display matches on screen
    755  if (p_wmnu) {
    756    if (compl_match_array) {
    757      compl_selected = findex;
    758      cmdline_pum_display(false);
    759    } else if (cmdline_compl_use_pum(true)) {
    760      cmdline_pum_create(get_cmdline_info(), xp, xp->xp_files, xp->xp_numfiles,
    761                         cmd_showtail, false);
    762      compl_selected = findex;
    763      pum_clear();
    764      cmdline_pum_display(true);
    765    } else {
    766      redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail);
    767    }
    768  }
    769 
    770  xp->xp_selected = findex;
    771  // Return the original text or the selected match
    772  return xstrdup(findex == -1 ? xp->xp_orig : xp->xp_files[findex]);
    773 }
    774 
    775 /// Start the command-line expansion and get the matches.
    776 static char *ExpandOne_start(int mode, expand_T *xp, char *str, int options)
    777 {
    778  int non_suf_match;  // number without matching suffix
    779  char *ss = NULL;
    780 
    781  // Do the expansion.
    782  if (ExpandFromContext(xp, str, &xp->xp_files, &xp->xp_numfiles, options) == FAIL) {
    783 #ifdef FNAME_ILLEGAL
    784    // Illegal file name has been silently skipped.  But when there
    785    // are wildcards, the real problem is that there was no match,
    786    // causing the pattern to be added, which has illegal characters.
    787    if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) {
    788      semsg(_(e_nomatch2), str);
    789    }
    790 #endif
    791  } else if (xp->xp_numfiles == 0) {
    792    if (!(options & WILD_SILENT)) {
    793      semsg(_(e_nomatch2), str);
    794    }
    795  } else {
    796    // Escape the matches for use on the command line.
    797    ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options);
    798 
    799    // Check for matching suffixes in file names.
    800    if (mode != WILD_ALL && mode != WILD_ALL_KEEP && mode != WILD_LONGEST) {
    801      if (xp->xp_numfiles) {
    802        non_suf_match = xp->xp_numfiles;
    803      } else {
    804        non_suf_match = 1;
    805      }
    806      if ((xp->xp_context == EXPAND_FILES
    807           || xp->xp_context == EXPAND_DIRECTORIES)
    808          && xp->xp_numfiles > 1) {
    809        // More than one match; check suffix.
    810        // The files will have been sorted on matching suffix in
    811        // expand_wildcards, only need to check the first two.
    812        non_suf_match = 0;
    813        for (int i = 0; i < 2; i++) {
    814          if (match_suffix(xp->xp_files[i])) {
    815            non_suf_match++;
    816          }
    817        }
    818      }
    819      if (non_suf_match != 1) {
    820        // Can we ever get here unless it's while expanding
    821        // interactively?  If not, we can get rid of this all
    822        // together. Don't really want to wait for this message
    823        // (and possibly have to hit return to continue!).
    824        if (!(options & WILD_SILENT)) {
    825          emsg(_(e_toomany));
    826        } else if (!(options & WILD_NO_BEEP)) {
    827          beep_flush();
    828        }
    829      }
    830      if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) {
    831        ss = xstrdup(xp->xp_files[0]);
    832      }
    833    }
    834  }
    835 
    836  return ss;
    837 }
    838 
    839 /// Return the longest common part in the list of cmdline completion matches.
    840 static char *find_longest_match(expand_T *xp, int options)
    841 {
    842  size_t len = 0;
    843 
    844  for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) {
    845    mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]);
    846    int c0 = utf_ptr2char(&xp->xp_files[0][len]);
    847    int i;
    848    for (i = 1; i < xp->xp_numfiles; i++) {
    849      int ci = utf_ptr2char(&xp->xp_files[i][len]);
    850 
    851      if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES
    852                    || xp->xp_context == EXPAND_FILES
    853                    || xp->xp_context == EXPAND_SHELLCMD
    854                    || xp->xp_context == EXPAND_BUFFERS)) {
    855        if (mb_tolower(c0) != mb_tolower(ci)) {
    856          break;
    857        }
    858      } else if (c0 != ci) {
    859        break;
    860      }
    861    }
    862    if (i < xp->xp_numfiles) {
    863      if (!(options & WILD_NO_BEEP)) {
    864        vim_beep(kOptBoFlagWildmode);
    865      }
    866      break;
    867    }
    868  }
    869 
    870  return xmemdupz(xp->xp_files[0], len);
    871 }
    872 
    873 /// Do wildcard expansion on the string "str".
    874 /// Chars that should not be expanded must be preceded with a backslash.
    875 /// Return a pointer to allocated memory containing the new string.
    876 /// Return NULL for failure.
    877 ///
    878 /// "orig" is the originally expanded string, copied to allocated memory.  It
    879 /// should either be kept in "xp->xp_orig" or freed.  When "mode" is WILD_NEXT
    880 /// or WILD_PREV "orig" should be NULL.
    881 ///
    882 /// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode"
    883 /// is WILD_EXPAND_FREE or WILD_ALL.
    884 ///
    885 /// mode = WILD_FREE:        just free previously expanded matches
    886 /// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches
    887 /// mode = WILD_EXPAND_KEEP: normal expansion, keep matches
    888 /// mode = WILD_NEXT:        use next match in multiple match, wrap to first
    889 /// mode = WILD_PREV:        use previous match in multiple match, wrap to first
    890 /// mode = WILD_ALL:         return all matches concatenated
    891 /// mode = WILD_LONGEST:     return longest matched part
    892 /// mode = WILD_ALL_KEEP:    get all matches, keep matches
    893 /// mode = WILD_APPLY:       apply the item selected in the cmdline completion
    894 ///                          popup menu and close the menu.
    895 /// mode = WILD_CANCEL:      cancel and close the cmdline completion popup and
    896 ///                          use the original text.
    897 /// mode = WILD_PUM_WANT:    use the match at index pum_want.item
    898 ///
    899 /// options = WILD_LIST_NOTFOUND:    list entries without a match
    900 /// options = WILD_HOME_REPLACE:     do home_replace() for buffer names
    901 /// options = WILD_USE_NL:           Use '\n' for WILD_ALL
    902 /// options = WILD_NO_BEEP:          Don't beep for multiple matches
    903 /// options = WILD_ADD_SLASH:        add a slash after directory names
    904 /// options = WILD_KEEP_ALL:         don't remove 'wildignore' entries
    905 /// options = WILD_SILENT:           don't print warning messages
    906 /// options = WILD_ESCAPE:           put backslash before special chars
    907 /// options = WILD_ICASE:            ignore case for files
    908 ///
    909 /// The variables xp->xp_context and xp->xp_backslash must have been set!
    910 ///
    911 /// @param orig  allocated copy of original of expanded string
    912 char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
    913 {
    914  char *ss = NULL;
    915  bool orig_saved = false;
    916 
    917  // first handle the case of using an old match
    918  if (mode == WILD_NEXT || mode == WILD_PREV
    919      || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN
    920      || mode == WILD_PUM_WANT) {
    921    return get_next_or_prev_match(mode, xp);
    922  }
    923 
    924  if (mode == WILD_CANCEL) {
    925    ss = xstrdup(xp->xp_orig ? xp->xp_orig : "");
    926  } else if (mode == WILD_APPLY) {
    927    ss = xstrdup(xp->xp_selected == -1
    928                 ? (xp->xp_orig ? xp->xp_orig : "")
    929                 : xp->xp_files[xp->xp_selected]);
    930  }
    931 
    932  // free old names
    933  if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
    934    FreeWild(xp->xp_numfiles, xp->xp_files);
    935    xp->xp_numfiles = -1;
    936    XFREE_CLEAR(xp->xp_orig);
    937 
    938    // The entries from xp_files may be used in the PUM, remove it.
    939    if (compl_match_array != NULL) {
    940      cmdline_pum_remove(false);
    941    }
    942  }
    943  xp->xp_selected = (options & WILD_NOSELECT) ? -1 : 0;
    944 
    945  if (mode == WILD_FREE) {      // only release file name
    946    return NULL;
    947  }
    948 
    949  if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
    950    xfree(xp->xp_orig);
    951    xp->xp_orig = orig;
    952    orig_saved = true;
    953 
    954    ss = ExpandOne_start(mode, xp, str, options);
    955  }
    956 
    957  // Find longest common part
    958  if (mode == WILD_LONGEST && xp->xp_numfiles > 0) {
    959    ss = find_longest_match(xp, options);
    960    xp->xp_selected = -1;  // next p_wc gets first one
    961  }
    962 
    963  // Concatenate all matching names.  Unless interrupted, this can be slow
    964  // and the result probably won't be used.
    965  if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) {
    966    size_t ss_size = 0;
    967    char *prefix = "";
    968    char *suffix = (options & WILD_USE_NL) ? "\n" : " ";
    969    const int n = xp->xp_numfiles - 1;
    970 
    971    if (xp->xp_prefix == XP_PREFIX_NO) {
    972      prefix = "no";
    973      ss_size = STRLEN_LITERAL("no") * (size_t)n;
    974    } else if (xp->xp_prefix == XP_PREFIX_INV) {
    975      prefix = "inv";
    976      ss_size = STRLEN_LITERAL("inv") * (size_t)n;
    977    }
    978 
    979    for (int i = 0; i < xp->xp_numfiles; i++) {
    980      ss_size += strlen(xp->xp_files[i]) + 1;  // +1 for the suffix
    981    }
    982    ss_size++;  // +1 for the NUL
    983 
    984    ss = xmalloc(ss_size);
    985    *ss = NUL;
    986    char *ssp = ss;
    987    for (int i = 0; i < xp->xp_numfiles; i++) {
    988      if (i > 0) {
    989        ssp = xstpcpy(ssp, prefix);
    990      }
    991      ssp = xstpcpy(ssp, xp->xp_files[i]);
    992      if (i < n) {
    993        ssp = xstpcpy(ssp, suffix);
    994      }
    995      assert(ssp < ss + ss_size);
    996    }
    997  }
    998 
    999  if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) {
   1000    ExpandCleanup(xp);
   1001  }
   1002 
   1003  // Free "orig" if it wasn't stored in "xp->xp_orig".
   1004  if (!orig_saved) {
   1005    xfree(orig);
   1006  }
   1007 
   1008  return ss;
   1009 }
   1010 
   1011 /// Prepare an expand structure for use.
   1012 void ExpandInit(expand_T *xp)
   1013  FUNC_ATTR_NONNULL_ALL
   1014 {
   1015  CLEAR_POINTER(xp);
   1016  xp->xp_backslash = XP_BS_NONE;
   1017  xp->xp_prefix = XP_PREFIX_NONE;
   1018  xp->xp_numfiles = -1;
   1019 }
   1020 
   1021 /// Cleanup an expand structure after use.
   1022 void ExpandCleanup(expand_T *xp)
   1023 {
   1024  if (xp->xp_numfiles >= 0) {
   1025    FreeWild(xp->xp_numfiles, xp->xp_files);
   1026    xp->xp_numfiles = -1;
   1027  }
   1028  XFREE_CLEAR(xp->xp_orig);
   1029 }
   1030 
   1031 void clear_cmdline_orig(void)
   1032 {
   1033  XFREE_CLEAR(cmdline_orig);
   1034 }
   1035 
   1036 /// Display one line of completion matches. Multiple matches are displayed in
   1037 /// each line (used by wildmode=list and CTRL-D)
   1038 ///
   1039 /// @param matches      list of completion match names
   1040 /// @param numMatches   number of completion matches in "matches"
   1041 /// @param lines        number of output lines
   1042 /// @param linenr       line number of matches to display
   1043 /// @param maxlen       maximum number of characters in each line
   1044 /// @param showtail     display only the tail of the full path of a file name
   1045 static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, int lines, int linenr,
   1046                                int maxlen, bool showtail)
   1047 {
   1048  char *p;
   1049  int lastlen = 999;
   1050  for (int j = linenr; j < numMatches; j += lines) {
   1051    if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
   1052      msg_outtrans(matches[j], HLF_D, false);
   1053      p = matches[j] + strlen(matches[j]) + 1;
   1054      msg_advance(maxlen + 1);
   1055      msg_puts(p);
   1056      msg_advance(maxlen + 3);
   1057      msg_outtrans_long(p + 2, HLF_D);
   1058      break;
   1059    }
   1060    for (int i = maxlen - lastlen; --i >= 0;) {
   1061      msg_putchar(' ');
   1062    }
   1063    bool isdir;
   1064    if (xp->xp_context == EXPAND_FILES
   1065        || xp->xp_context == EXPAND_SHELLCMD
   1066        || xp->xp_context == EXPAND_BUFFERS) {
   1067      // highlight directories
   1068      if (xp->xp_numfiles != -1) {
   1069        // Expansion was done before and special characters
   1070        // were escaped, need to halve backslashes.  Also
   1071        // $HOME has been replaced with ~/.
   1072        char *exp_path = expand_env_save_opt(matches[j], true);
   1073        char *path = exp_path != NULL ? exp_path : matches[j];
   1074        char *halved_slash = backslash_halve_save(path);
   1075        isdir = os_isdir(halved_slash);
   1076        xfree(exp_path);
   1077        if (halved_slash != path) {
   1078          xfree(halved_slash);
   1079        }
   1080      } else {
   1081        // Expansion was done here, file names are literal.
   1082        isdir = os_isdir(matches[j]);
   1083      }
   1084      if (showtail) {
   1085        p = SHOW_MATCH(j);
   1086      } else {
   1087        home_replace(NULL, matches[j], NameBuff, MAXPATHL, true);
   1088        p = NameBuff;
   1089      }
   1090    } else {
   1091      isdir = false;
   1092      p = SHOW_MATCH(j);
   1093    }
   1094    lastlen = msg_outtrans(p, isdir ? HLF_D : 0, false);
   1095  }
   1096  if (msg_col > 0) {  // when not wrapped around
   1097    msg_clr_eos();
   1098    msg_putchar('\n');
   1099  }
   1100 }
   1101 
   1102 /// Display completion matches.
   1103 /// Returns EXPAND_NOTHING when the character that triggered expansion should be
   1104 ///   inserted as a normal character.
   1105 int showmatches(expand_T *xp, bool display_wildmenu, bool display_list, bool noselect)
   1106 {
   1107  CmdlineInfo *const ccline = get_cmdline_info();
   1108  int numMatches;
   1109  char **matches;
   1110  int maxlen;
   1111  int lines;
   1112  int columns;
   1113  bool showtail;
   1114 
   1115  if (xp->xp_numfiles == -1) {
   1116    set_expand_context(xp);
   1117    if (xp->xp_context == EXPAND_LUA) {
   1118      nlua_expand_pat(xp);
   1119    }
   1120    int retval = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
   1121                                &numMatches, &matches);
   1122    if (retval != EXPAND_OK) {
   1123      return retval;
   1124    }
   1125    showtail = expand_showtail(xp);
   1126  } else {
   1127    numMatches = xp->xp_numfiles;
   1128    matches = xp->xp_files;
   1129    showtail = cmd_showtail;
   1130  }
   1131 
   1132  if (cmdline_compl_use_pum(display_wildmenu && !display_list)) {
   1133    cmdline_pum_create(ccline, xp, matches, numMatches, showtail, noselect);
   1134    compl_selected = noselect ? -1 : 0;
   1135    pum_clear();
   1136    cmdline_pum_display(true);
   1137    return EXPAND_OK;
   1138  }
   1139 
   1140  if (display_list) {
   1141    msg_didany = false;                 // lines_left will be set
   1142    msg_start();                        // prepare for paging
   1143    if (!ui_has(kUIMessages)) {
   1144      msg_putchar('\n');
   1145    }
   1146    ui_flush();
   1147    cmdline_row = msg_row;
   1148    msg_didany = false;                 // lines_left will be set again
   1149    msg_ext_set_kind("wildlist");
   1150    msg_start();                        // prepare for paging
   1151  }
   1152 
   1153  if (got_int) {
   1154    got_int = false;  // only interrupt the completion, not the cmd line
   1155  } else if (display_wildmenu && !display_list) {
   1156    redraw_wildmenu(xp, numMatches, matches, noselect ? -1 : 0,
   1157                    showtail);  // display statusbar menu
   1158  } else if (display_list) {
   1159    // find the length of the longest file name
   1160    maxlen = 0;
   1161    for (int i = 0; i < numMatches; i++) {
   1162      int len;
   1163      if (!showtail && (xp->xp_context == EXPAND_FILES
   1164                        || xp->xp_context == EXPAND_SHELLCMD
   1165                        || xp->xp_context == EXPAND_BUFFERS)) {
   1166        home_replace(NULL, matches[i], NameBuff, MAXPATHL, true);
   1167        len = vim_strsize(NameBuff);
   1168      } else {
   1169        len = vim_strsize(SHOW_MATCH(i));
   1170      }
   1171      maxlen = MAX(maxlen, len);
   1172    }
   1173 
   1174    if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
   1175      lines = numMatches;
   1176    } else {
   1177      // compute the number of columns and lines for the listing
   1178      maxlen += 2;          // two spaces between file names
   1179      columns = (Columns + 2) / maxlen;
   1180      if (columns < 1) {
   1181        columns = 1;
   1182      }
   1183      lines = (numMatches + columns - 1) / columns;
   1184    }
   1185 
   1186    if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
   1187      msg_puts_hl(_("tagname"), HLF_T, false);
   1188      msg_clr_eos();
   1189      msg_advance(maxlen - 3);
   1190      msg_puts_hl(_(" kind file\n"), HLF_T, false);
   1191    }
   1192 
   1193    // list the files line by line
   1194    for (int i = 0; i < lines; i++) {
   1195      showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail);
   1196      if (got_int) {
   1197        got_int = false;
   1198        break;
   1199      }
   1200    }
   1201 
   1202    // we redraw the command below the lines that we have just listed
   1203    // This is a bit tricky, but it saves a lot of screen updating.
   1204    cmdline_row = msg_row;      // will put it back later
   1205  }
   1206 
   1207  if (xp->xp_numfiles == -1) {
   1208    FreeWild(numMatches, matches);
   1209  }
   1210 
   1211  return EXPAND_OK;
   1212 }
   1213 
   1214 /// path_tail() version for showmatches() and redraw_wildmenu():
   1215 /// Return the tail of file name path "s", ignoring a trailing "/".
   1216 static char *showmatches_gettail(char *s, bool eager)
   1217 {
   1218  char *t = s;
   1219  bool had_sep = false;
   1220 
   1221  for (char *p = s; *p != NUL;) {
   1222    if (vim_ispathsep(*p)
   1223 #ifdef BACKSLASH_IN_FILENAME
   1224        && !rem_backslash(p)
   1225 #endif
   1226        ) {
   1227      if (eager) {
   1228        t = p + 1;
   1229      } else {
   1230        had_sep = true;
   1231      }
   1232    } else if (had_sep) {
   1233      t = p;
   1234      had_sep = false;
   1235    }
   1236    MB_PTR_ADV(p);
   1237  }
   1238  return t;
   1239 }
   1240 
   1241 /// Return true if we only need to show the tail of completion matches.
   1242 /// When not completing file names or there is a wildcard in the path false is
   1243 /// returned.
   1244 static bool expand_showtail(expand_T *xp)
   1245 {
   1246  // When not completing file names a "/" may mean something different.
   1247  if (xp->xp_context != EXPAND_FILES
   1248      && xp->xp_context != EXPAND_SHELLCMD
   1249      && xp->xp_context != EXPAND_DIRECTORIES) {
   1250    return false;
   1251  }
   1252 
   1253  char *end = path_tail(xp->xp_pattern);
   1254  if (end == xp->xp_pattern) {          // there is no path separator
   1255    return false;
   1256  }
   1257 
   1258  for (char *s = xp->xp_pattern; s < end; s++) {
   1259    // Skip escaped wildcards.  Only when the backslash is not a path
   1260    // separator, on DOS the '*' "path\*\file" must not be skipped.
   1261    if (rem_backslash(s)) {
   1262      s++;
   1263    } else if (vim_strchr("*?[", (uint8_t)(*s)) != NULL) {
   1264      return false;
   1265    }
   1266  }
   1267  return true;
   1268 }
   1269 
   1270 /// Prepare a string for expansion.
   1271 ///
   1272 /// When expanding file names: The string will be used with expand_wildcards().
   1273 /// Copy "fname[len]" into allocated memory and add a '*' at the end.
   1274 /// When expanding other names: The string will be used with regcomp().  Copy
   1275 /// the name into allocated memory and prepend "^".
   1276 ///
   1277 /// @param context EXPAND_FILES etc.
   1278 char *addstar(char *fname, size_t len, int context)
   1279  FUNC_ATTR_NONNULL_RET
   1280 {
   1281  char *retval;
   1282 
   1283  if (context != EXPAND_FILES
   1284      && context != EXPAND_FILES_IN_PATH
   1285      && context != EXPAND_SHELLCMD
   1286      && context != EXPAND_DIRECTORIES
   1287      && context != EXPAND_DIRS_IN_CDPATH) {
   1288    // Matching will be done internally (on something other than files).
   1289    // So we convert the file-matching-type wildcards into our kind for
   1290    // use with vim_regcomp().  First work out how long it will be:
   1291 
   1292    // For help tags the translation is done in find_help_tags().
   1293    // For a tag pattern starting with "/" no translation is needed.
   1294    if (context == EXPAND_FINDFUNC
   1295        || context == EXPAND_HELP
   1296        || context == EXPAND_COLORS
   1297        || context == EXPAND_COMPILER
   1298        || context == EXPAND_OWNSYNTAX
   1299        || context == EXPAND_FILETYPE
   1300        || context == EXPAND_KEYMAP
   1301        || context == EXPAND_PACKADD
   1302        || context == EXPAND_RUNTIME
   1303        || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
   1304            && fname[0] == '/')
   1305        || context == EXPAND_CHECKHEALTH
   1306        || context == EXPAND_LSP
   1307        || context == EXPAND_LUA) {
   1308      retval = xstrnsave(fname, len);
   1309    } else {
   1310      size_t new_len = len + 2;                // +2 for '^' at start, NUL at end
   1311      for (size_t i = 0; i < len; i++) {
   1312        if (fname[i] == '*' || fname[i] == '~') {
   1313          new_len++;                    // '*' needs to be replaced by ".*"
   1314                                        // '~' needs to be replaced by "\~"
   1315        }
   1316        // Buffer names are like file names.  "." should be literal
   1317        if (context == EXPAND_BUFFERS && fname[i] == '.') {
   1318          new_len++;                    // "." becomes "\."
   1319        }
   1320        // Custom expansion takes care of special things, match
   1321        // backslashes literally (perhaps also for other types?)
   1322        if ((context == EXPAND_USER_DEFINED
   1323             || context == EXPAND_USER_LIST) && fname[i] == '\\') {
   1324          new_len++;                    // '\' becomes "\\"
   1325        }
   1326      }
   1327      retval = xmalloc(new_len);
   1328      {
   1329        retval[0] = '^';
   1330        size_t j = 1;
   1331        for (size_t i = 0; i < len; i++, j++) {
   1332          // Skip backslash.  But why?  At least keep it for custom
   1333          // expansion.
   1334          if (context != EXPAND_USER_DEFINED
   1335              && context != EXPAND_USER_LIST
   1336              && fname[i] == '\\'
   1337              && ++i == len) {
   1338            break;
   1339          }
   1340 
   1341          switch (fname[i]) {
   1342          case '*':
   1343            retval[j++] = '.';
   1344            break;
   1345          case '~':
   1346            retval[j++] = '\\';
   1347            break;
   1348          case '?':
   1349            retval[j] = '.';
   1350            continue;
   1351          case '.':
   1352            if (context == EXPAND_BUFFERS) {
   1353              retval[j++] = '\\';
   1354            }
   1355            break;
   1356          case '\\':
   1357            if (context == EXPAND_USER_DEFINED
   1358                || context == EXPAND_USER_LIST) {
   1359              retval[j++] = '\\';
   1360            }
   1361            break;
   1362          }
   1363          retval[j] = fname[i];
   1364        }
   1365        retval[j] = NUL;
   1366      }
   1367    }
   1368  } else {
   1369    retval = xmalloc(len + 4);
   1370    xmemcpyz(retval, fname, len);
   1371 
   1372    // Don't add a star to *, ~, ~user, $var or `cmd`.
   1373    // * would become **, which walks the whole tree.
   1374    // ~ would be at the start of the file name, but not the tail.
   1375    // $ could be anywhere in the tail.
   1376    // ` could be anywhere in the file name.
   1377    // When the name ends in '$' don't add a star, remove the '$'.
   1378    char *tail = path_tail(retval);
   1379    int ends_in_star = (len > 0 && retval[len - 1] == '*');
   1380 #ifndef BACKSLASH_IN_FILENAME
   1381    for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
   1382      if (retval[k] != '\\') {
   1383        break;
   1384      }
   1385      ends_in_star = !ends_in_star;
   1386    }
   1387 #endif
   1388    if ((*retval != '~' || tail != retval)
   1389        && !ends_in_star
   1390        && vim_strchr(tail, '$') == NULL
   1391        && vim_strchr(retval, '`') == NULL) {
   1392      retval[len++] = '*';
   1393    } else if (len > 0 && retval[len - 1] == '$') {
   1394      len--;
   1395    }
   1396    retval[len] = NUL;
   1397  }
   1398  return retval;
   1399 }
   1400 
   1401 /// Must parse the command line so far to work out what context we are in.
   1402 /// Completion can then be done based on that context.
   1403 /// This routine sets the variables:
   1404 ///  xp->xp_pattern          The start of the pattern to be expanded within
   1405 ///                              the command line (ends at the cursor).
   1406 ///  xp->xp_context          The type of thing to expand.  Will be one of:
   1407 ///
   1408 ///  EXPAND_UNSUCCESSFUL     Used sometimes when there is something illegal on
   1409 ///                          the command line, like an unknown command.  Caller
   1410 ///                          should beep.
   1411 ///  EXPAND_NOTHING          Unrecognised context for completion, use char like
   1412 ///                          a normal char, rather than for completion.  eg
   1413 ///                          :s/^I/
   1414 ///  EXPAND_COMMANDS         Cursor is still touching the command, so complete
   1415 ///                          it.
   1416 ///  EXPAND_BUFFERS          Complete file names for :buf and :sbuf commands.
   1417 ///  EXPAND_FILES            After command with EX_XFILE set, or after setting
   1418 ///                          with kOptFlagExpand set.  eg :e ^I, :w>>^I
   1419 ///  EXPAND_DIRECTORIES      In some cases this is used instead of the latter
   1420 ///                          when we know only directories are of interest.
   1421 ///                          E.g.  :set dir=^I  and  :cd ^I
   1422 ///  EXPAND_SHELLCMD         After ":!cmd", ":r !cmd"  or ":w !cmd".
   1423 ///  EXPAND_SETTINGS         Complete variable names.  eg :set d^I
   1424 ///  EXPAND_BOOL_SETTINGS    Complete boolean variables only,  eg :set no^I
   1425 ///  EXPAND_TAGS             Complete tags from the files in p_tags.  eg :ta a^I
   1426 ///  EXPAND_TAGS_LISTFILES   As above, but list filenames on ^D, after :tselect
   1427 ///  EXPAND_HELP             Complete tags from the file 'helpfile'/tags
   1428 ///  EXPAND_EVENTS           Complete event names
   1429 ///  EXPAND_SYNTAX           Complete :syntax command arguments
   1430 ///  EXPAND_HIGHLIGHT        Complete highlight (syntax) group names
   1431 ///  EXPAND_AUGROUP          Complete autocommand group names
   1432 ///  EXPAND_USER_VARS        Complete user defined variable names, eg :unlet a^I
   1433 ///  EXPAND_MAPPINGS         Complete mapping and abbreviation names,
   1434 ///                            eg :unmap a^I , :cunab x^I
   1435 ///  EXPAND_FUNCTIONS        Complete internal or user defined function names,
   1436 ///                            eg :call sub^I
   1437 ///  EXPAND_USER_FUNC        Complete user defined function names, eg :delf F^I
   1438 ///  EXPAND_EXPRESSION       Complete internal or user defined function/variable
   1439 ///                          names in expressions, eg :while s^I
   1440 ///  EXPAND_ENV_VARS         Complete environment variable names
   1441 ///  EXPAND_USER             Complete user names
   1442 ///  EXPAND_PATTERN_IN_BUF   Complete pattern in '/', '?', ':s', ':g', etc.
   1443 void set_expand_context(expand_T *xp)
   1444 {
   1445  CmdlineInfo *const ccline = get_cmdline_info();
   1446 
   1447  // Handle search commands: '/' or '?'
   1448  if ((ccline->cmdfirstc == '/' || ccline->cmdfirstc == '?')
   1449      && may_expand_pattern) {
   1450    xp->xp_context = EXPAND_PATTERN_IN_BUF;
   1451    xp->xp_search_dir = (ccline->cmdfirstc == '/') ? FORWARD : BACKWARD;
   1452    xp->xp_pattern = ccline->cmdbuff;
   1453    xp->xp_pattern_len = (size_t)ccline->cmdpos;
   1454    search_first_line = 0;  // Search entire buffer
   1455    return;
   1456  }
   1457 
   1458  // Only handle ':', '>', or '=' command-lines, or expression input
   1459  if (ccline->cmdfirstc != ':'
   1460      && ccline->cmdfirstc != '>' && ccline->cmdfirstc != '='
   1461      && !ccline->input_fn) {
   1462    xp->xp_context = EXPAND_NOTHING;
   1463    return;
   1464  }
   1465 
   1466  // Fallback to command-line expansion
   1467  set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true);
   1468 }
   1469 
   1470 /// Sets the index of a built-in or user defined command "cmd" in eap->cmdidx.
   1471 /// For user defined commands, the completion context is set in "xp" and the
   1472 /// completion flags in "complp".
   1473 ///
   1474 /// @return  a pointer to the text after the command or NULL for failure.
   1475 static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp)
   1476 {
   1477  const char *p = NULL;
   1478  const bool fuzzy = cmdline_fuzzy_complete(cmd);
   1479 
   1480  // Isolate the command and search for it in the command table.
   1481  // Exceptions:
   1482  // - the 'k' command can directly be followed by any character, but do
   1483  // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can
   1484  // find matches anywhere in the command name, do this only for command
   1485  // expansion based on regular expression and not for fuzzy matching.
   1486  // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
   1487  if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) {
   1488    eap->cmdidx = CMD_k;
   1489    p = cmd + 1;
   1490  } else {
   1491    p = cmd;
   1492    while (ASCII_ISALPHA(*p) || *p == '*') {  // Allow * wild card
   1493      p++;
   1494    }
   1495    // a user command may contain digits
   1496    if (ASCII_ISUPPER(cmd[0])) {
   1497      while (ASCII_ISALNUM(*p) || *p == '*') {
   1498        p++;
   1499      }
   1500    }
   1501    // for python 3.x: ":py3*" commands completion
   1502    if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') {
   1503      p++;
   1504      while (ASCII_ISALPHA(*p) || *p == '*') {
   1505        p++;
   1506      }
   1507    }
   1508    // check for non-alpha command
   1509    if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) {
   1510      p++;
   1511    }
   1512    size_t len = (size_t)(p - cmd);
   1513 
   1514    if (len == 0) {
   1515      xp->xp_context = EXPAND_UNSUCCESSFUL;
   1516      return NULL;
   1517    }
   1518 
   1519    eap->cmdidx = excmd_get_cmdidx(cmd, len);
   1520 
   1521    // User defined commands support alphanumeric characters.
   1522    // Also when doing fuzzy expansion for non-shell commands, support
   1523    // alphanumeric characters.
   1524    if ((cmd[0] >= 'A' && cmd[0] <= 'Z')
   1525        || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) {
   1526      while (ASCII_ISALNUM(*p) || *p == '*') {  // Allow * wild card
   1527        p++;
   1528      }
   1529    }
   1530  }
   1531 
   1532  // If the cursor is touching the command, and it ends in an alphanumeric
   1533  // character, complete the command name.
   1534  if (*p == NUL && ASCII_ISALNUM(p[-1])) {
   1535    return NULL;
   1536  }
   1537 
   1538  if (eap->cmdidx == CMD_SIZE) {
   1539    if (*cmd == 's' && vim_strchr("cgriI", (uint8_t)cmd[1]) != NULL) {
   1540      eap->cmdidx = CMD_substitute;
   1541      p = cmd + 1;
   1542    } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') {
   1543      eap->cmd = (char *)cmd;
   1544      p = find_ucmd(eap, (char *)p, NULL, xp, complp);
   1545      if (p == NULL) {
   1546        eap->cmdidx = CMD_SIZE;  // Ambiguous user command.
   1547      }
   1548    }
   1549  }
   1550  if (eap->cmdidx == CMD_SIZE) {
   1551    // Not still touching the command and it was an illegal one
   1552    xp->xp_context = EXPAND_UNSUCCESSFUL;
   1553    return NULL;
   1554  }
   1555 
   1556  return p;
   1557 }
   1558 
   1559 /// Set the completion context for a command argument with wild card characters.
   1560 static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool usefilter,
   1561                                         expand_T *xp, int *complp)
   1562 {
   1563  bool in_quote = false;
   1564  const char *bow = NULL;  // Beginning of word.
   1565  size_t len = 0;
   1566 
   1567  // Allow spaces within back-quotes to count as part of the argument
   1568  // being expanded.
   1569  xp->xp_pattern = skipwhite(arg);
   1570  const char *p = xp->xp_pattern;
   1571  while (*p != NUL) {
   1572    int c = utf_ptr2char(p);
   1573    if (c == '\\' && p[1] != NUL) {
   1574      p++;
   1575    } else if (c == '`') {
   1576      if (!in_quote) {
   1577        xp->xp_pattern = (char *)p;
   1578        bow = p + 1;
   1579      }
   1580      in_quote = !in_quote;
   1581      // An argument can contain just about everything, except
   1582      // characters that end the command and white space.
   1583    } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) {
   1584      len = 0;  // avoid getting stuck when space is in 'isfname'
   1585      while (*p != NUL) {
   1586        c = utf_ptr2char(p);
   1587        if (c == '`' || vim_isfilec_or_wc(c)) {
   1588          break;
   1589        }
   1590        len = (size_t)utfc_ptr2len(p);
   1591        MB_PTR_ADV(p);
   1592      }
   1593      if (in_quote) {
   1594        bow = p;
   1595      } else {
   1596        xp->xp_pattern = (char *)p;
   1597      }
   1598      p -= len;
   1599    }
   1600    MB_PTR_ADV(p);
   1601  }
   1602 
   1603  // If we are still inside the quotes, and we passed a space, just
   1604  // expand from there.
   1605  if (bow != NULL && in_quote) {
   1606    xp->xp_pattern = (char *)bow;
   1607  }
   1608  xp->xp_context = EXPAND_FILES;
   1609 
   1610  // For a shell command more chars need to be escaped.
   1611  if (usefilter
   1612      || (eap != NULL && (eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal))
   1613      || *complp == EXPAND_SHELLCMDLINE) {
   1614 #ifndef BACKSLASH_IN_FILENAME
   1615    xp->xp_shell = true;
   1616 #endif
   1617    // When still after the command name expand executables.
   1618    if (xp->xp_pattern == skipwhite(arg)) {
   1619      xp->xp_context = EXPAND_SHELLCMD;
   1620    }
   1621  }
   1622 
   1623  // Check for environment variable.
   1624  if (*xp->xp_pattern == '$') {
   1625    for (p = xp->xp_pattern + 1; *p != NUL; p++) {
   1626      if (!vim_isIDc((uint8_t)(*p))) {
   1627        break;
   1628      }
   1629    }
   1630    if (*p == NUL) {
   1631      xp->xp_context = EXPAND_ENV_VARS;
   1632      xp->xp_pattern++;
   1633      // Avoid that the assignment uses EXPAND_FILES again.
   1634      if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) {
   1635        *complp = EXPAND_ENV_VARS;
   1636      }
   1637    }
   1638  }
   1639  // Check for user names.
   1640  if (*xp->xp_pattern == '~') {
   1641    for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {}
   1642    // Complete ~user only if it partially matches a user name.
   1643    // A full match ~user<Tab> will be replaced by user's home
   1644    // directory i.e. something like ~user<Tab> -> /home/user/
   1645    if (*p == NUL && p > xp->xp_pattern + 1 && match_user(xp->xp_pattern + 1) >= 1) {
   1646      xp->xp_context = EXPAND_USER;
   1647      xp->xp_pattern++;
   1648    }
   1649  }
   1650 }
   1651 
   1652 /// Set the completion context for the "++opt=arg" argument.  Always returns NULL.
   1653 static const char *set_context_in_argopt(expand_T *xp, const char *arg)
   1654 {
   1655  char *p = vim_strchr(arg, '=');
   1656  if (p == NULL) {
   1657    xp->xp_pattern = (char *)arg;
   1658  } else {
   1659    xp->xp_pattern = p + 1;
   1660  }
   1661 
   1662  xp->xp_context = EXPAND_ARGOPT;
   1663  return NULL;
   1664 }
   1665 
   1666 /// Set the completion context for the :filter command. Returns a pointer to the
   1667 /// next command after the :filter command.
   1668 static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg)
   1669 {
   1670  if (*arg != NUL) {
   1671    arg = skip_vimgrep_pat((char *)arg, NULL, NULL);
   1672  }
   1673  if (arg == NULL || *arg == NUL) {
   1674    xp->xp_context = EXPAND_NOTHING;
   1675    return NULL;
   1676  }
   1677  return skipwhite(arg);
   1678 }
   1679 
   1680 /// Set the completion context for the :match command. Returns a pointer to the
   1681 /// next command after the :match command.
   1682 static const char *set_context_in_match_cmd(expand_T *xp, const char *arg)
   1683 {
   1684  if (*arg == NUL || !ends_excmd(*arg)) {
   1685    // also complete "None"
   1686    set_context_in_echohl_cmd(xp, arg);
   1687    arg = skipwhite(skiptowhite(arg));
   1688    if (*arg != NUL) {
   1689      xp->xp_context = EXPAND_NOTHING;
   1690      arg = skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset());
   1691    }
   1692  }
   1693  return find_nextcmd(arg);
   1694 }
   1695 
   1696 /// Returns a pointer to the next command after a :global or a :v command.
   1697 /// Returns NULL if there is no next command.
   1698 static const char *find_cmd_after_global_cmd(const char *arg)
   1699 {
   1700  const int delim = (uint8_t)(*arg);  // Get the delimiter.
   1701  if (delim) {
   1702    arg++;  // Skip delimiter if there is one.
   1703  }
   1704 
   1705  while (arg[0] != NUL && (uint8_t)arg[0] != delim) {
   1706    if (arg[0] == '\\' && arg[1] != NUL) {
   1707      arg++;
   1708    }
   1709    arg++;
   1710  }
   1711  if (arg[0] != NUL) {
   1712    return arg + 1;
   1713  }
   1714 
   1715  return NULL;
   1716 }
   1717 
   1718 /// Returns a pointer to the next command after a :substitute or a :& command.
   1719 /// Returns NULL if there is no next command.
   1720 static const char *find_cmd_after_substitute_cmd(const char *arg)
   1721 {
   1722  const int delim = (uint8_t)(*arg);
   1723  if (delim) {
   1724    // Skip "from" part.
   1725    arg++;
   1726    arg = skip_regexp((char *)arg, delim, magic_isset());
   1727 
   1728    if (arg[0] != NUL && arg[0] == delim) {
   1729      // Skip "to" part.
   1730      arg++;
   1731      while (arg[0] != NUL && (uint8_t)arg[0] != delim) {
   1732        if (arg[0] == '\\' && arg[1] != NUL) {
   1733          arg++;
   1734        }
   1735        arg++;
   1736      }
   1737      if (arg[0] != NUL) {  // Skip delimiter.
   1738        arg++;
   1739      }
   1740    }
   1741  }
   1742  while (arg[0] && strchr("|\"#", arg[0]) == NULL) {
   1743    arg++;
   1744  }
   1745  if (arg[0] != NUL) {
   1746    return arg;
   1747  }
   1748 
   1749  return NULL;
   1750 }
   1751 
   1752 /// Returns a pointer to the next command after a :isearch/:dsearch/:ilist
   1753 /// :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command.
   1754 /// Returns NULL if there is no next command.
   1755 static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
   1756 {
   1757  // Skip count.
   1758  arg = skipwhite(skipdigits(arg));
   1759  if (*arg != '/') {
   1760    return NULL;
   1761  }
   1762 
   1763  // Match regexp, not just whole words.
   1764  for (++arg; *arg && *arg != '/'; arg++) {
   1765    if (*arg == '\\' && arg[1] != NUL) {
   1766      arg++;
   1767    }
   1768  }
   1769  if (*arg) {
   1770    arg = skipwhite(arg + 1);
   1771 
   1772    // Check for trailing illegal characters.
   1773    if (*arg == NUL || strchr("|\"\n", *arg) == NULL) {
   1774      xp->xp_context = EXPAND_NOTHING;
   1775    } else {
   1776      return arg;
   1777    }
   1778  }
   1779 
   1780  return NULL;
   1781 }
   1782 
   1783 /// Set the completion context for the :unlet command. Always returns NULL.
   1784 static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg)
   1785 {
   1786  while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
   1787    arg = xp->xp_pattern + 1;
   1788  }
   1789 
   1790  xp->xp_context = EXPAND_USER_VARS;
   1791  xp->xp_pattern = (char *)arg;
   1792 
   1793  if (*xp->xp_pattern == '$') {
   1794    xp->xp_context = EXPAND_ENV_VARS;
   1795    xp->xp_pattern++;
   1796  }
   1797 
   1798  return NULL;
   1799 }
   1800 
   1801 /// Set the completion context for the :language command. Always returns NULL.
   1802 static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg)
   1803 {
   1804  const char *p = skiptowhite(arg);
   1805  if (*p == NUL) {
   1806    xp->xp_context = EXPAND_LANGUAGE;
   1807    xp->xp_pattern = (char *)arg;
   1808  } else {
   1809    if (strncmp(arg, "messages", (size_t)(p - arg)) == 0
   1810        || strncmp(arg, "ctype", (size_t)(p - arg)) == 0
   1811        || strncmp(arg, "time", (size_t)(p - arg)) == 0
   1812        || strncmp(arg, "collate", (size_t)(p - arg)) == 0) {
   1813      xp->xp_context = EXPAND_LOCALES;
   1814      xp->xp_pattern = skipwhite(p);
   1815    } else {
   1816      xp->xp_context = EXPAND_NOTHING;
   1817    }
   1818  }
   1819 
   1820  return NULL;
   1821 }
   1822 
   1823 static enum {
   1824  EXP_FILETYPECMD_ALL,     ///< expand all :filetype values
   1825  EXP_FILETYPECMD_PLUGIN,  ///< expand plugin on off
   1826  EXP_FILETYPECMD_INDENT,  ///< expand indent on off
   1827  EXP_FILETYPECMD_ONOFF,   ///< expand on off
   1828 } filetype_expand_what;
   1829 
   1830 enum {
   1831  EXPAND_FILETYPECMD_PLUGIN = 0x01,
   1832  EXPAND_FILETYPECMD_INDENT = 0x02,
   1833  EXPAND_FILETYPECMD_ONOFF  = 0x04,
   1834 };
   1835 
   1836 static enum {
   1837  EXP_BREAKPT_ADD,  ///< expand ":breakadd" sub-commands
   1838  EXP_BREAKPT_DEL,  ///< expand ":breakdel" sub-commands
   1839  EXP_PROFDEL,      ///< expand ":profdel" sub-commands
   1840 } breakpt_expand_what;
   1841 
   1842 /// Set the completion context for the :breakadd command. Always returns NULL.
   1843 static const char *set_context_in_breakadd_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx)
   1844 {
   1845  xp->xp_context = EXPAND_BREAKPOINT;
   1846  xp->xp_pattern = (char *)arg;
   1847 
   1848  if (cmdidx == CMD_breakadd) {
   1849    breakpt_expand_what = EXP_BREAKPT_ADD;
   1850  } else if (cmdidx == CMD_breakdel) {
   1851    breakpt_expand_what = EXP_BREAKPT_DEL;
   1852  } else {
   1853    breakpt_expand_what = EXP_PROFDEL;
   1854  }
   1855 
   1856  const char *p = skipwhite(arg);
   1857  if (*p == NUL) {
   1858    return NULL;
   1859  }
   1860  const char *subcmd_start = p;
   1861 
   1862  if (strncmp("file ", p, 5) == 0 || strncmp("func ", p, 5) == 0) {
   1863    // :breakadd file [lnum] <filename>
   1864    // :breakadd func [lnum] <funcname>
   1865    p += 4;
   1866    p = skipwhite(p);
   1867 
   1868    // skip line number (if specified)
   1869    if (ascii_isdigit(*p)) {
   1870      p = skipdigits(p);
   1871      if (*p != ' ') {
   1872        xp->xp_context = EXPAND_NOTHING;
   1873        return NULL;
   1874      }
   1875      p = skipwhite(p);
   1876    }
   1877    if (strncmp("file", subcmd_start, 4) == 0) {
   1878      xp->xp_context = EXPAND_FILES;
   1879    } else {
   1880      xp->xp_context = EXPAND_USER_FUNC;
   1881    }
   1882    xp->xp_pattern = (char *)p;
   1883  } else if (strncmp("expr ", p, 5) == 0) {
   1884    // :breakadd expr <expression>
   1885    xp->xp_context = EXPAND_EXPRESSION;
   1886    xp->xp_pattern = skipwhite(p + 5);
   1887  }
   1888 
   1889  return NULL;
   1890 }
   1891 
   1892 static const char *set_context_in_scriptnames_cmd(expand_T *xp, const char *arg)
   1893 {
   1894  xp->xp_context = EXPAND_NOTHING;
   1895  xp->xp_pattern = NULL;
   1896 
   1897  char *p = skipwhite(arg);
   1898  if (ascii_isdigit(*p)) {
   1899    return NULL;
   1900  }
   1901 
   1902  xp->xp_context = EXPAND_SCRIPTNAMES;
   1903  xp->xp_pattern = p;
   1904 
   1905  return NULL;
   1906 }
   1907 
   1908 /// Set the completion context for the :filetype command. Always returns NULL.
   1909 static const char *set_context_in_filetype_cmd(expand_T *xp, const char *arg)
   1910 {
   1911  xp->xp_context = EXPAND_FILETYPECMD;
   1912  xp->xp_pattern = (char *)arg;
   1913  filetype_expand_what = EXP_FILETYPECMD_ALL;
   1914 
   1915  char *p = skipwhite(arg);
   1916  if (*p == NUL) {
   1917    return NULL;
   1918  }
   1919 
   1920  int val = 0;
   1921 
   1922  while (true) {
   1923    if (strncmp(p, "plugin", 6) == 0) {
   1924      val |= EXPAND_FILETYPECMD_PLUGIN;
   1925      p = skipwhite(p + 6);
   1926      continue;
   1927    }
   1928    if (strncmp(p, "indent", 6) == 0) {
   1929      val |= EXPAND_FILETYPECMD_INDENT;
   1930      p = skipwhite(p + 6);
   1931      continue;
   1932    }
   1933    break;
   1934  }
   1935 
   1936  if ((val & EXPAND_FILETYPECMD_PLUGIN) && (val & EXPAND_FILETYPECMD_INDENT)) {
   1937    filetype_expand_what = EXP_FILETYPECMD_ONOFF;
   1938  } else if ((val & EXPAND_FILETYPECMD_PLUGIN)) {
   1939    filetype_expand_what = EXP_FILETYPECMD_INDENT;
   1940  } else if ((val & EXPAND_FILETYPECMD_INDENT)) {
   1941    filetype_expand_what = EXP_FILETYPECMD_PLUGIN;
   1942  }
   1943 
   1944  xp->xp_pattern = p;
   1945 
   1946  return NULL;
   1947 }
   1948 
   1949 /// Sets the completion context for commands that involve a search pattern
   1950 /// and a line range (e.g., :s, :g, :v).
   1951 static void set_context_with_pattern(expand_T *xp)
   1952 {
   1953  CmdlineInfo *ccline = get_cmdline_info();
   1954 
   1955  emsg_off++;
   1956  int skiplen = 0;
   1957  int dummy, patlen;
   1958  int retval = parse_pattern_and_range(&pre_incsearch_pos, &dummy, &skiplen, &patlen);
   1959  emsg_off--;
   1960 
   1961  // Check if cursor is within search pattern
   1962  if (!retval || ccline->cmdpos <= skiplen || ccline->cmdpos > skiplen + patlen) {
   1963    return;
   1964  }
   1965 
   1966  xp->xp_pattern = ccline->cmdbuff + skiplen;
   1967  xp->xp_pattern_len = (size_t)(ccline->cmdpos - skiplen);
   1968  xp->xp_context = EXPAND_PATTERN_IN_BUF;
   1969  xp->xp_search_dir = FORWARD;
   1970 }
   1971 
   1972 /// Set the completion context in "xp" for command "cmd" with index "cmdidx".
   1973 /// The argument to the command is "arg" and the argument flags is "argt".
   1974 /// For user-defined commands and for environment variables, "context" has the
   1975 /// completion type.
   1976 ///
   1977 /// @return  a pointer to the next command, or NULL if there is no next command.
   1978 static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expand_T *xp,
   1979                                          const char *arg, uint32_t argt, int context, bool forceit)
   1980 {
   1981  const char *nextcmd;
   1982 
   1983  switch (cmdidx) {
   1984  case CMD_find:
   1985  case CMD_sfind:
   1986  case CMD_tabfind:
   1987    if (xp->xp_context == EXPAND_FILES) {
   1988      xp->xp_context = *get_findfunc() != NUL ? EXPAND_FINDFUNC : EXPAND_FILES_IN_PATH;
   1989    }
   1990    break;
   1991  case CMD_cd:
   1992  case CMD_chdir:
   1993  case CMD_lcd:
   1994  case CMD_lchdir:
   1995  case CMD_tcd:
   1996  case CMD_tchdir:
   1997    if (xp->xp_context == EXPAND_FILES) {
   1998      xp->xp_context = EXPAND_DIRS_IN_CDPATH;
   1999    }
   2000    break;
   2001  case CMD_help:
   2002    xp->xp_context = EXPAND_HELP;
   2003    xp->xp_pattern = (char *)arg;
   2004    break;
   2005 
   2006  // Command modifiers: return the argument.
   2007  // Also for commands with an argument that is a command.
   2008  case CMD_aboveleft:
   2009  case CMD_argdo:
   2010  case CMD_belowright:
   2011  case CMD_botright:
   2012  case CMD_browse:
   2013  case CMD_bufdo:
   2014  case CMD_cdo:
   2015  case CMD_cfdo:
   2016  case CMD_confirm:
   2017  case CMD_debug:
   2018  case CMD_folddoclosed:
   2019  case CMD_folddoopen:
   2020  case CMD_hide:
   2021  case CMD_horizontal:
   2022  case CMD_keepalt:
   2023  case CMD_keepjumps:
   2024  case CMD_keepmarks:
   2025  case CMD_keeppatterns:
   2026  case CMD_ldo:
   2027  case CMD_leftabove:
   2028  case CMD_lfdo:
   2029  case CMD_lockmarks:
   2030  case CMD_noautocmd:
   2031  case CMD_noswapfile:
   2032  case CMD_restart:
   2033  case CMD_rightbelow:
   2034  case CMD_sandbox:
   2035  case CMD_silent:
   2036  case CMD_tab:
   2037  case CMD_tabdo:
   2038  case CMD_topleft:
   2039  case CMD_unsilent:
   2040  case CMD_verbose:
   2041  case CMD_vertical:
   2042  case CMD_windo:
   2043    return arg;
   2044 
   2045  case CMD_filter:
   2046    return set_context_in_filter_cmd(xp, arg);
   2047 
   2048  case CMD_match:
   2049    return set_context_in_match_cmd(xp, arg);
   2050 
   2051  // All completion for the +cmdline_compl feature goes here.
   2052 
   2053  case CMD_command:
   2054    return set_context_in_user_cmd(xp, arg);
   2055 
   2056  case CMD_delcommand:
   2057    xp->xp_context = EXPAND_USER_COMMANDS;
   2058    xp->xp_pattern = (char *)arg;
   2059    break;
   2060 
   2061  case CMD_global:
   2062  case CMD_vglobal:
   2063    nextcmd = find_cmd_after_global_cmd(arg);
   2064    if (!nextcmd && may_expand_pattern) {
   2065      set_context_with_pattern(xp);
   2066    }
   2067    return nextcmd;
   2068 
   2069  case CMD_and:
   2070  case CMD_substitute:
   2071    nextcmd = find_cmd_after_substitute_cmd(arg);
   2072    if (!nextcmd && may_expand_pattern) {
   2073      set_context_with_pattern(xp);
   2074    }
   2075    return nextcmd;
   2076 
   2077  case CMD_isearch:
   2078  case CMD_dsearch:
   2079  case CMD_ilist:
   2080  case CMD_dlist:
   2081  case CMD_ijump:
   2082  case CMD_psearch:
   2083  case CMD_djump:
   2084  case CMD_isplit:
   2085  case CMD_dsplit:
   2086    return find_cmd_after_isearch_cmd(xp, arg);
   2087  case CMD_autocmd:
   2088    return set_context_in_autocmd(xp, (char *)arg, false);
   2089 
   2090  case CMD_doautocmd:
   2091  case CMD_doautoall:
   2092    return set_context_in_autocmd(xp, (char *)arg, true);
   2093  case CMD_set:
   2094    set_context_in_set_cmd(xp, (char *)arg, 0);
   2095    break;
   2096  case CMD_setglobal:
   2097    set_context_in_set_cmd(xp, (char *)arg, OPT_GLOBAL);
   2098    break;
   2099  case CMD_setlocal:
   2100    set_context_in_set_cmd(xp, (char *)arg, OPT_LOCAL);
   2101    break;
   2102  case CMD_tag:
   2103  case CMD_stag:
   2104  case CMD_ptag:
   2105  case CMD_ltag:
   2106  case CMD_tselect:
   2107  case CMD_stselect:
   2108  case CMD_ptselect:
   2109  case CMD_tjump:
   2110  case CMD_stjump:
   2111  case CMD_ptjump:
   2112    if (wop_flags & kOptWopFlagTagfile) {
   2113      xp->xp_context = EXPAND_TAGS_LISTFILES;
   2114    } else {
   2115      xp->xp_context = EXPAND_TAGS;
   2116    }
   2117    xp->xp_pattern = (char *)arg;
   2118    break;
   2119  case CMD_augroup:
   2120    xp->xp_context = EXPAND_AUGROUP;
   2121    xp->xp_pattern = (char *)arg;
   2122    break;
   2123  case CMD_syntax:
   2124    set_context_in_syntax_cmd(xp, arg);
   2125    break;
   2126  case CMD_const:
   2127  case CMD_let:
   2128  case CMD_if:
   2129  case CMD_elseif:
   2130  case CMD_while:
   2131  case CMD_for:
   2132  case CMD_echo:
   2133  case CMD_echon:
   2134  case CMD_execute:
   2135  case CMD_echomsg:
   2136  case CMD_echoerr:
   2137  case CMD_call:
   2138  case CMD_return:
   2139  case CMD_cexpr:
   2140  case CMD_caddexpr:
   2141  case CMD_cgetexpr:
   2142  case CMD_lexpr:
   2143  case CMD_laddexpr:
   2144  case CMD_lgetexpr:
   2145    set_context_for_expression(xp, (char *)arg, cmdidx);
   2146    break;
   2147 
   2148  case CMD_unlet:
   2149    return set_context_in_unlet_cmd(xp, arg);
   2150  case CMD_function:
   2151  case CMD_delfunction:
   2152    xp->xp_context = EXPAND_USER_FUNC;
   2153    xp->xp_pattern = (char *)arg;
   2154    break;
   2155 
   2156  case CMD_echohl:
   2157    set_context_in_echohl_cmd(xp, arg);
   2158    break;
   2159  case CMD_highlight:
   2160    set_context_in_highlight_cmd(xp, arg);
   2161    break;
   2162  case CMD_sign:
   2163    set_context_in_sign_cmd(xp, (char *)arg);
   2164    break;
   2165  case CMD_bdelete:
   2166  case CMD_bwipeout:
   2167  case CMD_bunload:
   2168    while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
   2169      arg = xp->xp_pattern + 1;
   2170    }
   2171    FALLTHROUGH;
   2172  case CMD_buffer:
   2173  case CMD_sbuffer:
   2174  case CMD_pbuffer:
   2175  case CMD_checktime:
   2176    xp->xp_context = EXPAND_BUFFERS;
   2177    xp->xp_pattern = (char *)arg;
   2178    break;
   2179  case CMD_diffget:
   2180  case CMD_diffput:
   2181    // If current buffer is in diff mode, complete buffer names
   2182    // which are in diff mode, and different than current buffer.
   2183    xp->xp_context = EXPAND_DIFF_BUFFERS;
   2184    xp->xp_pattern = (char *)arg;
   2185    break;
   2186 
   2187  case CMD_USER:
   2188  case CMD_USER_BUF:
   2189    return set_context_in_user_cmdarg(cmd, arg, argt, context, xp, forceit);
   2190 
   2191  case CMD_map:
   2192  case CMD_noremap:
   2193  case CMD_nmap:
   2194  case CMD_nnoremap:
   2195  case CMD_vmap:
   2196  case CMD_vnoremap:
   2197  case CMD_omap:
   2198  case CMD_onoremap:
   2199  case CMD_imap:
   2200  case CMD_inoremap:
   2201  case CMD_cmap:
   2202  case CMD_cnoremap:
   2203  case CMD_lmap:
   2204  case CMD_lnoremap:
   2205  case CMD_smap:
   2206  case CMD_snoremap:
   2207  case CMD_xmap:
   2208  case CMD_xnoremap:
   2209    return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
   2210                                  false, cmdidx);
   2211  case CMD_unmap:
   2212  case CMD_nunmap:
   2213  case CMD_vunmap:
   2214  case CMD_ounmap:
   2215  case CMD_iunmap:
   2216  case CMD_cunmap:
   2217  case CMD_lunmap:
   2218  case CMD_sunmap:
   2219  case CMD_xunmap:
   2220    return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
   2221                                  true, cmdidx);
   2222  case CMD_mapclear:
   2223  case CMD_nmapclear:
   2224  case CMD_vmapclear:
   2225  case CMD_omapclear:
   2226  case CMD_imapclear:
   2227  case CMD_cmapclear:
   2228  case CMD_lmapclear:
   2229  case CMD_smapclear:
   2230  case CMD_xmapclear:
   2231    xp->xp_context = EXPAND_MAPCLEAR;
   2232    xp->xp_pattern = (char *)arg;
   2233    break;
   2234 
   2235  case CMD_abbreviate:
   2236  case CMD_noreabbrev:
   2237  case CMD_cabbrev:
   2238  case CMD_cnoreabbrev:
   2239  case CMD_iabbrev:
   2240  case CMD_inoreabbrev:
   2241    return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
   2242                                  false, cmdidx);
   2243  case CMD_unabbreviate:
   2244  case CMD_cunabbrev:
   2245  case CMD_iunabbrev:
   2246    return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
   2247                                  true, cmdidx);
   2248  case CMD_menu:
   2249  case CMD_noremenu:
   2250  case CMD_unmenu:
   2251  case CMD_amenu:
   2252  case CMD_anoremenu:
   2253  case CMD_aunmenu:
   2254  case CMD_nmenu:
   2255  case CMD_nnoremenu:
   2256  case CMD_nunmenu:
   2257  case CMD_vmenu:
   2258  case CMD_vnoremenu:
   2259  case CMD_vunmenu:
   2260  case CMD_omenu:
   2261  case CMD_onoremenu:
   2262  case CMD_ounmenu:
   2263  case CMD_imenu:
   2264  case CMD_inoremenu:
   2265  case CMD_iunmenu:
   2266  case CMD_cmenu:
   2267  case CMD_cnoremenu:
   2268  case CMD_cunmenu:
   2269  case CMD_tlmenu:
   2270  case CMD_tlnoremenu:
   2271  case CMD_tlunmenu:
   2272  case CMD_tmenu:
   2273  case CMD_tunmenu:
   2274  case CMD_popup:
   2275  case CMD_emenu:
   2276    return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
   2277 
   2278  case CMD_colorscheme:
   2279    xp->xp_context = EXPAND_COLORS;
   2280    xp->xp_pattern = (char *)arg;
   2281    break;
   2282 
   2283  case CMD_compiler:
   2284    xp->xp_context = EXPAND_COMPILER;
   2285    xp->xp_pattern = (char *)arg;
   2286    break;
   2287 
   2288  case CMD_ownsyntax:
   2289    xp->xp_context = EXPAND_OWNSYNTAX;
   2290    xp->xp_pattern = (char *)arg;
   2291    break;
   2292 
   2293  case CMD_setfiletype:
   2294    xp->xp_context = EXPAND_FILETYPE;
   2295    xp->xp_pattern = (char *)arg;
   2296    break;
   2297 
   2298  case CMD_packadd:
   2299    xp->xp_context = EXPAND_PACKADD;
   2300    xp->xp_pattern = (char *)arg;
   2301    break;
   2302 
   2303  case CMD_runtime:
   2304    set_context_in_runtime_cmd(xp, arg);
   2305    break;
   2306 
   2307  case CMD_language:
   2308    return set_context_in_lang_cmd(xp, arg);
   2309 
   2310  case CMD_profile:
   2311    set_context_in_profile_cmd(xp, arg);
   2312    break;
   2313  case CMD_checkhealth:
   2314    xp->xp_context = EXPAND_CHECKHEALTH;
   2315    break;
   2316 
   2317  case CMD_lsp:
   2318    xp->xp_context = EXPAND_LSP;
   2319    break;
   2320 
   2321  case CMD_retab:
   2322    xp->xp_context = EXPAND_RETAB;
   2323    xp->xp_pattern = (char *)arg;
   2324    break;
   2325 
   2326  case CMD_messages:
   2327    xp->xp_context = EXPAND_MESSAGES;
   2328    xp->xp_pattern = (char *)arg;
   2329    break;
   2330 
   2331  case CMD_history:
   2332    xp->xp_context = EXPAND_HISTORY;
   2333    xp->xp_pattern = (char *)arg;
   2334    break;
   2335  case CMD_syntime:
   2336    xp->xp_context = EXPAND_SYNTIME;
   2337    xp->xp_pattern = (char *)arg;
   2338    break;
   2339 
   2340  case CMD_argdelete:
   2341    while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) {
   2342      arg = (xp->xp_pattern + 1);
   2343    }
   2344    xp->xp_context = EXPAND_ARGLIST;
   2345    xp->xp_pattern = (char *)arg;
   2346    break;
   2347 
   2348  case CMD_breakadd:
   2349  case CMD_profdel:
   2350  case CMD_breakdel:
   2351    return set_context_in_breakadd_cmd(xp, arg, cmdidx);
   2352 
   2353  case CMD_scriptnames:
   2354    return set_context_in_scriptnames_cmd(xp, arg);
   2355 
   2356  case CMD_filetype:
   2357    return set_context_in_filetype_cmd(xp, arg);
   2358 
   2359  case CMD_lua:
   2360  case CMD_equal:
   2361    xp->xp_context = EXPAND_LUA;
   2362    break;
   2363 
   2364  default:
   2365    break;
   2366  }
   2367  return NULL;
   2368 }
   2369 
   2370 /// This is all pretty much copied from do_one_cmd(), with all the extra stuff
   2371 /// we don't need/want deleted.  Maybe this could be done better if we didn't
   2372 /// repeat all this stuff.  The only problem is that they may not stay
   2373 /// perfectly compatible with each other, but then the command line syntax
   2374 /// probably won't change that much -- webb.
   2375 ///
   2376 /// @param buff  buffer for command string
   2377 static const char *set_one_cmd_context(expand_T *xp, const char *buff)
   2378 {
   2379  size_t len = 0;
   2380  exarg_T ea;
   2381  int context = EXPAND_NOTHING;
   2382  bool forceit = false;
   2383  bool usefilter = false;  // Filter instead of file name.
   2384 
   2385  ExpandInit(xp);
   2386  xp->xp_pattern = (char *)buff;
   2387  xp->xp_line = (char *)buff;
   2388  xp->xp_context = EXPAND_COMMANDS;  // Default until we get past command
   2389  ea.argt = 0;
   2390 
   2391  // 1. skip comment lines and leading space, colons or bars
   2392  const char *cmd;
   2393  for (cmd = buff; vim_strchr(" \t:|", (uint8_t)(*cmd)) != NULL; cmd++) {}
   2394  xp->xp_pattern = (char *)cmd;
   2395 
   2396  if (*cmd == NUL) {
   2397    return NULL;
   2398  }
   2399  if (*cmd == '"') {  // ignore comment lines
   2400    xp->xp_context = EXPAND_NOTHING;
   2401    return NULL;
   2402  }
   2403 
   2404  // 3. skip over a range specifier of the form: addr [,addr] [;addr] ..
   2405  cmd = skip_range(cmd, &xp->xp_context);
   2406  xp->xp_pattern = (char *)cmd;
   2407  if (*cmd == NUL) {
   2408    return NULL;
   2409  }
   2410  if (*cmd == '"') {
   2411    xp->xp_context = EXPAND_NOTHING;
   2412    return NULL;
   2413  }
   2414 
   2415  if (*cmd == '|' || *cmd == '\n') {
   2416    return cmd + 1;  // There's another command
   2417  }
   2418 
   2419  // Get the command index.
   2420  const char *p = set_cmd_index(cmd, &ea, xp, &context);
   2421  if (p == NULL) {
   2422    return NULL;
   2423  }
   2424 
   2425  xp->xp_context = EXPAND_NOTHING;  // Default now that we're past command
   2426 
   2427  if (*p == '!') {  // forced commands
   2428    forceit = true;
   2429    p++;
   2430  }
   2431 
   2432  // 6. parse arguments
   2433  if (!IS_USER_CMDIDX(ea.cmdidx)) {
   2434    ea.argt = excmd_get_argt(ea.cmdidx);
   2435  }
   2436 
   2437  const char *arg = skipwhite(p);
   2438 
   2439  // Does command allow "++argopt" argument?
   2440  if (ea.argt & EX_ARGOPT) {
   2441    while (*arg != NUL && strncmp(arg, "++", 2) == 0) {
   2442      p = arg + 2;
   2443      while (*p && !ascii_isspace(*p)) {
   2444        MB_PTR_ADV(p);
   2445      }
   2446 
   2447      // Still touching the command after "++"?
   2448      if (*p == NUL) {
   2449        if (ea.argt & EX_ARGOPT) {
   2450          return set_context_in_argopt(xp, arg + 2);
   2451        }
   2452      }
   2453 
   2454      arg = skipwhite(p);
   2455    }
   2456  }
   2457 
   2458  if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) {
   2459    if (*arg == '>') {  // append
   2460      if (*++arg == '>') {
   2461        arg++;
   2462      }
   2463      arg = skipwhite(arg);
   2464    } else if (*arg == '!' && ea.cmdidx == CMD_write) {  // :w !filter
   2465      arg++;
   2466      usefilter = true;
   2467    }
   2468  }
   2469 
   2470  if (ea.cmdidx == CMD_read) {
   2471    usefilter = forceit;  // :r! filter if forced
   2472    if (*arg == '!') {    // :r !filter
   2473      arg++;
   2474      usefilter = true;
   2475    }
   2476  }
   2477 
   2478  if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) {
   2479    while (*arg == *cmd) {  // allow any number of '>' or '<'
   2480      arg++;
   2481    }
   2482    arg = skipwhite(arg);
   2483  }
   2484 
   2485  // Does command allow "+command"?
   2486  if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') {
   2487    // Check if we're in the +command
   2488    p = arg + 1;
   2489    arg = skip_cmd_arg((char *)arg, false);
   2490 
   2491    // Still touching the command after '+'?
   2492    if (*arg == NUL) {
   2493      return p;
   2494    }
   2495 
   2496    // Skip space(s) after +command to get to the real argument.
   2497    arg = skipwhite(arg);
   2498  }
   2499 
   2500  // Check for '|' to separate commands and '"' to start comments.
   2501  // Don't do this for ":read !cmd" and ":write !cmd".
   2502  if ((ea.argt & EX_TRLBAR) && !usefilter) {
   2503    p = arg;
   2504    // ":redir @" is not the start of a comment
   2505    if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') {
   2506      p += 2;
   2507    }
   2508    while (*p) {
   2509      if (*p == Ctrl_V) {
   2510        if (p[1] != NUL) {
   2511          p++;
   2512        }
   2513      } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM))
   2514                 || *p == '|'
   2515                 || *p == '\n') {
   2516        if (*(p - 1) != '\\') {
   2517          if (*p == '|' || *p == '\n') {
   2518            return p + 1;
   2519          }
   2520          return NULL;  // It's a comment
   2521        }
   2522      }
   2523      MB_PTR_ADV(p);
   2524    }
   2525  }
   2526 
   2527  if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) {
   2528    // no arguments allowed but there is something
   2529    return NULL;
   2530  }
   2531 
   2532  // Find start of last argument (argument just before cursor):
   2533  p = buff;
   2534  xp->xp_pattern = (char *)p;
   2535  len = strlen(buff);
   2536  while (*p && p < buff + len) {
   2537    if (*p == ' ' || *p == TAB) {
   2538      // argument starts after a space
   2539      xp->xp_pattern = (char *)++p;
   2540    } else {
   2541      if (*p == '\\' && *(p + 1) != NUL) {
   2542        p++;  // skip over escaped character
   2543      }
   2544      MB_PTR_ADV(p);
   2545    }
   2546  }
   2547 
   2548  if (ea.argt & EX_XFILE) {
   2549    set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &context);
   2550  }
   2551 
   2552  // Switch on command name.
   2553  return set_context_by_cmdname(cmd, ea.cmdidx, xp, arg, ea.argt, context, forceit);
   2554 }
   2555 
   2556 /// Set the completion context in "xp" for command "str"
   2557 ///
   2558 /// @param str  start of command line
   2559 /// @param len  length of command line (excl. NUL)
   2560 /// @param col  position of cursor
   2561 /// @param use_ccline  use ccline for info
   2562 void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline)
   2563 {
   2564  CmdlineInfo *const ccline = get_cmdline_info();
   2565  char old_char = NUL;
   2566 
   2567  // Avoid a UMR warning from Purify, only save the character if it has been
   2568  // written before.
   2569  if (col < len) {
   2570    old_char = str[col];
   2571  }
   2572  str[col] = NUL;
   2573  const char *nextcomm = str;
   2574 
   2575  if (use_ccline && ccline->cmdfirstc == '=') {
   2576    // pass CMD_SIZE because there is no real command
   2577    set_context_for_expression(xp, str, CMD_SIZE);
   2578  } else if (use_ccline && ccline->input_fn) {
   2579    xp->xp_context = ccline->xp_context;
   2580    xp->xp_pattern = ccline->cmdbuff;
   2581    xp->xp_arg = ccline->xp_arg;
   2582    if (xp->xp_context == EXPAND_SHELLCMDLINE) {
   2583      int context = xp->xp_context;
   2584      set_context_for_wildcard_arg(NULL, xp->xp_pattern, false, xp, &context);
   2585    }
   2586  } else {
   2587    while (nextcomm != NULL) {
   2588      nextcomm = set_one_cmd_context(xp, nextcomm);
   2589    }
   2590  }
   2591 
   2592  // Store the string here so that call_user_expand_func() can get to them
   2593  // easily.
   2594  xp->xp_line = str;
   2595  xp->xp_col = col;
   2596 
   2597  str[col] = old_char;
   2598 }
   2599 
   2600 /// Expand the command line "str" from context "xp".
   2601 /// "xp" must have been set by set_cmd_context().
   2602 /// xp->xp_pattern points into "str", to where the text that is to be expanded
   2603 /// starts.
   2604 /// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the
   2605 /// cursor.
   2606 /// Returns EXPAND_NOTHING when there is nothing to expand, might insert the
   2607 /// key that triggered expansion literally.
   2608 /// Returns EXPAND_OK otherwise.
   2609 ///
   2610 /// @param str  start of command line
   2611 /// @param col  position of cursor
   2612 /// @param matchcount  return: nr of matches
   2613 /// @param matches  return: array of pointers to matches
   2614 int expand_cmdline(expand_T *xp, const char *str, int col, int *matchcount, char ***matches)
   2615 {
   2616  char *file_str = NULL;
   2617  int options = WILD_ADD_SLASH|WILD_SILENT;
   2618 
   2619  if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
   2620    beep_flush();
   2621    return EXPAND_UNSUCCESSFUL;      // Something illegal on command line
   2622  }
   2623  if (xp->xp_context == EXPAND_NOTHING) {
   2624    // Caller can use the character as a normal char instead
   2625    return EXPAND_NOTHING;
   2626  }
   2627 
   2628  // add star to file name, or convert to regexp if not exp. files.
   2629  assert((str + col) - xp->xp_pattern >= 0);
   2630  xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern);
   2631  if (cmdline_fuzzy_completion_supported(xp)) {
   2632    // If fuzzy matching, don't modify the search string
   2633    file_str = xstrdup(xp->xp_pattern);
   2634  } else {
   2635    file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
   2636  }
   2637 
   2638  if (p_wic) {
   2639    options += WILD_ICASE;
   2640  }
   2641 
   2642  // find all files that match the description
   2643  if (ExpandFromContext(xp, file_str, matches, matchcount, options) == FAIL) {
   2644    *matchcount = 0;
   2645    *matches = NULL;
   2646  }
   2647  xfree(file_str);
   2648 
   2649  return EXPAND_OK;
   2650 }
   2651 
   2652 /// Expand file or directory names.
   2653 static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *numMatches,
   2654                                 int flags, int options)
   2655 {
   2656  bool free_pat = false;
   2657 
   2658  // for ":set path=" and ":set tags=" halve backslashes for escaped space
   2659  if (xp->xp_backslash != XP_BS_NONE) {
   2660    free_pat = true;
   2661    size_t pat_len = strlen(pat);
   2662    pat = xstrnsave(pat, pat_len);
   2663 
   2664    char *pat_end = pat + pat_len;
   2665    for (char *p = pat; *p != NUL; p++) {
   2666      if (*p != '\\') {
   2667        continue;
   2668      }
   2669 
   2670      if (xp->xp_backslash & XP_BS_THREE
   2671          && *(p + 1) == '\\'
   2672          && *(p + 2) == '\\'
   2673          && *(p + 3) == ' ') {
   2674        char *from = p + 3;
   2675        memmove(p, from, (size_t)(pat_end - from) + 1);  // +1 for NUL
   2676        pat_end -= 3;
   2677      } else if (xp->xp_backslash & XP_BS_ONE
   2678                 && *(p + 1) == ' ') {
   2679        char *from = p + 1;
   2680        memmove(p, from, (size_t)(pat_end - from) + 1);  // +1 for NUL
   2681        pat_end--;
   2682      } else if (xp->xp_backslash & XP_BS_COMMA) {
   2683        if (*(p + 1) == '\\' && *(p + 2) == ',') {
   2684          char *from = p + 2;
   2685          memmove(p, from, (size_t)(pat_end - from) + 1);  // +1 for NUL
   2686          pat_end -= 2;
   2687 #ifdef BACKSLASH_IN_FILENAME
   2688        } else if (*(p + 1) == ',') {
   2689          char *from = p + 1;
   2690          memmove(p, from, (size_t)(pat_end - from) + 1);  // +1 for NUL
   2691          pat_end--;
   2692 #endif
   2693        }
   2694      }
   2695    }
   2696  }
   2697 
   2698  int ret = FAIL;
   2699  if (xp->xp_context == EXPAND_FINDFUNC) {
   2700    ret = expand_findfunc(pat, matches, numMatches);
   2701  } else {
   2702    if (xp->xp_context == EXPAND_FILES) {
   2703      flags |= EW_FILE;
   2704    } else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
   2705      flags |= (EW_FILE | EW_PATH);
   2706    } else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
   2707      flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE;
   2708    } else {
   2709      flags = (flags | EW_DIR) & ~EW_FILE;
   2710    }
   2711    if (options & WILD_ICASE) {
   2712      flags |= EW_ICASE;
   2713    }
   2714    // Expand wildcards, supporting %:h and the like.
   2715    ret = expand_wildcards_eval(&pat, numMatches, matches, flags);
   2716  }
   2717  if (free_pat) {
   2718    xfree(pat);
   2719  }
   2720 #ifdef BACKSLASH_IN_FILENAME
   2721  if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
   2722    for (int j = 0; j < *numMatches; j++) {
   2723      char *ptr = (*matches)[j];
   2724      while (*ptr != NUL) {
   2725        if (p_csl[0] == 's' && *ptr == '\\') {
   2726          *ptr = '/';
   2727        } else if (p_csl[0] == 'b' && *ptr == '/') {
   2728          *ptr = '\\';
   2729        }
   2730        ptr += utfc_ptr2len(ptr);
   2731      }
   2732    }
   2733  }
   2734 #endif
   2735  return ret;
   2736 }
   2737 
   2738 /// Function given to ExpandGeneric() to obtain the possible arguments of the
   2739 /// ":filetype {plugin,indent}" command.
   2740 static char *get_filetypecmd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2741 {
   2742  if (idx < 0) {
   2743    return NULL;
   2744  }
   2745 
   2746  if (filetype_expand_what == EXP_FILETYPECMD_ALL && idx < 4) {
   2747    char *opts_all[] = { "indent", "plugin", "on", "off" };
   2748    return opts_all[idx];
   2749  }
   2750  if (filetype_expand_what == EXP_FILETYPECMD_PLUGIN && idx < 3) {
   2751    char *opts_plugin[] = { "plugin", "on", "off" };
   2752    return opts_plugin[idx];
   2753  }
   2754  if (filetype_expand_what == EXP_FILETYPECMD_INDENT && idx < 3) {
   2755    char *opts_indent[] = { "indent", "on", "off" };
   2756    return opts_indent[idx];
   2757  }
   2758  if (filetype_expand_what == EXP_FILETYPECMD_ONOFF && idx < 2) {
   2759    char *opts_onoff[] = { "on", "off" };
   2760    return opts_onoff[idx];
   2761  }
   2762  return NULL;
   2763 }
   2764 
   2765 /// Function given to ExpandGeneric() to obtain the possible arguments of the
   2766 /// ":breakadd {expr, file, func, here}" command.
   2767 /// ":breakdel {func, file, here}" command.
   2768 static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2769 {
   2770  if (idx >= 0 && idx <= 3) {
   2771    char *opts[] = { "expr", "file", "func", "here" };
   2772 
   2773    // breakadd {expr, file, func, here}
   2774    if (breakpt_expand_what == EXP_BREAKPT_ADD) {
   2775      return opts[idx];
   2776    } else if (breakpt_expand_what == EXP_BREAKPT_DEL) {
   2777      // breakdel {func, file, here}
   2778      if (idx <= 2) {
   2779        return opts[idx + 1];
   2780      }
   2781    } else {
   2782      // profdel {func, file}
   2783      if (idx <= 1) {
   2784        return opts[idx + 1];
   2785      }
   2786    }
   2787  }
   2788  return NULL;
   2789 }
   2790 
   2791 /// Function given to ExpandGeneric() to obtain the possible arguments for the
   2792 /// ":scriptnames" command.
   2793 static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2794 {
   2795  if (!SCRIPT_ID_VALID(idx + 1)) {
   2796    return NULL;
   2797  }
   2798 
   2799  scriptitem_T *si = SCRIPT_ITEM(idx + 1);
   2800  home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true);
   2801  return NameBuff;
   2802 }
   2803 
   2804 /// Function given to ExpandGeneric() to obtain the possible arguments of the
   2805 /// ":retab {-indentonly}" option.
   2806 static char *get_retab_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2807 {
   2808  if (idx == 0) {
   2809    return "-indentonly";
   2810  }
   2811  return NULL;
   2812 }
   2813 
   2814 /// Function given to ExpandGeneric() to obtain the possible arguments of the
   2815 /// ":messages {clear}" command.
   2816 static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2817 {
   2818  if (idx == 0) {
   2819    return "clear";
   2820  }
   2821  return NULL;
   2822 }
   2823 
   2824 static char *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2825 {
   2826  if (idx == 0) {
   2827    return "<buffer>";
   2828  }
   2829  return NULL;
   2830 }
   2831 
   2832 /// Completion for |:checkhealth| command.
   2833 ///
   2834 /// Given to ExpandGeneric() to obtain all available heathcheck names.
   2835 /// @param[in] idx  Index of the healthcheck item.
   2836 /// @param[in] xp  Not used.
   2837 static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2838 {
   2839  static Object names = OBJECT_INIT;
   2840  static unsigned last_gen = 0;
   2841 
   2842  if (last_gen != get_cmdline_last_prompt_id() || last_gen == 0) {
   2843    Array a = ARRAY_DICT_INIT;
   2844    Error err = ERROR_INIT;
   2845    Object res = NLUA_EXEC_STATIC("return vim.health._complete()", a, kRetObject, NULL, &err);
   2846    api_clear_error(&err);
   2847    api_free_object(names);
   2848    names = res;
   2849    last_gen = get_cmdline_last_prompt_id();
   2850  }
   2851 
   2852  if (names.type == kObjectTypeArray && idx < (int)names.data.array.size
   2853      && names.data.array.items[idx].type == kObjectTypeString) {
   2854    return names.data.array.items[idx].data.string.data;
   2855  }
   2856  return NULL;
   2857 }
   2858 
   2859 /// Completion for |:lsp| command.
   2860 ///
   2861 /// Given to ExpandGeneric() to obtain `:lsp` completion.
   2862 /// @param[in] idx  Index of the item.
   2863 /// @param[in] xp  Not used.
   2864 static char *get_lsp_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
   2865 {
   2866  static Object names = OBJECT_INIT;
   2867  static char *last_xp_line = NULL;
   2868  static unsigned last_gen = 0;
   2869 
   2870  if (last_xp_line == NULL || strcmp(last_xp_line,
   2871                                     xp->xp_line) != 0
   2872      || last_gen != get_cmdline_last_prompt_id()) {
   2873    xfree(last_xp_line);
   2874    last_xp_line = xstrdup(xp->xp_line);
   2875    MAXSIZE_TEMP_ARRAY(args, 1);
   2876    Error err = ERROR_INIT;
   2877 
   2878    ADD_C(args, CSTR_AS_OBJ(xp->xp_line));
   2879    // Build the current command line as a Lua string argument
   2880    Object res = NLUA_EXEC_STATIC("return require'vim._core.ex_cmd'.lsp_complete(...)", args,
   2881                                  kRetObject, NULL,
   2882                                  &err);
   2883    api_clear_error(&err);
   2884    api_free_object(names);
   2885    names = res;
   2886    last_gen = get_cmdline_last_prompt_id();
   2887  }
   2888 
   2889  if (names.type == kObjectTypeArray && idx < (int)names.data.array.size
   2890      && names.data.array.items[idx].type == kObjectTypeString) {
   2891    return names.data.array.items[idx].data.string.data;
   2892  }
   2893  return NULL;
   2894 }
   2895 
   2896 /// Do the expansion based on xp->xp_context and "rmp".
   2897 static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
   2898 {
   2899  typedef CompleteListItemGetter ExpandFunc;
   2900  static struct expgen {
   2901    int context;
   2902    ExpandFunc func;
   2903    int ic;
   2904    int escaped;
   2905  } tab[] = {
   2906    { EXPAND_COMMANDS, get_command_name, false, true },
   2907    { EXPAND_FILETYPECMD, get_filetypecmd_arg, true, true },
   2908    { EXPAND_MAPCLEAR, get_mapclear_arg, true, true },
   2909    { EXPAND_MESSAGES, get_messages_arg, true, true },
   2910    { EXPAND_HISTORY, get_history_arg, true, true },
   2911    { EXPAND_USER_COMMANDS, get_user_commands, false, true },
   2912    { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true },
   2913    { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true },
   2914    { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true },
   2915    { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true },
   2916    { EXPAND_USER_VARS, get_user_var_name, false, true },
   2917    { EXPAND_FUNCTIONS, get_function_name, false, true },
   2918    { EXPAND_USER_FUNC, get_user_func_name, false, true },
   2919    { EXPAND_EXPRESSION, get_expr_name, false, true },
   2920    { EXPAND_MENUS, get_menu_name, false, true },
   2921    { EXPAND_MENUNAMES, get_menu_names, false, true },
   2922    { EXPAND_SYNTAX, get_syntax_name, true, true },
   2923    { EXPAND_SYNTIME, get_syntime_arg, true, true },
   2924    { EXPAND_HIGHLIGHT, get_highlight_name, true, false },
   2925    { EXPAND_EVENTS, expand_get_event_name, true, false },
   2926    { EXPAND_AUGROUP, expand_get_augroup_name, true, false },
   2927    { EXPAND_SIGN, get_sign_name, true, true },
   2928    { EXPAND_PROFILE, get_profile_name, true, true },
   2929    { EXPAND_LANGUAGE, get_lang_arg, true, false },
   2930    { EXPAND_LOCALES, get_locales, true, false },
   2931    { EXPAND_ENV_VARS, get_env_name, true, true },
   2932    { EXPAND_USER, get_users, true, false },
   2933    { EXPAND_ARGLIST, get_arglist_name, true, false },
   2934    { EXPAND_BREAKPOINT, get_breakadd_arg, true, true },
   2935    { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false },
   2936    { EXPAND_RETAB, get_retab_arg, true, true },
   2937    { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
   2938    { EXPAND_LSP, get_lsp_arg, true, false },
   2939  };
   2940  int ret = FAIL;
   2941 
   2942  // Find a context in the table and call the ExpandGeneric() with the
   2943  // right function to do the expansion.
   2944  for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) {
   2945    if (xp->xp_context == tab[i].context) {
   2946      if (tab[i].ic) {
   2947        rmp->rm_ic = true;
   2948      }
   2949      ExpandGeneric(pat, xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped);
   2950      ret = OK;
   2951      break;
   2952    }
   2953  }
   2954 
   2955  return ret;
   2956 }
   2957 
   2958 /// Map wild expand options to flags for expand_wildcards()
   2959 static int map_wildopts_to_ewflags(int options)
   2960 {
   2961  int flags = EW_DIR;       // include directories
   2962  if (options & WILD_LIST_NOTFOUND) {
   2963    flags |= EW_NOTFOUND;
   2964  }
   2965  if (options & WILD_ADD_SLASH) {
   2966    flags |= EW_ADDSLASH;
   2967  }
   2968  if (options & WILD_KEEP_ALL) {
   2969    flags |= EW_KEEPALL;
   2970  }
   2971  if (options & WILD_SILENT) {
   2972    flags |= EW_SILENT;
   2973  }
   2974  if (options & WILD_NOERROR) {
   2975    flags |= EW_NOERROR;
   2976  }
   2977  if (options & WILD_ALLLINKS) {
   2978    flags |= EW_ALLLINKS;
   2979  }
   2980 
   2981  return flags;
   2982 }
   2983 
   2984 /// Do the expansion based on xp->xp_context and "pat".
   2985 ///
   2986 /// @param options  WILD_ flags
   2987 static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numMatches, int options)
   2988 {
   2989  regmatch_T regmatch = { .rm_ic = false };
   2990  int ret;
   2991  int flags = map_wildopts_to_ewflags(options);
   2992  const bool fuzzy = cmdline_fuzzy_complete(pat)
   2993                     && cmdline_fuzzy_completion_supported(xp);
   2994 
   2995  if (xp->xp_context == EXPAND_FILES
   2996      || xp->xp_context == EXPAND_DIRECTORIES
   2997      || xp->xp_context == EXPAND_FILES_IN_PATH
   2998      || xp->xp_context == EXPAND_FINDFUNC
   2999      || xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
   3000    return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options);
   3001  }
   3002 
   3003  *matches = NULL;
   3004  *numMatches = 0;
   3005  if (xp->xp_context == EXPAND_HELP) {
   3006    // With an empty argument we would get all the help tags, which is
   3007    // very slow.  Get matches for "help" instead.
   3008    if (find_help_tags(*pat == NUL ? "help" : pat,
   3009                       numMatches, matches, false) == OK) {
   3010      cleanup_help_tags(*numMatches, *matches);
   3011      return OK;
   3012    }
   3013    return FAIL;
   3014  }
   3015 
   3016  if (xp->xp_context == EXPAND_SHELLCMD) {
   3017    expand_shellcmd(pat, matches, numMatches, flags);
   3018    return OK;
   3019  }
   3020  if (xp->xp_context == EXPAND_OLD_SETTING) {
   3021    return ExpandOldSetting(numMatches, matches);
   3022  }
   3023  if (xp->xp_context == EXPAND_BUFFERS) {
   3024    return ExpandBufnames(pat, numMatches, matches, options);
   3025  }
   3026  if (xp->xp_context == EXPAND_DIFF_BUFFERS) {
   3027    return ExpandBufnames(pat, numMatches, matches, options | BUF_DIFF_FILTER);
   3028  }
   3029  if (xp->xp_context == EXPAND_TAGS
   3030      || xp->xp_context == EXPAND_TAGS_LISTFILES) {
   3031    return expand_tags(xp->xp_context == EXPAND_TAGS, pat, numMatches, matches);
   3032  }
   3033  if (xp->xp_context == EXPAND_COLORS) {
   3034    char *directories[] = { "colors", NULL };
   3035    return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, directories);
   3036  }
   3037  if (xp->xp_context == EXPAND_COMPILER) {
   3038    char *directories[] = { "compiler", NULL };
   3039    return ExpandRTDir(pat, 0, numMatches, matches, directories);
   3040  }
   3041  if (xp->xp_context == EXPAND_OWNSYNTAX) {
   3042    char *directories[] = { "syntax", NULL };
   3043    return ExpandRTDir(pat, 0, numMatches, matches, directories);
   3044  }
   3045  if (xp->xp_context == EXPAND_FILETYPE) {
   3046    char *directories[] = { "syntax", "indent", "ftplugin", NULL };
   3047    return ExpandRTDir(pat, 0, numMatches, matches, directories);
   3048  }
   3049  if (xp->xp_context == EXPAND_KEYMAP) {
   3050    char *directories[] = { "keymap", NULL };
   3051    return ExpandRTDir(pat, 0, numMatches, matches, directories);
   3052  }
   3053  if (xp->xp_context == EXPAND_USER_LIST) {
   3054    return ExpandUserList(xp, matches, numMatches);
   3055  }
   3056  if (xp->xp_context == EXPAND_USER_LUA) {
   3057    return ExpandUserLua(xp, numMatches, matches);
   3058  }
   3059  if (xp->xp_context == EXPAND_PACKADD) {
   3060    return ExpandPackAddDir(pat, numMatches, matches);
   3061  }
   3062  if (xp->xp_context == EXPAND_RUNTIME) {
   3063    return expand_runtime_cmd(pat, numMatches, matches);
   3064  }
   3065  if (xp->xp_context == EXPAND_PATTERN_IN_BUF) {
   3066    return expand_pattern_in_buf(pat, xp->xp_search_dir, matches, numMatches);
   3067  }
   3068 
   3069  // When expanding a function name starting with s:, match the <SNR>nr_
   3070  // prefix.
   3071  char *tofree = NULL;
   3072  if (xp->xp_context == EXPAND_USER_FUNC && strncmp(pat, "^s:", 3) == 0) {
   3073    const size_t len = strlen(pat) + 20;
   3074 
   3075    tofree = xmalloc(len);
   3076    snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3);
   3077    pat = tofree;
   3078  }
   3079 
   3080  if (xp->xp_context == EXPAND_LUA) {
   3081    return nlua_expand_get_matches(numMatches, matches);
   3082  }
   3083 
   3084  if (!fuzzy) {
   3085    regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
   3086    if (regmatch.regprog == NULL) {
   3087      xfree(tofree);
   3088      return FAIL;
   3089    }
   3090    // set ignore-case according to p_ic, p_scs and pat
   3091    regmatch.rm_ic = ignorecase(pat);
   3092  }
   3093 
   3094  if (xp->xp_context == EXPAND_SETTINGS
   3095      || xp->xp_context == EXPAND_BOOL_SETTINGS) {
   3096    ret = ExpandSettings(xp, &regmatch, pat, numMatches, matches, fuzzy);
   3097  } else if (xp->xp_context == EXPAND_STRING_SETTING) {
   3098    ret = ExpandStringSetting(xp, &regmatch, numMatches, matches);
   3099  } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) {
   3100    ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
   3101  } else if (xp->xp_context == EXPAND_MAPPINGS) {
   3102    ret = ExpandMappings(pat, &regmatch, numMatches, matches);
   3103  } else if (xp->xp_context == EXPAND_ARGOPT) {
   3104    ret = expand_argopt(pat, xp, &regmatch, matches, numMatches);
   3105  } else if (xp->xp_context == EXPAND_USER_DEFINED) {
   3106    ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
   3107  } else {
   3108    ret = ExpandOther(pat, xp, &regmatch, matches, numMatches);
   3109  }
   3110 
   3111  if (!fuzzy) {
   3112    vim_regfree(regmatch.regprog);
   3113  }
   3114  xfree(tofree);
   3115 
   3116  return ret;
   3117 }
   3118 
   3119 /// Expand a list of names.
   3120 ///
   3121 /// Generic function for command line completion.  It calls a function to
   3122 /// obtain strings, one by one.  The strings are matched against a regexp
   3123 /// program.  Matching strings are copied into an array, which is returned.
   3124 ///
   3125 /// @param func  returns a string from the list
   3126 void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, char ***matches,
   3127                   int *numMatches, CompleteListItemGetter func, bool escaped)
   3128 {
   3129  const bool fuzzy = cmdline_fuzzy_complete(pat);
   3130  *matches = NULL;
   3131  *numMatches = 0;
   3132 
   3133  garray_T ga;
   3134  if (!fuzzy) {
   3135    ga_init(&ga, sizeof(char *), 30);
   3136  } else {
   3137    ga_init(&ga, sizeof(fuzmatch_str_T), 30);
   3138  }
   3139 
   3140  for (int i = 0;; i++) {
   3141    char *str = (*func)(xp, i);
   3142    if (str == NULL) {  // End of list.
   3143      break;
   3144    }
   3145    if (*str == NUL) {  // Skip empty strings.
   3146      continue;
   3147    }
   3148 
   3149    bool match;
   3150    int score = 0;
   3151    if (xp->xp_pattern[0] != NUL) {
   3152      if (!fuzzy) {
   3153        match = vim_regexec(regmatch, str, 0);
   3154      } else {
   3155        score = fuzzy_match_str(str, pat);
   3156        match = (score != FUZZY_SCORE_NONE);
   3157      }
   3158    } else {
   3159      match = true;
   3160    }
   3161 
   3162    if (!match) {
   3163      continue;
   3164    }
   3165 
   3166    if (escaped) {
   3167      str = vim_strsave_escaped(str, " \t\\.");
   3168    } else {
   3169      str = xstrdup(str);
   3170    }
   3171 
   3172    if (fuzzy) {
   3173      GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){
   3174        .idx = ga.ga_len,
   3175        .str = str,
   3176        .score = score,
   3177      }));
   3178    } else {
   3179      GA_APPEND(char *, &ga, str);
   3180    }
   3181 
   3182    if (func == get_menu_names) {
   3183      // Test for separator added by get_menu_names().
   3184      str += strlen(str) - 1;
   3185      if (*str == '\001') {
   3186        *str = '.';
   3187      }
   3188    }
   3189  }
   3190 
   3191  if (ga.ga_len == 0) {
   3192    return;
   3193  }
   3194 
   3195  // Sort the matches when using regular expression matching and sorting
   3196  // applies to the completion context. Menus and scriptnames should be kept
   3197  // in the specified order.
   3198  const bool sort_matches = !fuzzy
   3199                            && xp->xp_context != EXPAND_MENUNAMES
   3200                            && xp->xp_context != EXPAND_STRING_SETTING
   3201                            && xp->xp_context != EXPAND_MENUS
   3202                            && xp->xp_context != EXPAND_SCRIPTNAMES
   3203                            && xp->xp_context != EXPAND_ARGOPT;
   3204 
   3205  // <SNR> functions should be sorted to the end.
   3206  const bool funcsort = xp->xp_context == EXPAND_EXPRESSION
   3207                        || xp->xp_context == EXPAND_FUNCTIONS
   3208                        || xp->xp_context == EXPAND_USER_FUNC;
   3209 
   3210  // Sort the matches.
   3211  if (sort_matches) {
   3212    if (funcsort) {
   3213      // <SNR> functions should be sorted to the end.
   3214      qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(char *), sort_func_compare);
   3215    } else {
   3216      sort_strings(ga.ga_data, ga.ga_len);
   3217    }
   3218  }
   3219 
   3220  if (!fuzzy) {
   3221    *matches = ga.ga_data;
   3222    *numMatches = ga.ga_len;
   3223  } else {
   3224    fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, funcsort);
   3225    *numMatches = ga.ga_len;
   3226  }
   3227 
   3228  // Reset the variables used for special highlight names expansion, so that
   3229  // they don't show up when getting normal highlight names by ID.
   3230  reset_expand_highlight();
   3231 }
   3232 
   3233 /// Expand shell command matches in one directory of $PATH.
   3234 ///
   3235 /// @param pathed_pattern  fully pathed pattern
   3236 /// @param pathlen         length of the path portion of pathed_pattern (0 if no path)
   3237 static void expand_shellcmd_onedir(char *pathed_pattern, size_t pathlen, char ***matches,
   3238                                   int *numMatches, int flags, hashtab_T *ht, garray_T *gap)
   3239 {
   3240  // Expand matches in one directory of $PATH.
   3241  if (expand_wildcards(1, &pathed_pattern, numMatches, matches, flags) != OK) {
   3242    return;
   3243  }
   3244 
   3245  ga_grow(gap, *numMatches);
   3246 
   3247  for (int i = 0; i < *numMatches; i++) {
   3248    char *name = (*matches)[i];
   3249    size_t namelen = strlen(name);
   3250 
   3251    if (namelen > pathlen) {
   3252      // Check if this name was already found.
   3253      hash_T hash = hash_hash(name + pathlen);
   3254      hashitem_T *hi = hash_lookup(ht, name + pathlen, namelen - pathlen, hash);
   3255      if (HASHITEM_EMPTY(hi)) {
   3256        // Remove the path that was prepended.
   3257        memmove(name, name + pathlen, namelen - pathlen + 1);  // +1 for NUL
   3258        ((char **)gap->ga_data)[gap->ga_len++] = name;
   3259        hash_add_item(ht, hi, name, hash);
   3260        name = NULL;
   3261      }
   3262    }
   3263    xfree(name);
   3264  }
   3265  xfree(*matches);
   3266 }
   3267 
   3268 /// Complete a shell command.
   3269 ///
   3270 /// @param      filepat     is a pattern to match with command names.
   3271 /// @param[out] matches     is pointer to array of pointers to matches.
   3272 ///                         *matches will either be set to NULL or point to
   3273 ///                         allocated memory.
   3274 /// @param[out] numMatches  is pointer to number of matches.
   3275 /// @param      flagsarg    is a combination of EW_* flags.
   3276 static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg)
   3277  FUNC_ATTR_NONNULL_ALL
   3278 {
   3279  char *path = NULL;
   3280  garray_T ga;
   3281  char *buf = xmalloc(MAXPATHL);
   3282  int flags = flagsarg;
   3283  bool did_curdir = false;
   3284 
   3285  // for ":set path=" and ":set tags=" halve backslashes for escaped space
   3286  size_t patlen = strlen(filepat);
   3287  char *pat = xmemdupz(filepat, patlen);
   3288  // Replace "\ " with " ".
   3289  char *e = pat + patlen;
   3290  for (char *s = pat; *s != NUL; s++) {
   3291    if (*s != '\\') {
   3292      continue;
   3293    }
   3294    char *p = s + 1;
   3295    if (*p == ' ') {
   3296      memmove(s, p, (size_t)(e - p) + 1);  // +1 for NUL
   3297      e--;
   3298    }
   3299  }
   3300  patlen = (size_t)(e - pat);
   3301 
   3302  flags |= EW_FILE | EW_EXEC | EW_SHELLCMD;
   3303 
   3304  bool mustfree = false;  // Track memory allocation for *path.
   3305  if (pat[0] == '.' && (vim_ispathsep(pat[1])
   3306                        || (pat[1] == '.' && vim_ispathsep(pat[2])))) {
   3307    path = ".";
   3308  } else {
   3309    // For an absolute name we don't use $PATH.
   3310    if (!path_is_absolute(pat)) {
   3311      path = vim_getenv("PATH");
   3312    }
   3313    if (path == NULL) {
   3314      path = "";
   3315    } else {
   3316      mustfree = true;
   3317    }
   3318  }
   3319 
   3320  // Go over all directories in $PATH.  Expand matches in that directory and
   3321  // collect them in "ga". When "." is not in $PATH also expand for the
   3322  // current directory, to find "subdir/cmd".
   3323  ga_init(&ga, (int)sizeof(char *), 10);
   3324  hashtab_T found_ht;
   3325  hash_init(&found_ht);
   3326  for (char *s = path;; s = e) {
   3327    size_t pathlen;  // length of the path portion of buf (including trailing slash).
   3328    size_t seplen;
   3329 
   3330    if (*s == NUL) {
   3331      if (did_curdir) {
   3332        break;
   3333      }
   3334 
   3335      // Find directories in the current directory, path is empty.
   3336      did_curdir = true;
   3337      flags |= EW_DIR;
   3338 
   3339      e = s;
   3340      pathlen = 0;
   3341      seplen = 0;
   3342    } else {
   3343      e = vim_strchr(s, ENV_SEPCHAR);
   3344      if (e == NULL) {
   3345        e = s + strlen(s);
   3346      }
   3347 
   3348      pathlen = (size_t)(e - s);
   3349      if (strncmp(s, ".", pathlen) == 0) {
   3350        did_curdir = true;
   3351        flags |= EW_DIR;
   3352      } else {
   3353        // Do not match directories inside a $PATH item.
   3354        flags &= ~EW_DIR;
   3355      }
   3356 
   3357      seplen = !after_pathsep(s, e) ? STRLEN_LITERAL(PATHSEPSTR) : 0;
   3358    }
   3359 
   3360    // Make sure that the pathed pattern (ie the path and pattern concatenated
   3361    // together) will fit inside the buffer. If not skip it and move on to the
   3362    // next path.
   3363    if (pathlen + seplen + patlen + 1 <= MAXPATHL) {
   3364      if (pathlen > 0) {
   3365        xmemcpyz(buf, s, pathlen);
   3366        if (seplen > 0) {
   3367          xmemcpyz(buf + pathlen, S_LEN(PATHSEPSTR));
   3368          pathlen += seplen;
   3369        }
   3370      }
   3371      xmemcpyz(buf + pathlen, pat, patlen);
   3372 
   3373      expand_shellcmd_onedir(buf, pathlen, matches, numMatches, flags, &found_ht, &ga);
   3374    }
   3375 
   3376    if (*e != NUL) {
   3377      e++;
   3378    }
   3379  }
   3380  *matches = ga.ga_data;
   3381  *numMatches = ga.ga_len;
   3382 
   3383  xfree(buf);
   3384  xfree(pat);
   3385  if (mustfree) {
   3386    xfree(path);
   3387  }
   3388  hash_clear(&found_ht);
   3389 }
   3390 
   3391 /// Call "user_expand_func()" to invoke a user defined Vim script function and
   3392 /// return the result (either a string, a List or NULL).
   3393 static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp)
   3394  FUNC_ATTR_NONNULL_ALL
   3395 {
   3396  CmdlineInfo *const ccline = get_cmdline_info();
   3397  char keep = 0;
   3398  typval_T args[4];
   3399  const sctx_T save_current_sctx = current_sctx;
   3400 
   3401  if (xp->xp_arg == NULL || xp->xp_arg[0] == NUL || xp->xp_line == NULL) {
   3402    return NULL;
   3403  }
   3404 
   3405  if (ccline->cmdbuff != NULL) {
   3406    keep = ccline->cmdbuff[ccline->cmdlen];
   3407    ccline->cmdbuff[ccline->cmdlen] = 0;
   3408  }
   3409 
   3410  char *pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
   3411  args[0].v_type = VAR_STRING;
   3412  args[1].v_type = VAR_STRING;
   3413  args[2].v_type = VAR_NUMBER;
   3414  args[3].v_type = VAR_UNKNOWN;
   3415  args[0].vval.v_string = pat;
   3416  args[1].vval.v_string = xp->xp_line;
   3417  args[2].vval.v_number = xp->xp_col;
   3418 
   3419  current_sctx = xp->xp_script_ctx;
   3420 
   3421  void *const ret = user_expand_func(xp->xp_arg, 3, args);
   3422 
   3423  current_sctx = save_current_sctx;
   3424  if (ccline->cmdbuff != NULL) {
   3425    ccline->cmdbuff[ccline->cmdlen] = keep;
   3426  }
   3427 
   3428  xfree(pat);
   3429  return ret;
   3430 }
   3431 
   3432 /// Expand names with a function defined by the user (EXPAND_USER_DEFINED and
   3433 /// EXPAND_USER_LIST).
   3434 static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *regmatch,
   3435                             char ***matches, int *numMatches)
   3436 {
   3437  const bool fuzzy = cmdline_fuzzy_complete(pat);
   3438  *matches = NULL;
   3439  *numMatches = 0;
   3440 
   3441  char *const retstr = call_user_expand_func(call_func_retstr, xp);
   3442  if (retstr == NULL) {
   3443    return FAIL;
   3444  }
   3445 
   3446  garray_T ga;
   3447  if (!fuzzy) {
   3448    ga_init(&ga, (int)sizeof(char *), 3);
   3449  } else {
   3450    ga_init(&ga, (int)sizeof(fuzmatch_str_T), 3);
   3451  }
   3452 
   3453  for (char *s = retstr, *e; *s != NUL; s = e) {
   3454    e = vim_strchr(s, '\n');
   3455    if (e == NULL) {
   3456      e = s + strlen(s);
   3457    }
   3458    const char keep = *e;
   3459    *e = NUL;
   3460 
   3461    bool match;
   3462    int score = 0;
   3463    if (xp->xp_pattern[0] != NUL) {
   3464      if (!fuzzy) {
   3465        match = vim_regexec(regmatch, s, 0);
   3466      } else {
   3467        score = fuzzy_match_str(s, pat);
   3468        match = (score != FUZZY_SCORE_NONE);
   3469      }
   3470    } else {
   3471      match = true;               // match everything
   3472    }
   3473 
   3474    *e = keep;
   3475 
   3476    if (match) {
   3477      char *p = xmemdupz(s, (size_t)(e - s));
   3478 
   3479      if (!fuzzy) {
   3480        GA_APPEND(char *, &ga, p);
   3481      } else {
   3482        GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){
   3483          .idx = ga.ga_len,
   3484          .str = p,
   3485          .score = score,
   3486        }));
   3487      }
   3488    }
   3489 
   3490    if (*e != NUL) {
   3491      e++;
   3492    }
   3493  }
   3494  xfree(retstr);
   3495 
   3496  if (ga.ga_len == 0) {
   3497    return OK;
   3498  }
   3499 
   3500  if (!fuzzy) {
   3501    *matches = ga.ga_data;
   3502    *numMatches = ga.ga_len;
   3503  } else {
   3504    fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false);
   3505    *numMatches = ga.ga_len;
   3506  }
   3507  return OK;
   3508 }
   3509 
   3510 /// Expand names with a list returned by a function defined by the user.
   3511 static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches)
   3512 {
   3513  *matches = NULL;
   3514  *numMatches = 0;
   3515  list_T *const retlist = call_user_expand_func(call_func_retlist, xp);
   3516  if (retlist == NULL) {
   3517    return FAIL;
   3518  }
   3519 
   3520  garray_T ga;
   3521  ga_init(&ga, (int)sizeof(char *), 3);
   3522  // Loop over the items in the list.
   3523  TV_LIST_ITER_CONST(retlist, li, {
   3524    if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
   3525        || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
   3526      continue;  // Skip non-string items and empty strings.
   3527    }
   3528    char *p = xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string);
   3529 
   3530    GA_APPEND(char *, &ga, p);
   3531  });
   3532  tv_list_unref(retlist);
   3533 
   3534  *matches = ga.ga_data;
   3535  *numMatches = ga.ga_len;
   3536  return OK;
   3537 }
   3538 
   3539 static int ExpandUserLua(expand_T *xp, int *num_file, char ***file)
   3540 {
   3541  typval_T rettv = TV_INITIAL_VALUE;
   3542  nlua_call_user_expand_func(xp, &rettv);
   3543  if (rettv.v_type != VAR_LIST) {
   3544    tv_clear(&rettv);
   3545    return FAIL;
   3546  }
   3547 
   3548  list_T *const retlist = rettv.vval.v_list;
   3549 
   3550  garray_T ga;
   3551  ga_init(&ga, (int)sizeof(char *), 3);
   3552  // Loop over the items in the list.
   3553  TV_LIST_ITER_CONST(retlist, li, {
   3554    if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
   3555        || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
   3556      continue;  // Skip non-string items and empty strings.
   3557    }
   3558 
   3559    GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string));
   3560  });
   3561  tv_list_unref(retlist);
   3562 
   3563  *file = ga.ga_data;
   3564  *num_file = ga.ga_len;
   3565  return OK;
   3566 }
   3567 
   3568 /// Expand `file` for all comma-separated directories in `path`.
   3569 /// Adds matches to `ga`.
   3570 /// If "dirs" is true only expand directory names.
   3571 void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dirs)
   3572  FUNC_ATTR_NONNULL_ALL
   3573 {
   3574  char *buf = xmalloc(MAXPATHL);
   3575 
   3576  expand_T xpc;
   3577  ExpandInit(&xpc);
   3578  xpc.xp_context = dirs ? EXPAND_DIRECTORIES : EXPAND_FILES;
   3579 
   3580  size_t filelen = strlen(file);
   3581 
   3582 #if defined(MSWIN)
   3583  // Using the platform's path separator (\) makes vim incorrectly
   3584  // treat it as an escape character, use '/' instead.
   3585 # define TMP_PATHSEPSTR "/"
   3586 #else
   3587 # define TMP_PATHSEPSTR PATHSEPSTR
   3588 #endif
   3589 
   3590  // Loop over all entries in {path}.
   3591  while (*path != NUL) {
   3592    // Copy one item of the path to buf[] and concatenate the file name.
   3593 
   3594    // length of the path portion of buf (including trailing slash).
   3595    size_t pathlen = copy_option_part(&path, buf, MAXPATHL, ",");
   3596    size_t seplen = (*buf != NUL && !after_pathsep(buf, buf + pathlen))
   3597                    ? STRLEN_LITERAL(TMP_PATHSEPSTR) : 0;
   3598 
   3599    if (pathlen + seplen + filelen + 1 <= MAXPATHL) {
   3600      if (seplen > 0) {
   3601        xmemcpyz(buf + pathlen, S_LEN(TMP_PATHSEPSTR));
   3602        pathlen += seplen;
   3603      }
   3604      xmemcpyz(buf + pathlen, file, filelen);
   3605 
   3606      char **p;
   3607      int num_p = 0;
   3608      ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options);
   3609      if (num_p > 0) {
   3610        ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options);
   3611 
   3612        // Concatenate new results to previous ones.
   3613        ga_grow(ga, num_p);
   3614        // take over the pointers and put them in "ga"
   3615        for (int i = 0; i < num_p; i++) {
   3616          ((char **)ga->ga_data)[ga->ga_len] = p[i];
   3617          ga->ga_len++;
   3618        }
   3619        xfree(p);
   3620      }
   3621    }
   3622  }
   3623 
   3624  xfree(buf);
   3625 }
   3626 #undef TMP_PATHSEPSTR
   3627 
   3628 /// Translate some keys pressed when 'wildmenu' is used.
   3629 int wildmenu_translate_key(CmdlineInfo *cclp, int key, expand_T *xp, bool did_wild_list)
   3630 {
   3631  int c = key;
   3632 
   3633  if (cmdline_pum_active() || did_wild_list || wild_menu_showing) {
   3634    if (c == K_LEFT) {
   3635      c = Ctrl_P;
   3636    } else if (c == K_RIGHT) {
   3637      c = Ctrl_N;
   3638    }
   3639  }
   3640 
   3641  // Hitting CR after "emenu Name.": complete submenu
   3642  if (xp->xp_context == EXPAND_MENUNAMES
   3643      && cclp->cmdpos > 1
   3644      && cclp->cmdbuff[cclp->cmdpos - 1] == '.'
   3645      && cclp->cmdbuff[cclp->cmdpos - 2] != '\\'
   3646      && (c == '\n' || c == '\r' || c == K_KENTER)) {
   3647    c = K_DOWN;
   3648  }
   3649 
   3650  return c;
   3651 }
   3652 
   3653 /// Delete characters on the command line, from "from" to the current position.
   3654 static void cmdline_del(CmdlineInfo *cclp, int from)
   3655 {
   3656  assert(cclp->cmdpos <= cclp->cmdlen);
   3657  memmove(cclp->cmdbuff + from, cclp->cmdbuff + cclp->cmdpos,
   3658          (size_t)cclp->cmdlen - (size_t)cclp->cmdpos + 1);
   3659  cclp->cmdlen -= cclp->cmdpos - from;
   3660  cclp->cmdpos = from;
   3661 }
   3662 
   3663 /// Handle a key pressed when the wild menu for the menu names
   3664 /// (EXPAND_MENUNAMES) is displayed.
   3665 static int wildmenu_process_key_menunames(CmdlineInfo *cclp, int key, expand_T *xp)
   3666 {
   3667  // Hitting <Down> after "emenu Name.": complete submenu
   3668  if (key == K_DOWN && cclp->cmdpos > 0
   3669      && cclp->cmdbuff[cclp->cmdpos - 1] == '.') {
   3670    key = (int)p_wc;
   3671    KeyTyped = true;  // in case the key was mapped
   3672  } else if (key == K_UP) {
   3673    // Hitting <Up>: Remove one submenu name in front of the
   3674    // cursor
   3675    bool found = false;
   3676 
   3677    int j = (int)(xp->xp_pattern - cclp->cmdbuff);
   3678    int i = 0;
   3679    while (--j > 0) {
   3680      // check for start of menu name
   3681      if (cclp->cmdbuff[j] == ' '
   3682          && cclp->cmdbuff[j - 1] != '\\') {
   3683        i = j + 1;
   3684        break;
   3685      }
   3686      // check for start of submenu name
   3687      if (cclp->cmdbuff[j] == '.'
   3688          && cclp->cmdbuff[j - 1] != '\\') {
   3689        if (found) {
   3690          i = j + 1;
   3691          break;
   3692        } else {
   3693          found = true;
   3694        }
   3695      }
   3696    }
   3697    if (i > 0) {
   3698      cmdline_del(cclp, i);
   3699    }
   3700    key = (int)p_wc;
   3701    KeyTyped = true;  // in case the key was mapped
   3702    xp->xp_context = EXPAND_NOTHING;
   3703  }
   3704 
   3705  return key;
   3706 }
   3707 
   3708 /// Handle a key pressed when the wild menu for file names (EXPAND_FILES) or
   3709 /// directory names (EXPAND_DIRECTORIES) or shell command names
   3710 /// (EXPAND_SHELLCMD) is displayed.
   3711 static int wildmenu_process_key_filenames(CmdlineInfo *cclp, int key, expand_T *xp)
   3712 {
   3713  char upseg[5];
   3714  upseg[0] = PATHSEP;
   3715  upseg[1] = '.';
   3716  upseg[2] = '.';
   3717  upseg[3] = PATHSEP;
   3718  upseg[4] = NUL;
   3719 
   3720  if (key == K_DOWN
   3721      && cclp->cmdpos > 0
   3722      && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP
   3723      && (cclp->cmdpos < 3
   3724          || cclp->cmdbuff[cclp->cmdpos - 2] != '.'
   3725          || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) {
   3726    // go down a directory
   3727    key = (int)p_wc;
   3728    KeyTyped = true;  // in case the key was mapped
   3729  } else if (strncmp(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN) {
   3730    // If in a direct ancestor, strip off one ../ to go down
   3731    bool found = false;
   3732 
   3733    int j = cclp->cmdpos;
   3734    int i = (int)(xp->xp_pattern - cclp->cmdbuff);
   3735    while (--j > i) {
   3736      j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j);
   3737      if (vim_ispathsep(cclp->cmdbuff[j])) {
   3738        found = true;
   3739        break;
   3740      }
   3741    }
   3742    if (found
   3743        && cclp->cmdbuff[j - 1] == '.'
   3744        && cclp->cmdbuff[j - 2] == '.'
   3745        && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) {
   3746      cmdline_del(cclp, j - 2);
   3747      key = (int)p_wc;
   3748      KeyTyped = true;  // in case the key was mapped
   3749    }
   3750  } else if (key == K_UP) {
   3751    // go up a directory
   3752    bool found = false;
   3753 
   3754    int j = cclp->cmdpos - 1;
   3755    int i = (int)(xp->xp_pattern - cclp->cmdbuff);
   3756    while (--j > i) {
   3757      j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j);
   3758      if (vim_ispathsep(cclp->cmdbuff[j])
   3759 #ifdef BACKSLASH_IN_FILENAME
   3760          && vim_strchr(" *?[{`$%#", (uint8_t)cclp->cmdbuff[j + 1]) == NULL
   3761 #endif
   3762          ) {
   3763        if (found) {
   3764          i = j + 1;
   3765          break;
   3766        } else {
   3767          found = true;
   3768        }
   3769      }
   3770    }
   3771 
   3772    if (!found) {
   3773      j = i;
   3774    } else if (strncmp(cclp->cmdbuff + j, upseg, 4) == 0) {
   3775      j += 4;
   3776    } else if (strncmp(cclp->cmdbuff + j, upseg + 1, 3) == 0
   3777               && j == i) {
   3778      j += 3;
   3779    } else {
   3780      j = 0;
   3781    }
   3782 
   3783    if (j > 0) {
   3784      // TODO(tarruda): this is only for DOS/Unix systems - need to put in
   3785      // machine-specific stuff here and in upseg init
   3786      cmdline_del(cclp, j);
   3787      put_on_cmdline(upseg + 1, 3, false);
   3788    } else if (cclp->cmdpos > i) {
   3789      cmdline_del(cclp, i);
   3790    }
   3791 
   3792    // Now complete in the new directory. Set KeyTyped in case the
   3793    // Up key came from a mapping.
   3794    key = (int)p_wc;
   3795    KeyTyped = true;
   3796  }
   3797 
   3798  return key;
   3799 }
   3800 
   3801 /// Handle a key pressed when wild menu is displayed
   3802 int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp)
   3803 {
   3804  // Special translations for 'wildmenu'
   3805  if (xp->xp_context == EXPAND_MENUNAMES) {
   3806    return wildmenu_process_key_menunames(cclp, key, xp);
   3807  }
   3808  if (xp->xp_context == EXPAND_FILES
   3809      || xp->xp_context == EXPAND_DIRECTORIES
   3810      || xp->xp_context == EXPAND_SHELLCMD) {
   3811    return wildmenu_process_key_filenames(cclp, key, xp);
   3812  }
   3813 
   3814  return key;
   3815 }
   3816 
   3817 /// Free expanded names when finished walking through the matches
   3818 void wildmenu_cleanup(CmdlineInfo *cclp)
   3819 {
   3820  if (!p_wmnu || wild_menu_showing == 0) {
   3821    return;
   3822  }
   3823 
   3824  const bool skt = KeyTyped;
   3825  const int old_RedrawingDisabled = RedrawingDisabled;
   3826 
   3827  if (cclp->input_fn) {
   3828    RedrawingDisabled = 0;
   3829  }
   3830 
   3831  // Clear highlighting applied during wildmenu activity
   3832  set_no_hlsearch(true);
   3833 
   3834  if (wild_menu_showing == WM_SCROLLED) {
   3835    // Entered command line, move it up
   3836    cmdline_row--;
   3837    redrawcmd();
   3838    wild_menu_showing = 0;
   3839  } else if (save_p_ls != -1) {
   3840    // restore 'laststatus' and 'winminheight'
   3841    p_ls = save_p_ls;
   3842    p_wmh = save_p_wmh;
   3843    last_status(false);
   3844    update_screen();  // redraw the screen NOW
   3845    redrawcmd();
   3846    save_p_ls = -1;
   3847    wild_menu_showing = 0;
   3848  } else {
   3849    win_redraw_last_status(topframe);
   3850    wild_menu_showing = 0;  // must be before redraw_statuslines #8385
   3851    redraw_statuslines();
   3852  }
   3853  KeyTyped = skt;
   3854  if (cclp->input_fn) {
   3855    RedrawingDisabled = old_RedrawingDisabled;
   3856  }
   3857 }
   3858 
   3859 /// "getcompletion()" function
   3860 void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   3861 {
   3862  expand_T xpc;
   3863  bool filtered = false;
   3864  int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
   3865                | WILD_NO_BEEP | WILD_HOME_REPLACE;
   3866 
   3867  if (tv_check_for_string_arg(argvars, 1) == FAIL) {
   3868    return;
   3869  }
   3870  const char *const type = tv_get_string(&argvars[1]);
   3871 
   3872  if (argvars[2].v_type != VAR_UNKNOWN) {
   3873    filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
   3874  }
   3875 
   3876  if (p_wic) {
   3877    options |= WILD_ICASE;
   3878  }
   3879 
   3880  // For filtered results, 'wildignore' is used
   3881  if (!filtered) {
   3882    options |= WILD_KEEP_ALL;
   3883  }
   3884 
   3885  if (argvars[0].v_type != VAR_STRING) {
   3886    emsg(_(e_invarg));
   3887    return;
   3888  }
   3889  const char *const pattern = tv_get_string(&argvars[0]);
   3890  const char *pattern_start = pattern;
   3891 
   3892  if (strcmp(type, "cmdline") == 0) {
   3893    const int cmdline_len = (int)strlen(pattern);
   3894    set_cmd_context(&xpc, (char *)pattern, cmdline_len, cmdline_len, false);
   3895    pattern_start = xpc.xp_pattern;
   3896    xpc.xp_pattern_len = strlen(xpc.xp_pattern);
   3897    xpc.xp_col = cmdline_len;
   3898    goto theend;
   3899  }
   3900 
   3901  ExpandInit(&xpc);
   3902  xpc.xp_pattern = (char *)pattern;
   3903  xpc.xp_pattern_len = strlen(xpc.xp_pattern);
   3904  xpc.xp_line = (char *)pattern;
   3905 
   3906  xpc.xp_context = cmdcomplete_str_to_type(type);
   3907  switch (xpc.xp_context) {
   3908  case EXPAND_NOTHING:
   3909    semsg(_(e_invarg2), type);
   3910    return;
   3911 
   3912  case EXPAND_USER_DEFINED:
   3913    // Must be "custom,funcname" pattern
   3914    if (strncmp(type, "custom,", 7) != 0) {
   3915      semsg(_(e_invarg2), type);
   3916      return;
   3917    }
   3918 
   3919    xpc.xp_arg = (char *)(type + 7);
   3920    break;
   3921 
   3922  case EXPAND_USER_LIST:
   3923    // Must be "customlist,funcname" pattern
   3924    if (strncmp(type, "customlist,", 11) != 0) {
   3925      semsg(_(e_invarg2), type);
   3926      return;
   3927    }
   3928 
   3929    xpc.xp_arg = (char *)(type + 11);
   3930    break;
   3931 
   3932  case EXPAND_MENUS:
   3933    set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false);
   3934    xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start);
   3935    break;
   3936 
   3937  case EXPAND_SIGN:
   3938    set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
   3939    xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start);
   3940    break;
   3941 
   3942  case EXPAND_RUNTIME:
   3943    set_context_in_runtime_cmd(&xpc, xpc.xp_pattern);
   3944    xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start);
   3945    break;
   3946 
   3947  case EXPAND_SHELLCMDLINE: {
   3948    int context = EXPAND_SHELLCMDLINE;
   3949    set_context_for_wildcard_arg(NULL, xpc.xp_pattern, false, &xpc, &context);
   3950    xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start);
   3951    break;
   3952  }
   3953 
   3954  case EXPAND_FILETYPECMD:
   3955    filetype_expand_what = EXP_FILETYPECMD_ALL;
   3956    break;
   3957 
   3958  default:
   3959    break;
   3960  }
   3961 
   3962 theend:
   3963  if (xpc.xp_context == EXPAND_LUA) {
   3964    xpc.xp_col = (int)strlen(xpc.xp_line);
   3965    nlua_expand_pat(&xpc);
   3966    xpc.xp_pattern_len -= (size_t)(xpc.xp_pattern - pattern_start);
   3967  }
   3968  char *pat;
   3969  if (cmdline_fuzzy_completion_supported(&xpc)) {
   3970    // when fuzzy matching, don't modify the search string
   3971    pat = xmemdupz(xpc.xp_pattern, xpc.xp_pattern_len);
   3972  } else {
   3973    pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
   3974  }
   3975 
   3976  ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);
   3977  tv_list_alloc_ret(rettv, xpc.xp_numfiles);
   3978 
   3979  for (int i = 0; i < xpc.xp_numfiles; i++) {
   3980    tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
   3981  }
   3982  xfree(pat);
   3983  ExpandCleanup(&xpc);
   3984 }
   3985 
   3986 /// "getcompletiontype()" function
   3987 void f_getcompletiontype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   3988 {
   3989  rettv->v_type = VAR_STRING;
   3990  rettv->vval.v_string = NULL;
   3991 
   3992  if (tv_check_for_string_arg(argvars, 0) == FAIL) {
   3993    return;
   3994  }
   3995 
   3996  const char *pat = tv_get_string(&argvars[0]);
   3997  expand_T xpc;
   3998  ExpandInit(&xpc);
   3999 
   4000  int cmdline_len = (int)strlen(pat);
   4001  set_cmd_context(&xpc, (char *)pat, cmdline_len, cmdline_len, false);
   4002  rettv->vval.v_string = cmdcomplete_type_to_str(xpc.xp_context, xpc.xp_arg);
   4003 
   4004  ExpandCleanup(&xpc);
   4005 }
   4006 
   4007 /// "cmdcomplete_info()" function
   4008 void f_cmdcomplete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   4009 {
   4010  CmdlineInfo *ccline = get_cmdline_info();
   4011 
   4012  tv_dict_alloc_ret(rettv);
   4013  if (ccline == NULL || ccline->xpc == NULL || ccline->xpc->xp_files == NULL) {
   4014    return;
   4015  }
   4016 
   4017  dict_T *retdict = rettv->vval.v_dict;
   4018  int ret = tv_dict_add_str(retdict, S_LEN("cmdline_orig"), cmdline_orig);
   4019  if (ret == OK) {
   4020    ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible());
   4021  }
   4022  if (ret == OK) {
   4023    ret = tv_dict_add_nr(retdict, S_LEN("selected"), ccline->xpc->xp_selected);
   4024  }
   4025  if (ret == OK) {
   4026    list_T *li = tv_list_alloc(ccline->xpc->xp_numfiles);
   4027    ret = tv_dict_add_list(retdict, S_LEN("matches"), li);
   4028    for (int idx = 0; ret == OK && idx < ccline->xpc->xp_numfiles; idx++) {
   4029      tv_list_append_string(li, ccline->xpc->xp_files[idx], -1);
   4030    }
   4031  }
   4032 }
   4033 
   4034 /// Copy a substring from the current buffer (curbuf), spanning from the given
   4035 /// 'start' position to the word boundary after 'end' position.
   4036 /// The copied string is stored in '*match', and the actual end position of the
   4037 /// matched text is returned in '*match_end'.
   4038 static int copy_substring_from_pos(pos_T *start, pos_T *end, char **match, pos_T *match_end)
   4039 {
   4040  bool exacttext = wop_flags & kOptWopFlagExacttext;
   4041 
   4042  if (start->lnum > end->lnum
   4043      || (start->lnum == end->lnum && start->col >= end->col)) {
   4044    return FAIL;  // invalid range
   4045  }
   4046 
   4047  // Use a growable string (ga)
   4048  garray_T ga;
   4049  ga_init(&ga, 1, 128);
   4050 
   4051  // Append start line from start->col to end
   4052  char *start_line = ml_get(start->lnum);
   4053  char *start_ptr = start_line + start->col;
   4054  bool is_single_line = start->lnum == end->lnum;
   4055 
   4056  int segment_len = is_single_line ? (int)(end->col - start->col)
   4057                                   : (int)(ml_get_len(start->lnum) - start->col);
   4058  ga_grow(&ga, segment_len + 2);
   4059  ga_concat_len(&ga, start_ptr, (size_t)segment_len);
   4060  if (!is_single_line) {
   4061    if (exacttext) {
   4062      GA_CONCAT_LITERAL(&ga, "\\n");
   4063    } else {
   4064      ga_append(&ga, '\n');
   4065    }
   4066  }
   4067 
   4068  // Append full lines between start and end
   4069  if (!is_single_line) {
   4070    for (linenr_T lnum = start->lnum + 1; lnum < end->lnum; lnum++) {
   4071      char *line = ml_get(lnum);
   4072      int linelen = ml_get_len(lnum);
   4073      ga_grow(&ga, linelen + 2);
   4074      ga_concat_len(&ga, line, (size_t)linelen);
   4075      if (exacttext) {
   4076        GA_CONCAT_LITERAL(&ga, "\\n");
   4077      } else {
   4078        ga_append(&ga, '\n');
   4079      }
   4080    }
   4081  }
   4082 
   4083  // Append partial end line (up to word end)
   4084  char *end_line = ml_get(end->lnum);
   4085  char *word_end = find_word_end(end_line + end->col);
   4086  segment_len = (int)(word_end - end_line);
   4087  ga_grow(&ga, segment_len);
   4088  ga_concat_len(&ga, end_line + (is_single_line ? end->col : 0),
   4089                (size_t)(segment_len - (is_single_line ? end->col : 0)));
   4090 
   4091  // Null-terminate
   4092  ga_grow(&ga, 1);
   4093  ga_append(&ga, NUL);
   4094 
   4095  *match = (char *)ga.ga_data;
   4096  match_end->lnum = end->lnum;
   4097  match_end->col = segment_len;
   4098 
   4099  return OK;
   4100 }
   4101 
   4102 /// Returns true if the given string `str` matches the regex pattern `pat`.
   4103 /// Honors the 'ignorecase' (p_ic) and 'smartcase' (p_scs) settings to determine
   4104 /// case sensitivity.
   4105 static bool is_regex_match(char *pat, char *str)
   4106 {
   4107  if (strcmp(pat, str) == 0) {
   4108    return true;
   4109  }
   4110 
   4111  regmatch_T regmatch;
   4112 
   4113  emsg_off++;
   4114  msg_silent++;
   4115  regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
   4116  emsg_off--;
   4117  msg_silent--;
   4118 
   4119  if (regmatch.regprog == NULL) {
   4120    return false;
   4121  }
   4122  regmatch.rm_ic = p_ic;
   4123  if (p_ic && p_scs) {
   4124    regmatch.rm_ic = !pat_has_uppercase(pat);
   4125  }
   4126 
   4127  emsg_off++;
   4128  msg_silent++;
   4129  bool result = vim_regexec_nl(&regmatch, str, (colnr_T)0);
   4130  emsg_off--;
   4131  msg_silent--;
   4132 
   4133  vim_regfree(regmatch.regprog);
   4134  return result;
   4135 }
   4136 
   4137 /// Constructs a new match string by appending text from the buffer (starting at
   4138 /// end_match_pos) to the given pattern `pat`. The result is a concatenation of
   4139 /// `pat` and the word following end_match_pos.
   4140 /// If 'lowercase' is true, the appended text is converted to lowercase before
   4141 /// being combined. Returns the newly allocated match string, or NULL on failure.
   4142 static char *concat_pattern_with_buffer_match(char *pat, int pat_len, pos_T *end_match_pos,
   4143                                              bool lowercase)
   4144  FUNC_ATTR_NONNULL_RET
   4145 {
   4146  char *line = ml_get(end_match_pos->lnum);
   4147  char *word_end = find_word_end(line + end_match_pos->col);
   4148  int match_len = (int)(word_end - (line + end_match_pos->col));
   4149  char *match = xmalloc((size_t)match_len + (size_t)pat_len + 1);  // +1 for NUL
   4150 
   4151  memmove(match, pat, (size_t)pat_len);
   4152  if (match_len > 0) {
   4153    if (lowercase) {
   4154      char *mword = xstrnsave(line + end_match_pos->col, (size_t)match_len);
   4155      char *lower = strcase_save(mword, false);
   4156      xfree(mword);
   4157      memmove(match + pat_len, lower, (size_t)match_len);
   4158      xfree(lower);
   4159    } else {
   4160      memmove(match + pat_len, line + end_match_pos->col, (size_t)match_len);
   4161    }
   4162  }
   4163  match[pat_len + match_len] = NUL;
   4164  return match;
   4165 }
   4166 
   4167 /// Search for strings matching "pat" in the specified range and return them.
   4168 /// Returns OK on success, FAIL otherwise.
   4169 ///
   4170 /// @param      pat        pattern to match
   4171 /// @param      dir        FORWARD or BACKWARD
   4172 /// @param[out] matches    array with matched string
   4173 /// @param[out] numMatches number of matches
   4174 static int expand_pattern_in_buf(char *pat, Direction dir, char ***matches, int *numMatches)
   4175 {
   4176  bool exacttext = wop_flags & kOptWopFlagExacttext;
   4177  bool has_range = search_first_line != 0;
   4178 
   4179  *matches = NULL;
   4180  *numMatches = 0;
   4181 
   4182  if (pat == NULL || *pat == NUL) {
   4183    return FAIL;
   4184  }
   4185 
   4186  int pat_len = (int)strlen(pat);
   4187  pos_T cur_match_pos = { 0 }, prev_match_pos = { 0 };
   4188  if (has_range) {
   4189    cur_match_pos.lnum = search_first_line;
   4190  } else {
   4191    cur_match_pos = pre_incsearch_pos;
   4192  }
   4193 
   4194  int search_flags = SEARCH_OPT | SEARCH_NOOF | SEARCH_PEEK | SEARCH_NFMSG
   4195                     | (has_range ? SEARCH_START : 0);
   4196 
   4197  garray_T ga;
   4198  ga_init(&ga, sizeof(char *), 10);  // Use growable array of char *
   4199 
   4200  pos_T end_match_pos, word_end_pos;
   4201  bool looped_around = false;
   4202  bool compl_started = false;
   4203  char *match, *full_match;
   4204 
   4205  while (true) {
   4206    emsg_off++;
   4207    msg_silent++;
   4208    int found_new_match = searchit(NULL, curbuf, &cur_match_pos,
   4209                                   &end_match_pos, dir, pat, (size_t)pat_len, 1L,
   4210                                   search_flags, RE_LAST, NULL);
   4211    msg_silent--;
   4212    emsg_off--;
   4213 
   4214    if (found_new_match == FAIL) {
   4215      break;
   4216    }
   4217 
   4218    // If in range mode, check if match is within the range
   4219    if (has_range && (cur_match_pos.lnum < search_first_line
   4220                      || cur_match_pos.lnum > search_last_line)) {
   4221      break;
   4222    }
   4223 
   4224    if (compl_started) {
   4225      // If we've looped back to an earlier match, stop
   4226      if ((dir == FORWARD && ltoreq(cur_match_pos, prev_match_pos))
   4227          || (dir == BACKWARD && ltoreq(prev_match_pos, cur_match_pos))) {
   4228        if (looped_around) {
   4229          break;
   4230        } else {
   4231          looped_around = true;
   4232        }
   4233      }
   4234    }
   4235 
   4236    compl_started = true;
   4237    prev_match_pos = cur_match_pos;
   4238 
   4239    // Abort if user typed a character or interrupted
   4240    if (char_avail() || got_int) {
   4241      if (got_int) {
   4242        (void)vpeekc();  // Remove <C-C> from input stream
   4243        got_int = false;  // Don't abandon the command line
   4244      }
   4245      goto cleanup;
   4246    }
   4247 
   4248    // searchit() can return line number +1 past the last line when
   4249    // searching for "foo\n" if "foo" is at end of buffer.
   4250    if (end_match_pos.lnum > curbuf->b_ml.ml_line_count) {
   4251      cur_match_pos.lnum = 1;
   4252      cur_match_pos.col = 0;
   4253      cur_match_pos.coladd = 0;
   4254      continue;
   4255    }
   4256 
   4257    // Extract the matching text prepended to completed word
   4258    if (!copy_substring_from_pos(&cur_match_pos, &end_match_pos, &full_match,
   4259                                 &word_end_pos)) {
   4260      break;
   4261    }
   4262 
   4263    if (exacttext) {
   4264      match = full_match;
   4265    } else {
   4266      // Construct a new match from completed word appended to pattern itself
   4267      match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, false);
   4268 
   4269      // The regex pattern may include '\C' or '\c'. First, try matching the
   4270      // buffer word as-is. If it doesn't match, try again with the lowercase
   4271      // version of the word to handle smartcase behavior.
   4272      if (!is_regex_match(match, full_match)) {
   4273        xfree(match);
   4274        match = concat_pattern_with_buffer_match(pat, pat_len, &end_match_pos, true);
   4275        if (!is_regex_match(match, full_match)) {
   4276          xfree(match);
   4277          xfree(full_match);
   4278          continue;
   4279        }
   4280      }
   4281      xfree(full_match);
   4282    }
   4283 
   4284    // Include this match if it is not a duplicate
   4285    for (int i = 0; i < ga.ga_len; i++) {
   4286      if (strcmp(match, ((char **)ga.ga_data)[i]) == 0) {
   4287        XFREE_CLEAR(match);
   4288        break;
   4289      }
   4290    }
   4291    if (match != NULL) {
   4292      ga_grow(&ga, 1);
   4293      ((char **)ga.ga_data)[ga.ga_len++] = match;
   4294      if (ga.ga_len > TAG_MANY) {
   4295        break;
   4296      }
   4297    }
   4298    if (has_range) {
   4299      cur_match_pos = word_end_pos;
   4300    }
   4301  }
   4302 
   4303  *matches = (char **)ga.ga_data;
   4304  *numMatches = ga.ga_len;
   4305  return OK;
   4306 
   4307 cleanup:
   4308  ga_clear_strings(&ga);
   4309  return FAIL;
   4310 }