Patch 8.2.4398
Problem:    Some command completion functions are too long.
Solution:   Refactor code into separate functions.  Add a few more tests.
            (Yegappan Lakshmanan, closes #9785)
Files:      src/cmdexpand.c, src/ex_getln.c, src/usercmd.c,
            src/proto/usercmd.pro, src/testdir/test_cmdline.vim


*** ../vim-8.2.4397/src/cmdexpand.c     2022-02-15 11:35:51.148044050 +0000
--- src/cmdexpand.c     2022-02-16 12:40:43.234362212 +0000
***************
*** 682,687 ****
--- 682,779 ----
  }
  
  /*
+  * Display one line of completion matches. Multiple matches are displayed in
+  * each line (used by wildmode=list and CTRL-D)
+  *   files_found - list of completion match names
+  *   num_files - number of completion matches in "files_found"
+  *   lines - number of output lines
+  *   linenr - line number of matches to display
+  *   maxlen - maximum number of characters in each line
+  *   showtail - display only the tail of the full path of a file name
+  *   dir_attr - highlight attribute to use for directory names
+  */
+     static void
+ showmatches_oneline(
+       expand_T        *xp,
+       char_u          **files_found,
+       int             num_files,
+       int             lines,
+       int             linenr,
+       int             maxlen,
+       int             showtail,
+       int             dir_attr)
+ {
+     int               i, j;
+     int               isdir;
+     int               lastlen;
+     char_u    *p;
+ 
+     lastlen = 999;
+     for (j = linenr; j < num_files; j += lines)
+     {
+       if (xp->xp_context == EXPAND_TAGS_LISTFILES)
+       {
+           msg_outtrans_attr(files_found[j], HL_ATTR(HLF_D));
+           p = files_found[j] + STRLEN(files_found[j]) + 1;
+           msg_advance(maxlen + 1);
+           msg_puts((char *)p);
+           msg_advance(maxlen + 3);
+           msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
+           break;
+       }
+       for (i = maxlen - lastlen; --i >= 0; )
+           msg_putchar(' ');
+       if (xp->xp_context == EXPAND_FILES
+               || xp->xp_context == EXPAND_SHELLCMD
+               || xp->xp_context == EXPAND_BUFFERS)
+       {
+           // highlight directories
+           if (xp->xp_numfiles != -1)
+           {
+               char_u  *halved_slash;
+               char_u  *exp_path;
+               char_u  *path;
+ 
+               // Expansion was done before and special characters
+               // were escaped, need to halve backslashes.  Also
+               // $HOME has been replaced with ~/.
+               exp_path = expand_env_save_opt(files_found[j], TRUE);
+               path = exp_path != NULL ? exp_path : files_found[j];
+               halved_slash = backslash_halve_save(path);
+               isdir = mch_isdir(halved_slash != NULL ? halved_slash
+                       : files_found[j]);
+               vim_free(exp_path);
+               if (halved_slash != path)
+                   vim_free(halved_slash);
+           }
+           else
+               // Expansion was done here, file names are literal.
+               isdir = mch_isdir(files_found[j]);
+           if (showtail)
+               p = SHOW_FILE_TEXT(j);
+           else
+           {
+               home_replace(NULL, files_found[j], NameBuff, MAXPATHL,
+                       TRUE);
+               p = NameBuff;
+           }
+       }
+       else
+       {
+           isdir = FALSE;
+           p = SHOW_FILE_TEXT(j);
+       }
+       lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0);
+     }
+     if (msg_col > 0)  // when not wrapped around
+     {
+       msg_clr_eos();
+       msg_putchar('\n');
+     }
+     out_flush();                  // show one line at a time
+ }
+ 
+ /*
   * Show all matches for completion on the command line.
   * Returns EXPAND_NOTHING when the character that triggered expansion should
   * be inserted like a normal character.
***************
*** 692,703 ****
      cmdline_info_T    *ccline = get_cmdline_info();
      int               num_files;
      char_u    **files_found;
!     int               i, j, k;
      int               maxlen;
      int               lines;
      int               columns;
-     char_u    *p;
-     int               lastlen;
      int               attr;
      int               showtail;
  
--- 784,793 ----
      cmdline_info_T    *ccline = get_cmdline_info();
      int               num_files;
      char_u    **files_found;
!     int               i, j;
      int               maxlen;
      int               lines;
      int               columns;
      int               attr;
      int               showtail;
  
***************
*** 709,715 ****
        showtail = expand_showtail(xp);
        if (i != EXPAND_OK)
            return i;
- 
      }
      else
      {
--- 799,804 ----
***************
*** 789,857 ****
        // list the files line by line
        for (i = 0; i < lines; ++i)
        {
!           lastlen = 999;
!           for (k = i; k < num_files; k += lines)
!           {
!               if (xp->xp_context == EXPAND_TAGS_LISTFILES)
!               {
!                   msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D));
!                   p = files_found[k] + STRLEN(files_found[k]) + 1;
!                   msg_advance(maxlen + 1);
!                   msg_puts((char *)p);
!                   msg_advance(maxlen + 3);
!                   msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
!                   break;
!               }
!               for (j = maxlen - lastlen; --j >= 0; )
!                   msg_putchar(' ');
!               if (xp->xp_context == EXPAND_FILES
!                                         || xp->xp_context == EXPAND_SHELLCMD
!                                         || xp->xp_context == EXPAND_BUFFERS)
!               {
!                   // highlight directories
!                   if (xp->xp_numfiles != -1)
!                   {
!                       char_u  *halved_slash;
!                       char_u  *exp_path;
!                       char_u  *path;
! 
!                       // Expansion was done before and special characters
!                       // were escaped, need to halve backslashes.  Also
!                       // $HOME has been replaced with ~/.
!                       exp_path = expand_env_save_opt(files_found[k], TRUE);
!                       path = exp_path != NULL ? exp_path : files_found[k];
!                       halved_slash = backslash_halve_save(path);
!                       j = mch_isdir(halved_slash != NULL ? halved_slash
!                                                           : files_found[k]);
!                       vim_free(exp_path);
!                       if (halved_slash != path)
!                           vim_free(halved_slash);
!                   }
!                   else
!                       // Expansion was done here, file names are literal.
!                       j = mch_isdir(files_found[k]);
!                   if (showtail)
!                       p = SHOW_FILE_TEXT(k);
!                   else
!                   {
!                       home_replace(NULL, files_found[k], NameBuff, MAXPATHL,
!                                                                       TRUE);
!                       p = NameBuff;
!                   }
!               }
!               else
!               {
!                   j = FALSE;
!                   p = SHOW_FILE_TEXT(k);
!               }
!               lastlen = msg_outtrans_attr(p, j ? attr : 0);
!           }
!           if (msg_col > 0)    // when not wrapped around
!           {
!               msg_clr_eos();
!               msg_putchar('\n');
!           }
!           out_flush();                    // show one line at a time
            if (got_int)
            {
                got_int = FALSE;
--- 878,885 ----
        // list the files line by line
        for (i = 0; i < lines; ++i)
        {
!           showmatches_oneline(xp, files_found, num_files, lines, i,
!                                               maxlen, showtail, attr);
            if (got_int)
            {
                got_int = FALSE;
***************
*** 1334,1339 ****
--- 1362,1434 ----
  }
  
  /*
+  * Returns a pointer to the next command after a :substitute or a :& command.
+  * Returns NULL if there is no next command.
+  */
+     static char_u *
+ find_cmd_after_substitute_cmd(char_u *arg)
+ {
+     int               delim;
+ 
+     delim = *arg;
+     if (delim)
+     {
+       // skip "from" part
+       ++arg;
+       arg = skip_regexp(arg, delim, magic_isset());
+ 
+       if (arg[0] != NUL && arg[0] == delim)
+       {
+           // skip "to" part
+           ++arg;
+           while (arg[0] != NUL && arg[0] != delim)
+           {
+               if (arg[0] == '\\' && arg[1] != NUL)
+                   ++arg;
+               ++arg;
+           }
+           if (arg[0] != NUL)  // skip delimiter
+               ++arg;
+       }
+     }
+     while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL)
+       ++arg;
+     if (arg[0] != NUL)
+       return arg;
+ 
+     return NULL;
+ }
+ 
+ /*
+  * Returns a pointer to the next command after a :isearch/:dsearch/:ilist
+  * :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command.
+  * Returns NULL if there is no next command.
+  */
+     static char_u *
+ find_cmd_after_isearch_cmd(char_u *arg, expand_T *xp)
+ {
+     arg = skipwhite(skipdigits(arg));     // skip count
+     if (*arg == '/')  // Match regexp, not just whole words
+     {
+       for (++arg; *arg && *arg != '/'; arg++)
+           if (*arg == '\\' && arg[1] != NUL)
+               arg++;
+       if (*arg)
+       {
+           arg = skipwhite(arg + 1);
+ 
+           // Check for trailing illegal characters
+           if (*arg == NUL || vim_strchr((char_u *)"|\"\n", *arg) == NULL)
+               xp->xp_context = EXPAND_NOTHING;
+           else
+               return arg;
+       }
+     }
+ 
+     return NULL;
+ }
+ 
+ /*
   * Set the completion context in 'xp' for command 'cmd' with index 'cmdidx'.
   * The argument to the command is 'arg' and the argument flags is 'argt'.
   * For user-defined commands and for environment variables, 'compl' has the
***************
*** 1467,1498 ****
            break;
        case CMD_and:
        case CMD_substitute:
!           delim = *arg;
!           if (delim)
!           {
!               // skip "from" part
!               ++arg;
!               arg = skip_regexp(arg, delim, magic_isset());
! 
!               if (arg[0] != NUL && arg[0] == delim)
!               {
!                   // skip "to" part
!                   ++arg;
!                   while (arg[0] != NUL && arg[0] != delim)
!                   {
!                       if (arg[0] == '\\' && arg[1] != NUL)
!                           ++arg;
!                       ++arg;
!                   }
!                   if (arg[0] != NUL)  // skip delimiter
!                       ++arg;
!               }
!           }
!           while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL)
!               ++arg;
!           if (arg[0] != NUL)
!               return arg;
!           break;
        case CMD_isearch:
        case CMD_dsearch:
        case CMD_ilist:
--- 1562,1568 ----
            break;
        case CMD_and:
        case CMD_substitute:
!           return find_cmd_after_substitute_cmd(arg);
        case CMD_isearch:
        case CMD_dsearch:
        case CMD_ilist:
***************
*** 1502,1527 ****
        case CMD_djump:
        case CMD_isplit:
        case CMD_dsplit:
!           arg = skipwhite(skipdigits(arg));       // skip count
!           if (*arg == '/')    // Match regexp, not just whole words
!           {
!               for (++arg; *arg && *arg != '/'; arg++)
!                   if (*arg == '\\' && arg[1] != NUL)
!                       arg++;
!               if (*arg)
!               {
!                   arg = skipwhite(arg + 1);
! 
!                   // Check for trailing illegal characters
!                   if (*arg == NUL ||
!                               vim_strchr((char_u *)"|\"\n", *arg) == NULL)
!                       xp->xp_context = EXPAND_NOTHING;
!                   else
!                       return arg;
!               }
!           }
!           break;
! 
        case CMD_autocmd:
            return set_context_in_autocmd(xp, arg, FALSE);
        case CMD_doautocmd:
--- 1572,1578 ----
        case CMD_djump:
        case CMD_isplit:
        case CMD_dsplit:
!           return find_cmd_after_isearch_cmd(arg, xp);
        case CMD_autocmd:
            return set_context_in_autocmd(xp, arg, FALSE);
        case CMD_doautocmd:
***************
*** 1652,1687 ****
  #endif
        case CMD_USER:
        case CMD_USER_BUF:
!           if (compl != EXPAND_NOTHING)
!           {
!               // EX_XFILE: file names are handled above
!               if (!(argt & EX_XFILE))
!               {
! #ifdef FEAT_MENU
!                   if (compl == EXPAND_MENUS)
!                       return set_context_in_menu_cmd(xp, cmd, arg, forceit);
! #endif
!                   if (compl == EXPAND_COMMANDS)
!                       return arg;
!                   if (compl == EXPAND_MAPPINGS)
!                       return set_context_in_map_cmd(xp, (char_u *)"map",
!                                        arg, forceit, FALSE, FALSE, CMD_map);
!                   // Find start of last argument.
!                   p = arg;
!                   while (*p)
!                   {
!                       if (*p == ' ')
!                           // argument starts after a space
!                           arg = p + 1;
!                       else if (*p == '\\' && *(p + 1) != NUL)
!                           ++p; // skip over escaped character
!                       MB_PTR_ADV(p);
!                   }
!                   xp->xp_pattern = arg;
!               }
!               xp->xp_context = compl;
!           }
!           break;
  
        case CMD_map:       case CMD_noremap:
        case CMD_nmap:      case CMD_nnoremap:
--- 1703,1710 ----
  #endif
        case CMD_USER:
        case CMD_USER_BUF:
!           return set_context_in_user_cmdarg(cmd, arg, argt, compl, xp,
!                                                               forceit);
  
        case CMD_map:       case CMD_noremap:
        case CMD_nmap:      case CMD_nnoremap:
***************
*** 2313,2332 ****
  }
  
  /*
!  * Do the expansion based on xp->xp_context and "pat".
   */
      static int
