Merge pull request #21850 from zeertzjq/vim-8.2.4463

vim-patch:8.2.{4463,4465,4475,4477,4478,4479,4608}: fuzzy cmdline builtin completion
This commit is contained in:
zeertzjq 2023-01-17 14:34:27 +08:00 committed by GitHub
commit 2093e574c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 943 additions and 139 deletions

View File

@ -3000,6 +3000,10 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
is applied to filter the results. Otherwise all the matches
are returned. The 'wildignorecase' option always applies.
If the 'wildoptions' option contains "fuzzy", then fuzzy
matching is used to get the completion matches. Otherwise
regular expression matching is used.
If {type} is "cmdline", then the |cmdline-completion| result is
returned. For example, to complete the possible values after
a ":call" command: >

View File

@ -7126,6 +7126,14 @@ A jump table for the options with a short description can be found at |Q_op|.
global
A list of words that change how |cmdline-completion| is done.
The following values are supported:
fuzzy Use fuzzy matching to find completion matches. When
this value is specified, wildcard expansion will not
be used for completion. The matches will be sorted by
the "best match" rather than alphabetically sorted.
This will find more matches than the wildcard
expansion. Currently fuzzy matching based completion
is not supported for file and directory names and
instead wildcard expansion is used.
pum Display the completion matches using the popup menu
in the same style as the |ins-completion-menu|.
tagfile When using CTRL-D to list matching tags, the kind of

View File

