# HG changeset patch # User Bram Moolenaar # Date 1645015504 -3600 # Node ID 515ce8e07bf243fc31df10af46227a8fe0079674 # Parent 629769fd1ff1cf46e6ee30865b8c689b936e8e12 patch 8.2.4398: some command completion functions are too long Commit: https://github.com/vim/vim/commit/b31aec3b9387ed12677dca09069c3ae98c6c7447 Author: Yegappan Lakshmanan Date: Wed Feb 16 12:44:29 2022 +0000 patch 8.2.4398: some command completion functions are too long Problem: Some command completion functions are too long. Solution: Refactor code into separate functions. Add a few more tests. (Yegappan Lakshmanan, closes #9785) diff --git a/src/cmdexpand.c b/src/cmdexpand.c --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -682,6 +682,98 @@ ExpandCleanup(expand_T *xp) } /* + * 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,12 +784,10 @@ showmatches(expand_T *xp, int wildmenu U cmdline_info_T *ccline = get_cmdline_info(); int num_files; char_u **files_found; - int i, j, k; + int i, j; int maxlen; int lines; int columns; - char_u *p; - int lastlen; int attr; int showtail; @@ -709,7 +799,6 @@ showmatches(expand_T *xp, int wildmenu U showtail = expand_showtail(xp); if (i != EXPAND_OK) return i; - } else { @@ -789,69 +878,8 @@ showmatches(expand_T *xp, int wildmenu U // 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 + showmatches_oneline(xp, files_found, num_files, lines, i, + maxlen, showtail, attr); if (got_int) { got_int = FALSE; @@ -1334,6 +1362,73 @@ set_context_for_wildcard_arg( } /* + * 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,32 +1562,7 @@ set_context_by_cmdname( 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; + return find_cmd_after_substitute_cmd(arg); case CMD_isearch: case CMD_dsearch: case CMD_ilist: @@ -1502,26 +1572,7 @@ set_context_by_cmdname( 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; - + return find_cmd_after_isearch_cmd(arg, xp); case CMD_autocmd: return set_context_in_autocmd(xp, arg, FALSE); case CMD_doautocmd: @@ -1652,36 +1703,8 @@ set_context_by_cmdname( #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; + 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,6 +2336,31 @@ ExpandOther( } /* + * 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) + flags |= EW_NOTFOUND; + if (options & WILD_ADD_SLASH) + flags |= EW_ADDSLASH; + if (options & WILD_KEEP_ALL) + flags |= EW_KEEPALL; + if (options & WILD_SILENT) + flags |= EW_SILENT; + if (options & WILD_NOERROR) + flags |= EW_NOERROR; + if (options & WILD_ALLLINKS) + flags |= EW_ALLLINKS; + + return flags; +} + +/* * Do the expansion based on xp->xp_context and "pat". */ static int @@ -2328,19 +2376,7 @@ ExpandFromContext( int flags; char_u *tofree = NULL; - flags = EW_DIR; // include directories - if (options & WILD_LIST_NOTFOUND) - flags |= EW_NOTFOUND; - if (options & WILD_ADD_SLASH) - flags |= EW_ADDSLASH; - if (options & WILD_KEEP_ALL) - flags |= EW_KEEPALL; - if (options & WILD_SILENT) - flags |= EW_SILENT; - if (options & WILD_NOERROR) - flags |= EW_NOERROR; - if (options & WILD_ALLLINKS) - flags |= EW_ALLLINKS; + flags = map_wildopts_to_ewflags(options); if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES @@ -2550,6 +2586,64 @@ ExpandGeneric( } /* + * 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,11 +2663,8 @@ expand_shellcmd( 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) @@ -2640,42 +2731,10 @@ expand_shellcmd( 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); - } - } + expand_shellcmd_onedir(buf, s, l, pat, file, num_file, flags, + &found_ht, &ga); + if (*e != NUL) ++e; } @@ -2924,7 +2983,7 @@ wildmenu_translate_key( } #endif - if (did_wild_list && p_wmnu) + if (did_wild_list) { if (c == K_LEFT) c = Ctrl_P; @@ -2933,7 +2992,7 @@ wildmenu_translate_key( } // Hitting CR after "emenu Name.": complete submenu - if (xp->xp_context == EXPAND_MENUNAMES && p_wmnu + if (xp->xp_context == EXPAND_MENUNAMES && cclp->cmdpos > 1 && cclp->cmdbuff[cclp->cmdpos - 1] == '.' && cclp->cmdbuff[cclp->cmdpos - 2] != '\\' @@ -2957,170 +3016,188 @@ cmdline_del(cmdline_info_T *cclp, int fr } /* - * Handle a key pressed when wild menu is displayed + * Handle a key pressed when the wild menu for the menu names + * (EXPAND_MENUNAMES) is displayed. */ - int -wildmenu_process_key(cmdline_info_T *cclp, int key, expand_T *xp) + static int +wildmenu_process_key_menunames(cmdline_info_T *cclp, int key, expand_T *xp) { - int c = key; int i; int j; - if (!p_wmnu) - return c; + // Hitting 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 : Remove one submenu name in front of the + // cursor + int found = FALSE; - // Special translations for 'wildmenu' - if (xp->xp_context == EXPAND_MENUNAMES) - { - // Hitting after "emenu Name.": complete submenu - if (c == K_DOWN && cclp->cmdpos > 0 - && cclp->cmdbuff[cclp->cmdpos - 1] == '.') + j = (int)(xp->xp_pattern - cclp->cmdbuff); + i = 0; + while (--j > 0) { - c = p_wc; - KeyTyped = TRUE; // in case the key was mapped - } - else if (c == K_UP) - { - // Hitting : 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] != '\\') { - // 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; } - // check for start of submenu name - if (cclp->cmdbuff[j] == '.' - && cclp->cmdbuff[j - 1] != '\\') - { - if (found) - { - i = j + 1; - break; - } - else - found = TRUE; - } + else + found = TRUE; } - if (i > 0) - cmdline_del(cclp, i); - c = p_wc; + } + 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 - xp->xp_context = EXPAND_NOTHING; } } - if ((xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_SHELLCMD)) + else if (key == K_UP) { - char_u upseg[5]; + // go up a directory + int found = FALSE; - 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] != '.')) + j = cclp->cmdpos - 1; + i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { - // 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]) +# ifdef BACKSLASH_IN_FILENAME + && vim_strchr((char_u *)" *?[{`$%#", + cclp->cmdbuff[j + 1]) == NULL +# endif + ) { - if (has_mbyte) - j -= (*mb_head_off)(cclp->cmdbuff, cclp->cmdbuff + j); - if (vim_ispathsep(cclp->cmdbuff[j])) + if (found) { - found = TRUE; + i = j + 1; 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 + found = TRUE; } } - 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); - 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; - } + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + key = p_wc; + KeyTyped = TRUE; } - return c; + 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; } /* diff --git a/src/ex_getln.c b/src/ex_getln.c --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -1856,7 +1856,8 @@ getcmdline_int( c = Ctrl_P; #ifdef FEAT_WILDMENU - c = wildmenu_translate_key(&ccline, c, &xpc, did_wild_list); + if (p_wmnu) + c = wildmenu_translate_key(&ccline, c, &xpc, did_wild_list); if (cmdline_pum_active()) { @@ -1900,7 +1901,8 @@ getcmdline_int( } #ifdef FEAT_WILDMENU - c = wildmenu_process_key(&ccline, c, &xpc); + if (p_wmnu) + c = wildmenu_process_key(&ccline, c, &xpc); #endif // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert diff --git a/src/proto/usercmd.pro b/src/proto/usercmd.pro --- a/src/proto/usercmd.pro +++ b/src/proto/usercmd.pro @@ -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); diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -53,9 +53,13 @@ func Test_complete_list() set completeslash=backslash call feedkeys(":e Xtest\\\"\", 'xt') call assert_equal('"e Xtest\', @:) + call feedkeys(":e Xtest/\\\"\", 'xt') + call assert_equal('"e Xtest\a.', @:) set completeslash=slash call feedkeys(":e Xtest\\\"\", 'xt') call assert_equal('"e Xtest/', @:) + call feedkeys(":e Xtest\\\\\"\", 'xt') + call assert_equal('"e Xtest/a.', @:) set completeslash& endif @@ -139,6 +143,7 @@ func Test_complete_wildmenu() call assert_equal('"e Xtestfile3 Xtestfile4', @:) cd - + " test for wildmenumode() cnoremap wildmenumode() call feedkeys(":cd Xdir\\\\"\", 'tx') call assert_equal('"cd Xdir1/0', @:) @@ -148,12 +153,7 @@ func Test_complete_wildmenu() " 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') + call delete('Xdir1', 'rf') set nowildmenu endfunc @@ -1100,6 +1100,10 @@ func Test_cmdline_complete_various() call feedkeys(":e Xx\*\\\"\", 'xt') call assert_equal('"e Xx\*Yy', @:) call delete('Xx*Yy') + + " use a literal star + call feedkeys(":e \\*\\\"\", 'xt') + call assert_equal('"e \*', @:) endif call feedkeys(":py3f\\\"\", 'xt') @@ -2005,28 +2009,34 @@ endfunc func Test_wildmenu_dirstack() CheckUnix %bw! - call mkdir('Xdir1/dir2/dir3', 'p') + 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') - cd Xdir1/dir2/dir3 + 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 \\\"\", 'xt') - call assert_equal('"e file3_1.txt', @:) + call assert_equal('"e file4_1.txt', @:) call feedkeys(":e \\\\"\", 'xt') - call assert_equal('"e ../dir3/', @:) + call assert_equal('"e ../dir4/', @:) call feedkeys(":e \\\\\"\", 'xt') - call assert_equal('"e ../../dir2/', @:) + call assert_equal('"e ../../dir3/', @:) + call feedkeys(":e \\\\\\"\", 'xt') + call assert_equal('"e ../../../dir2/', @:) call feedkeys(":e \\\\\\"\", 'xt') - call assert_equal('"e ../../dir2/dir3/', @:) + call assert_equal('"e ../../dir3/dir4/', @:) call feedkeys(":e \\\\\\\"\", 'xt') - call assert_equal('"e ../../dir2/dir3/file3_1.txt', @:) + call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:) + cd - + call feedkeys(":e Xdir1/\\\\\\"\", 'xt') + call assert_equal('"e Xdir1/dir2/dir3/dir4/file4_1.txt', @:) - cd - call delete('Xdir1', 'rf') set wildmenu& endfunc diff --git a/src/usercmd.c b/src/usercmd.c --- a/src/usercmd.c +++ b/src/usercmd.c @@ -231,6 +231,9 @@ find_ucmd( return p; } +/* + * Set completion context for :command + */ char_u * set_context_in_user_cmd(expand_T *xp, char_u *arg_in) { @@ -292,6 +295,56 @@ set_context_in_user_cmd(expand_T *xp, ch 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) { diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4398, +/**/ 4397, /**/ 4396,