! ExpandFromContext(
!     expand_T  *xp,
!     char_u    *pat,
!     int               *num_file,
!     char_u    ***file,
!     int               options)  // WILD_ flags
  {
-     regmatch_T        regmatch;
-     int               ret;
      int               flags;
-     char_u    *tofree = NULL;
  
      flags = EW_DIR;   // include directories
      if (options & WILD_LIST_NOTFOUND)
--- 2336,2347 ----
  }
  
  /*
!  * Map wild expand options to flags for expand_wildcards()
   */
      static int
! map_wildopts_to_ewflags(int options)
  {
      int               flags;
  
      flags = EW_DIR;   // include directories
      if (options & WILD_LIST_NOTFOUND)
***************
*** 2342,2347 ****
--- 2357,2383 ----
      if (options & WILD_ALLLINKS)
        flags |= EW_ALLLINKS;
  
+     return flags;
+ }
+ 
+ /*
+  * Do the expansion based on xp->xp_context and "pat".
+  */
+     static int
+ ExpandFromContext(
+     expand_T  *xp,
+     char_u    *pat,
+     int               *num_file,
+     char_u    ***file,
+     int               options)  // WILD_ flags
+ {
+     regmatch_T        regmatch;
+     int               ret;
+     int               flags;
+     char_u    *tofree = NULL;
+ 
+     flags = map_wildopts_to_ewflags(options);
+ 
      if (xp->xp_context == EXPAND_FILES
            || xp->xp_context == EXPAND_DIRECTORIES
            || xp->xp_context == EXPAND_FILES_IN_PATH)
***************
*** 2550,2555 ****
--- 2586,2649 ----
  }
  
  /*
+  * Expand shell command matches in one directory of $PATH.
+  */
+     static void
+ expand_shellcmd_onedir(
+       char_u          *buf,
+       char_u          *s,
+       size_t          l,
+       char_u          *pat,
+       char_u          ***files,
+       int             *num_files,
+       int             flags,
+       hashtab_T       *ht,
+       garray_T        *gap)
+ {
+     int               ret;
+     int               i;
+     hash_T    hash;
+     hashitem_T        *hi;
+ 
+     vim_strncpy(buf, s, l);
+     add_pathsep(buf);
+     l = STRLEN(buf);
+     vim_strncpy(buf + l, pat, MAXPATHL - 1 - l);
+ 
+     // Expand matches in one directory of $PATH.
+     ret = expand_wildcards(1, &buf, num_files, files, flags);
+     if (ret == OK)
+     {
+       if (ga_grow(gap, *num_files) == FAIL)
+           FreeWild(*num_files, *files);
+       else
+       {
+           for (i = 0; i < *num_files; ++i)
+           {
+               char_u *name = (*files)[i];
+ 
+               if (STRLEN(name) > l)
+               {
+                   // Check if this name was already found.
+                   hash = hash_hash(name + l);
+                   hi = hash_lookup(ht, name + l, hash);
+                   if (HASHITEM_EMPTY(hi))
+                   {
+                       // Remove the path that was prepended.
+                       STRMOVE(name, name + l);
+                       ((char_u **)gap->ga_data)[gap->ga_len++] = name;
+                       hash_add_item(ht, hi, name, hash);
+                       name = NULL;
+                   }
+               }
+               vim_free(name);
+           }
+           vim_free(*files);
+       }
+     }
+ }
+ 
+ /*
   * Complete a shell command.
   * Returns FAIL or OK;
   */