@ -88,6 +88,7 @@
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
#include "nvim/statusline.h"
@ -2337,7 +2338,6 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
int round;
char *p;
int attempt;
char *patc;
bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
@ -2347,31 +2347,40 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
return FAIL;
}
// Make a copy of "pat" and change "^" to "\(^\|[\/]\)".
if (*pat == '^') {
patc = xmalloc(strlen(pat) + 11);
STRCPY(patc, "\\(^\\|[\\/]\\)");
STRCPY(patc + 11, pat + 1);
} else {
patc = pat;
const bool fuzzy = cmdline_fuzzy_complete(pat);
char *patc = NULL;
// Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular
// expression matching)
if (!fuzzy) {
if (*pat == '^') {
patc = xmalloc(strlen(pat) + 11);
STRCPY(patc, "\\(^\\|[\\/]\\)");
STRCPY(patc + 11, pat + 1);
} else {
patc = pat;
}
}
fuzmatch_str_T *fuzmatch = NULL;
// attempt == 0: try match with '\<', match at start of word
// attempt == 1: try match without '\<', match anywhere
for (attempt = 0; attempt <= 1; attempt++) {
if (attempt > 0 && patc == pat) {
break; // there was no anchor, no need to try again
}
for (attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) {
regmatch_T regmatch;
regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC);
if (regmatch.regprog == NULL) {
if (patc != pat) {
xfree(patc);
if (!fuzzy) {
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) {
xfree(patc);
}
return FAIL;
}
return FAIL;
}
int score = 0;
// round == 1: Count the matches.
// round == 2: Build the array to keep the matches.
for (round = 1; round <= 2; round++) {
@ -2387,7 +2396,23 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
continue;
}
}
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) {
count++;
@ -2397,12 +2422,20 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
} else {
p = xstrdup(p);
}
if (matches != NULL) {
matches[count].buf = buf;
matches[count].match = p;
count++;
if (!fuzzy) {
if (matches != NULL) {
matches[count].buf = buf;
matches[count].match = p;
count++;
} else {
(*file)[count++] = p;
}
} else {
(*file)[count++] = p;
fuzmatch[count].idx = count;
fuzmatch[count].str = p;
fuzmatch[count].score = score;
count++;
}
}
}
@ -2411,40 +2444,50 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
break;
}
if (round == 1) {
*file = xmalloc((size_t)count * sizeof(**file));
if (options & WILD_BUFLASTUSED) {
matches = xmalloc((size_t)count * sizeof(*matches));
if (!fuzzy) {
*file = xmalloc((size_t)count * sizeof(**file));
if (options & WILD_BUFLASTUSED) {
matches = xmalloc((size_t)count * sizeof(*matches));
}
} else {
fuzmatch = xmalloc((size_t)count * sizeof(fuzmatch_str_T));
}
}
}
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) {
xfree(patc);
}
if (matches != NULL) {
if (count > 1) {
qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare);
}
if (!fuzzy) {
if (matches != NULL) {
if (count > 1) {
qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare);
}
// if the current buffer is first in the list, place it at the end
if (matches[0].buf == curbuf) {
for (int i = 1; i < count; i++) {
(*file)[i - 1] = matches[i].match;
}
(*file)[count - 1] = matches[0].match;
} else {
for (int i = 0; i < count; i++) {
(*file)[i] = matches[i].match;
// if the current buffer is first in the list, place it at the end
if (matches[0].buf == curbuf) {
for (int i = 1; i < count; i++) {
(*file)[i - 1] = matches[i].match;
}
(*file)[count - 1] = matches[0].match;
} else {
for (int i = 0; i < count; i++) {
(*file)[i] = matches[i].match;
}
}
xfree(matches);
}
xfree(matches);
} else {
fuzzymatches_to_strmatches(fuzmatch, file, count, false);
}
*num_file = count;

View File

@ -91,6 +91,38 @@ static int compl_selected;
#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_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.
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.
static int sort_func_compare(const void *s1, const void *s2)
@ -223,8 +255,13 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
// 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
@ -1335,13 +1372,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 {
@ -1375,7 +1415,9 @@ 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, support alphanumeric characters.
if ((cmd[0] >= 'A' && cmd[0] <= 'Z') || (fuzzy && *p != NUL)) {
while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card
p++;
}
@ -2330,7 +2372,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;
@ -2490,7 +2537,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 {
@ -2538,10 +2585,16 @@ static int ExpandOther(expand_T *xp, regmatch_T *rmp, char ***matches, int *numM
// right function to do the expansion.
for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) {
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.
const bool fuzzy = cmdline_fuzzy_complete(pat);
if (tab[i].ic) {
rmp->rm_ic = true;
}
ExpandGeneric(xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped);
ExpandGeneric(xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped,
fuzzy ? pat : NULL);
ret = OK;
break;
}
@ -2581,9 +2634,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
@ -2664,26 +2719,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);
} 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);
} 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;
@ -2695,13 +2754,17 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
/// obtain strings, one by one. The strings are matched against a regexp
/// program. Matching strings are copied into an array, which is returned.
///
/// If "fuzzystr" is not NULL, then fuzzy matching is used. Otherwise,
/// regex matching is used.
///
/// @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)
CompleteListItemGetter func, int escaped, const char *const fuzzystr)
{
int i;
size_t count = 0;
char *str;
const bool fuzzy = fuzzystr != NULL;
// count the number of matching names
for (i = 0;; i++) {
@ -2712,7 +2775,14 @@ static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char ***matches, i
if (*str == NUL) { // skip empty strings
continue;
}
if (vim_regexec(regmatch, str, (colnr_T)0)) {
bool match;
if (!fuzzy) {
match = vim_regexec(regmatch, str, (colnr_T)0);
} else {
match = fuzzy_match_str(str, fuzzystr) != 0;
}
if (match) {
count++;
}
}
@ -2721,7 +2791,12 @@ static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char ***matches, i
}
assert(count < INT_MAX);
*numMatches = (int)count;
*matches = xmalloc(count * sizeof(char *));
fuzmatch_str_T *fuzmatch = NULL;
if (fuzzy) {
fuzmatch = xmalloc(count * sizeof(fuzmatch_str_T));
} else {
*matches = xmalloc(count * sizeof(char *));
}
// copy the matching names into allocated memory
count = 0;
@ -2733,38 +2808,66 @@ static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, char ***matches, i
if (*str == NUL) { // Skip empty strings.
continue;
}
if (vim_regexec(regmatch, str, (colnr_T)0)) {
if (escaped) {
str = vim_strsave_escaped(str, " \t\\.");
} else {
str = xstrdup(str);
}
(*matches)[count++] = str;
if (func == get_menu_names) {
// Test for separator added by get_menu_names().
str += strlen(str) - 1;
if (*str == '\001') {
*str = '.';
}
bool match;
int score = 0;
if (!fuzzy) {
match = vim_regexec(regmatch, str, (colnr_T)0);
} else {
score = fuzzy_match_str(str, fuzzystr);
match = (score != 0);
}
if (!match) {
continue;
}
if (escaped) {
str = vim_strsave_escaped(str, " \t\\.");
} else {
str = xstrdup(str);
}
if (fuzzy) {
fuzmatch[count].idx = (int)count;
fuzmatch[count].str = str;
fuzmatch[count].score = score;
} else {
(*matches)[count] = str;
}
count++;
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.
bool funcsort = false;
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) {
// <SNR> functions should be sorted to the end.
qsort((void *)(*matches), (size_t)(*numMatches), sizeof(char *), sort_func_compare);
funcsort = true;
if (!fuzzy) {
qsort(*matches, (size_t)(*numMatches), sizeof(char *), sort_func_compare);
}
} else {
sort_strings(*matches, *numMatches);
if (!fuzzy) {
sort_strings(*matches, *numMatches);
}
}
}
// 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();
if (fuzzy) {
fuzzymatches_to_strmatches(fuzmatch, matches, (int)count, funcsort);
}
}
/// Expand shell command matches in one directory of $PATH.
@ -3369,7 +3472,13 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
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);

