summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2022-02-24 13:28:41 +0000
committerBram Moolenaar <Bram@vim.org>2022-02-24 13:28:41 +0000
commit38b85cb4d7216705058708bacbc25ab90cd61595 (patch)
tree3eb078ed612a835d9d6eecf7e3a4b84f209f1618 /src
parent9c9be05b17ececb1515a2f41a4dedbf848d3d8b6 (diff)
patch 8.2.4463: completion only uses strict matchingv8.2.4463
Problem: Completion only uses strict matching. Solution: Add the "fuzzy" item for 'wildoptions'. (Yegappan Lakshmanan, closes #9803)
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c169
-rw-r--r--src/cmdexpand.c121
-rw-r--r--src/option.c190
-rw-r--r--src/option.h1
-rw-r--r--src/optionstr.c2
-rw-r--r--src/proto/cmdexpand.pro1
-rw-r--r--src/proto/option.pro2
-rw-r--r--src/proto/search.pro3
-rw-r--r--src/search.c134
-rw-r--r--src/structs.h8
-rw-r--r--src/testdir/gen_opt_test.vim3
-rw-r--r--src/testdir/test_cmdline.vim323
-rw-r--r--src/version.c2
13 files changed, 842 insertions, 117 deletions
diff --git a/src/buffer.c b/src/buffer.c
index 27e8643870..e580874906 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2728,10 +2728,12 @@ ExpandBufnames(
int round;
char_u *p;
int attempt;
- char_u *patc;
+ char_u *patc = NULL;
#ifdef FEAT_VIMINFO
bufmatch_T *matches = NULL;
#endif
+ int fuzzy;
+ fuzmatch_str_T *fuzmatch = NULL;
*num_file = 0; // return values in case of FAIL
*file = NULL;
@@ -2741,32 +2743,42 @@ ExpandBufnames(
return FAIL;
#endif
- // Make a copy of "pat" and change "^" to "\(^\|[\/]\)".
- if (*pat == '^')
+ fuzzy = cmdline_fuzzy_complete(pat);
+
+ // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular
+ // expression matching)
+ if (!fuzzy)
{
- patc = alloc(STRLEN(pat) + 11);
- if (patc == NULL)
- return FAIL;
- STRCPY(patc, "\\(^\\|[\\/]\\)");
- STRCPY(patc + 11, pat + 1);
+ if (*pat == '^')
+ {
+ patc = alloc(STRLEN(pat) + 11);
+ if (patc == NULL)
+ return FAIL;
+ STRCPY(patc, "\\(^\\|[\\/]\\)");
+ STRCPY(patc + 11, pat + 1);
+ }
+ else
+ patc = pat;
}
- else
- patc = pat;
// attempt == 0: try match with '\<', match at start of word
// attempt == 1: try match without '\<', match anywhere
- for (attempt = 0; attempt <= 1; ++attempt)
+ for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt)
{
regmatch_T regmatch;
+ int score = 0;
- if (attempt > 0 && patc == pat)
- break; // there was no anchor, no need to try again
- regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC);
- if (regmatch.regprog == NULL)
+ if (!fuzzy)
{
- if (patc != pat)
- vim_free(patc);
- return FAIL;
+ if (attempt > 0 && patc == pat)
+ break; // there was no anchor, no need to try again
+ regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC);
+ if (regmatch.regprog == NULL)
+ {
+ if (patc != pat)
+ vim_free(patc);
+ return FAIL;
+ }
}
// round == 1: Count the matches.
@@ -2786,7 +2798,22 @@ ExpandBufnames(
continue;
#endif
- p = buflist_match(&regmatch, buf, p_wic);
+ if (!fuzzy)
+ p = buflist_match(&regmatch, buf, p_wic);
+ else
+ {
+ p = NULL;
+ // first try matching with the short file name
+ if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0)
+ p = buf->b_sfname;
+ if (p == NULL)
+ {
+ // next try matching with the full path file name
+ if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0)
+ p = buf->b_ffname;
+ }
+ }
+
if (p != NULL)
{
if (round == 1)
@@ -2797,16 +2824,27 @@ ExpandBufnames(
p = home_replace_save(buf, p);
else
p = vim_strsave(p);
+
+ if (!fuzzy)
+ {
#ifdef FEAT_VIMINFO
- if (matches != NULL)
+ if (matches != NULL)
+ {
+ matches[count].buf = buf;
+ matches[count].match = p;
+ count++;
+ }
+ else
+#endif
+ (*file)[count++] = p;
+ }
+ else
{
- matches[count].buf = buf;
- matches[count].match = p;
+ fuzmatch[count].idx = count;
+ fuzmatch[count].str = p;
+ fuzmatch[count].score = score;
count++;
}
- else
-#endif
- (*file)[count++] = p;
}
}
}
@@ -2814,47 +2852,72 @@ ExpandBufnames(
break;
if (round == 1)
{
- *file = ALLOC_MULT(char_u *, count);
- if (*file == NULL)
+ if (!fuzzy)
{
- vim_regfree(regmatch.regprog);
- if (patc != pat)
- vim_free(patc);
- return FAIL;
- }
+ *file = ALLOC_MULT(char_u *, count);
+ if (*file == NULL)
+ {
+ vim_regfree(regmatch.regprog);
+ if (patc != pat)
+ vim_free(patc);
+ return FAIL;
+ }
#ifdef FEAT_VIMINFO
- if (options & WILD_BUFLASTUSED)
- matches = ALLOC_MULT(bufmatch_T, count);
+ if (options & WILD_BUFLASTUSED)
+ matches = ALLOC_MULT(bufmatch_T, count);
#endif
+ }
+ else
+ {
+ fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
+ if (fuzmatch == NULL)
+ {
+ *num_file = 0;
+ *file = NULL;
+ return FAIL;
+ }
+ }
}
}
- vim_regfree(regmatch.regprog);
- if (count) // match(es) found, break here
- break;
+
+ if (!fuzzy)
+ {
+ vim_regfree(regmatch.regprog);
+ if (count) // match(es) found, break here
+ break;
+ }
}
- if (patc != pat)
+ if (!fuzzy && patc != pat)
vim_free(patc);
#ifdef FEAT_VIMINFO
- if (matches != NULL)
+ if (!fuzzy)
{
- int i;
- if (count > 1)
- qsort(matches, count, sizeof(bufmatch_T), buf_compare);
- // if the current buffer is first in the list, place it at the end
- if (matches[0].buf == curbuf)
+ if (matches != NULL)
{
- for (i = 1; i < count; i++)
- (*file)[i-1] = matches[i].match;
- (*file)[count-1] = matches[0].match;
- }
- else
- {
- for (i = 0; i < count; i++)
- (*file)[i] = matches[i].match;
+ int i;
+ if (count > 1)
+ qsort(matches, count, sizeof(bufmatch_T), buf_compare);
+ // if the current buffer is first in the list, place it at the end
+ if (matches[0].buf == curbuf)
+ {
+ for (i = 1; i < count; i++)
+ (*file)[i-1] = matches[i].match;
+ (*file)[count-1] = matches[0].match;
+ }
+ else
+ {
+ for (i = 0; i < count; i++)
+ (*file)[i] = matches[i].match;
+ }
+ vim_free(matches);
}
- vim_free(matches);
+ }
+ else
+ {
+ if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL)
+ return FAIL;
}
#endif
diff --git a/src/cmdexpand.c b/src/cmdexpand.c
index 2e4a0ebdee..909706d1de 100644
--- a/src/cmdexpand.c
+++ b/src/cmdexpand.c
@@ -18,7 +18,8 @@ static int cmd_showtail; // Only show path tail in lists ?
static void set_expand_context(expand_T *xp);
static int ExpandGeneric(expand_T *xp, regmatch_T *regmatch,
char_u ***matches, int *numMatches,
- char_u *((*func)(expand_T *, int)), int escaped);
+ char_u *((*func)(expand_T *, int)), int escaped,
+ char_u *fuzzystr);
static int ExpandFromContext(expand_T *xp, char_u *, char_u ***, int *, int);
static int expand_showtail(expand_T *xp);
static int expand_shellcmd(char_u *filepat, char_u ***matches, int *numMatches, int flagsarg);
@@ -40,6 +41,43 @@ static int compl_selected;
#define SHOW_FILE_TEXT(m) (showtail ? sm_gettail(matches[m]) : matches[m])
/*
+ * Returns TRUE if fuzzy completion is supported for a given cmdline completion
+ * context.
+ */
+ static int
+cmdline_fuzzy_completion_supported(expand_T *xp)
+{
+ return (vim_strchr(p_wop, WOP_FUZZY) != NULL
+ && 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_MAPPINGS
+ && xp->xp_context != EXPAND_OLD_SETTING
+ && xp->xp_context != EXPAND_OWNSYNTAX
+ && xp->xp_context != EXPAND_PACKADD
+ && xp->xp_context != EXPAND_SHELLCMD
+ && xp->xp_context != EXPAND_TAGS
+ && xp->xp_context != EXPAND_TAGS_LISTFILES
+ && xp->xp_context != EXPAND_USER_DEFINED
+ && xp->xp_context != EXPAND_USER_LIST);
+}
+
+/*
+ * Returns TRUE if fuzzy completion for cmdline completion is enabled and
+ * 'fuzzystr' is not empty.
+ */
+ int
+cmdline_fuzzy_complete(char_u *fuzzystr)
+{
+ return vim_strchr(p_wop, WOP_FUZZY) != NULL && *fuzzystr != NUL;
+}
+
+/*
* sort function for the completion matches.
* <SNR> functions should be sorted to the end.
*/
@@ -195,9 +233,14 @@ nextwild(
}
else
{
+ if (cmdline_fuzzy_completion_supported(xp))
+ // If fuzzy matching, don't modify the search string
+ p1 = vim_strsave(xp->xp_pattern);
+ else
+ p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
+
// Translate string into pattern and expand it.
- if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len,
- xp->xp_context)) == NULL)
+ if (p1 == NULL)
p2 = NULL;
else
{
@@ -2188,9 +2231,15 @@ expand_cmdline(
// add star to file name, or convert to regexp if not exp. files.
xp->xp_pattern_len = (int)(str + col - xp->xp_pattern);
- file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
- if (file_str == NULL)
- return EXPAND_UNSUCCESSFUL;
+ if (cmdline_fuzzy_completion_supported(xp))
+ // If fuzzy matching, don't modify the search string
+ file_str = vim_strsave(xp->xp_pattern);
+ else
+ {
+ file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
+ if (file_str == NULL)
+ return EXPAND_UNSUCCESSFUL;
+ }
if (p_wic)
options += WILD_ICASE;
@@ -2317,6 +2366,7 @@ get_mapclear_arg(expand_T *xp UNUSED, int idx)
*/
static int
ExpandOther(
+ char_u *pat,
expand_T *xp,
regmatch_T *rmp,
char_u ***matches,
@@ -2386,10 +2436,16 @@ ExpandOther(
{
if (xp->xp_context == tab[i].context)
{
+ // Use fuzzy matching if 'wildoptions' has 'fuzzy'.
+ // If no search pattern is supplied, then don't use fuzzy
+ // matching and return all the found items.
+ int fuzzy = cmdline_fuzzy_complete(pat);
+
if (tab[i].ic)
rmp->rm_ic = TRUE;
ret = ExpandGeneric(xp, rmp, matches, numMatches,
- tab[i].func, tab[i].escaped);
+ tab[i].func, tab[i].escaped,
+ fuzzy ? pat : NULL);
break;
}
}
@@ -2530,7 +2586,7 @@ ExpandFromContext(
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);
else if (xp->xp_context == EXPAND_MAPPINGS)
ret = ExpandMappings(&regmatch, numMatches, matches);
# if defined(FEAT_EVAL)
@@ -2538,7 +2594,7 @@ ExpandFromContext(
ret = ExpandUserDefined(xp, &regmatch, matches, numMatches);
# endif
else
- ret = ExpandOther(xp, &regmatch, matches, numMatches);
+ ret = ExpandOther(pat, xp, &regmatch, matches, numMatches);
vim_regfree(regmatch.regprog);
vim_free(tofree);
@@ -2553,6 +2609,9 @@ ExpandFromContext(
* obtain strings, one by one. The strings are matched against a regexp
* program. Matching strings are copied into an array, which is returned.
*
+ * If 'fuzzy' is TRUE, then fuzzy matching is used. Otherwise, regex matching
+ * is used.
+ *
* Returns OK when no problems encountered, FAIL for error (out of memory).
*/
static int
@@ -2563,12 +2622,17 @@ ExpandGeneric(
int *numMatches,
char_u *((*func)(expand_T *, int)),
// returns a string from the list
- int escaped)
+ int escaped,
+ char_u *fuzzystr)
{
int i;
int count = 0;
int round;
char_u *str;
+ fuzmatch_str_T *fuzmatch = NULL;
+ int score = 0;
+ int fuzzy = (fuzzystr != NULL);
+ int funcsort = FALSE;
// do this loop twice:
// round == 0: count the number of matching names
@@ -2583,7 +2647,8 @@ ExpandGeneric(
if (*str == NUL) // skip empty strings
continue;
- if (vim_regexec(regmatch, str, (colnr_T)0))
+ if (vim_regexec(regmatch, str, (colnr_T)0) ||
+ (fuzzy && ((score = fuzzy_match_str(str, fuzzystr)) != 0)))
{
if (round)
{
@@ -2594,11 +2659,20 @@ ExpandGeneric(
if (str == NULL)
{
FreeWild(count, *matches);
+ if (fuzzy)
+ fuzmatch_str_free(fuzmatch, count);
*numMatches = 0;
*matches = NULL;
return FAIL;
}
- (*matches)[count] = str;
+ if (fuzzy)
+ {
+ fuzmatch[count].idx = count;
+ fuzmatch[count].str = str;
+ fuzmatch[count].score = score;
+ }
+ else
+ (*matches)[count] = str;
# ifdef FEAT_MENU
if (func == get_menu_names && str != NULL)
{
@@ -2616,8 +2690,11 @@ ExpandGeneric(
{
if (count == 0)
return OK;
- *matches = ALLOC_MULT(char_u *, count);
- if (*matches == NULL)
+ if (fuzzy)
+ fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
+ else
+ *matches = ALLOC_MULT(char_u *, count);
+ if ((fuzzy && (fuzmatch == NULL)) || (*matches == NULL))
{
*numMatches = 0;
*matches = NULL;
@@ -2635,11 +2712,18 @@ ExpandGeneric(
|| xp->xp_context == EXPAND_FUNCTIONS
|| xp->xp_context == EXPAND_USER_FUNC
|| xp->xp_context == EXPAND_DISASSEMBLE)
+ {
// <SNR> functions should be sorted to the end.
- qsort((void *)*matches, (size_t)*numMatches, sizeof(char_u *),
+ funcsort = TRUE;
+ if (!fuzzy)
+ qsort((void *)*matches, (size_t)*numMatches, sizeof(char_u *),
sort_func_compare);
+ }
else
- sort_strings(*matches, *numMatches);
+ {
+ if (!fuzzy)
+ sort_strings(*matches, *numMatches);
+ }
}
#if defined(FEAT_SYN_HL)
@@ -2647,6 +2731,11 @@ ExpandGeneric(
// they don't show up when getting normal highlight names by ID.
reset_expand_highlight();
#endif
+
+ if (fuzzy && fuzzymatches_to_strmatches(fuzmatch, matches, count,
+ funcsort) == FAIL)
+ return FAIL;
+
return OK;
}
diff --git a/src/option.c b/src/option.c
index 44a0520ad0..936906649d 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6447,12 +6447,70 @@ set_context_in_set_cmd(
}
}
+/*
+ * Returns TRUE if 'str' either matches 'regmatch' or fuzzy matches 'pat'.
+ *
+ * If 'test_only' is TRUE and 'fuzzy' is FALSE and if 'str' matches the regular
+ * expression 'regmatch', then returns TRUE. Otherwise returns FALSE.
+ *
+ * If 'test_only' is FALSE and 'fuzzy' is FALSE and if 'str' matches the
+ * regular expression 'regmatch', then stores the match in matches[idx] and
+ * returns TRUE.
+ *
+ * If 'test_only' is TRUE and 'fuzzy' is TRUE and if 'str' fuzzy matches
+ * 'fuzzystr', then returns TRUE. Otherwise returns FALSE.
+ *
+ * If 'test_only' is FALSE and 'fuzzy' is TRUE and if 'str' fuzzy matches
+ * 'fuzzystr', then stores the match details in fuzmatch[idx] and returns TRUE.
+ */
+ static int
+match_str(
+ char_u *str,
+ regmatch_T *regmatch,
+ char_u **matches,
+ int idx,
+ int test_only,
+ int fuzzy,
+ char_u *fuzzystr,
+ fuzmatch_str_T *fuzmatch)
+{
+ if (!fuzzy)
+ {
+ if (vim_regexec(regmatch, str, (colnr_T)0))
+ {
+ if (!test_only)
+ matches[idx] = vim_strsave(str);
+ return TRUE;
+ }
+ }
+ else
+ {
+ int score;
+
+ score = fuzzy_match_str(str, fuzzystr);
+ if (score != 0)
+ {
+ if (!test_only)
+ {
+ fuzmatch[idx].idx = idx;
+ fuzmatch[idx].str = vim_strsave(str);
+ fuzmatch[idx].score = score;
+ }
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
int
ExpandSettings(
expand_T *xp,
regmatch_T *regmatch,
- int *num_file,
- char_u ***file)
+ char_u *fuzzystr,
+ int *numMatches,
+ char_u ***matches)
{
int num_normal = 0; // Nr of matching non-term-code settings
int num_term = 0; // Nr of matching terminal code settings
@@ -6465,6 +6523,10 @@ ExpandSettings(
char_u name_buf[MAX_KEY_NAME_LEN];
static char *(names[]) = {"all", "termcap"};
int ic = regmatch->rm_ic; // remember the ignore-case flag
+ int fuzzy;
+ fuzmatch_str_T *fuzmatch = NULL;
+
+ fuzzy = cmdline_fuzzy_complete(fuzzystr);
// do this loop twice:
// loop == 0: count the number of matching options
@@ -6475,13 +6537,16 @@ ExpandSettings(
if (xp->xp_context != EXPAND_BOOL_SETTINGS)
{
for (match = 0; match < (int)ARRAY_LENGTH(names); ++match)
- if (vim_regexec(regmatch, (char_u *)names[match], (colnr_T)0))
+ {
+ if (match_str((char_u *)names[match], regmatch, *matches,
+ count, (loop == 0), fuzzy, fuzzystr, fuzmatch))
{
if (loop == 0)
num_normal++;
else
- (*file)[count++] = vim_strsave((char_u *)names[match]);
+ count++;
}
+ }
}
for (opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL;
opt_idx++)
@@ -6494,12 +6559,37 @@ ExpandSettings(
is_term_opt = istermoption_idx(opt_idx);
if (is_term_opt && num_normal > 0)
continue;
- match = FALSE;
- if (vim_regexec(regmatch, str, (colnr_T)0)
- || (options[opt_idx].shortname != NULL
+
+ if (match_str(str, regmatch, *matches, count, (loop == 0),
+ fuzzy, fuzzystr, fuzmatch))
+ {
+ if (loop == 0)
+ {
+ if (is_term_opt)
+ num_term++;
+ else
+ num_normal++;
+ }
+ else
+ count++;
+ }
+ else if (!fuzzy && options[opt_idx].shortname != NULL
&& vim_regexec(regmatch,
- (char_u *)options[opt_idx].shortname, (colnr_T)0)))
- match = TRUE;
+ (char_u *)options[opt_idx].shortname, (colnr_T)0))
+ {
+ // Compare against the abbreviated option name (for regular
+ // expression match). Fuzzy matching (previous if) already
+ // matches against both the expanded and abbreviated names.
+ if (loop == 0)
+ {
+ if (is_term_opt)
+ num_term++;
+ else
+ num_normal++;
+ }
+ else
+ (*matches)[count++] = vim_strsave(str);
+ }
else if (is_term_opt)
{
name_buf[0] = '<';
@@ -6509,25 +6599,18 @@ ExpandSettings(
name_buf[4] = str[3];
name_buf[5] = '>';
name_buf[6] = NUL;
- if (vim_regexec(regmatch, name_buf, (colnr_T)0))
- {
- match = TRUE;
- str = name_buf;
- }
- }
- if (match)
- {
- if (loop == 0)
+
+ if (match_str(name_buf, regmatch, *matches, count, (loop == 0),
+ fuzzy, fuzzystr, fuzmatch))
{
- if (is_term_opt)
+ if (loop == 0)
num_term++;
else
- num_normal++;
+ count++;
}
- else
- (*file)[count++] = vim_strsave(str);
}
}
+
/*
* Check terminal key codes, these are not in the option table
*/
@@ -6544,9 +6627,14 @@ ExpandSettings(
name_buf[3] = str[1];
name_buf[4] = NUL;
- match = FALSE;
- if (vim_regexec(regmatch, name_buf, (colnr_T)0))
- match = TRUE;
+ if (match_str(name_buf, regmatch, *matches, count,
+ (loop == 0), fuzzy, fuzzystr, fuzmatch))
+ {
+ if (loop == 0)
+ num_term++;
+ else
+ count++;
+ }
else
{
name_buf[0] = '<';
@@ -6557,15 +6645,15 @@ ExpandSettings(
name_buf[5] = '>';
name_buf[6] = NUL;
- if (vim_regexec(regmatch, name_buf, (colnr_T)0))
- match = TRUE;
- }
- if (match)
- {
- if (loop == 0)
- num_term++;
- else
- (*file)[count++] = vim_strsave(name_buf);
+ if (match_str(name_buf, regmatch, *matches, count,
+ (loop == 0), fuzzy, fuzzystr,
+ fuzmatch))
+ {
+ if (loop == 0)
+ num_term++;
+ else
+ count++;
+ }
}
}
@@ -6579,31 +6667,49 @@ ExpandSettings(
STRCPY(name_buf + 1, str);
STRCAT(name_buf, ">");
- if (vim_regexec(regmatch, name_buf, (colnr_T)0))
+ if (match_str(name_buf, regmatch, *matches, count, (loop == 0),
+ fuzzy, fuzzystr, fuzmatch))
{
if (loop == 0)
num_term++;
else
- (*file)[count++] = vim_strsave(name_buf);
+ count++;
}
}
}
if (loop == 0)
{
if (num_normal > 0)
- *num_file = num_normal;
+ *numMatches = num_normal;
else if (num_term > 0)
- *num_file = num_term;
+ *numMatches = num_term;
else
return OK;
- *file = ALLOC_MULT(char_u *, *num_file);
- if (*file == NULL)
+ if (!fuzzy)
{
- *file = (char_u **)"";
- return FAIL;
+ *matches = ALLOC_MULT(char_u *, *numMatches);
+ if (*matches == NULL)
+ {
+ *matches = (char_u **)"";
+ return FAIL;
+ }
+ }
+ else
+ {
+ fuzmatch = ALLOC_MULT(fuzmatch_str_T, *numMatches);
+ if (fuzmatch == NULL)
+ {
+ *matches = (char_u **)"";
+ return FAIL;
+ }
}
}
}
+
+ if (fuzzy &&
+ fuzzymatches_to_strmatches(fuzmatch, matches, count, FALSE) == FAIL)
+ return FAIL;
+
return OK;
}
diff --git a/src/option.h b/src/option.h
index ebbf94b069..2116fe9951 100644
--- a/src/option.h
+++ b/src/option.h
@@ -358,6 +358,7 @@ typedef enum {
// flags for the 'wildoptions' option
// each defined char should be unique over all values.
+#define WOP_FUZZY 'z'
#define WOP_TAGFILE 't'
#define WOP_PUM 'p'
diff --git a/src/optionstr.c b/src/optionstr.c
index c8bef03099..5c3f76dc9e 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -57,7 +57,7 @@ static char *(p_tbis_values[]) = {"tiny", "small", "medium", "large", "huge", "g
static char *(p_ttym_values[]) = {"xterm", "xterm2", "dec", "netterm", "jsbterm", "pterm", "urxvt", "sgr", NULL};
#endif
static char *(p_ve_values[]) = {"block", "insert", "all", "onemore", "none", "NONE", NULL};
-static char *(p_wop_values[]) = {"tagfile", "pum", NULL};
+static char *(p_wop_values[]) = {"fuzzy", "tagfile", "pum", NULL};
#ifdef FEAT_WAK
static char *(p_wak_values[]) = {"yes", "menu", "no", NULL};
#endif
diff --git a/src/proto/cmdexpand.pro b/src/proto/cmdexpand.pro
index b64c6bbdc6..5ffab8c545 100644
--- a/src/proto/cmdexpand.pro
+++ b/src/proto/cmdexpand.pro
@@ -1,4 +1,5 @@
/* cmdexpand.c */
+int cmdline_fuzzy_complete(char_u *fuzzystr);
int nextwild(expand_T *xp, int type, int options, int escape);
char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode);
void ExpandInit(expand_T *xp);
diff --git a/src/proto/option.pro b/src/proto/option.pro
index b58e79afef..373a60ca0e 100644
--- a/src/proto/option.pro
+++ b/src/proto/option.pro
@@ -63,7 +63,7 @@ void reset_modifiable(void);
void set_iminsert_global(void);
void set_imsearch_global(void);
void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags);
-int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file);
+int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char_u *pat, int *numMatches, char_u ***matches);
int ExpandOldSetting(int *num_file, char_u ***file);
int shortmess(int x);
void vimrc_found(char_u *fname, char_u *envname);
diff --git a/src/proto/search.pro b/src/proto/search.pro
index 27c609e200..bdd9213c75 100644
--- a/src/proto/search.pro
+++ b/src/proto/search.pro
@@ -40,4 +40,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv);
int fuzzy_match(char_u *str, char_u *pat_arg, int matchseq, int *outScore, int_u *matches, int maxMatches);
void f_matchfuzzy(typval_T *argvars, typval_T *rettv);
void f_matchfuzzypos(typval_T *argvars, typval_T *rettv);
+int fuzzy_match_str(char_u *str, char_u *pat);
+int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort);
+void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count);
/* vim: set ft=c : */
diff --git a/src/search.c b/src/search.c
index dbc766f9f5..00a9fdfc6d 100644
--- a/src/search.c
+++ b/src/search.c
@@ -1166,7 +1166,7 @@ searchit(
return submatch + 1;
}
-#ifdef FEAT_EVAL
+#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
void
set_search_direction(int cdir)
{
@@ -4107,7 +4107,7 @@ get_spat_last_idx(void)
}
#endif
-#ifdef FEAT_EVAL
+#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
/*
* "searchcount()" function
*/
@@ -4230,6 +4230,7 @@ the_end:
restore_incsearch_state();
#endif
}
+#endif
/*
* Fuzzy string matching
@@ -4611,6 +4612,7 @@ fuzzy_match(
return numMatches != 0;
}
+#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
/*
* Sort the fuzzy matches in the descending order of the match score.
* For items with same score, retain the order using the index (stable sort)
@@ -4933,5 +4935,131 @@ f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
{
do_fuzzymatch(argvars, rettv, TRUE);
}
-
#endif
+
+/*
+ * Same as fuzzy_match_item_compare() except for use with a string match
+ */
+ static int
+fuzzy_match_str_compare(const void *s1, const void *s2)
+{
+ int v1 = ((fuzmatch_str_T *)s1)->score;
+ int v2 = ((fuzmatch_str_T *)s2)->score;
+ int idx1 = ((fuzmatch_str_T *)s1)->idx;
+ int idx2 = ((fuzmatch_str_T *)s2)->idx;
+
+ return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+}
+
+/*
+ * Sort fuzzy matches by score
+ */
+ static void
+fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz)
+{
+ // Sort the list by the descending order of the match score
+ qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
+ fuzzy_match_str_compare);
+}
+
+/*
+ * Same as fuzzy_match_item_compare() except for use with a function name
+ * string match. <SNR> functions should be sorted to the end.
+ */
+ static int
+fuzzy_match_func_compare(const void *s1, const void *s2)
+{
+ int v1 = ((fuzmatch_str_T *)s1)->score;
+ int v2 = ((fuzmatch_str_T *)s2)->score;
+ int idx1 = ((fuzmatch_str_T *)s1)->idx;
+ int idx2 = ((fuzmatch_str_T *)s2)->idx;
+ char_u *str1 = ((fuzmatch_str_T *)s1)->str;
+ char_u *str2 = ((fuzmatch_str_T *)s2)->str;
+
+ if (*str1 != '<' && *str2 == '<') return -1;
+ if (*str1 == '<' && *str2 != '<') return 1;
+ return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
+}
+
+/*
+ * Sort fuzzy matches of function names by score.
+ * <SNR> functions should be sorted to the end.
+ */
+ static void
+fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz)
+{
+ // Sort the list by the descending order of the match score
+ qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
+ fuzzy_match_func_compare);
+}
+
+/*
+ * Fuzzy match 'pat' in 'str'. Returns 0 if there is no match. Otherwise,
+ * returns the match score.
+ */
+ int
+fuzzy_match_str(char_u *str, char_u *pat)
+{
+ int score = 0;
+ int_u matchpos[256];
+
+ if (str == NULL || pat == NULL)
+ return 0;
+
+ fuzzy_match(str, pat, FALSE, &score, matchpos,
+ sizeof(matchpos) / sizeof(matchpos[0]));
+
+ return score;
+}
+
+/*
+ * Copy a list of fuzzy matches into a string list after sorting the matches by
+ * the fuzzy score. Frees the memory allocated for 'fuzmatch'.
+ * Returns OK on success and FAIL on memory allocation failure.
+ */
+ int
+fuzzymatches_to_strmatches(
+ fuzmatch_str_T *fuzmatch,
+ char_u ***matches,
+ int count,
+ int funcsort)
+{
+ int i;
+
+ if (count <= 0)
+ return OK;
+
+ *matches = ALLOC_MULT(char_u *, count);
+ if (*matches == NULL)
+ {
+ for (i = 0; i < count; i++)
+ vim_free(fuzmatch[i].str);
+ vim_free(fuzmatch);
+ return FAIL;
+ }
+
+ // Sort the list by the descending order of the match score
+ if (funcsort)
+ fuzzy_match_func_sort((void *)fuzmatch, (size_t)count);
+ else
+ fuzzy_match_str_sort((void *)fuzmatch, (size_t)count);
+
+ for (i = 0; i < count; i++)
+ (*matches)[i] = fuzmatch[i].str;
+ vim_free(fuzmatch);
+
+ return OK;
+}
+
+/*
+ * Free a list of fuzzy string matches.
+ */
+ void
+fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count)
+{
+ if (count <= 0 || fuzmatch == NULL)
+ return;
+ while (count--)
+ vim_free(fuzmatch[count].str);
+ vim_free(fuzmatch);
+}
diff --git a/src/structs.h b/src/structs.h
index 1e759f5dfd..1a98004527 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4516,3 +4516,11 @@ typedef struct {
int sw_same_win; // VIsual_active was not reset
int sw_visual_active;
} switchwin_T;
+
+// Fuzzy matched string list item. Used for fuzzy match completion. Items are
+// usually sorted by 'score'. The 'idx' member is used for stable-sort.
+typedef struct {
+ int idx;
+ char_u *str;
+ int score;
+} fuzmatch_str_T;
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index abf11fc9dc..e6b16c9def 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -11,6 +11,7 @@ set nomore
" Clear out t_WS, we don't want to resize the actual terminal.
let script = [
\ '" DO NOT EDIT: Generated with gen_opt_test.vim',
+ \ '" Used by test_options.vim.',
\ '',
\ 'let save_columns = &columns',
\ 'let save_lines = &lines',
@@ -152,7 +153,7 @@ let test_values = {
\ 'virtualedit': [['', 'all', 'all,block'], ['xxx']],
\ 'whichwrap': [['', 'b,s', 'bs'], ['xxx']],
\ 'wildmode': [['', 'full', 'list:full', 'full,longest'], ['xxx', 'a4', 'full,full,full,full,full']],
- \ 'wildoptions': [['', 'tagfile'], ['xxx']],
+ \ 'wildoptions': [['', 'tagfile', 'pum', 'fuzzy'], ['xxx']],
\ 'winaltkeys': [['menu', 'no'], ['', 'xxx']],
\
\ 'luadll': [[], []],
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 337cb42988..1ee0d718e2 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -1574,6 +1574,12 @@ func Test_cmdwin_jump_to_win()
call assert_equal(1, winnr('$'))
endfunc
+func Test_cmdwin_tabpage()
+ tabedit
+ call assert_fails("silent norm q/g :I\<Esc>", 'E11:')
+ tabclose!
+endfunc