***************
*** 2569,2579 ****
      size_t    l;
      char_u    *s, *e;
      int               flags = flagsarg;
-     int               ret;
      int               did_curdir = FALSE;
      hashtab_T found_ht;
-     hashitem_T        *hi;
-     hash_T    hash;
  
      buf = alloc(MAXPATHL);
      if (buf == NULL)
--- 2663,2670 ----
***************
*** 2640,2681 ****
        l = e - s;
        if (l > MAXPATHL - 5)
            break;
-       vim_strncpy(buf, s, l);
-       add_pathsep(buf);
-       l = STRLEN(buf);
-       vim_strncpy(buf + l, pat, MAXPATHL - 1 - l);
- 
-       // Expand matches in one directory of $PATH.
-       ret = expand_wildcards(1, &buf, num_file, file, flags);
-       if (ret == OK)
-       {
-           if (ga_grow(&ga, *num_file) == FAIL)
-               FreeWild(*num_file, *file);
-           else
-           {
-               for (i = 0; i < *num_file; ++i)
-               {
-                   char_u *name = (*file)[i];
  
!                   if (STRLEN(name) > l)
!                   {
!                       // Check if this name was already found.
!                       hash = hash_hash(name + l);
!                       hi = hash_lookup(&found_ht, name + l, hash);
!                       if (HASHITEM_EMPTY(hi))
!                       {
!                           // Remove the path that was prepended.
!                           STRMOVE(name, name + l);
!                           ((char_u **)ga.ga_data)[ga.ga_len++] = name;
!                           hash_add_item(&found_ht, hi, name, hash);
!                           name = NULL;
!                       }
!                   }
!                   vim_free(name);
!               }
!               vim_free(*file);
!           }
!       }
        if (*e != NUL)
            ++e;
      }