View File

@ -18,6 +18,7 @@
#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@ -39,6 +40,7 @@
#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/vim.h"
@ -1268,7 +1270,7 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit,
/// Find all mapping/abbreviation names that match regexp "regmatch".
/// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes.
/// @return OK if matches found, FAIL otherwise.
int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***matches)
{
mapblock_T *mp;
int hash;
@ -1277,14 +1279,18 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
char *p;
int i;
*num_file = 0; // return values in case of FAIL
*file = NULL;
fuzmatch_str_T *fuzmatch = NULL;
const bool fuzzy = cmdline_fuzzy_complete(pat);
*numMatches = 0; // return values in case of FAIL
*matches = NULL;
// round == 1: Count the matches.
// round == 2: Build the array to keep the matches.
for (round = 1; round <= 2; round++) {
count = 0;
// First search in map modifier arguments
for (i = 0; i < 7; i++) {
if (i == 0) {
p = "<silent>";
@ -1304,13 +1310,29 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
continue;
}
if (vim_regexec(regmatch, p, (colnr_T)0)) {
if (round == 1) {
count++;
bool match;
int score = 0;
if (!fuzzy) {
match = vim_regexec(regmatch, p, (colnr_T)0);
} else {
score = fuzzy_match_str(p, pat);
match = (score != 0);
}
if (!match) {
continue;
}
if (round == 2) {
if (fuzzy) {
fuzmatch[count].idx = count;
fuzmatch[count].str = xstrdup(p);
fuzmatch[count].score = score;
} else {
(*file)[count++] = xstrdup(p);
(*matches)[count] = xstrdup(p);
}
}
count++;
}
for (hash = 0; hash < 256; hash++) {
@ -1327,12 +1349,28 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
for (; mp; mp = mp->m_next) {
if (mp->m_mode & expand_mapmodes) {
p = (char *)translate_mapping((char_u *)mp->m_keys, CPO_TO_CPO_FLAGS);
if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) {
if (round == 1) {
count++;
if (p != NULL) {
bool match;
int score = 0;
if (!fuzzy) {
match = vim_regexec(regmatch, p, (colnr_T)0);
} else {
(*file)[count++] = p;
p = NULL;
score = fuzzy_match_str(p, pat);
match = (score != 0);
}
if (match) {
if (round == 2) {
if (fuzzy) {
fuzmatch[count].idx = count;
fuzmatch[count].str = p;
fuzmatch[count].score = score;
} else {
(*matches)[count] = p;
}
p = NULL;
}
count++;
}
}
xfree(p);
@ -1345,16 +1383,27 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
}
if (round == 1) {
*file = xmalloc((size_t)count * sizeof(char *));
if (fuzzy) {
fuzmatch = xmalloc((size_t)count * sizeof(fuzmatch_str_T));
} else {
*matches = xmalloc((size_t)count * sizeof(char *));
}
}
} // for (round)
if (fuzzy) {
fuzzymatches_to_strmatches(fuzmatch, matches, count, false);
}
if (count > 1) {
// Sort the matches
sort_strings(*file, count);
// Fuzzy matching already sorts the matches
if (!fuzzy) {
sort_strings(*matches, count);
}
// Remove multiple entries
char **ptr1 = *file;
char **ptr1 = *matches;
char **ptr2 = ptr1 + 1;
char **ptr3 = ptr1 + count;
@ -1368,7 +1417,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file)
}
}
*num_file = count;
*numMatches = count;
return count == 0 ? FAIL : OK;
}

View File

@ -36,6 +36,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
@ -81,6 +82,7 @@
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign_defs.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
@ -4698,13 +4700,56 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
}
int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file)
/// 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 bool match_str(char *const str, regmatch_T *const regmatch, char **const matches,
const int idx, const bool test_only, const bool fuzzy,
const char *const fuzzystr, fuzmatch_str_T *const fuzmatch)
{
if (!fuzzy) {
if (vim_regexec(regmatch, str, (colnr_T)0)) {
if (!test_only) {
matches[idx] = xstrdup(str);
}
return true;
}
} else {
const int score = fuzzy_match_str(str, fuzzystr);
if (score != 0) {
if (!test_only) {
fuzmatch[idx].idx = idx;
fuzmatch[idx].str = xstrdup(str);
fuzmatch[idx].score = score;
}
return true;
}
}
return false;
}
int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numMatches,
char ***matches)
{
int num_normal = 0; // Nr of matching non-term-code settings
int count = 0;
static char *(names[]) = { "all" };
int ic = regmatch->rm_ic; // remember the ignore-case flag
fuzmatch_str_T *fuzmatch = NULL;
const bool fuzzy = cmdline_fuzzy_complete(fuzzystr);
// do this loop twice:
// loop == 0: count the number of matching options
// loop == 1: copy the matching options into allocated memory
@ -4714,11 +4759,12 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***fi
if (xp->xp_context != EXPAND_BOOL_SETTINGS) {
for (match = 0; match < (int)ARRAY_SIZE(names);
match++) {
if (vim_regexec(regmatch, names[match], (colnr_T)0)) {
if (match_str(names[match], regmatch, *matches,
count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) {
if (loop == 0) {
num_normal++;
} else {
(*file)[count++] = xstrdup(names[match]);
count++;
}
}
}
@ -4733,33 +4779,45 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***fi
&& !(options[opt_idx].flags & P_BOOL)) {
continue;
}
match = false;
if (vim_regexec(regmatch, str, (colnr_T)0)
|| (options[opt_idx].shortname != NULL
&& vim_regexec(regmatch,
options[opt_idx].shortname,
(colnr_T)0))) {
match = true;
}
if (match) {
if (match_str(str, regmatch, *matches, count, (loop == 0),
fuzzy, fuzzystr, fuzmatch)) {
if (loop == 0) {
num_normal++;
} else {
(*file)[count++] = xstrdup(str);
count++;
}
} else if (!fuzzy && options[opt_idx].shortname != NULL
&& vim_regexec(regmatch, 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) {
num_normal++;
} else {
(*matches)[count++] = xstrdup(str);
}
}
}
if (loop == 0) {
if (num_normal > 0) {
*num_file = num_normal;
*numMatches = num_normal;
} else {
return OK;
}
*file = xmalloc((size_t)(*num_file) * sizeof(char *));
if (!fuzzy) {
*matches = xmalloc((size_t)(*numMatches) * sizeof(char *));
} else {
fuzmatch = xmalloc((size_t)(*numMatches) * sizeof(fuzmatch_str_T));
}
}
}
if (fuzzy) {
fuzzymatches_to_strmatches(fuzmatch, matches, count, false);
}
return OK;
}

View File

@ -795,6 +795,7 @@ EXTERN char *p_wop; // 'wildoptions'
EXTERN unsigned wop_flags;
#define WOP_TAGFILE 0x01
#define WOP_PUM 0x02
#define WOP_FUZZY 0x04
EXTERN long p_window; // 'window'
EXTERN char *p_wak; // 'winaltkeys'
EXTERN char *p_wig; // 'wildignore'

View File

@ -91,7 +91,7 @@ static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vspli
static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL };
static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL };
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[]) = { "tagfile", "pum", "fuzzy", NULL };
static char *(p_wak_values[]) = { "yes", "menu", "no", NULL };
static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL };
static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL };

View File

@ -5230,8 +5230,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
const size_t sz = sizeof(matches) / sizeof(matches[0]);
// Fuzzy string match
while (fuzzy_match((char_u *)str + col, (char_u *)spat, false, &score, matches,
(int)sz) > 0) {
while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) {
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.

View File

@ -2942,7 +2942,7 @@ typedef struct {
#define FUZZY_MATCH_RECURSION_LIMIT 10
/// Compute a score for a fuzzy matched string. The matching character locations
/// are in 'matches'.
/// are in "matches".
static int fuzzy_match_compute_score(const char *const str, const int strSz,
const uint32_t *const matches, const int numMatches)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@ -3007,7 +3007,7 @@ static int fuzzy_match_compute_score(const char *const str, const int strSz,
return score;
}
/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
/// Perform a recursive search for fuzzy matching "fuzpat" in "str".
/// @return the number of matching characters.
static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t strIdx,
int *const outScore, const char *const strBegin, const int strLen,
@ -3107,23 +3107,23 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
/// Uses char_u for match indices. Therefore patterns are limited to
/// MAX_FUZZY_MATCHES characters.
///
/// @return true if 'pat_arg' matches 'str'. Also returns the match score in
/// 'outScore' and the matching character positions in 'matches'.
bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq,
/// @return true if "pat_arg" matches "str". Also returns the match score in
/// "outScore" and the matching character positions in "matches".
bool fuzzy_match(char *const str, const char *const pat_arg, const bool matchseq,
int *const outScore, uint32_t *const matches, const int maxMatches)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ALL
{
const int len = mb_charlen((char *)str);
const int len = mb_charlen(str);
bool complete = false;
int numMatches = 0;
*outScore = 0;
char *const save_pat = xstrdup((char *)pat_arg);
char *const save_pat = xstrdup(pat_arg);
char *pat = save_pat;
char *p = pat;
// Try matching each word in 'pat_arg' in 'str'
// Try matching each word in "pat_arg" in "str"
while (true) {
if (matchseq) {
complete = true;
@ -3146,7 +3146,7 @@ bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matc
int score = 0;
int recursionCount = 0;
const int matchCount
= fuzzy_match_recursive(pat, (char *)str, 0, &score, (char *)str, len, NULL,
= fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
matches + numMatches,
maxMatches - numMatches, 0, &recursionCount);
if (matchCount == 0) {
@ -3183,14 +3183,14 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
}
/// Fuzzy search the string 'str' in a list of 'items' and return the matching
/// strings in 'fmatchlist'.
/// If 'matchseq' is true, then for multi-word search strings, match all the
/// Fuzzy search the string "str" in a list of "items" and return the matching
/// strings in "fmatchlist".
/// If "matchseq" is true, then for multi-word search strings, match all the
/// words in sequence.
/// If 'items' is a list of strings, then search for 'str' in the list.
/// If 'items' is a list of dicts, then either use 'key' to lookup the string
/// for each item or use 'item_cb' Funcref function to get the string.
/// If 'retmatchpos' is true, then return a list of positions where 'str'
/// If "items" is a list of strings, then search for "str" in the list.
/// If "items" is a list of dicts, then either use "key" to lookup the string
/// for each item or use "item_cb" Funcref function to get the string.
/// If "retmatchpos" is true, then return a list of positions where "str"
/// matches for each item.
static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq,
const char *const key, Callback *const item_cb,
@ -3245,14 +3245,14 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
}
int score;
if (itemstr != NULL && fuzzy_match((char_u *)itemstr, (char_u *)str, matchseq, &score, matches,
if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches,
MAX_FUZZY_MATCHES)) {
items[match_count].idx = (int)match_count;
items[match_count].item = li;
items[match_count].score = score;
// Copy the list of matching positions in itemstr to a list, if
// 'retmatchpos' is set.
// "retmatchpos" is set.
if (retmatchpos) {
items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow);
int j = 0;
@ -3326,8 +3326,8 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
xfree(items);
}
/// Do fuzzy matching. Returns the list of matched strings in 'rettv'.
/// If 'retmatchpos' is true, also returns the matching character positions.
/// Do fuzzy matching. Returns the list of matched strings in "rettv".
/// If "retmatchpos" is true, also returns the matching character positions.
static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
const bool retmatchpos)
FUNC_ATTR_NONNULL_ALL
@ -3411,6 +3411,109 @@ void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
do_fuzzymatch(argvars, rettv, true);
}
/// Same as fuzzy_match_item_compare() except for use with a string match
static int fuzzy_match_str_compare(const void *const s1, const void *const s2)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
const int v1 = ((fuzmatch_str_T *)s1)->score;
const int v2 = ((fuzmatch_str_T *)s2)->score;
const int idx1 = ((fuzmatch_str_T *)s1)->idx;
const 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 *const fm, const int sz)
FUNC_ATTR_NONNULL_ALL
{
// Sort the list by the descending order of the match score
qsort(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 *const s1, const void *const s2)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
const int v1 = ((fuzmatch_str_T *)s1)->score;
const int v2 = ((fuzmatch_str_T *)s2)->score;
const int idx1 = ((fuzmatch_str_T *)s1)->idx;
const int idx2 = ((fuzmatch_str_T *)s2)->idx;
const char *const str1 = ((fuzmatch_str_T *)s1)->str;
const char *const 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 *const fm, const int sz)
FUNC_ATTR_NONNULL_ALL
{
// Sort the list by the descending order of the match score
qsort(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 *const str, const char *const pat)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (str == NULL || pat == NULL) {
return 0;
}
int score = 0;
uint32_t matchpos[MAX_FUZZY_MATCHES];
fuzzy_match(str, pat, true, &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".
void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches,
const int count, const bool funcsort)
FUNC_ATTR_NONNULL_ARG(2)
{
if (count <= 0) {
return;
}
*matches = xmalloc((size_t)count * sizeof(char *));
// Sort the list by the descending order of the match score
if (funcsort) {
fuzzy_match_func_sort(fuzmatch, count);
} else {
fuzzy_match_str_sort(fuzmatch, count);
}
for (int i = 0; i < count; i++) {
(*matches)[i] = fuzmatch[i].str;
}
xfree(fuzmatch);
}
/// Free a list of fuzzy string matches.
void fuzmatch_str_free(fuzmatch_str_T *const fuzmatch, int count)
{
if (count <= 0 || fuzmatch == NULL) {
return;
}
while (count--) {
xfree(fuzmatch[count].str);
}
xfree(fuzmatch);
}
/// Get line "lnum" and copy it into "buf[LSIZE]".
/// The copy is made because the regexp may make the line invalid when using a
/// mark.

View File

@ -99,6 +99,14 @@ typedef struct searchstat {
int last_maxcount; // the max count of the last search
} searchstat_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 *str;
int score;
} fuzmatch_str_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif

View File

@ -650,6 +650,22 @@ func Test_getcompletion()
call assert_fails('call getcompletion("abc", [])', 'E475:')
endfunc
" Test for getcompletion() with "fuzzy" in 'wildoptions'
func Test_getcompletion_wildoptions()
let save_wildoptions = &wildoptions
set wildoptions&
let l = getcompletion('space', 'option')
call assert_equal([], l)
let l = getcompletion('ier', 'command')
call assert_equal([], l)
set wildoptions=fuzzy
let l = getcompletion('space', 'option')
call assert_true(index(l, 'backspace') >= 0)
let l = getcompletion('ier', 'command')
call assert_true(index(l, 'compiler') >= 0)
let &wildoptions = save_wildoptions
endfunc
func Test_fullcommand()
let tests = {
\ '': '',
@ -2654,6 +2670,412 @@ func Test_cmdline_complete_dlist()
call assert_equal("\"dlist 10 /pat/ | chistory", @:)
endfunc
" Test for 'fuzzy' in 'wildoptions' (fuzzy completion)
func Test_wildoptions_fuzzy()
" argument list (only for :argdel)
argadd change.py count.py charge.py
set wildoptions&
call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"argdel cge', @:)
set wildoptions=fuzzy
call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"argdel change.py charge.py', @:)
%argdelete
" autocmd group name fuzzy completion
set wildoptions&
augroup MyFuzzyGroup
augroup END
call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"augroup mfg', @:)
call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"augroup MyFuzzyGroup', @:)
set wildoptions=fuzzy
call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"augroup MyFuzzyGroup', @:)
call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"augroup My*p', @:)
augroup! MyFuzzyGroup
" buffer name fuzzy completion
set wildoptions&
edit SomeFile.txt
enew
call feedkeys(":b SF\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"b SF', @:)
call feedkeys(":b S*File.txt\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"b SomeFile.txt', @:)
set wildoptions=fuzzy
call feedkeys(":b SF\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"b SomeFile.txt', @:)
call feedkeys(":b S*File.txt\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"b S*File.txt', @:)
%bw!
" buffer name (full path) fuzzy completion
if has('unix')
set wildoptions&
call mkdir('Xcmd/Xstate/Xfile.js', 'p')
edit Xcmd/Xstate/Xfile.js
cd Xcmd/Xstate
enew
call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"b CmdStateFile', @:)
set wildoptions=fuzzy
call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_match('Xcmd/Xstate/Xfile.js$', @:)
cd -
call delete('Xcmd', 'rf')
endif
" :behave suboptions fuzzy completion
set wildoptions&
call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"behave xm', @:)
call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"behave xterm', @:)
set wildoptions=fuzzy
call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"behave xterm', @:)
call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"behave xt*m', @:)
let g:Sline = ''
call feedkeys(":behave win\<C-D>\<F4>\<C-B>\"\<CR>", 'tx')
call assert_equal('mswin', g:Sline)
call assert_equal('"behave win', @:)
" colorscheme name fuzzy completion - NOT supported
" built-in command name fuzzy completion
set wildoptions&
call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sbwin', @:)
call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sbrewind', @:)
set wildoptions=fuzzy
call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sbrewind', @:)
call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sbr*d', @:)
" compiler name fuzzy completion - NOT supported
" :cscope suboptions fuzzy completion
if has('cscope')
set wildoptions&
call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cscope ret', @:)
call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cscope reset', @:)
set wildoptions=fuzzy
call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cscope reset', @:)
call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cscope re*t', @:)
endif
" :diffget/:diffput buffer name fuzzy completion
new SomeBuffer
diffthis
new OtherBuffer
diffthis
set wildoptions&
call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"diffget sbuf', @:)
call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"diffput sbuf', @:)
set wildoptions=fuzzy
call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"diffget SomeBuffer', @:)
call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"diffput SomeBuffer', @:)
%bw!
" directory name fuzzy completion - NOT supported
" environment variable name fuzzy completion
set wildoptions&
call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"echo $VUT', @:)
set wildoptions=fuzzy
call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"echo $VIMRUNTIME', @:)
" autocmd event fuzzy completion
set wildoptions&
call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"autocmd BWout', @:)
set wildoptions=fuzzy
call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"autocmd BufWipeout', @:)
" vim expression fuzzy completion
let g:PerPlaceCount = 10
set wildoptions&
call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"let c = ppc', @:)
set wildoptions=fuzzy
call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"let c = PerPlaceCount', @:)
" file name fuzzy completion - NOT supported
" files in path fuzzy completion - NOT supported
" filetype name fuzzy completion - NOT supported
" user defined function name completion
set wildoptions&
call feedkeys(":call Test_w_fuz\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"call Test_w_fuz', @:)
set wildoptions=fuzzy
call feedkeys(":call Test_w_fuz\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"call Test_wildoptions_fuzzy()', @:)
" user defined command name completion
set wildoptions&
call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"MsFeat', @:)
set wildoptions=fuzzy
call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"MissingFeature', @:)
" :help tag fuzzy completion - NOT supported
" highlight group name fuzzy completion
set wildoptions&
call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"highlight SKey', @:)
call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"highlight SpecialKey', @:)
set wildoptions=fuzzy
call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"highlight SpecialKey', @:)
call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"highlight Sp*Key', @:)
" :history suboptions fuzzy completion
set wildoptions&
call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"history dg', @:)
call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"history search', @:)
set wildoptions=fuzzy
call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"history debug', @:)
call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"history se*h', @:)
" :language locale name fuzzy completion
if has('unix')
set wildoptions&
call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"lang psx', @:)
set wildoptions=fuzzy
call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"lang POSIX', @:)
endif
" :mapclear buffer argument fuzzy completion
set wildoptions&
call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"mapclear buf', @:)
set wildoptions=fuzzy
call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"mapclear <buffer>', @:)
" map name fuzzy completion
" test regex completion works
set wildoptions=fuzzy
call feedkeys(":cnoremap <ex\<Tab> <esc> \<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"cnoremap <expr> <esc> \<Tab>", @:)
nmap <plug>MyLongMap :p<CR>
call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap <Plug>MyLongMap", @:)
call feedkeys(":nmap MLM \<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap MLM \t", @:)
call feedkeys(":nmap <F2> one two \<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap <F2> one two \t", @:)
" duplicate entries should be removed
vmap <plug>MyLongMap :<C-U>#<CR>
call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap <Plug>MyLongMap", @:)
nunmap <plug>MyLongMap
vunmap <plug>MyLongMap
call feedkeys(":nmap ABC\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap ABC\t", @:)
" results should be sorted by best match
nmap <Plug>format :
nmap <Plug>goformat :
nmap <Plug>TestFOrmat :
nmap <Plug>fendoff :
nmap <Plug>state :
nmap <Plug>FendingOff :
call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:)
nunmap <Plug>format
nunmap <Plug>goformat
nunmap <Plug>TestFOrmat
nunmap <Plug>fendoff
nunmap <Plug>state
nunmap <Plug>FendingOff
" abbreviation fuzzy completion
set wildoptions=fuzzy
call feedkeys(":iabbr wait\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"iabbr <nowait>", @:)
iabbr WaitForCompletion WFC
call feedkeys(":iabbr fcl\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"iabbr WaitForCompletion", @:)
call feedkeys(":iabbr a1z\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"iabbr a1z\t", @:)
iunabbrev WaitForCompletion
" menu name fuzzy completion
if has('gui_running')
set wildoptions&
call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"menu pup', @:)
set wildoptions=fuzzy
call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"menu PopUp.', @:)
endif
" :messages suboptions fuzzy completion
set wildoptions&
call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"messages clr', @:)
set wildoptions=fuzzy
call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"messages clear', @:)
" :set option name fuzzy completion
set wildoptions&
call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set brkopt', @:)
set wildoptions=fuzzy
call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set breakindentopt', @:)
set wildoptions&
call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set fixendofline', @:)
set wildoptions=fuzzy
call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set fixendofline', @:)
" :set <term_option>
" Nvim does not support term options
" set wildoptions&
" call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
" call assert_equal('"set t_EC', @:)
" call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
" call assert_equal('"set <t_EC>', @:)
" set wildoptions=fuzzy
" call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx')
" call assert_equal('"set t_EC', @:)
" call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx')
" call assert_equal('"set <t_EC>', @:)
" :packadd directory name fuzzy completion - NOT supported
" shell command name fuzzy completion - NOT supported
" :sign suboptions fuzzy completion
set wildoptions&
call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign ufe', @:)
set wildoptions=fuzzy
call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign undefine', @:)
" :syntax suboptions fuzzy completion
set wildoptions&
call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syntax kwd', @:)
set wildoptions=fuzzy
call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syntax keyword', @:)
" syntax group name fuzzy completion
set wildoptions&
call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syntax list mpar', @:)
set wildoptions=fuzzy
call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx')
" Fuzzy match favours NvimParenthesis over MatchParen
" call assert_equal('"syntax list MatchParen', @:)
call assert_equal('"syntax list NvimParenthesis', @:)
" :syntime suboptions fuzzy completion
if has('profile')
set wildoptions&
call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syntime clr', @:)
set wildoptions=fuzzy
call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"syntime clear', @:)
endif
" tag name fuzzy completion - NOT supported
" tag name and file fuzzy completion - NOT supported
" user names fuzzy completion - how to test this functionality?
" user defined variable name fuzzy completion
let g:SomeVariable=10
set wildoptions&
call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"let SVar', @:)
set wildoptions=fuzzy
call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"let SomeVariable', @:)
" Test for sorting the results by the best match
%bw!
command T123format :
command T123goformat :
command T123TestFOrmat :
command T123fendoff :
command T123state :
command T123FendingOff :
set wildoptions=fuzzy
call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:)
delcommand T123format
delcommand T123goformat
delcommand T123TestFOrmat
delcommand T123fendoff
delcommand T123state
delcommand T123FendingOff
%bw
" Test for fuzzy completion of a command with lower case letters and a
" number
command Foo2Bar :
set wildoptions=fuzzy
call feedkeys(":foo2\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"Foo2Bar', @:)
call feedkeys(":foo\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"Foo2Bar', @:)
call feedkeys(":bar\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"Foo2Bar', @:)
delcommand Foo2Bar
" Test for command completion for a command starting with 'k'
command KillKillKill :
set wildoptions&
call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"killkill\<Tab>", @:)
set wildoptions=fuzzy
call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"KillKillKill', @:)
delcom KillKillKill
set wildoptions&
%bw!
endfunc
" Test for :breakadd argument completion
func Test_cmdline_complete_breakadd()
call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx')