aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/cmdexpand.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/cmdexpand.c')
-rw-r--r--src/nvim/cmdexpand.c672
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, &regmatch, numMatches, matches);
+ ret = ExpandSettings(xp, &regmatch, pat, numMatches, matches, fuzzy);
} else if (xp->xp_context == EXPAND_MAPPINGS) {
- ret = ExpandMappings(&regmatch, numMatches, matches);
+ ret = ExpandMappings(pat, &regmatch, numMatches, matches);
} else if (xp->xp_context == EXPAND_USER_DEFINED) {
- ret = ExpandUserDefined(xp, &regmatch, matches, numMatches);
+ ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
} else {
- ret = ExpandOther(xp, &regmatch, matches, numMatches);
+ ret = ExpandOther(pat, xp, &regmatch, 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);