--- 2731,2740 ----
        l = e - s;
        if (l > MAXPATHL - 5)
            break;
  
!       expand_shellcmd_onedir(buf, s, l, pat, file, num_file, flags,
!                                                       &found_ht, &ga);
! 
        if (*e != NUL)
            ++e;
      }
***************
*** 2924,2930 ****
      }
  #endif
  
!     if (did_wild_list && p_wmnu)
      {
        if (c == K_LEFT)
            c = Ctrl_P;
--- 2983,2989 ----
      }
  #endif
  
!     if (did_wild_list)
      {
        if (c == K_LEFT)
            c = Ctrl_P;
***************
*** 2933,2939 ****
      }
  
      // Hitting CR after "emenu Name.": complete submenu
!     if (xp->xp_context == EXPAND_MENUNAMES && p_wmnu
            && cclp->cmdpos > 1
            && cclp->cmdbuff[cclp->cmdpos - 1] == '.'
            && cclp->cmdbuff[cclp->cmdpos - 2] != '\\'
--- 2992,2998 ----
      }
  
      // Hitting CR after "emenu Name.": complete submenu
!     if (xp->xp_context == EXPAND_MENUNAMES
            && cclp->cmdpos > 1
            && cclp->cmdbuff[cclp->cmdpos - 1] == '.'
            && cclp->cmdbuff[cclp->cmdpos - 2] != '\\'
***************
*** 2957,3126 ****
  }
  
  /*
!  * Handle a key pressed when wild menu is displayed
   */
