To: vim_dev@googlegroups.com Subject: Patch 8.2.4398 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 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 : 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 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; ! ! 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\\\"\", '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,144 **** --- 143,149 ---- call assert_equal('"e Xtestfile3 Xtestfile4', @:) cd - + " test for wildmenumode() cnoremap wildmenumode() call feedkeys(":cd Xdir\\\\"\", '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\*\\\"\", '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,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 \\\"\", 'xt') ! call assert_equal('"e file3_1.txt', @:) call feedkeys(":e \\\\"\", 'xt') ! 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 feedkeys(":e \\\\\\\"\", '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 \\\"\", 'xt') ! call assert_equal('"e file4_1.txt', @:) call feedkeys(":e \\\\"\", 'xt') ! call assert_equal('"e ../dir4/', @:) call feedkeys(":e \\\\\"\", 'xt') ! call assert_equal('"e ../../dir3/', @:) ! call feedkeys(":e \\\\\\"\", 'xt') ! call assert_equal('"e ../../../dir2/', @:) call feedkeys(":e \\\\\\"\", 'xt') ! call assert_equal('"e ../../dir3/dir4/', @:) call feedkeys(":e \\\\\\\"\", 'xt') ! 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', @:) + 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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///