diff options
Diffstat (limited to 'src/nvim/cmdexpand.c')
-rw-r--r-- | src/nvim/cmdexpand.c | 672 |
1 files changed, 492 insertions, 180 deletions
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index b5ec3e7032..5e4b49db24 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -89,9 +89,42 @@ static int compl_match_arraysize; static int compl_startcol; static int compl_selected; -#define SHOW_FILE_TEXT(m) (showtail \ - ? showmatches_gettail(matches[m], false) \ - : matches[m]) +#define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m]) + +/// Returns true if fuzzy completion is supported for a given cmdline completion +/// context. +static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) + && xp->xp_context != EXPAND_BOOL_SETTINGS + && xp->xp_context != EXPAND_COLORS + && xp->xp_context != EXPAND_COMPILER + && xp->xp_context != EXPAND_DIRECTORIES + && xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_FILES_IN_PATH + && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_HELP + && xp->xp_context != EXPAND_LUA + && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_OWNSYNTAX + && xp->xp_context != EXPAND_PACKADD + && xp->xp_context != EXPAND_RUNTIME + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_TAGS + && xp->xp_context != EXPAND_TAGS_LISTFILES + && xp->xp_context != EXPAND_USER_LIST + && xp->xp_context != EXPAND_USER_LUA; +} + +/// Returns true if fuzzy completion for cmdline completion is enabled and +/// "fuzzystr" is not empty. If search pattern is empty, then don't use fuzzy +/// matching. +bool cmdline_fuzzy_complete(const char *const fuzzystr) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) && *fuzzystr != NUL; +} /// Sort function for the completion matches. /// <SNR> functions should be sorted to the end. @@ -110,71 +143,75 @@ static int sort_func_compare(const void *s1, const void *s2) } /// Escape special characters in the cmdline completion matches. -static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options) +static void wildescape(expand_T *xp, const char *str, int numfiles, char **files) { - int i; char *p; const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; - // May change home directory back to "~" - if (options & WILD_HOME_REPLACE) { - tilde_replace(str, numfiles, files); - } - - if (options & WILD_ESCAPE) { - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_FILES_IN_PATH - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS - || xp->xp_context == EXPAND_DIRECTORIES) { - // Insert a backslash into a file name before a space, \, %, # - // and wildmatch characters, except '~'. - for (i = 0; i < numfiles; i++) { - // for ":set path=" we need to escape spaces twice - if (xp->xp_backslash == XP_BS_THREE) { - p = vim_strsave_escaped(files[i], " "); - xfree(files[i]); - files[i] = p; + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (int i = 0; i < numfiles; i++) { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash == XP_BS_THREE) { + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #if defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - xfree(files[i]); - files[i] = p; + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #endif - } + } #ifdef BACKSLASH_IN_FILENAME - p = vim_strsave_fnameescape((const char *)files[i], vse_what); + p = vim_strsave_fnameescape(files[i], vse_what); #else - p = vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell ? VSE_SHELL : vse_what); + p = vim_strsave_fnameescape(files[i], xp->xp_shell ? VSE_SHELL : vse_what); #endif - xfree(files[i]); - files[i] = p; + xfree(files[i]); + files[i] = p; - // If 'str' starts with "\~", replace "~" at start of - // files[i] with "\~". - if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { - escape_fname(&files[i]); - } + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { + escape_fname(&files[i]); } - xp->xp_backslash = XP_BS_NONE; + } + xp->xp_backslash = XP_BS_NONE; - // If the first file starts with a '+' escape it. Otherwise it - // could be seen as "+cmd". - if (*files[0] == '+') { - escape_fname(&files[0]); - } - } else if (xp->xp_context == EXPAND_TAGS) { - // Insert a backslash before characters in a tag name that - // would terminate the ":tag" command. - for (i = 0; i < numfiles; i++) { - p = vim_strsave_escaped(files[i], "\\|\""); - xfree(files[i]); - files[i] = p; - } + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') { + escape_fname(&files[0]); + } + } else if (xp->xp_context == EXPAND_TAGS) { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (int i = 0; i < numfiles; i++) { + p = vim_strsave_escaped(files[i], "\\|\""); + xfree(files[i]); + files[i] = p; } } } +/// Escape special characters in the cmdline completion matches. +static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options) +{ + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) { + tilde_replace(str, numfiles, files); + } + + if (options & WILD_ESCAPE) { + wildescape(xp, str, numfiles, files); + } +} + /// Return FAIL if this is not an appropriate context in which to do /// completion of anything, return OK if it is (even if there are no matches). /// For the caller, this means that the character is just passed through like a @@ -215,12 +252,19 @@ int nextwild(expand_T *xp, int type, int options, bool escape) assert(ccline->cmdpos >= i); xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; - if (type == WILD_NEXT || type == WILD_PREV || type == WILD_PUM_WANT) { + if (type == WILD_NEXT || type == WILD_PREV + || type == WILD_PAGEUP || type == WILD_PAGEDOWN + || type == WILD_PUM_WANT) { // Get next/previous match for a previous expanded pattern. p2 = ExpandOne(xp, NULL, NULL, 0, type); } else { + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + p1 = xstrdup(xp->xp_pattern); + } else { + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } // Translate string into pattern and expand it. - p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); const int use_options = (options | WILD_HOME_REPLACE | WILD_ADD_SLASH @@ -296,7 +340,7 @@ static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); for (int i = 0; i < numMatches; i++) { compl_match_array[i] = (pumitem_T){ - .pum_text = SHOW_FILE_TEXT(i), + .pum_text = SHOW_MATCH(i), .pum_info = NULL, .pum_extra = NULL, .pum_kind = NULL, @@ -397,7 +441,6 @@ static int wildmenu_match_len(expand_T *xp, char *s) /// @param matches list of matches static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) { -#define L_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m]) int row; char *buf; int len; @@ -426,7 +469,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m highlight = false; } // count 1 for the ending ">" - clen = wildmenu_match_len(xp, L_MATCH(match)) + 3; + clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3; if (match == 0) { first_match = 0; } else if (match < first_match) { @@ -436,7 +479,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m } else { // check if match fits on the screen for (i = first_match; i < match; i++) { - clen += wildmenu_match_len(xp, L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; } if (first_match > 0) { clen += 2; @@ -447,7 +490,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m // if showing the last match, we can add some on the left clen = 2; for (i = match; i < num_matches; i++) { - clen += wildmenu_match_len(xp, L_MATCH(i)) + 2; + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; if ((long)clen >= Columns) { break; } @@ -459,7 +502,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m } if (add_left) { while (first_match > 0) { - clen += wildmenu_match_len(xp, L_MATCH(first_match - 1)) + 2; + clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2; if ((long)clen >= Columns) { break; } @@ -479,13 +522,13 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m clen = len; i = first_match; - while (clen + wildmenu_match_len(xp, L_MATCH(i)) + 2 < Columns) { + while (clen + wildmenu_match_len(xp, SHOW_MATCH(i)) + 2 < Columns) { if (i == match) { selstart = buf + len; selstart_col = clen; } - s = L_MATCH(i); + s = SHOW_MATCH(i); // Check for menu separators - replace with '|' emenu = (xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES); @@ -592,6 +635,44 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char findex--; } else if (mode == WILD_NEXT) { findex++; + } else if (mode == WILD_PAGEUP) { + if (findex == 0) { + // at the first entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the last entry + findex = xp->xp_numfiles - 1; + } else { + // go up by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex -= ht; + if (findex < 0) { + // few entries left, select the first entry + findex = 0; + } + } + } else if (mode == WILD_PAGEDOWN) { + if (findex == xp->xp_numfiles - 1) { + // at the last entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the first entry + findex = 0; + } else { + // go down by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex += ht; + if (findex >= xp->xp_numfiles) { + // few entries left, select the last entry + findex = xp->xp_numfiles - 1; + } + } } else { // mode == WILD_PUM_WANT assert(pum_want.active); findex = pum_want.item; @@ -769,7 +850,9 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) int i; // first handle the case of using an old match - if (mode == WILD_NEXT || mode == WILD_PREV || mode == WILD_PUM_WANT) { + if (mode == WILD_NEXT || mode == WILD_PREV + || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN + || mode == WILD_PUM_WANT) { return get_next_or_prev_match(mode, xp, &findex, orig_save); } @@ -897,7 +980,7 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in // Expansion was done before and special characters // were escaped, need to halve backslashes. Also // $HOME has been replaced with ~/. - char *exp_path = (char *)expand_env_save_opt(matches[j], true); + char *exp_path = expand_env_save_opt(matches[j], true); char *path = exp_path != NULL ? exp_path : matches[j]; char *halved_slash = backslash_halve_save(path); isdir = os_isdir(halved_slash); @@ -910,14 +993,14 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in isdir = os_isdir(matches[j]); } if (showtail) { - p = SHOW_FILE_TEXT(j); + p = SHOW_MATCH(j); } else { home_replace(NULL, matches[j], NameBuff, MAXPATHL, true); p = NameBuff; } } else { isdir = false; - p = SHOW_FILE_TEXT(j); + p = SHOW_MATCH(j); } lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0); } @@ -990,7 +1073,7 @@ int showmatches(expand_T *xp, int wildmenu) home_replace(NULL, matches[i], NameBuff, MAXPATHL, true); j = vim_strsize(NameBuff); } else { - j = vim_strsize(SHOW_FILE_TEXT(i)); + j = vim_strsize(SHOW_MATCH(i)); } if (j > maxlen) { maxlen = j; @@ -1092,7 +1175,7 @@ static bool expand_showtail(expand_T *xp) // separator, on DOS the '*' "path\*\file" must not be skipped. if (rem_backslash(s)) { s++; - } else if (vim_strchr("*?[", *s) != NULL) { + } else if (vim_strchr("*?[", (uint8_t)(*s)) != NULL) { return false; } } @@ -1133,6 +1216,7 @@ char *addstar(char *fname, size_t len, int context) || context == EXPAND_OWNSYNTAX || context == EXPAND_FILETYPE || context == EXPAND_PACKADD + || context == EXPAND_RUNTIME || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) && fname[0] == '/')) { retval = xstrnsave(fname, len); @@ -1292,13 +1376,16 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in { const char *p = NULL; size_t len = 0; + const bool fuzzy = cmdline_fuzzy_complete(cmd); // Isolate the command and search for it in the command table. // Exceptions: - // - the 'k' command can directly be followed by any character, but - // do accept "keepmarks", "keepalt" and "keepjumps". + // - the 'k' command can directly be followed by any character, but do + // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can + // find matches anywhere in the command name, do this only for command + // expansion based on regular expression and not for fuzzy matching. // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - if (*cmd == 'k' && cmd[1] != 'e') { + if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) { eap->cmdidx = CMD_k; p = cmd + 1; } else { @@ -1320,7 +1407,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in } } // check for non-alpha command - if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { + if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) { p++; } len = (size_t)(p - cmd); @@ -1332,7 +1419,11 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in eap->cmdidx = excmd_get_cmdidx(cmd, len); - if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + // User defined commands support alphanumeric characters. + // Also when doing fuzzy expansion for non-shell commands, support + // alphanumeric characters. + if ((cmd[0] >= 'A' && cmd[0] <= 'Z') + || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) { while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card p++; } @@ -1346,7 +1437,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in } if (eap->cmdidx == CMD_SIZE) { - if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { + if (*cmd == 's' && vim_strchr("cgriI", (uint8_t)cmd[1]) != NULL) { eap->cmdidx = CMD_substitute; p = cmd + 1; } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { @@ -1615,6 +1706,78 @@ static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg) return NULL; } +static enum { + EXP_BREAKPT_ADD, ///< expand ":breakadd" sub-commands + EXP_BREAKPT_DEL, ///< expand ":breakdel" sub-commands + EXP_PROFDEL, ///< expand ":profdel" sub-commands +} breakpt_expand_what; + +/// Set the completion context for the :breakadd command. Always returns NULL. +static const char *set_context_in_breakadd_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) +{ + xp->xp_context = EXPAND_BREAKPOINT; + xp->xp_pattern = (char *)arg; + + if (cmdidx == CMD_breakadd) { + breakpt_expand_what = EXP_BREAKPT_ADD; + } else if (cmdidx == CMD_breakdel) { + breakpt_expand_what = EXP_BREAKPT_DEL; + } else { + breakpt_expand_what = EXP_PROFDEL; + } + + const char *p = skipwhite(arg); + if (*p == NUL) { + return NULL; + } + const char *subcmd_start = p; + + if (strncmp("file ", p, 5) == 0 || strncmp("func ", p, 5) == 0) { + // :breakadd file [lnum] <filename> + // :breakadd func [lnum] <funcname> + p += 4; + p = skipwhite(p); + + // skip line number (if specified) + if (ascii_isdigit(*p)) { + p = skipdigits(p); + if (*p != ' ') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + p = skipwhite(p); + } + if (strncmp("file", subcmd_start, 4) == 0) { + xp->xp_context = EXPAND_FILES; + } else { + xp->xp_context = EXPAND_USER_FUNC; + } + xp->xp_pattern = (char *)p; + } else if (strncmp("expr ", p, 5) == 0) { + // :breakadd expr <expression> + xp->xp_context = EXPAND_EXPRESSION; + xp->xp_pattern = skipwhite(p + 5); + } + + return NULL; +} + +static const char *set_context_in_scriptnames_cmd(expand_T *xp, const char *arg) +{ + xp->xp_context = EXPAND_NOTHING; + xp->xp_pattern = NULL; + + char *p = skipwhite(arg); + if (ascii_isdigit(*p)) { + return NULL; + } + + xp->xp_context = EXPAND_SCRIPTNAMES; + xp->xp_pattern = p; + + 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, "context" has the @@ -1931,6 +2094,10 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa xp->xp_pattern = (char *)arg; break; + case CMD_runtime: + set_context_in_runtime_cmd(xp, arg); + break; + #ifdef HAVE_WORKING_LIBINTL case CMD_language: return set_context_in_lang_cmd(xp, arg); @@ -1969,6 +2136,14 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa xp->xp_pattern = (char *)arg; break; + case CMD_breakadd: + case CMD_profdel: + case CMD_breakdel: + return set_context_in_breakadd_cmd(xp, arg, cmdidx); + + case CMD_scriptnames: + return set_context_in_scriptnames_cmd(xp, arg); + case CMD_lua: xp->xp_context = EXPAND_LUA; break; @@ -2002,7 +2177,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) // 1. skip comment lines and leading space, colons or bars const char *cmd; - for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} + for (cmd = buff; vim_strchr(" \t:|", (uint8_t)(*cmd)) != NULL; cmd++) {} xp->xp_pattern = (char *)cmd; if (*cmd == NUL) { @@ -2180,7 +2355,7 @@ void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline) } else if (use_ccline && ccline->input_fn) { xp->xp_context = ccline->xp_context; xp->xp_pattern = ccline->cmdbuff; - xp->xp_arg = (char *)ccline->xp_arg; + xp->xp_arg = ccline->xp_arg; } else { while (nextcomm != NULL) { nextcomm = set_one_cmd_context(xp, nextcomm); @@ -2226,7 +2401,12 @@ int expand_cmdline(expand_T *xp, const char *str, int col, int *matchcount, char // add star to file name, or convert to regexp if not exp. files. assert((str + col) - xp->xp_pattern >= 0); xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern); - file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + file_str = xstrdup(xp->xp_pattern); + } else { + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } if (p_wic) { options += WILD_ICASE; @@ -2316,6 +2496,45 @@ static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":breakadd {expr, file, func, here}" command. +/// ":breakdel {func, file, here}" command. +static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + char *opts[] = { "expr", "file", "func", "here" }; + + if (idx >= 0 && idx <= 3) { + // breakadd {expr, file, func, here} + if (breakpt_expand_what == EXP_BREAKPT_ADD) { + return opts[idx]; + } else if (breakpt_expand_what == EXP_BREAKPT_DEL) { + // breakdel {func, file, here} + if (idx <= 2) { + return opts[idx + 1]; + } + } else { + // profdel {func, file} + if (idx <= 1) { + return opts[idx + 1]; + } + } + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments for the +/// ":scriptnames" command. +static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (!SCRIPT_ID_VALID(idx + 1)) { + return NULL; + } + + scriptitem_T *si = &SCRIPT_ITEM(idx + 1); + home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true); + return NameBuff; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":messages {clear}" command. static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { @@ -2360,7 +2579,7 @@ static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Do the expansion based on xp->xp_context and "rmp". -static int ExpandOther(expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) +static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) { typedef CompleteListItemGetter ExpandFunc; static struct expgen { @@ -2399,6 +2618,8 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, char ***matches, int *numM { EXPAND_ENV_VARS, get_env_name, true, true }, { EXPAND_USER, get_users, true, false }, { EXPAND_ARGLIST, get_arglist_name, true, false }, + { EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, + { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, }; int ret = FAIL; @@ -2410,7 +2631,7 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, char ***matches, int *numM if (tab[i].ic) { rmp->rm_ic = true; } - ExpandGeneric(xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped); + ExpandGeneric(pat, xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped); ret = OK; break; } @@ -2450,9 +2671,11 @@ static int map_wildopts_to_ewflags(int options) /// @param options WILD_ flags static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numMatches, int options) { - regmatch_T regmatch; + regmatch_T regmatch = { .rm_ic = false }; int ret; int flags = map_wildopts_to_ewflags(options); + const bool fuzzy = cmdline_fuzzy_complete(pat) + && cmdline_fuzzy_completion_supported(xp); if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES @@ -2493,11 +2716,11 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; - return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, numMatches, matches, directories); + return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, directories); } if (xp->xp_context == EXPAND_COMPILER) { char *directories[] = { "compiler", NULL }; - return ExpandRTDir(pat, DIP_LUA, numMatches, matches, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_OWNSYNTAX) { char *directories[] = { "syntax", NULL }; @@ -2505,7 +2728,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM } if (xp->xp_context == EXPAND_FILETYPE) { char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir(pat, DIP_LUA, numMatches, matches, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, matches, numMatches); @@ -2516,6 +2739,9 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, numMatches, matches); } + if (xp->xp_context == EXPAND_RUNTIME) { + return expand_runtime_cmd(pat, numMatches, matches); + } // When expanding a function name starting with s:, match the <SNR>nr_ // prefix. @@ -2533,26 +2759,30 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM return nlua_expand_pat(xp, pat, numMatches, matches); } - regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - return FAIL; - } + if (!fuzzy) { + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + return FAIL; + } - // set ignore-case according to p_ic, p_scs and pat - regmatch.rm_ic = ignorecase(pat); + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + } if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) { - ret = ExpandSettings(xp, ®match, numMatches, matches); + ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); } else if (xp->xp_context == EXPAND_MAPPINGS) { - ret = ExpandMappings(®match, numMatches, matches); + ret = ExpandMappings(pat, ®match, numMatches, matches); } else if (xp->xp_context == EXPAND_USER_DEFINED) { - ret = ExpandUserDefined(xp, ®match, matches, numMatches); + ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); } else { - ret = ExpandOther(xp, ®match, matches, numMatches); + ret = ExpandOther(pat, xp, ®match, matches, numMatches); } - vim_regfree(regmatch.regprog); + if (!fuzzy) { + vim_regfree(regmatch.regprog); + } xfree(tofree); return ret; @@ -2565,72 +2795,107 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM /// program. Matching strings are copied into an array, which is returned. /// /// @param func returns a string from the list -static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char ***matches, int *numMatches, - CompleteListItemGetter func, int escaped) +static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches, CompleteListItemGetter func, + int escaped) { - int i; - size_t count = 0; - char *str; + const bool fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; - // count the number of matching names - for (i = 0;; i++) { - str = (*func)(xp, i); - if (str == NULL) { // end of list - break; - } - if (*str == NUL) { // skip empty strings - continue; - } - if (vim_regexec(regmatch, str, (colnr_T)0)) { - count++; - } - } - if (count == 0) { - return; + garray_T ga; + if (!fuzzy) { + ga_init(&ga, sizeof(char *), 30); + } else { + ga_init(&ga, sizeof(fuzmatch_str_T), 30); } - assert(count < INT_MAX); - *numMatches = (int)count; - *matches = xmalloc(count * sizeof(char *)); - // copy the matching names into allocated memory - count = 0; - for (i = 0;; i++) { - str = (*func)(xp, i); + for (int i = 0;; i++) { + char *str = (*func)(xp, i); if (str == NULL) { // End of list. break; } if (*str == NUL) { // Skip empty strings. continue; } - if (vim_regexec(regmatch, str, (colnr_T)0)) { - if (escaped) { - str = vim_strsave_escaped(str, " \t\\."); + + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, str, (colnr_T)0); } else { - str = xstrdup(str); + score = fuzzy_match_str(str, pat); + match = (score != 0); } - (*matches)[count++] = str; - if (func == get_menu_names) { - // Test for separator added by get_menu_names(). - str += strlen(str) - 1; - if (*str == '\001') { - *str = '.'; - } + } else { + match = true; + } + + if (!match) { + continue; + } + + if (escaped) { + str = vim_strsave_escaped(str, " \t\\."); + } else { + str = xstrdup(str); + } + + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = str, + .score = score, + })); + } else { + GA_APPEND(char *, &ga, str); + } + + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). + str += strlen(str) - 1; + if (*str == '\001') { + *str = '.'; } } } - // Sort the results. Keep menu's in the specified order. - if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { - if (xp->xp_context == EXPAND_EXPRESSION - || xp->xp_context == EXPAND_FUNCTIONS - || xp->xp_context == EXPAND_USER_FUNC) { + if (ga.ga_len == 0) { + return; + } + + // Sort the matches when using regular expression matching and sorting + // applies to the completion context. Menus and scriptnames should be kept + // in the specified order. + const bool sort_matches = !fuzzy + && xp->xp_context != EXPAND_MENUNAMES + && xp->xp_context != EXPAND_MENUS + && xp->xp_context != EXPAND_SCRIPTNAMES; + + // <SNR> functions should be sorted to the end. + const bool funcsort = xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC; + + // Sort the matches. + if (sort_matches) { + if (funcsort) { // <SNR> functions should be sorted to the end. - qsort((void *)(*matches), (size_t)(*numMatches), sizeof(char *), sort_func_compare); + qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(char *), sort_func_compare); } else { - sort_strings(*matches, *numMatches); + sort_strings(ga.ga_data, ga.ga_len); } } + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, funcsort); + *numMatches = ga.ga_len; + } + // Reset the variables used for special highlight names expansion, so that // they don't show up when getting normal highlight names by ID. reset_expand_highlight(); @@ -2647,30 +2912,31 @@ static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char // Expand matches in one directory of $PATH. int ret = expand_wildcards(1, &buf, numMatches, matches, flags); - if (ret == OK) { - ga_grow(gap, *numMatches); - { - for (int i = 0; i < *numMatches; i++) { - char *name = (*matches)[i]; - - if (strlen(name) > l) { - // Check if this name was already found. - hash_T hash = hash_hash((char_u *)name + l); - hashitem_T *hi = - hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash); - if (HASHITEM_EMPTY(hi)) { - // Remove the path that was prepended. - STRMOVE(name, name + l); - ((char **)gap->ga_data)[gap->ga_len++] = name; - hash_add_item(ht, hi, (char_u *)name, hash); - name = NULL; - } - } - xfree(name); + if (ret != OK) { + return; + } + + ga_grow(gap, *numMatches); + + for (int i = 0; i < *numMatches; i++) { + char *name = (*matches)[i]; + + if (strlen(name) > l) { + // Check if this name was already found. + hash_T hash = hash_hash(name + l); + hashitem_T *hi = + hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash); + if (HASHITEM_EMPTY(hi)) { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char **)gap->ga_data)[gap->ga_len++] = name; + hash_add_item(ht, hi, name, hash); + name = NULL; } - xfree(*matches); } + xfree(name); } + xfree(*matches); } /// Complete a shell command. @@ -2710,7 +2976,7 @@ static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int path = "."; } else { // For an absolute name we don't use $PATH. - if (!path_is_absolute((char_u *)pat)) { + if (!path_is_absolute(pat)) { path = vim_getenv("PATH"); } if (path == NULL) { @@ -2809,22 +3075,28 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T return ret; } -/// Expand names with a function defined by the user. -static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, char ***matches, int *numMatches) +/// Expand names with a function defined by the user (EXPAND_USER_DEFINED and +/// EXPAND_USER_LIST). +static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches) { - char *e; - garray_T ga; - + const bool fuzzy = cmdline_fuzzy_complete(pat); *matches = NULL; *numMatches = 0; - char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp); + char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp); if (retstr == NULL) { return FAIL; } - ga_init(&ga, (int)sizeof(char *), 3); - for (char *s = retstr; *s != NUL; s = e) { + garray_T ga; + if (!fuzzy) { + ga_init(&ga, (int)sizeof(char *), 3); + } else { + ga_init(&ga, (int)sizeof(fuzmatch_str_T), 3); + } + + for (char *s = retstr, *e; *s != NUL; s = e) { e = vim_strchr(s, '\n'); if (e == NULL) { e = s + strlen(s); @@ -2832,10 +3104,31 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, char ***matches const char keep = *e; *e = NUL; - const bool skip = xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0; + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, s, (colnr_T)0); + } else { + score = fuzzy_match_str(s, pat); + match = (score != 0); + } + } else { + match = true; // match everything + } + *e = keep; - if (!skip) { - GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s))); + + if (match) { + if (!fuzzy) { + GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s))); + } else { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = xstrnsave(s, (size_t)(e - s)), + .score = score, + })); + } } if (*e != NUL) { @@ -2843,8 +3136,18 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, char ***matches } } xfree(retstr); - *matches = ga.ga_data; - *numMatches = ga.ga_len; + + if (ga.ga_len == 0) { + return OK; + } + + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); + *numMatches = ga.ga_len; + } return OK; } @@ -2907,11 +3210,12 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) /// Expand `file` for all comma-separated directories in `path`. /// Adds matches to `ga`. -void globpath(char *path, char *file, garray_T *ga, int expand_options) +/// If "dirs" is true only expand directory names. +void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dirs) { expand_T xpc; ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; + xpc.xp_context = dirs ? EXPAND_DIRECTORIES : EXPAND_FILES; char *buf = xmalloc(MAXPATHL); @@ -3077,8 +3381,7 @@ static int wildmenu_process_key_filenames(CmdlineInfo *cclp, int key, expand_T * j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); if (vim_ispathsep(cclp->cmdbuff[j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", cclp->cmdbuff[j + 1]) - == NULL + && vim_strchr(" *?[{`$%#", (uint8_t)cclp->cmdbuff[j + 1]) == NULL #endif ) { if (found) { @@ -3231,14 +3534,23 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); xpc.xp_pattern_len = strlen(xpc.xp_pattern); } - if (xpc.xp_context == EXPAND_SIGN) { set_context_in_sign_cmd(&xpc, xpc.xp_pattern); xpc.xp_pattern_len = strlen(xpc.xp_pattern); } + if (xpc.xp_context == EXPAND_RUNTIME) { + set_context_in_runtime_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = strlen(xpc.xp_pattern); + } theend: - pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + if (cmdline_fuzzy_completion_supported(&xpc)) { + // when fuzzy matching, don't modify the search string + pat = xstrdup(xpc.xp_pattern); + } else { + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + } + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); |