!     int
! wildmenu_process_key(cmdline_info_T *cclp, int key, expand_T *xp)
  {
-     int               c = key;
      int               i;
      int               j;
  
!     if (!p_wmnu)
!       return c;
! 
!     // Special translations for 'wildmenu'
!     if (xp->xp_context == EXPAND_MENUNAMES)
      {
!       // Hitting <Down> after "emenu Name.": complete submenu
!       if (c == K_DOWN && cclp->cmdpos > 0
!               && cclp->cmdbuff[cclp->cmdpos - 1] == '.')
!       {
!           c = p_wc;
!           KeyTyped = TRUE;  // in case the key was mapped
!       }
!       else if (c == K_UP)
        {
!           // Hitting <Up>: Remove one submenu name in front of the
!           // cursor
!           int found = FALSE;
! 
!           j = (int)(xp->xp_pattern - cclp->cmdbuff);
!           i = 0;
!           while (--j > 0)
!           {
!               // check for start of menu name
!               if (cclp->cmdbuff[j] == ' '
!                       && cclp->cmdbuff[j - 1] != '\\')
                {
                    i = j + 1;
                    break;
                }
!               // check for start of submenu name
!               if (cclp->cmdbuff[j] == '.'
!                       && cclp->cmdbuff[j - 1] != '\\')
!               {
!                   if (found)
!                   {
!                       i = j + 1;
!                       break;
!                   }
!                   else
!                       found = TRUE;
!               }
            }
-           if (i > 0)
-               cmdline_del(cclp, i);
-           c = p_wc;
-           KeyTyped = TRUE;  // in case the key was mapped
-           xp->xp_context = EXPAND_NOTHING;
        }
      }
-     if ((xp->xp_context == EXPAND_FILES
-               || xp->xp_context == EXPAND_DIRECTORIES
-               || xp->xp_context == EXPAND_SHELLCMD))
-     {
-       char_u upseg[5];
  
!       upseg[0] = PATHSEP;
!       upseg[1] = '.';
!       upseg[2] = '.';
!       upseg[3] = PATHSEP;
!       upseg[4] = NUL;
! 
!       if (c == K_DOWN
!               && cclp->cmdpos > 0
!               && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP
!               && (cclp->cmdpos < 3
!                   || cclp->cmdbuff[cclp->cmdpos - 2] != '.'
!                   || cclp->cmdbuff[cclp->cmdpos - 3] != '.'))
!       {
!           // go down a directory
!           c = p_wc;
!           KeyTyped = TRUE;  // in case the key was mapped
!       }
!       else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 && c == K_DOWN)
!       {
!           // If in a direct ancestor, strip off one ../ to go down
!           int found = FALSE;
  
!           j = cclp->cmdpos;
!           i = (int)(xp->xp_pattern - cclp->cmdbuff);
!           while (--j > i)
!           {
!               if (has_mbyte)
!                   j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j);
!               if (vim_ispathsep(cclp->cmdbuff[j]))
!               {
!                   found = TRUE;
!                   break;
!               }
!           }
!           if (found
!                   && cclp->cmdbuff[j - 1] == '.'
!                   && cclp->cmdbuff[j - 2] == '.'
!                   && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2))
            {
!               cmdline_del(cclp, j - 2);
!               c = p_wc;
!               KeyTyped = TRUE;  // in case the key was mapped
            }
        }
!       else if (c == K_UP)
        {
!           // go up a directory
!           int found = FALSE;
  
!           j = cclp->cmdpos - 1;
!           i = (int)(xp->xp_pattern - cclp->cmdbuff);
!           while (--j > i)
!           {
!               if (has_mbyte)
!                   j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j);
!               if (vim_ispathsep(cclp->cmdbuff[j])
  # ifdef BACKSLASH_IN_FILENAME
!                       && vim_strchr((char_u *)" *?[{`$%#",
!                           cclp->cmdbuff[j + 1]) == NULL
  # endif
!                  )
                {
!                   if (found)
!                   {
!                       i = j + 1;
!                       break;
!                   }
!                   else
!                       found = TRUE;
                }
            }
  
!           if (!found)
!               j = i;
!           else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0)
!               j += 4;
!           else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0
!                   && j == i)
!               j += 3;
!           else
!               j = 0;
!           if (j > 0)
!           {
!               // TODO this is only for DOS/UNIX systems - need to put in
!               // machine-specific stuff here and in upseg init
!               cmdline_del(cclp, j);
!               put_on_cmdline(upseg + 1, 3, FALSE);
!           }
!           else if (cclp->cmdpos > i)
!               cmdline_del(cclp, i);
! 
!           // Now complete in the new directory. Set KeyTyped in case the
!           // Up key came from a mapping.
!           c = p_wc;
!           KeyTyped = TRUE;
        }
      }
  
!     return c;
  }
  
  /*
--- 3016,3203 ----
  }
  
  /*
!  * Handle a key pressed when the wild menu for the menu names
!  * (EXPAND_MENUNAMES) is displayed.
   */
!     static int
! wildmenu_process_key_menunames(cmdline_info_T *cclp, int key, expand_T *xp)
  {
      int               i;
      int               j;
  
!     // Hitting <Down> after "emenu Name.": complete submenu
!     if (key == K_DOWN && cclp->cmdpos > 0
!           && cclp->cmdbuff[cclp->cmdpos - 1] == '.')
      {
!       key = p_wc;
!       KeyTyped = TRUE;  // in case the key was mapped
!     }
!     else if (key == K_UP)
!     {
!       // Hitting <Up>: Remove one submenu name in front of the
!       // cursor
!       int found = FALSE;
! 
!       j = (int)(xp->xp_pattern - cclp->cmdbuff);
!       i = 0;
!       while (--j > 0)
        {
!           // check for start of menu name
!           if (cclp->cmdbuff[j] == ' '
!                   && cclp->cmdbuff[j - 1] != '\\')
!           {
!               i = j + 1;
!               break;
!           }
!           // check for start of submenu name
!           if (cclp->cmdbuff[j] == '.'
!                   && cclp->cmdbuff[j - 1] != '\\')
!           {
!               if (found)
                {
                    i = j + 1;
                    break;
                }
!               else
!                   found = TRUE;
            }
        }
+       if (i > 0)
+           cmdline_del(cclp, i);
+       key = p_wc;
+       KeyTyped = TRUE;  // in case the key was mapped
+       xp->xp_context = EXPAND_NOTHING;
      }
  
!     return key;
! }
  
! /*
!  * Handle a key pressed when the wild menu for file names (EXPAND_FILES) or
!  * directory names (EXPAND_DIRECTORIES) or shell command names
!  * (EXPAND_SHELLCMD) is displayed.
!  */
!     static int
! wildmenu_process_key_filenames(cmdline_info_T *cclp, int key, expand_T *xp)
! {
!     int               i;
!     int               j;
!     char_u    upseg[5];
! 
!     upseg[0] = PATHSEP;
!     upseg[1] = '.';
!     upseg[2] = '.';
!     upseg[3] = PATHSEP;
!     upseg[4] = NUL;
! 
!     if (key == K_DOWN
!           && cclp->cmdpos > 0
!           && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP
!           && (cclp->cmdpos < 3
!               || cclp->cmdbuff[cclp->cmdpos - 2] != '.'
!               || cclp->cmdbuff[cclp->cmdpos - 3] != '.'))
!     {
!       // go down a directory
!       key = p_wc;
!       KeyTyped = TRUE;  // in case the key was mapped
!     }
!     else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN)
!     {
!       // If in a direct ancestor, strip off one ../ to go down
!       int found = FALSE;
! 
!       j = cclp->cmdpos;
!       i = (int)(xp->xp_pattern - cclp->cmdbuff);
!       while (--j > i)
!       {
!           if (has_mbyte)
!               j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j);
!           if (vim_ispathsep(cclp->cmdbuff[j]))
            {
!               found = TRUE;
!               break;
            }
        }
!       if (found
!               && cclp->cmdbuff[j - 1] == '.'
!               && cclp->cmdbuff[j - 2] == '.'
!               && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2))
        {
!           cmdline_del(cclp, j - 2);
!           key = p_wc;
!           KeyTyped = TRUE;  // in case the key was mapped
!       }
!     }
!     else if (key == K_UP)
!     {
!       // go up a directory
!       int found = FALSE;
  
!       j = cclp->cmdpos - 1;
!       i = (int)(xp->xp_pattern - cclp->cmdbuff);
!       while (--j > i)
!       {
!           if (has_mbyte)
!               j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j);
!           if (vim_ispathsep(cclp->cmdbuff[j])
  # ifdef BACKSLASH_IN_FILENAME
!                   && vim_strchr((char_u *)" *?[{`$%#",
!                       cclp->cmdbuff[j + 1]) == NULL
  # endif
!              )
!           {
!               if (found)
                {
!                   i = j + 1;
!                   break;
                }
+               else
+                   found = TRUE;
            }
+       }
  
!       if (!found)
!           j = i;
!       else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0)
!           j += 4;
!       else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0
!               && j == i)
!           j += 3;
!       else
!           j = 0;
!       if (j > 0)
!       {
!           // TODO this is only for DOS/UNIX systems - need to put in
!           // machine-specific stuff here and in upseg init
!           cmdline_del(cclp, j);
!           put_on_cmdline(upseg + 1, 3, FALSE);
        }
+       else if (cclp->cmdpos > i)
+           cmdline_del(cclp, i);
+ 
+       // Now complete in the new directory. Set KeyTyped in case the
+       // Up key came from a mapping.
+       key = p_wc;
+       KeyTyped = TRUE;
      }
  
!     return key;
! }
! 
! /*
!  * Handle a key pressed when the wild menu is displayed
!  */
!     int
! wildmenu_process_key(cmdline_info_T *cclp, int key, expand_T *xp)
! {
!     if (xp->xp_context == EXPAND_MENUNAMES)
!       return wildmenu_process_key_menunames(cclp, key, xp);
!     else if ((xp->xp_context == EXPAND_FILES
!               || xp->xp_context == EXPAND_DIRECTORIES
!               || xp->xp_context == EXPAND_SHELLCMD))
!       return wildmenu_process_key_filenames(cclp, key, xp);
! 
!     return key;
  }
  
  /*
*** ../vim-8.2.4397/src/ex_getln.c      2022-02-10 19:51:42.545569904 +0000
--- src/ex_getln.c      2022-02-16 12:40:43.234362212 +0000
***************
*** 1856,1862 ****
            c = Ctrl_P;
  
  #ifdef FEAT_WILDMENU
!       c = wildmenu_translate_key(&ccline, c, &xpc, did_wild_list);
  
        if (cmdline_pum_active())
        {
--- 1856,1863 ----
            c = Ctrl_P;
  
  #ifdef FEAT_WILDMENU
!       if (p_wmnu)
!           c = wildmenu_translate_key(&ccline, c, &xpc, did_wild_list);
  
        if (cmdline_pum_active())
        {
***************
*** 1900,1906 ****
        }
  
  #ifdef FEAT_WILDMENU
!       c = wildmenu_process_key(&ccline, c, &xpc);
  #endif
  
        // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
--- 1901,1908 ----
        }
  
  #ifdef FEAT_WILDMENU
!       if (p_wmnu)
!           c = wildmenu_process_key(&ccline, c, &xpc);
  #endif
  
        // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
*** ../vim-8.2.4397/src/usercmd.c       2022-01-17 22:16:29.862803979 +0000
--- src/usercmd.c       2022-02-16 12:40:43.238362204 +0000
***************
*** 231,236 ****
--- 231,239 ----
      return p;
  }
  
+ /*
+  * Set completion context for :command
+  */
      char_u *
  set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
  {
***************
*** 292,297 ****
--- 295,350 ----
      return skipwhite(p);
  }
  
+ /*
+  * Set the completion context for the argument of a user defined command.
+  */
+     char_u *
+ set_context_in_user_cmdarg(
+       char_u          *cmd UNUSED,
+       char_u          *arg,
+       long            argt,
+       int             compl,
+       expand_T        *xp,
+       int             forceit)
+ {
+     char_u    *p;
+ 
+     if (compl == EXPAND_NOTHING)
+       return NULL;
+ 
+     if (argt & EX_XFILE)
+     {
+       // EX_XFILE: file names are handled before this call
+       xp->xp_context = compl;
+       return NULL;
+     }
+ 
+ #ifdef FEAT_MENU
+     if (compl == EXPAND_MENUS)
+       return set_context_in_menu_cmd(xp, cmd, arg, forceit);
+ #endif
+     if (compl == EXPAND_COMMANDS)
+       return arg;
+     if (compl == EXPAND_MAPPINGS)
+       return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
+                                                       FALSE, CMD_map);
+     // Find start of last argument.
+     p = arg;
+     while (*p)
+     {
+       if (*p == ' ')
+           // argument starts after a space
+           arg = p + 1;
+       else if (*p == '\\' && *(p + 1) != NUL)
+           ++p; // skip over escaped character
+       MB_PTR_ADV(p);
+     }
+     xp->xp_pattern = arg;
+     xp->xp_context = compl;
+ 
+     return NULL;
+ }
+ 
      char_u *
  expand_user_command_name(int idx)
  {
*** ../vim-8.2.4397/src/proto/usercmd.pro       2021-09-08 13:29:43.113509774 
+0100
--- src/proto/usercmd.pro       2022-02-16 12:40:43.234362212 +0000
***************
*** 1,5 ****
--- 1,6 ----
  /* usercmd.c */
  char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int 
*complp);
+ char_u *set_context_in_user_cmdarg(char_u *cmd, char_u *arg, long argt, int 
compl, expand_T *xp, int forceit);
  char_u *set_context_in_user_cmd(expand_T *xp, char_u *arg_in);
  char_u *expand_user_command_name(int idx);
  char_u *get_user_commands(expand_T *xp, int idx);
