To: vim_dev@googlegroups.com Subject: Patch 8.2.4356 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.4356 Problem: Command line completion functions are very long. Solution: Refactor into multiple functions. (Yegappan Lakshmanan, closes #9753) Files: src/cmdexpand.c *** ../vim-8.2.4355/src/cmdexpand.c 2022-02-10 21:09:26.387235267 +0000 --- src/cmdexpand.c 2022-02-12 12:00:45.831907799 +0000 *************** *** 351,356 **** --- 351,539 ---- #endif /* + * Get the next or prev cmdline completion match. The index of the match is set + * in 'p_findex' + */ + static char_u * + get_next_or_prev_match( + int mode, + expand_T *xp, + int *p_findex, + char_u *orig_save) + { + int findex = *p_findex; + + if (xp->xp_numfiles <= 0) + return NULL; + + if (mode == WILD_PREV) + { + if (findex == -1) + findex = xp->xp_numfiles; + --findex; + } + else // mode == WILD_NEXT + ++findex; + + // When wrapping around, return the original string, set findex to + // -1. + if (findex < 0) + { + if (orig_save == NULL) + findex = xp->xp_numfiles - 1; + else + findex = -1; + } + if (findex >= xp->xp_numfiles) + { + if (orig_save == NULL) + findex = 0; + else + findex = -1; + } + #ifdef FEAT_WILDMENU + if (compl_match_array) + { + compl_selected = findex; + cmdline_pum_display(); + } + else if (p_wmnu) + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); + #endif + *p_findex = findex; + + if (findex == -1) + return vim_strsave(orig_save); + + return vim_strsave(xp->xp_files[findex]); + } + + /* + * Start the command-line expansion and get the matches. + */ + static char_u * + ExpandOne_start(int mode, expand_T *xp, char_u *str, int options) + { + int non_suf_match; // number without matching suffix + int i; + char_u *ss = NULL; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, + options) == FAIL) + { + #ifdef FNAME_ILLEGAL + // Illegal file name has been silently skipped. But when there + // are wildcards, the real problem is that there was no match, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) + semsg(_(e_no_match_str_2), str); + #endif + } + else if (xp->xp_numfiles == 0) + { + if (!(options & WILD_SILENT)) + semsg(_(e_no_match_str_2), str); + } + else + { + // Escape the matches for use on the command line. + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + // Check for matching suffixes in file names. + if (mode != WILD_ALL && mode != WILD_ALL_KEEP + && mode != WILD_LONGEST) + { + if (xp->xp_numfiles) + non_suf_match = xp->xp_numfiles; + else + non_suf_match = 1; + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) + { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (i = 0; i < 2; ++i) + if (match_suffix(xp->xp_files[i])) + ++non_suf_match; + } + if (non_suf_match != 1) + { + // Can we ever get here unless it's while expanding + // interactively? If not, we can get rid of this all + // together. Don't really want to wait for this message + // (and possibly have to hit return to continue!). + if (!(options & WILD_SILENT)) + emsg(_(e_too_many_file_names)); + else if (!(options & WILD_NO_BEEP)) + beep_flush(); + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) + ss = vim_strsave(xp->xp_files[0]); + } + } + + return ss; + } + + /* + * Return the longest common part in the list of cmdline completion matches. + */ + static char_u * + find_longest_match(expand_T *xp, int options) + { + long_u len; + int mb_len = 1; + int c0, ci; + int i; + char_u *ss; + + for (len = 0; xp->xp_files[0][len]; len += mb_len) + { + if (has_mbyte) + { + mb_len = (*mb_ptr2len)(&xp->xp_files[0][len]); + c0 =(* mb_ptr2char)(&xp->xp_files[0][len]); + } + else + c0 = xp->xp_files[0][len]; + for (i = 1; i < xp->xp_numfiles; ++i) + { + if (has_mbyte) + ci =(* mb_ptr2char)(&xp->xp_files[i][len]); + else + ci = xp->xp_files[i][len]; + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) + { + if (MB_TOLOWER(c0) != MB_TOLOWER(ci)) + break; + } + else if (c0 != ci) + break; + } + if (i < xp->xp_numfiles) + { + if (!(options & WILD_NO_BEEP)) + vim_beep(BO_WILD); + break; + } + } + + ss = alloc(len + 1); + if (ss) + vim_strncpy(ss, xp->xp_files[0], (size_t)len); + + return ss; + } + + /* * Do wildcard expansion on the string 'str'. * Chars that should not be expanded must be preceded with a backslash. * Return a pointer to allocated memory containing the new string. *************** *** 371,376 **** --- 554,563 ---- * mode = WILD_ALL: return all matches concatenated * mode = WILD_LONGEST: return longest matched part * mode = WILD_ALL_KEEP: get all matches, keep matches + * mode = WILD_APPLY: apply the item selected in the cmdline completion + * popup menu and close the menu. + * mode = WILD_CANCEL: cancel and close the cmdline completion popup and + * use the original text. * * options = WILD_LIST_NOTFOUND: list entries without a match * options = WILD_HOME_REPLACE: do home_replace() for buffer names *************** *** 399,453 **** int orig_saved = FALSE; int i; long_u len; - int non_suf_match; // number without matching suffix // first handle the case of using an old match if (mode == WILD_NEXT || mode == WILD_PREV) ! { ! if (xp->xp_numfiles > 0) ! { ! if (mode == WILD_PREV) ! { ! if (findex == -1) ! findex = xp->xp_numfiles; ! --findex; ! } ! else // mode == WILD_NEXT ! ++findex; ! ! // When wrapping around, return the original string, set findex to ! // -1. ! if (findex < 0) ! { ! if (orig_save == NULL) ! findex = xp->xp_numfiles - 1; ! else ! findex = -1; ! } ! if (findex >= xp->xp_numfiles) ! { ! if (orig_save == NULL) ! findex = 0; ! else ! findex = -1; ! } ! #ifdef FEAT_WILDMENU ! if (compl_match_array) ! { ! compl_selected = findex; ! cmdline_pum_display(); ! } ! else if (p_wmnu) ! win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, ! findex, cmd_showtail); ! #endif ! if (findex == -1) ! return vim_strsave(orig_save); ! return vim_strsave(xp->xp_files[findex]); ! } ! else ! return NULL; ! } if (mode == WILD_CANCEL) ss = vim_strsave(orig_save ? orig_save : (char_u *)""); --- 586,595 ---- int orig_saved = FALSE; int i; long_u len; // first handle the case of using an old match if (mode == WILD_NEXT || mode == WILD_PREV) ! return get_next_or_prev_match(mode, xp, &findex, orig_save); if (mode == WILD_CANCEL) ss = vim_strsave(orig_save ? orig_save : (char_u *)""); *************** *** 473,580 **** orig_save = orig; orig_saved = TRUE; ! // Do the expansion. ! if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, ! options) == FAIL) ! { ! #ifdef FNAME_ILLEGAL ! // Illegal file name has been silently skipped. But when there ! // are wildcards, the real problem is that there was no match, ! // causing the pattern to be added, which has illegal characters. ! if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) ! semsg(_(e_no_match_str_2), str); ! #endif ! } ! else if (xp->xp_numfiles == 0) ! { ! if (!(options & WILD_SILENT)) ! semsg(_(e_no_match_str_2), str); ! } ! else ! { ! // Escape the matches for use on the command line. ! ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); ! ! // Check for matching suffixes in file names. ! if (mode != WILD_ALL && mode != WILD_ALL_KEEP ! && mode != WILD_LONGEST) ! { ! if (xp->xp_numfiles) ! non_suf_match = xp->xp_numfiles; ! else ! non_suf_match = 1; ! if ((xp->xp_context == EXPAND_FILES ! || xp->xp_context == EXPAND_DIRECTORIES) ! && xp->xp_numfiles > 1) ! { ! // More than one match; check suffix. ! // The files will have been sorted on matching suffix in ! // expand_wildcards, only need to check the first two. ! non_suf_match = 0; ! for (i = 0; i < 2; ++i) ! if (match_suffix(xp->xp_files[i])) ! ++non_suf_match; ! } ! if (non_suf_match != 1) ! { ! // Can we ever get here unless it's while expanding ! // interactively? If not, we can get rid of this all ! // together. Don't really want to wait for this message ! // (and possibly have to hit return to continue!). ! if (!(options & WILD_SILENT)) ! emsg(_(e_too_many_file_names)); ! else if (!(options & WILD_NO_BEEP)) ! beep_flush(); ! } ! if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) ! ss = vim_strsave(xp->xp_files[0]); ! } ! } } // Find longest common part if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { ! int mb_len = 1; ! int c0, ci; ! ! for (len = 0; xp->xp_files[0][len]; len += mb_len) ! { ! if (has_mbyte) ! { ! mb_len = (*mb_ptr2len)(&xp->xp_files[0][len]); ! c0 =(* mb_ptr2char)(&xp->xp_files[0][len]); ! } ! else ! c0 = xp->xp_files[0][len]; ! for (i = 1; i < xp->xp_numfiles; ++i) ! { ! if (has_mbyte) ! ci =(* mb_ptr2char)(&xp->xp_files[i][len]); ! else ! ci = xp->xp_files[i][len]; ! if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES ! || xp->xp_context == EXPAND_FILES ! || xp->xp_context == EXPAND_SHELLCMD ! || xp->xp_context == EXPAND_BUFFERS)) ! { ! if (MB_TOLOWER(c0) != MB_TOLOWER(ci)) ! break; ! } ! else if (c0 != ci) ! break; ! } ! if (i < xp->xp_numfiles) ! { ! if (!(options & WILD_NO_BEEP)) ! vim_beep(BO_WILD); ! break; ! } ! } ! ! ss = alloc(len + 1); ! if (ss) ! vim_strncpy(ss, xp->xp_files[0], (size_t)len); findex = -1; // next p_wc gets first one } --- 615,627 ---- orig_save = orig; orig_saved = TRUE; ! ss = ExpandOne_start(mode, xp, str, options); } // Find longest common part if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { ! ss = find_longest_match(xp, options); findex = -1; // next p_wc gets first one } *************** *** 1077,1134 **** } /* ! * This is all pretty much copied from do_one_cmd(), with all the extra stuff ! * we don't need/want deleted. Maybe this could be done better if we didn't ! * repeat all this stuff. The only problem is that they may not stay ! * perfectly compatible with each other, but then the command line syntax ! * probably won't change that much -- webb. */ static char_u * ! set_one_cmd_context( ! expand_T *xp, ! char_u *buff) // buffer for command string { ! char_u *p; ! char_u *cmd, *arg; ! int len = 0; ! exarg_T ea; ! int compl = EXPAND_NOTHING; ! int delim; ! int forceit = FALSE; ! int usefilter = FALSE; // filter instead of file name ! ! ExpandInit(xp); ! xp->xp_pattern = buff; ! xp->xp_line = buff; ! xp->xp_context = EXPAND_COMMANDS; // Default until we get past command ! ea.argt = 0; ! ! // 1. skip comment lines and leading space, colons or bars ! for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++) ! ; ! xp->xp_pattern = cmd; ! ! if (*cmd == NUL) ! return NULL; ! if (*cmd == '"') // ignore comment lines ! { ! xp->xp_context = EXPAND_NOTHING; ! return NULL; ! } ! ! // 3. Skip over the range to find the command. ! cmd = skip_range(cmd, TRUE, &xp->xp_context); ! xp->xp_pattern = cmd; ! if (*cmd == NUL) ! return NULL; ! if (*cmd == '"') ! { ! xp->xp_context = EXPAND_NOTHING; ! return NULL; ! } ! ! if (*cmd == '|' || *cmd == '\n') ! return cmd + 1; // There's another command // Isolate the command and search for it in the command table. // Exceptions: --- 1124,1140 ---- } /* ! * Sets the index of a built-in or user defined command 'cmd' in eap->cmdidx. ! * For user defined commands, the completion context is set in 'xp' and the ! * completion flags in 'complp'. ! * ! * Returns a pointer to the text after the command or NULL for failure. */ static char_u * ! set_cmd_index(char_u *cmd, exarg_T *eap, expand_T *xp, int *complp) { ! char_u *p = NULL; ! int len = 0; // Isolate the command and search for it in the command table. // Exceptions: *************** *** 1137,1143 **** // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' if (*cmd == 'k' && cmd[1] != 'e') { ! ea.cmdidx = CMD_k; p = cmd + 1; } else --- 1143,1149 ---- // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' if (*cmd == 'k' && cmd[1] != 'e') { ! eap->cmdidx = CMD_k; p = cmd + 1; } else *************** *** 1168,1174 **** return NULL; } ! ea.cmdidx = excmd_get_cmdidx(cmd, len); if (cmd[0] >= 'A' && cmd[0] <= 'Z') while (ASCII_ISALNUM(*p) || *p == '*') // Allow * wild card --- 1174,1180 ---- return NULL; } ! eap->cmdidx = excmd_get_cmdidx(cmd, len); if (cmd[0] >= 'A' && cmd[0] <= 'Z') while (ASCII_ISALNUM(*p) || *p == '*') // Allow * wild card *************** *** 1180,1441 **** if (*p == NUL && ASCII_ISALNUM(p[-1])) return NULL; ! if (ea.cmdidx == CMD_SIZE) { if (*cmd == 's' && vim_strchr((char_u *)"cgriI", cmd[1]) != NULL) { ! ea.cmdidx = CMD_substitute; p = cmd + 1; } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { ! ea.cmd = cmd; ! p = find_ucmd(&ea, p, NULL, xp, &compl); if (p == NULL) ! ea.cmdidx = CMD_SIZE; // ambiguous user command } } ! if (ea.cmdidx == CMD_SIZE) { // Not still touching the command and it was an illegal one xp->xp_context = EXPAND_UNSUCCESSFUL; return NULL; } ! xp->xp_context = EXPAND_NOTHING; // Default now that we're past command ! ! if (*p == '!') // forced commands ! { ! forceit = TRUE; ! ++p; ! } ! ! // 6. parse arguments ! if (!IS_USER_CMDIDX(ea.cmdidx)) ! ea.argt = excmd_get_argt(ea.cmdidx); ! ! arg = skipwhite(p); ! ! // Skip over ++argopt argument ! if ((ea.argt & EX_ARGOPT) && *arg != NUL && STRNCMP(arg, "++", 2) == 0) ! { ! p = arg; ! while (*p && !vim_isspace(*p)) ! MB_PTR_ADV(p); ! arg = skipwhite(p); ! } ! ! if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) ! { ! if (*arg == '>') // append ! { ! if (*++arg == '>') ! ++arg; ! arg = skipwhite(arg); ! } ! else if (*arg == '!' && ea.cmdidx == CMD_write) // :w !filter ! { ! ++arg; ! usefilter = TRUE; ! } ! } ! ! if (ea.cmdidx == CMD_read) ! { ! usefilter = forceit; // :r! filter if forced ! if (*arg == '!') // :r !filter ! { ! ++arg; ! usefilter = TRUE; ! } ! } ! ! if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) ! { ! while (*arg == *cmd) // allow any number of '>' or '<' ! ++arg; ! arg = skipwhite(arg); ! } ! ! // Does command allow "+command"? ! if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') ! { ! // Check if we're in the +command ! p = arg + 1; ! arg = skip_cmd_arg(arg, FALSE); ! ! // Still touching the command after '+'? ! if (*arg == NUL) ! return p; ! ! // Skip space(s) after +command to get to the real argument ! arg = skipwhite(arg); ! } ! ! ! // Check for '|' to separate commands and '"' to start comments. ! // Don't do this for ":read !cmd" and ":write !cmd". ! if ((ea.argt & EX_TRLBAR) && !usefilter) ! { ! p = arg; ! // ":redir @" is not the start of a comment ! if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') ! p += 2; ! while (*p) ! { ! if (*p == Ctrl_V) ! { ! if (p[1] != NUL) ! ++p; ! } ! else if ( (*p == '"' && !(ea.argt & EX_NOTRLCOM)) ! || *p == '|' || *p == '\n') ! { ! if (*(p - 1) != '\\') ! { ! if (*p == '|' || *p == '\n') ! return p + 1; ! return NULL; // It's a comment ! } ! } ! MB_PTR_ADV(p); ! } ! } ! ! if (!(ea.argt & EX_EXTRA) && *arg != NUL ! && vim_strchr((char_u *)"|\"", *arg) == NULL) ! // no arguments allowed but there is something ! return NULL; ! // Find start of last argument (argument just before cursor): ! p = buff; ! xp->xp_pattern = p; ! len = (int)STRLEN(buff); ! while (*p && p < buff + len) { ! if (*p == ' ' || *p == TAB) ! { ! // argument starts after a space ! xp->xp_pattern = ++p; ! } else { ! if (*p == '\\' && *(p + 1) != NUL) ! ++p; // skip over escaped character ! MB_PTR_ADV(p); ! } ! } ! ! if (ea.argt & EX_XFILE) ! { ! int c; ! int in_quote = FALSE; ! char_u *bow = NULL; // Beginning of word ! ! // Allow spaces within back-quotes to count as part of the argument ! // being expanded. ! xp->xp_pattern = skipwhite(arg); ! p = xp->xp_pattern; ! while (*p != NUL) ! { ! if (has_mbyte) ! c = mb_ptr2char(p); ! else ! c = *p; ! if (c == '\\' && p[1] != NUL) ! ++p; ! else if (c == '`') { ! if (!in_quote) ! { ! xp->xp_pattern = p; ! bow = p + 1; ! } ! in_quote = !in_quote; } ! // An argument can contain just about everything, except ! // characters that end the command and white space. ! else if (c == '|' || c == '\n' || c == '"' || (VIM_ISWHITE(c) #ifdef SPACE_IN_FILENAME ! && (!(ea.argt & EX_NOSPC) || usefilter) #endif )) { ! len = 0; // avoid getting stuck when space is in 'isfname' ! while (*p != NUL) ! { ! if (has_mbyte) ! c = mb_ptr2char(p); ! else ! c = *p; ! if (c == '`' || vim_isfilec_or_wc(c)) ! break; ! if (has_mbyte) ! len = (*mb_ptr2len)(p); ! else ! len = 1; ! MB_PTR_ADV(p); ! } ! if (in_quote) ! bow = p; else ! xp->xp_pattern = p; ! p -= len; } ! MB_PTR_ADV(p); } ! // If we are still inside the quotes, and we passed a space, just ! // expand from there. ! if (bow != NULL && in_quote) ! xp->xp_pattern = bow; ! xp->xp_context = EXPAND_FILES; ! // For a shell command more chars need to be escaped. ! if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) ! { #ifndef BACKSLASH_IN_FILENAME ! xp->xp_shell = TRUE; #endif ! // When still after the command name expand executables. ! if (xp->xp_pattern == skipwhite(arg)) ! xp->xp_context = EXPAND_SHELLCMD; ! } ! // Check for environment variable. ! if (*xp->xp_pattern == '$') { ! for (p = xp->xp_pattern + 1; *p != NUL; ++p) ! if (!vim_isIDc(*p)) ! break; ! if (*p == NUL) ! { ! xp->xp_context = EXPAND_ENV_VARS; ! ++xp->xp_pattern; ! // Avoid that the assignment uses EXPAND_FILES again. ! if (compl != EXPAND_USER_DEFINED && compl != EXPAND_USER_LIST) ! compl = EXPAND_ENV_VARS; ! } ! } ! // Check for user names. ! if (*xp->xp_pattern == '~') ! { ! for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; ++p) ! ; ! // Complete ~user only if it partially matches a user name. ! // A full match ~user will be replaced by user's home ! // directory i.e. something like ~user -> /home/user/ ! if (*p == NUL && p > xp->xp_pattern + 1 ! && match_user(xp->xp_pattern + 1) >= 1) ! { ! xp->xp_context = EXPAND_USER; ! ++xp->xp_pattern; ! } } } ! // 6. Switch on command name. ! switch (ea.cmdidx) { case CMD_find: case CMD_sfind: --- 1186,1357 ---- if (*p == NUL && ASCII_ISALNUM(p[-1])) return NULL; ! if (eap->cmdidx == CMD_SIZE) { if (*cmd == 's' && vim_strchr((char_u *)"cgriI", cmd[1]) != NULL) { ! eap->cmdidx = CMD_substitute; p = cmd + 1; } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { ! eap->cmd = cmd; ! p = find_ucmd(eap, p, NULL, xp, complp); if (p == NULL) ! eap->cmdidx = CMD_SIZE; // ambiguous user command } } ! if (eap->cmdidx == CMD_SIZE) { // Not still touching the command and it was an illegal one xp->xp_context = EXPAND_UNSUCCESSFUL; return NULL; } ! return p; ! } ! /* ! * Set the completion context for a command argument with wild card characters. ! */ ! static void ! set_context_for_wildcard_arg( ! exarg_T *eap, ! char_u *arg, ! int usefilter, ! expand_T *xp, ! int *complp) ! { ! char_u *p; ! int c; ! int in_quote = FALSE; ! char_u *bow = NULL; // Beginning of word ! int len = 0; ! ! // Allow spaces within back-quotes to count as part of the argument ! // being expanded. ! xp->xp_pattern = skipwhite(arg); ! p = xp->xp_pattern; ! while (*p != NUL) { ! if (has_mbyte) ! c = mb_ptr2char(p); else + c = *p; + if (c == '\\' && p[1] != NUL) + ++p; + else if (c == '`') { ! if (!in_quote) { ! xp->xp_pattern = p; ! bow = p + 1; } ! in_quote = !in_quote; ! } ! // An argument can contain just about everything, except ! // characters that end the command and white space. ! else if (c == '|' || c == '\n' || c == '"' || (VIM_ISWHITE(c) #ifdef SPACE_IN_FILENAME ! && (!(eap->argt & EX_NOSPC) || usefilter) #endif )) + { + len = 0; // avoid getting stuck when space is in 'isfname' + while (*p != NUL) { ! if (has_mbyte) ! c = mb_ptr2char(p); ! else ! c = *p; ! if (c == '`' || vim_isfilec_or_wc(c)) ! break; ! if (has_mbyte) ! len = (*mb_ptr2len)(p); else ! len = 1; ! MB_PTR_ADV(p); } ! if (in_quote) ! bow = p; ! else ! xp->xp_pattern = p; ! p -= len; } + MB_PTR_ADV(p); + } ! // If we are still inside the quotes, and we passed a space, just ! // expand from there. ! if (bow != NULL && in_quote) ! xp->xp_pattern = bow; ! xp->xp_context = EXPAND_FILES; ! // For a shell command more chars need to be escaped. ! if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) ! { #ifndef BACKSLASH_IN_FILENAME ! xp->xp_shell = TRUE; #endif ! // When still after the command name expand executables. ! if (xp->xp_pattern == skipwhite(arg)) ! xp->xp_context = EXPAND_SHELLCMD; ! } ! // Check for environment variable. ! if (*xp->xp_pattern == '$') ! { ! for (p = xp->xp_pattern + 1; *p != NUL; ++p) ! if (!vim_isIDc(*p)) ! break; ! if (*p == NUL) { ! xp->xp_context = EXPAND_ENV_VARS; ! ++xp->xp_pattern; ! // Avoid that the assignment uses EXPAND_FILES again. ! if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) ! *complp = EXPAND_ENV_VARS; ! } ! } ! // Check for user names. ! if (*xp->xp_pattern == '~') ! { ! for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; ++p) ! ; ! // Complete ~user only if it partially matches a user name. ! // A full match ~user will be replaced by user's home ! // directory i.e. something like ~user -> /home/user/ ! if (*p == NUL && p > xp->xp_pattern + 1 ! && match_user(xp->xp_pattern + 1) >= 1) ! { ! xp->xp_context = EXPAND_USER; ! ++xp->xp_pattern; } } + } ! /* ! * 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 ! * completion type. ! * Returns a pointer to the next command. Returns NULL if there is no next ! * command. ! */ ! static char_u * ! set_context_by_cmdname( ! char_u *cmd, ! cmdidx_T cmdidx, ! char_u *arg, ! long argt, ! int compl, ! expand_T *xp, ! int forceit) ! { ! char_u *p; ! int delim; ! ! switch (cmdidx) { case CMD_find: case CMD_sfind: *************** *** 1658,1664 **** case CMD_lexpr: case CMD_laddexpr: case CMD_lgetexpr: ! set_context_for_expression(xp, arg, ea.cmdidx); break; case CMD_unlet: --- 1574,1580 ---- case CMD_lexpr: case CMD_laddexpr: case CMD_lgetexpr: ! set_context_for_expression(xp, arg, cmdidx); break; case CMD_unlet: *************** *** 1696,1702 **** case CMD_cscope: case CMD_lcscope: case CMD_scscope: ! set_context_in_cscope_cmd(xp, arg, ea.cmdidx); break; #endif #ifdef FEAT_SIGNS --- 1612,1618 ---- case CMD_cscope: case CMD_lcscope: case CMD_scscope: ! set_context_in_cscope_cmd(xp, arg, cmdidx); break; #endif #ifdef FEAT_SIGNS *************** *** 1730,1736 **** if (compl != EXPAND_NOTHING) { // EX_XFILE: file names are handled above ! if (!(ea.argt & EX_XFILE)) { #ifdef FEAT_MENU if (compl == EXPAND_MENUS) --- 1646,1652 ---- if (compl != EXPAND_NOTHING) { // EX_XFILE: file names are handled above ! if (!(argt & EX_XFILE)) { #ifdef FEAT_MENU if (compl == EXPAND_MENUS) *************** *** 1769,1775 **** case CMD_tmap: case CMD_tnoremap: case CMD_xmap: case CMD_xnoremap: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! FALSE, FALSE, ea.cmdidx); case CMD_unmap: case CMD_nunmap: case CMD_vunmap: --- 1685,1691 ---- case CMD_tmap: case CMD_tnoremap: case CMD_xmap: case CMD_xnoremap: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! FALSE, FALSE, cmdidx); case CMD_unmap: case CMD_nunmap: case CMD_vunmap: *************** *** 1781,1787 **** case CMD_tunmap: case CMD_xunmap: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! FALSE, TRUE, ea.cmdidx); case CMD_mapclear: case CMD_nmapclear: case CMD_vmapclear: --- 1697,1703 ---- case CMD_tunmap: case CMD_xunmap: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! FALSE, TRUE, cmdidx); case CMD_mapclear: case CMD_nmapclear: case CMD_vmapclear: *************** *** 1800,1811 **** case CMD_cabbrev: case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! TRUE, FALSE, ea.cmdidx); case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! TRUE, TRUE, ea.cmdidx); #ifdef FEAT_MENU case CMD_menu: case CMD_noremenu: case CMD_unmenu: case CMD_amenu: case CMD_anoremenu: case CMD_aunmenu: --- 1716,1727 ---- case CMD_cabbrev: case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! TRUE, FALSE, cmdidx); case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: return set_context_in_map_cmd(xp, cmd, arg, forceit, ! TRUE, TRUE, cmdidx); #ifdef FEAT_MENU case CMD_menu: case CMD_noremenu: case CMD_unmenu: case CMD_amenu: case CMD_anoremenu: case CMD_aunmenu: *************** *** 1907,1912 **** --- 1823,2018 ---- return NULL; } + /* + * This is all pretty much copied from do_one_cmd(), with all the extra stuff + * we don't need/want deleted. Maybe this could be done better if we didn't + * repeat all this stuff. The only problem is that they may not stay + * perfectly compatible with each other, but then the command line syntax + * probably won't change that much -- webb. + */ + static char_u * + set_one_cmd_context( + expand_T *xp, + char_u *buff) // buffer for command string + { + char_u *p; + char_u *cmd, *arg; + int len = 0; + exarg_T ea; + int compl = EXPAND_NOTHING; + int forceit = FALSE; + int usefilter = FALSE; // filter instead of file name + + ExpandInit(xp); + xp->xp_pattern = buff; + xp->xp_line = buff; + xp->xp_context = EXPAND_COMMANDS; // Default until we get past command + ea.argt = 0; + + // 1. skip comment lines and leading space, colons or bars + for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++) + ; + xp->xp_pattern = cmd; + + if (*cmd == NUL) + return NULL; + if (*cmd == '"') // ignore comment lines + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + // 3. Skip over the range to find the command. + cmd = skip_range(cmd, TRUE, &xp->xp_context); + xp->xp_pattern = cmd; + if (*cmd == NUL) + return NULL; + if (*cmd == '"') + { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') + return cmd + 1; // There's another command + + // Get the command index. + p = set_cmd_index(cmd, &ea, xp, &compl); + if (p == NULL) + return NULL; + + xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + + if (*p == '!') // forced commands + { + forceit = TRUE; + ++p; + } + + // 6. parse arguments + if (!IS_USER_CMDIDX(ea.cmdidx)) + ea.argt = excmd_get_argt(ea.cmdidx); + + arg = skipwhite(p); + + // Skip over ++argopt argument + if ((ea.argt & EX_ARGOPT) && *arg != NUL && STRNCMP(arg, "++", 2) == 0) + { + p = arg; + while (*p && !vim_isspace(*p)) + MB_PTR_ADV(p); + arg = skipwhite(p); + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) + { + if (*arg == '>') // append + { + if (*++arg == '>') + ++arg; + arg = skipwhite(arg); + } + else if (*arg == '!' && ea.cmdidx == CMD_write) // :w !filter + { + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_read) + { + usefilter = forceit; // :r! filter if forced + if (*arg == '!') // :r !filter + { + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) + { + while (*arg == *cmd) // allow any number of '>' or '<' + ++arg; + arg = skipwhite(arg); + } + + // Does command allow "+command"? + if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') + { + // Check if we're in the +command + p = arg + 1; + arg = skip_cmd_arg(arg, FALSE); + + // Still touching the command after '+'? + if (*arg == NUL) + return p; + + // Skip space(s) after +command to get to the real argument + arg = skipwhite(arg); + } + + + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". + if ((ea.argt & EX_TRLBAR) && !usefilter) + { + p = arg; + // ":redir @" is not the start of a comment + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') + p += 2; + while (*p) + { + if (*p == Ctrl_V) + { + if (p[1] != NUL) + ++p; + } + else if ( (*p == '"' && !(ea.argt & EX_NOTRLCOM)) + || *p == '|' || *p == '\n') + { + if (*(p - 1) != '\\') + { + if (*p == '|' || *p == '\n') + return p + 1; + return NULL; // It's a comment + } + } + MB_PTR_ADV(p); + } + } + + if (!(ea.argt & EX_EXTRA) && *arg != NUL + && vim_strchr((char_u *)"|\"", *arg) == NULL) + // no arguments allowed but there is something + return NULL; + + // Find start of last argument (argument just before cursor): + p = buff; + xp->xp_pattern = p; + len = (int)STRLEN(buff); + while (*p && p < buff + len) + { + if (*p == ' ' || *p == TAB) + { + // argument starts after a space + xp->xp_pattern = ++p; + } + else + { + if (*p == '\\' && *(p + 1) != NUL) + ++p; // skip over escaped character + MB_PTR_ADV(p); + } + } + + if (ea.argt & EX_XFILE) + set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &compl); + + // 6. Switch on command name. + return set_context_by_cmdname(cmd, ea.cmdidx, arg, ea.argt, compl, xp, + forceit); + } + void set_cmd_context( expand_T *xp, *************** *** 2007,2012 **** --- 2113,2190 ---- } /* + * Expand file or directory names. + */ + static int + expand_files_and_dirs( + expand_T *xp, + char_u *pat, + char_u ***file, + int *num_file, + int flags, + int options) + { + int free_pat = FALSE; + int i; + int ret; + + // for ":set path=" and ":set tags=" halve backslashes for escaped + // space + if (xp->xp_backslash != XP_BS_NONE) + { + free_pat = TRUE; + pat = vim_strsave(pat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\') + { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') + STRMOVE(pat + i, pat + i + 3); + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + } + } + + if (xp->xp_context == EXPAND_FILES) + flags |= EW_FILE; + else if (xp->xp_context == EXPAND_FILES_IN_PATH) + flags |= (EW_FILE | EW_PATH); + else + flags = (flags | EW_DIR) & ~EW_FILE; + if (options & WILD_ICASE) + flags |= EW_ICASE; + + // Expand wildcards, supporting %:h and the like. + ret = expand_wildcards_eval(&pat, num_file, file, flags); + if (free_pat) + vim_free(pat); + #ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) + { + int j; + + for (j = 0; j < *num_file; ++j) + { + char_u *ptr = (*file)[j]; + + while (*ptr != NUL) + { + if (p_csl[0] == 's' && *ptr == '\\') + *ptr = '/'; + else if (p_csl[0] == 'b' && *ptr == '/') + *ptr = '\\'; + ptr += (*mb_ptr2len)(ptr); + } + } + } + #endif + return ret; + } + + /* * Function given to ExpandGeneric() to obtain the possible arguments of the * ":behave {mswin,xterm}" command. */ *************** *** 2041,2046 **** --- 2219,2309 ---- } /* + * Do the expansion based on xp->xp_context and 'rmp'. + */ + static int + ExpandOther( + expand_T *xp, + regmatch_T *rmp, + int *num_file, + char_u ***file) + { + static struct expgen + { + int context; + char_u *((*func)(expand_T *, int)); + int ic; + int escaped; + } tab[] = + { + {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, + {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, + {EXPAND_MAPCLEAR, get_mapclear_arg, TRUE, TRUE}, + {EXPAND_MESSAGES, get_messages_arg, TRUE, TRUE}, + {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, + {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, + {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, + {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, + {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, + {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, + # ifdef FEAT_EVAL + {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, + {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, + {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, + {EXPAND_DISASSEMBLE, get_disassemble_argument, FALSE, TRUE}, + {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, + # endif + # ifdef FEAT_MENU + {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, + {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, + # endif + # ifdef FEAT_SYN_HL + {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, + # endif + # ifdef FEAT_PROFILE + {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, + # endif + {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, + {EXPAND_EVENTS, get_event_name, TRUE, FALSE}, + {EXPAND_AUGROUP, get_augroup_name, TRUE, FALSE}, + # ifdef FEAT_CSCOPE + {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, + # endif + # ifdef FEAT_SIGNS + {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, + # endif + # ifdef FEAT_PROFILE + {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, + # endif + # if defined(HAVE_LOCALE_H) || defined(X_LOCALE) + {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, + {EXPAND_LOCALES, get_locales, TRUE, FALSE}, + # endif + {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, + {EXPAND_USER, get_users, TRUE, FALSE}, + {EXPAND_ARGLIST, get_arglist_name, TRUE, FALSE}, + }; + int i; + int ret = FAIL; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + for (i = 0; i < (int)ARRAY_LENGTH(tab); ++i) + { + if (xp->xp_context == tab[i].context) + { + if (tab[i].ic) + rmp->rm_ic = TRUE; + ret = ExpandGeneric(xp, rmp, num_file, file, + tab[i].func, tab[i].escaped); + break; + } + } + + return ret; + } + + /* * Do the expansion based on xp->xp_context and "pat". */ static int *************** *** 2073,2138 **** if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES_IN_PATH) ! { ! // Expand file or directory names. ! int free_pat = FALSE; ! int i; ! ! // for ":set path=" and ":set tags=" halve backslashes for escaped ! // space ! if (xp->xp_backslash != XP_BS_NONE) ! { ! free_pat = TRUE; ! pat = vim_strsave(pat); ! for (i = 0; pat[i]; ++i) ! if (pat[i] == '\\') ! { ! if (xp->xp_backslash == XP_BS_THREE ! && pat[i + 1] == '\\' ! && pat[i + 2] == '\\' ! && pat[i + 3] == ' ') ! STRMOVE(pat + i, pat + i + 3); ! if (xp->xp_backslash == XP_BS_ONE ! && pat[i + 1] == ' ') ! STRMOVE(pat + i, pat + i + 1); ! } ! } ! ! if (xp->xp_context == EXPAND_FILES) ! flags |= EW_FILE; ! else if (xp->xp_context == EXPAND_FILES_IN_PATH) ! flags |= (EW_FILE | EW_PATH); ! else ! flags = (flags | EW_DIR) & ~EW_FILE; ! if (options & WILD_ICASE) ! flags |= EW_ICASE; ! ! // Expand wildcards, supporting %:h and the like. ! ret = expand_wildcards_eval(&pat, num_file, file, flags); ! if (free_pat) ! vim_free(pat); ! #ifdef BACKSLASH_IN_FILENAME ! if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) ! { ! int j; ! ! for (j = 0; j < *num_file; ++j) ! { ! char_u *ptr = (*file)[j]; ! ! while (*ptr != NUL) ! { ! if (p_csl[0] == 's' && *ptr == '\\') ! *ptr = '/'; ! else if (p_csl[0] == 'b' && *ptr == '/') ! *ptr = '\\'; ! ptr += (*mb_ptr2len)(ptr); ! } ! } ! } ! #endif ! return ret; ! } *file = (char_u **)""; *num_file = 0; --- 2336,2342 ---- if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES_IN_PATH) ! return expand_files_and_dirs(xp, pat, file, num_file, flags, options); *file = (char_u **)""; *num_file = 0; *************** *** 2222,2298 **** ret = ExpandUserDefined(xp, ®match, num_file, file); # endif else ! { ! static struct expgen ! { ! int context; ! char_u *((*func)(expand_T *, int)); ! int ic; ! int escaped; ! } tab[] = ! { ! {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, ! {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, ! {EXPAND_MAPCLEAR, get_mapclear_arg, TRUE, TRUE}, ! {EXPAND_MESSAGES, get_messages_arg, TRUE, TRUE}, ! {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, ! {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, ! {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, ! {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, ! {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, ! {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, ! # ifdef FEAT_EVAL ! {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, ! {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, ! {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, ! {EXPAND_DISASSEMBLE, get_disassemble_argument, FALSE, TRUE}, ! {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, ! # endif ! # ifdef FEAT_MENU ! {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, ! {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, ! # endif ! # ifdef FEAT_SYN_HL ! {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, ! # endif ! # ifdef FEAT_PROFILE ! {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, ! # endif ! {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, ! {EXPAND_EVENTS, get_event_name, TRUE, FALSE}, ! {EXPAND_AUGROUP, get_augroup_name, TRUE, FALSE}, ! # ifdef FEAT_CSCOPE ! {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, ! # endif ! # ifdef FEAT_SIGNS ! {EXPAND_SIGN, get_sign_name, TRUE, TRUE}, ! # endif ! # ifdef FEAT_PROFILE ! {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, ! # endif ! # if defined(HAVE_LOCALE_H) || defined(X_LOCALE) ! {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, ! {EXPAND_LOCALES, get_locales, TRUE, FALSE}, ! # endif ! {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, ! {EXPAND_USER, get_users, TRUE, FALSE}, ! {EXPAND_ARGLIST, get_arglist_name, TRUE, FALSE}, ! }; ! int i; ! ! // Find a context in the table and call the ExpandGeneric() with the ! // right function to do the expansion. ! ret = FAIL; ! for (i = 0; i < (int)ARRAY_LENGTH(tab); ++i) ! if (xp->xp_context == tab[i].context) ! { ! if (tab[i].ic) ! regmatch.rm_ic = TRUE; ! ret = ExpandGeneric(xp, ®match, num_file, file, ! tab[i].func, tab[i].escaped); ! break; ! } ! } vim_regfree(regmatch.regprog); vim_free(tofree); --- 2426,2432 ---- ret = ExpandUserDefined(xp, ®match, num_file, file); # endif else ! ret = ExpandOther(xp, ®match, num_file, file); vim_regfree(regmatch.regprog); vim_free(tofree); *** ../vim-8.2.4355/src/version.c 2022-02-12 11:51:20.048953988 +0000 --- src/version.c 2022-02-12 12:02:00.343789971 +0000 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 4356, /**/ -- hundred-and-one symptoms of being an internet addict: 29. Your phone bill comes to your doorstep in a box. /// 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 ///