*** ../vim-8.2.4397/src/testdir/test_cmdline.vim        2022-02-15 
11:35:51.148044050 +0000
--- src/testdir/test_cmdline.vim        2022-02-16 12:40:43.238362204 +0000
***************
*** 53,61 ****
--- 53,65 ----
      set completeslash=backslash
      call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
      call assert_equal('"e Xtest\', @:)
+     call feedkeys(":e Xtest/\<Tab>\<C-B>\"\<CR>", 'xt')
+     call assert_equal('"e Xtest\a.', @:)
      set completeslash=slash
      call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt')
      call assert_equal('"e Xtest/', @:)
+     call feedkeys(":e Xtest\\\<Tab>\<C-B>\"\<CR>", 'xt')
+     call assert_equal('"e Xtest/a.', @:)
      set completeslash&
    endif
  
***************
*** 139,144 ****
--- 143,149 ----
    call assert_equal('"e Xtestfile3 Xtestfile4', @:)
    cd -
  
+   " test for wildmenumode()
    cnoremap <expr> <F2> wildmenumode()
    call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
    call assert_equal('"cd Xdir1/0', @:)
***************
*** 148,159 ****
  
    " cleanup
    %bwipe
!   call delete('Xdir1/Xdir2/Xtestfile4')
!   call delete('Xdir1/Xdir2/Xtestfile3')
!   call delete('Xdir1/Xtestfile2')
!   call delete('Xdir1/Xtestfile1')
!   call delete('Xdir1/Xdir2', 'd')
!   call delete('Xdir1', 'd')
    set nowildmenu
  endfunc
  
--- 153,159 ----
  
    " cleanup
    %bwipe
!   call delete('Xdir1', 'rf')
    set nowildmenu
  endfunc
  
***************
*** 1100,1105 ****
--- 1100,1109 ----
      call feedkeys(":e Xx\*\<Tab>\<C-B>\"\<CR>", 'xt')
      call assert_equal('"e Xx\*Yy', @:)
      call delete('Xx*Yy')
+ 
+     " use a literal star
+     call feedkeys(":e \\*\<Tab>\<C-B>\"\<CR>", 'xt')
+     call assert_equal('"e \*', @:)
    endif
  
    call feedkeys(":py3f\<Tab>\<C-B>\"\<CR>", 'xt')
***************
*** 2005,2032 ****
  func Test_wildmenu_dirstack()
    CheckUnix
    %bw!
!   call mkdir('Xdir1/dir2/dir3', 'p')
    call writefile([], 'Xdir1/file1_1.txt')
    call writefile([], 'Xdir1/file1_2.txt')
    call writefile([], 'Xdir1/dir2/file2_1.txt')
    call writefile([], 'Xdir1/dir2/file2_2.txt')
    call writefile([], 'Xdir1/dir2/dir3/file3_1.txt')
    call writefile([], 'Xdir1/dir2/dir3/file3_2.txt')
!   cd Xdir1/dir2/dir3
    set wildmenu
  
    call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e file3_1.txt', @:)
    call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../dir3/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir2/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir2/dir3/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir2/dir3/file3_1.txt', @:)
! 
    cd -
    call delete('Xdir1', 'rf')
    set wildmenu&
  endfunc
--- 2009,2042 ----
  func Test_wildmenu_dirstack()
    CheckUnix
    %bw!
!   call mkdir('Xdir1/dir2/dir3/dir4', 'p')
    call writefile([], 'Xdir1/file1_1.txt')
    call writefile([], 'Xdir1/file1_2.txt')
    call writefile([], 'Xdir1/dir2/file2_1.txt')
    call writefile([], 'Xdir1/dir2/file2_2.txt')
    call writefile([], 'Xdir1/dir2/dir3/file3_1.txt')
    call writefile([], 'Xdir1/dir2/dir3/file3_2.txt')
!   call writefile([], 'Xdir1/dir2/dir3/dir4/file4_1.txt')
!   call writefile([], 'Xdir1/dir2/dir3/dir4/file4_2.txt')
    set wildmenu
  
+   cd Xdir1/dir2/dir3/dir4
    call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e file4_1.txt', @:)
    call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../dir4/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir3/', @:)
!   call feedkeys(":e \<Tab>\<Up>\<Up>\<Up>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../../dir2/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir3/dir4/', @:)
    call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
!   call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:)
    cd -
+   call feedkeys(":e Xdir1/\<Tab>\<Down>\<Down>\<Down>\<C-B>\"\<CR>", 'xt')
+   call assert_equal('"e Xdir1/dir2/dir3/dir4/file4_1.txt', @:)
+ 
    call delete('Xdir1', 'rf')
    set wildmenu&
  endfunc
*** ../vim-8.2.4397/src/version.c       2022-02-16 12:16:15.553130173 +0000
--- src/version.c       2022-02-16 12:42:47.210108854 +0000
***************
*** 752,753 ****
--- 752,755 ----
  {   /* Add new patch number below this line */
+ /**/
+     4398,
  /**/

-- 
hundred-and-one symptoms of being an internet addict:
49. You never have to deal with busy signals when calling your ISP...because
    you never log off.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220216124539.5F7D51C0FE0%40moolenaar.net.

Raspunde prin e-mail lui