mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
vim-patch:9.1.1178: not possible to generate completion candidates using fuzzy matching
Problem: not possible to generate completion candidates using fuzzy
matching
Solution: add the 'completefuzzycollect' option for (some) ins-completion
modes (glepnir)
fixes vim/vim#15296
fixes vim/vim#15295
fixes vim/vim#15294
closes: vim/vim#16032
f31cfa29bf
Co-authored-by: glepnir <glephunter@gmail.com>
This commit is contained in:
@@ -131,7 +131,8 @@ LUA
|
||||
|
||||
OPTIONS
|
||||
|
||||
• todo
|
||||
• 'completefuzzycollect' enables fuzzy collection of candidates for (some)
|
||||
|ins-completion| modes.
|
||||
|
||||
PERFORMANCE
|
||||
|
||||
|
@@ -1517,6 +1517,18 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
This option cannot be set from a |modeline| or in the |sandbox|, for
|
||||
security reasons.
|
||||
|
||||
*'completefuzzycollect'* *'cfc'*
|
||||
'completefuzzycollect' 'cfc' string (default "")
|
||||
global
|
||||
This option enables fuzzy collection for (only some) specific
|
||||
|ins-completion| modes, adjusting how items are gathered for fuzzy
|
||||
matching based on input.
|
||||
The option can contain the following values (separated by commas),
|
||||
each enabling fuzzy collection for a specific completion mode:
|
||||
files file names
|
||||
keyword keyword completion in 'complete' and current file
|
||||
whole_line whole lines
|
||||
|
||||
*'completeitemalign'* *'cia'*
|
||||
'completeitemalign' 'cia' string (default "abbr,kind,menu")
|
||||
global
|
||||
@@ -1536,7 +1548,12 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
fuzzy Enable |fuzzy-matching| for completion candidates. This
|
||||
allows for more flexible and intuitive matching, where
|
||||
characters can be skipped and matches can be found even
|
||||
if the exact sequence is not typed.
|
||||
if the exact sequence is not typed. Note: This option
|
||||
does not affect the collection of candidate list, it only
|
||||
controls how completion candidates are reduced from the
|
||||
list of alternatives. If you want to use |fuzzy-matching|
|
||||
to gather more alternatives for your candidate list,
|
||||
see |'completefuzzycollect'|.
|
||||
|
||||
longest Only insert the longest common text of the matches. If
|
||||
the menu is displayed you can use CTRL-L to add more
|
||||
|
22
runtime/lua/vim/_meta/options.lua
generated
22
runtime/lua/vim/_meta/options.lua
generated
@@ -1044,6 +1044,21 @@ vim.o.cfu = vim.o.completefunc
|
||||
vim.bo.completefunc = vim.o.completefunc
|
||||
vim.bo.cfu = vim.bo.completefunc
|
||||
|
||||
--- This option enables fuzzy collection for (only some) specific
|
||||
--- `ins-completion` modes, adjusting how items are gathered for fuzzy
|
||||
--- matching based on input.
|
||||
--- The option can contain the following values (separated by commas),
|
||||
--- each enabling fuzzy collection for a specific completion mode:
|
||||
--- files file names
|
||||
--- keyword keyword completion in 'complete' and current file
|
||||
--- whole_line whole lines
|
||||
---
|
||||
--- @type string
|
||||
vim.o.completefuzzycollect = ""
|
||||
vim.o.cfc = vim.o.completefuzzycollect
|
||||
vim.go.completefuzzycollect = vim.o.completefuzzycollect
|
||||
vim.go.cfc = vim.go.completefuzzycollect
|
||||
|
||||
--- A comma-separated list of strings that controls the alignment and
|
||||
--- display order of items in the popup menu during Insert mode
|
||||
--- completion. The supported values are "abbr", "kind", and "menu".
|
||||
@@ -1063,7 +1078,12 @@ vim.go.cia = vim.go.completeitemalign
|
||||
--- fuzzy Enable `fuzzy-matching` for completion candidates. This
|
||||
--- allows for more flexible and intuitive matching, where
|
||||
--- characters can be skipped and matches can be found even
|
||||
--- if the exact sequence is not typed.
|
||||
--- if the exact sequence is not typed. Note: This option
|
||||
--- does not affect the collection of candidate list, it only
|
||||
--- controls how completion candidates are reduced from the
|
||||
--- list of alternatives. If you want to use `fuzzy-matching`
|
||||
--- to gather more alternatives for your candidate list,
|
||||
--- see `'completefuzzycollect'`.
|
||||
---
|
||||
--- longest Only insert the longest common text of the matches. If
|
||||
--- the menu is displayed you can use CTRL-L to add more
|
||||
|
@@ -725,6 +725,8 @@ endif
|
||||
if has("insert_expand")
|
||||
call <SID>AddOption("complete", gettext("specifies how Insert mode completion works for CTRL-N and CTRL-P"))
|
||||
call append("$", "\t" .. s:local_to_buffer)
|
||||
call <SID>OptionL("cfc")
|
||||
call <SID>AddOption("completefuzzycollect", gettext("using fuzzy collect for defaule completion mode"))
|
||||
call <SID>OptionL("cpt")
|
||||
call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion"))
|
||||
call <SID>OptionL("cot")
|
||||
|
@@ -215,6 +215,13 @@ static compl_T *compl_curr_match = NULL;
|
||||
static compl_T *compl_shown_match = NULL;
|
||||
static compl_T *compl_old_match = NULL;
|
||||
|
||||
/// list used to store the compl_T which have the max score
|
||||
/// used for completefuzzycollect
|
||||
static compl_T **compl_best_matches = NULL;
|
||||
static int compl_num_bests = 0;
|
||||
/// inserted a longest when completefuzzycollect enabled
|
||||
static bool compl_cfc_longest_ins = false;
|
||||
|
||||
/// After using a cursor key <Enter> selects a match in the popup menu,
|
||||
/// otherwise it inserts a line break.
|
||||
static bool compl_enter_selects = false;
|
||||
@@ -732,7 +739,7 @@ static char *ins_compl_infercase_gettext(const char *str, int char_len, int comp
|
||||
///
|
||||
/// @param[in] cont_s_ipos next ^X<> will set initial_pos
|
||||
int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Direction dir,
|
||||
bool cont_s_ipos)
|
||||
bool cont_s_ipos, int score)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
char *str = str_arg;
|
||||
@@ -777,11 +784,26 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir
|
||||
flags |= CP_ICASE;
|
||||
}
|
||||
|
||||
int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL);
|
||||
int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, NULL, score);
|
||||
xfree(tofree);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Check if ctrl_x_mode has been configured in 'completefuzzycollect'
|
||||
static bool cfc_has_mode(void)
|
||||
{
|
||||
switch (ctrl_x_mode) {
|
||||
case CTRL_X_NORMAL:
|
||||
return (cfc_flags & kOptCfcFlagKeyword) != 0;
|
||||
case CTRL_X_FILES:
|
||||
return (cfc_flags & kOptCfcFlagFiles) != 0;
|
||||
case CTRL_X_WHOLE_LINE:
|
||||
return (cfc_flags & kOptCfcFlagWholeLine) != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// free cptext
|
||||
static inline void free_cptext(char *const *const cptext)
|
||||
{
|
||||
@@ -818,12 +840,13 @@ static inline void free_cptext(char *const *const cptext)
|
||||
/// returned in case of error.
|
||||
static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext,
|
||||
const bool cptext_allocated, typval_T *user_data, const Direction cdir,
|
||||
int flags_arg, const bool adup, const int *user_hl)
|
||||
int flags_arg, const bool adup, const int *user_hl, const int score)
|
||||
FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
compl_T *match;
|
||||
const Direction dir = (cdir == kDirectionNotSet ? compl_direction : cdir);
|
||||
int flags = flags_arg;
|
||||
bool inserted = false;
|
||||
|
||||
if (flags & CP_FAST) {
|
||||
fast_breakcheck();
|
||||
@@ -864,6 +887,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
|
||||
match = xcalloc(1, sizeof(compl_T));
|
||||
match->cp_number = flags & CP_ORIGINAL_TEXT ? 0 : -1;
|
||||
match->cp_str = cbuf_to_string(str, (size_t)len);
|
||||
match->cp_score = score;
|
||||
|
||||
// match-fname is:
|
||||
// - compl_curr_match->cp_fname if it is a string equal to fname.
|
||||
@@ -907,6 +931,33 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
|
||||
// current match in the list of matches .
|
||||
if (compl_first_match == NULL) {
|
||||
match->cp_next = match->cp_prev = NULL;
|
||||
} else if (cfc_has_mode() && score > 0 && compl_get_longest) {
|
||||
compl_T *current = compl_first_match->cp_next;
|
||||
compl_T *prev = compl_first_match;
|
||||
inserted = false;
|
||||
// The direction is ignored when using longest and
|
||||
// completefuzzycollect, because matches are inserted
|
||||
// and sorted by score.
|
||||
while (current != NULL && current != compl_first_match) {
|
||||
if (current->cp_score < score) {
|
||||
match->cp_next = current;
|
||||
match->cp_prev = current->cp_prev;
|
||||
if (current->cp_prev) {
|
||||
current->cp_prev->cp_next = match;
|
||||
}
|
||||
current->cp_prev = match;
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
prev = current;
|
||||
current = current->cp_next;
|
||||
}
|
||||
if (!inserted) {
|
||||
prev->cp_next = match;
|
||||
match->cp_prev = prev;
|
||||
match->cp_next = compl_first_match;
|
||||
compl_first_match->cp_prev = match;
|
||||
}
|
||||
} else if (dir == FORWARD) {
|
||||
match->cp_next = compl_curr_match->cp_next;
|
||||
match->cp_prev = compl_curr_match;
|
||||
@@ -925,7 +976,7 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
|
||||
compl_curr_match = match;
|
||||
|
||||
// Find the longest common string if still doing that.
|
||||
if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0) {
|
||||
if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode()) {
|
||||
ins_compl_longest_match(match);
|
||||
}
|
||||
|
||||
@@ -1013,9 +1064,7 @@ static void ins_compl_longest_match(compl_T *match)
|
||||
compl_leader = copy_string(match->cp_str, NULL);
|
||||
|
||||
bool had_match = (curwin->w_cursor.col > compl_col);
|
||||
ins_compl_delete(false);
|
||||
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);
|
||||
ins_redraw(false);
|
||||
ins_compl_longest_insert(compl_leader.data);
|
||||
|
||||
// When the match isn't there (to avoid matching itself) remove it
|
||||
// again after redrawing.
|
||||
@@ -1049,9 +1098,7 @@ static void ins_compl_longest_match(compl_T *match)
|
||||
compl_leader.size = (size_t)(p - compl_leader.data);
|
||||
|
||||
bool had_match = (curwin->w_cursor.col > compl_col);
|
||||
ins_compl_delete(false);
|
||||
ins_compl_insert_bytes(compl_leader.data + get_compl_len(), -1);
|
||||
ins_redraw(false);
|
||||
ins_compl_longest_insert(compl_leader.data);
|
||||
|
||||
// When the match isn't there (to avoid matching itself) remove it
|
||||
// again after redrawing.
|
||||
@@ -1072,7 +1119,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase)
|
||||
|
||||
for (int i = 0; i < num_matches && add_r != FAIL; i++) {
|
||||
add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
|
||||
CP_FAST | (icase ? CP_ICASE : 0), false, NULL);
|
||||
CP_FAST | (icase ? CP_ICASE : 0), false, NULL, 0);
|
||||
if (add_r == OK) {
|
||||
// If dir was BACKWARD then honor it just once.
|
||||
dir = FORWARD;
|
||||
@@ -1238,6 +1285,7 @@ static int ins_compl_build_pum(void)
|
||||
bool compl_no_select = (cur_cot_flags & kOptCotFlagNoselect) != 0;
|
||||
bool fuzzy_filter = (cur_cot_flags & kOptCotFlagFuzzy) != 0;
|
||||
bool fuzzy_sort = fuzzy_filter && !(cur_cot_flags & kOptCotFlagNosort);
|
||||
|
||||
compl_T *match_head = NULL, *match_tail = NULL;
|
||||
|
||||
// If the current match is the original text don't find the first
|
||||
@@ -1554,7 +1602,7 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, bool
|
||||
spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0);
|
||||
} else if (count > 0) { // avoid warning for using "files" uninit
|
||||
ins_compl_files(count, files, thesaurus, flags,
|
||||
®match, buf, &dir);
|
||||
(cfc_has_mode() ? NULL : ®match), buf, &dir);
|
||||
if (flags != DICT_EXACT) {
|
||||
FreeWild(count, files);
|
||||
}
|
||||
@@ -1605,7 +1653,7 @@ static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, con
|
||||
// Add the word. Skip the regexp match.
|
||||
if (wstart != skip_word) {
|
||||
status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic,
|
||||
fname, dir, false);
|
||||
fname, dir, false, 0);
|
||||
if (status == FAIL) {
|
||||
break;
|
||||
}
|
||||
@@ -1622,6 +1670,11 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags,
|
||||
regmatch_T *regmatch, char *buf, Direction *dir)
|
||||
FUNC_ATTR_NONNULL_ARG(2, 7)
|
||||
{
|
||||
bool in_fuzzy_collect = cfc_has_mode() && ctrl_x_mode_normal();
|
||||
|
||||
char *leader = in_fuzzy_collect ? ins_compl_leader() : NULL;
|
||||
int leader_len = in_fuzzy_collect ? (int)ins_compl_leader_len() : 0;
|
||||
|
||||
for (int i = 0; i < count && !got_int && !compl_interrupted; i++) {
|
||||
FILE *fp = os_fopen(files[i], "r"); // open dictionary file
|
||||
if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) {
|
||||
@@ -1640,27 +1693,51 @@ static void ins_compl_files(int count, char **files, bool thesaurus, int flags,
|
||||
// Check each line for a match.
|
||||
while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) {
|
||||
char *ptr = buf;
|
||||
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) {
|
||||
ptr = regmatch->startp[0];
|
||||
ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr);
|
||||
int add_r = ins_compl_add_infercase(regmatch->startp[0],
|
||||
(int)(ptr - regmatch->startp[0]),
|
||||
p_ic, files[i], *dir, false);
|
||||
if (thesaurus) {
|
||||
// For a thesaurus, add all the words in the line
|
||||
ptr = buf;
|
||||
add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]);
|
||||
if (regmatch != NULL) {
|
||||
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) {
|
||||
ptr = regmatch->startp[0];
|
||||
ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr);
|
||||
int add_r = ins_compl_add_infercase(regmatch->startp[0],
|
||||
(int)(ptr - regmatch->startp[0]),
|
||||
p_ic, files[i], *dir, false, 0);
|
||||
if (thesaurus) {
|
||||
// For a thesaurus, add all the words in the line
|
||||
ptr = buf;
|
||||
add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]);
|
||||
}
|
||||
if (add_r == OK) {
|
||||
// if dir was BACKWARD then honor it just once
|
||||
*dir = FORWARD;
|
||||
} else if (add_r == FAIL) {
|
||||
break;
|
||||
}
|
||||
// avoid expensive call to vim_regexec() when at end
|
||||
// of line
|
||||
if (*ptr == '\n' || got_int) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (add_r == OK) {
|
||||
// if dir was BACKWARD then honor it just once
|
||||
*dir = FORWARD;
|
||||
} else if (add_r == FAIL) {
|
||||
break;
|
||||
}
|
||||
// avoid expensive call to vim_regexec() when at end
|
||||
// of line
|
||||
if (*ptr == '\n' || got_int) {
|
||||
break;
|
||||
} else if (in_fuzzy_collect && leader_len > 0) {
|
||||
char *line_end = find_line_end(ptr);
|
||||
while (ptr < line_end) {
|
||||
int score = 0;
|
||||
int len = 0;
|
||||
if (fuzzy_match_str_in_line(&ptr, leader, &len, NULL, &score)) {
|
||||
char *end_ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr) : find_word_end(ptr);
|
||||
int add_r = ins_compl_add_infercase(ptr, (int)(end_ptr - ptr),
|
||||
p_ic, files[i], *dir, false, score);
|
||||
if (add_r == FAIL) {
|
||||
break;
|
||||
}
|
||||
ptr = end_ptr; // start from next word
|
||||
if (compl_get_longest && ctrl_x_mode_normal()
|
||||
&& compl_first_match->cp_next
|
||||
&& score == compl_first_match->cp_next->cp_score) {
|
||||
compl_num_bests++;
|
||||
}
|
||||
} else if (find_word_end(ptr) == line_end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
line_breakcheck();
|
||||
@@ -1746,6 +1823,7 @@ void ins_compl_clear(void)
|
||||
{
|
||||
compl_cont_status = 0;
|
||||
compl_started = false;
|
||||
compl_cfc_longest_ins = false;
|
||||
compl_matches = 0;
|
||||
compl_selected_item = -1;
|
||||
compl_ins_end_col = 0;
|
||||
@@ -2744,7 +2822,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
|
||||
return FAIL;
|
||||
}
|
||||
int status = ins_compl_add((char *)word, -1, NULL, cptext, true,
|
||||
&user_data, dir, flags, dup, user_hl);
|
||||
&user_data, dir, flags, dup, user_hl, 0);
|
||||
if (status != OK) {
|
||||
tv_clear(&user_data);
|
||||
}
|
||||
@@ -2840,7 +2918,7 @@ static void set_completion(colnr_T startcol, list_T *list)
|
||||
}
|
||||
if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size,
|
||||
NULL, NULL, false, NULL, 0,
|
||||
flags | CP_FAST, false, NULL) != OK) {
|
||||
flags | CP_FAST, false, NULL, 0) != OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3310,6 +3388,87 @@ static int compare_scores(const void *a, const void *b)
|
||||
: (score_a > score_b ? -1 : 1);
|
||||
}
|
||||
|
||||
/// insert prefix with redraw
|
||||
static void ins_compl_longest_insert(char *prefix)
|
||||
{
|
||||
ins_compl_delete(false);
|
||||
ins_compl_insert_bytes(prefix + get_compl_len(), -1);
|
||||
ins_redraw(false);
|
||||
}
|
||||
|
||||
/// Calculate the longest common prefix among the best fuzzy matches
|
||||
/// stored in compl_best_matches, and insert it as the longest.
|
||||
static void fuzzy_longest_match(void)
|
||||
{
|
||||
if (compl_num_bests == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
compl_T *nn_compl = compl_first_match->cp_next->cp_next;
|
||||
bool more_candidates = nn_compl && nn_compl != compl_first_match;
|
||||
|
||||
compl_T *compl = ctrl_x_mode_whole_line() ? compl_first_match
|
||||
: compl_first_match->cp_next;
|
||||
if (compl_num_bests == 1) {
|
||||
// no more candidates insert the match str
|
||||
if (!more_candidates) {
|
||||
ins_compl_longest_insert(compl->cp_str.data);
|
||||
compl_num_bests = 0;
|
||||
}
|
||||
compl_num_bests = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
compl_best_matches = (compl_T **)xmalloc((size_t)compl_num_bests * sizeof(compl_T *));
|
||||
|
||||
for (int i = 0; compl != NULL && i < compl_num_bests; i++) {
|
||||
compl_best_matches[i] = compl;
|
||||
compl = compl->cp_next;
|
||||
}
|
||||
|
||||
char *prefix = compl_best_matches[0]->cp_str.data;
|
||||
int prefix_len = (int)strlen(prefix);
|
||||
|
||||
for (int i = 1; i < compl_num_bests; i++) {
|
||||
char *match_str = compl_best_matches[i]->cp_str.data;
|
||||
char *prefix_ptr = prefix;
|
||||
char *match_ptr = match_str;
|
||||
int j = 0;
|
||||
|
||||
while (j < prefix_len && *match_ptr != NUL && *prefix_ptr != NUL) {
|
||||
if (strncmp(prefix_ptr, match_ptr, (size_t)utfc_ptr2len(prefix_ptr)) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
MB_PTR_ADV(prefix_ptr);
|
||||
MB_PTR_ADV(match_ptr);
|
||||
j++;
|
||||
}
|
||||
|
||||
if (j > 0) {
|
||||
prefix_len = j;
|
||||
}
|
||||
}
|
||||
|
||||
char *leader = ins_compl_leader();
|
||||
size_t leader_len = leader != NULL ? strlen(leader) : 0;
|
||||
|
||||
// skip non-consecutive prefixes
|
||||
if (strncmp(prefix, leader, leader_len) != 0) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
prefix = xmemdupz(compl_best_matches[0]->cp_str.data, (size_t)prefix_len);
|
||||
ins_compl_longest_insert(prefix);
|
||||
compl_cfc_longest_ins = true;
|
||||
xfree(prefix);
|
||||
|
||||
end:
|
||||
xfree(compl_best_matches);
|
||||
compl_best_matches = NULL;
|
||||
compl_num_bests = 0;
|
||||
}
|
||||
|
||||
/// Get the next set of filename matching "compl_pattern".
|
||||
static void get_next_filename_completion(void)
|
||||
{
|
||||
@@ -3317,7 +3476,10 @@ static void get_next_filename_completion(void)
|
||||
int num_matches;
|
||||
char *leader = ins_compl_leader();
|
||||
size_t leader_len = ins_compl_leader_len();
|
||||
bool in_fuzzy = ((get_cot_flags() & kOptCotFlagFuzzy) != 0 && leader_len > 0);
|
||||
bool in_fuzzy_collect = (cfc_has_mode() && leader_len > 0);
|
||||
bool need_collect_bests = in_fuzzy_collect && compl_get_longest;
|
||||
int max_score = 0;
|
||||
Direction dir = compl_direction;
|
||||
|
||||
#ifdef BACKSLASH_IN_FILENAME
|
||||
char pathsep = (curbuf->b_p_csl[0] == 's')
|
||||
@@ -3326,7 +3488,7 @@ static void get_next_filename_completion(void)
|
||||
char pathsep = PATHSEP;
|
||||
#endif
|
||||
|
||||
if (in_fuzzy) {
|
||||
if (in_fuzzy_collect) {
|
||||
#ifdef BACKSLASH_IN_FILENAME
|
||||
if (curbuf->b_p_csl[0] == 's') {
|
||||
for (size_t i = 0; i < leader_len; i++) {
|
||||
@@ -3349,7 +3511,7 @@ static void get_next_filename_completion(void)
|
||||
API_CLEAR_STRING(compl_pattern);
|
||||
compl_pattern = cbuf_to_string("*", 1);
|
||||
} else if (*(last_sep + 1) == NUL) {
|
||||
in_fuzzy = false;
|
||||
in_fuzzy_collect = false;
|
||||
} else {
|
||||
// Split leader into path and file parts
|
||||
size_t path_len = (size_t)(last_sep - leader) + 1;
|
||||
@@ -3389,7 +3551,7 @@ static void get_next_filename_completion(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (in_fuzzy) {
|
||||
if (in_fuzzy_collect) {
|
||||
garray_T fuzzy_indices;
|
||||
ga_init(&fuzzy_indices, sizeof(int), 10);
|
||||
compl_fuzzy_scores = (int *)xmalloc(sizeof(int) * (size_t)num_matches);
|
||||
@@ -3408,14 +3570,24 @@ static void get_next_filename_completion(void)
|
||||
int *fuzzy_indices_data = (int *)fuzzy_indices.ga_data;
|
||||
qsort(fuzzy_indices_data, (size_t)fuzzy_indices.ga_len, sizeof(int), compare_scores);
|
||||
|
||||
char **sorted_matches = (char **)xmalloc(sizeof(char *) * (size_t)fuzzy_indices.ga_len);
|
||||
for (int i = 0; i < fuzzy_indices.ga_len; i++) {
|
||||
sorted_matches[i] = xstrdup(matches[fuzzy_indices_data[i]]);
|
||||
char *match = matches[fuzzy_indices_data[i]];
|
||||
int current_score = compl_fuzzy_scores[fuzzy_indices_data[i]];
|
||||
if (ins_compl_add(match, -1, NULL, NULL, false, NULL, dir,
|
||||
CP_FAST | ((p_fic || p_wic) ? CP_ICASE : 0),
|
||||
false, NULL, current_score) == OK) {
|
||||
dir = FORWARD;
|
||||
}
|
||||
|
||||
if (need_collect_bests) {
|
||||
if (i == 0 || current_score == max_score) {
|
||||
compl_num_bests++;
|
||||
max_score = current_score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FreeWild(num_matches, matches);
|
||||
matches = sorted_matches;
|
||||
num_matches = fuzzy_indices.ga_len;
|
||||
} else if (leader_len > 0) {
|
||||
FreeWild(num_matches, matches);
|
||||
num_matches = 0;
|
||||
@@ -3423,6 +3595,11 @@ static void get_next_filename_completion(void)
|
||||
|
||||
xfree(compl_fuzzy_scores);
|
||||
ga_clear(&fuzzy_indices);
|
||||
|
||||
if (compl_num_bests > 0 && compl_get_longest) {
|
||||
fuzzy_longest_match();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_matches > 0) {
|
||||
@@ -3552,8 +3729,9 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
|
||||
{
|
||||
char *ptr = NULL;
|
||||
int len = 0;
|
||||
bool in_fuzzy = (get_cot_flags() & kOptCotFlagFuzzy) != 0 && compl_length > 0;
|
||||
bool in_collect = (cfc_has_mode() && compl_length > 0);
|
||||
char *leader = ins_compl_leader();
|
||||
int score = 0;
|
||||
|
||||
// If 'infercase' is set, don't use 'smartcase' here
|
||||
const int save_p_scs = p_scs;
|
||||
@@ -3569,7 +3747,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
|
||||
const int save_p_ws = p_ws;
|
||||
if (st->ins_buf != curbuf) {
|
||||
p_ws = false;
|
||||
} else if (*st->e_cpt == '.' && !in_fuzzy) {
|
||||
} else if (*st->e_cpt == '.') {
|
||||
p_ws = true;
|
||||
}
|
||||
bool looped_around = false;
|
||||
@@ -3578,16 +3756,17 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
|
||||
bool cont_s_ipos = false;
|
||||
|
||||
msg_silent++; // Don't want messages for wrapscan.
|
||||
// ctrl_x_mode_line_or_eval() || word-wise search that
|
||||
// has added a word that was at the beginning of the line.
|
||||
if ((ctrl_x_mode_whole_line() && !in_fuzzy) || ctrl_x_mode_eval()
|
||||
|| (compl_cont_status & CONT_SOL)) {
|
||||
found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos,
|
||||
compl_direction, compl_pattern.data);
|
||||
} else if (in_fuzzy) {
|
||||
|
||||
if (in_collect) {
|
||||
found_new_match = search_for_fuzzy_match(st->ins_buf,
|
||||
st->cur_match_pos, leader, compl_direction,
|
||||
start_pos, &len, &ptr, ctrl_x_mode_whole_line());
|
||||
start_pos, &len, &ptr, &score);
|
||||
// ctrl_x_mode_line_or_eval() || word-wise search that
|
||||
// has added a word that was at the beginning of the line.
|
||||
} else if (ctrl_x_mode_whole_line() || ctrl_x_mode_eval()
|
||||
|| (compl_cont_status & CONT_SOL)) {
|
||||
found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos,
|
||||
compl_direction, compl_pattern.data);
|
||||
} else {
|
||||
found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos,
|
||||
NULL, compl_direction, compl_pattern.data,
|
||||
@@ -3635,7 +3814,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_fuzzy) {
|
||||
if (!in_collect) {
|
||||
ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos,
|
||||
&len, &cont_s_ipos);
|
||||
}
|
||||
@@ -3646,7 +3825,10 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
|
||||
|
||||
if (ins_compl_add_infercase(ptr, len, p_ic,
|
||||
st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname,
|
||||
0, cont_s_ipos) != NOTDONE) {
|
||||
0, cont_s_ipos, score) != NOTDONE) {
|
||||
if (in_collect && score == compl_first_match->cp_next->cp_score) {
|
||||
compl_num_bests++;
|
||||
}
|
||||
found_new_match = OK;
|
||||
break;
|
||||
}
|
||||
@@ -3725,7 +3907,7 @@ static void get_next_bufname_token(void)
|
||||
char *tail = path_tail(b->b_sfname);
|
||||
if (strncmp(tail, compl_orig_text.data, compl_orig_text.size) == 0) {
|
||||
ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
|
||||
p_ic ? CP_ICASE : 0, false, NULL);
|
||||
p_ic ? CP_ICASE : 0, false, NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3839,6 +4021,10 @@ static int ins_compl_get_exp(pos_T *ini)
|
||||
i = ins_compl_make_cyclic();
|
||||
}
|
||||
|
||||
if (cfc_has_mode() && compl_get_longest && compl_num_bests > 0) {
|
||||
fuzzy_longest_match();
|
||||
}
|
||||
|
||||
if (compl_old_match != NULL) {
|
||||
// If several matches were added (FORWARD) or the search failed and has
|
||||
// just been made cyclic then we have to move compl_curr_match to the
|
||||
@@ -4865,9 +5051,9 @@ static int ins_compl_start(void)
|
||||
if (p_ic) {
|
||||
flags |= CP_ICASE;
|
||||
}
|
||||
if (ins_compl_add(compl_orig_text.data, (int)compl_orig_text.size,
|
||||
if (ins_compl_add(compl_orig_text.data, -1,
|
||||
NULL, NULL, false, NULL, 0,
|
||||
flags, false, NULL) != OK) {
|
||||
flags, false, NULL, 0) != OK) {
|
||||
API_CLEAR_STRING(compl_pattern);
|
||||
API_CLEAR_STRING(compl_orig_text);
|
||||
kv_destroy(compl_orig_extmarks);
|
||||
|
@@ -294,8 +294,10 @@ EXTERN char *p_cms; ///< 'commentstring'
|
||||
EXTERN char *p_cpt; ///< 'complete'
|
||||
EXTERN OptInt p_columns; ///< 'columns'
|
||||
EXTERN int p_confirm; ///< 'confirm'
|
||||
EXTERN char *p_cfc; ///< 'completefuzzycollect'
|
||||
EXTERN unsigned cfc_flags; ///< flags from 'completefuzzycollect'
|
||||
EXTERN char *p_cia; ///< 'completeitemalign'
|
||||
EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign'
|
||||
EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign'
|
||||
EXTERN char *p_cot; ///< 'completeopt'
|
||||
EXTERN unsigned cot_flags; ///< flags from 'completeopt'
|
||||
#ifdef BACKSLASH_IN_FILENAME
|
||||
|
@@ -1459,6 +1459,30 @@ local options = {
|
||||
type = 'string',
|
||||
varname = 'p_cfu',
|
||||
},
|
||||
{
|
||||
abbreviation = 'cfc',
|
||||
defaults = '',
|
||||
values = { 'keyword', 'files', 'whole_line' },
|
||||
flags = true,
|
||||
deny_duplicates = true,
|
||||
desc = [=[
|
||||
This option enables fuzzy collection for (only some) specific
|
||||
|ins-completion| modes, adjusting how items are gathered for fuzzy
|
||||
matching based on input.
|
||||
The option can contain the following values (separated by commas),
|
||||
each enabling fuzzy collection for a specific completion mode:
|
||||
files file names
|
||||
keyword keyword completion in 'complete' and current file
|
||||
whole_line whole lines
|
||||
]=],
|
||||
full_name = 'completefuzzycollect',
|
||||
list = 'onecomma',
|
||||
scope = { 'global' },
|
||||
short_desc = N_('use fuzzy collection for specific completion modes'),
|
||||
type = 'string',
|
||||
varname = 'p_cfc',
|
||||
flags_varname = 'cfc_flags',
|
||||
},
|
||||
{
|
||||
abbreviation = 'cia',
|
||||
cb = 'did_set_completeitemalign',
|
||||
@@ -1505,7 +1529,12 @@ local options = {
|
||||
fuzzy Enable |fuzzy-matching| for completion candidates. This
|
||||
allows for more flexible and intuitive matching, where
|
||||
characters can be skipped and matches can be found even
|
||||
if the exact sequence is not typed.
|
||||
if the exact sequence is not typed. Note: This option
|
||||
does not affect the collection of candidate list, it only
|
||||
controls how completion candidates are reduced from the
|
||||
list of alternatives. If you want to use |fuzzy-matching|
|
||||
to gather more alternatives for your candidate list,
|
||||
see |'completefuzzycollect'|.
|
||||
|
||||
longest Only insert the longest common text of the matches. If
|
||||
the menu is displayed you can use CTRL-L to add more
|
||||
|
@@ -84,6 +84,7 @@ void didset_string_options(void)
|
||||
check_str_opt(kOptCasemap, NULL);
|
||||
check_str_opt(kOptBackupcopy, NULL);
|
||||
check_str_opt(kOptBelloff, NULL);
|
||||
check_str_opt(kOptCompletefuzzycollect, NULL);
|
||||
check_str_opt(kOptCompleteopt, NULL);
|
||||
check_str_opt(kOptSessionoptions, NULL);
|
||||
check_str_opt(kOptViewoptions, NULL);
|
||||
|
@@ -3620,11 +3620,18 @@ garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat)
|
||||
return match_positions;
|
||||
}
|
||||
|
||||
/// This function searches for a fuzzy match of the pattern `pat` within the
|
||||
/// line pointed to by `*ptr`. It splits the line into words, performs fuzzy
|
||||
/// matching on each word, and returns the length and position of the first
|
||||
/// matched word.
|
||||
static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos)
|
||||
/// This function splits the line pointed to by `*ptr` into words and performs
|
||||
/// a fuzzy match for the pattern `pat` on each word. It iterates through the
|
||||
/// line, moving `*ptr` to the start of each word during the process.
|
||||
///
|
||||
/// If a match is found:
|
||||
/// - `*ptr` points to the start of the matched word.
|
||||
/// - `*len` is set to the length of the matched word.
|
||||
/// - `*score` contains the match score.
|
||||
///
|
||||
/// If no match is found, `*ptr` is updated to point beyond the last word
|
||||
/// or to the end of the line.
|
||||
bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *current_pos, int *score)
|
||||
{
|
||||
char *str = *ptr;
|
||||
char *strBegin = str;
|
||||
@@ -3649,14 +3656,16 @@ static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *curr
|
||||
*end = NUL;
|
||||
|
||||
// Perform fuzzy match
|
||||
int result = fuzzy_match_str(start, pat);
|
||||
*score = fuzzy_match_str(start, pat);
|
||||
*end = save_end;
|
||||
|
||||
if (result > 0) {
|
||||
if (*score > 0) {
|
||||
*len = (int)(end - start);
|
||||
current_pos->col += (int)(end - strBegin);
|
||||
found = true;
|
||||
*ptr = start;
|
||||
if (current_pos) {
|
||||
current_pos->col += (int)(end - strBegin);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3678,13 +3687,14 @@ static bool fuzzy_match_str_in_line(char **ptr, char *pat, int *len, pos_T *curr
|
||||
///
|
||||
/// Return true if a match is found, otherwise false.
|
||||
bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_T *start_pos,
|
||||
int *len, char **ptr, bool whole_line)
|
||||
int *len, char **ptr, int *score)
|
||||
{
|
||||
pos_T current_pos = *pos;
|
||||
pos_T circly_end;
|
||||
bool found_new_match = false;
|
||||
bool looped_around = false;
|
||||
|
||||
bool whole_line = ctrl_x_mode_whole_line();
|
||||
if (whole_line) {
|
||||
current_pos.lnum += dir;
|
||||
}
|
||||
@@ -3709,11 +3719,13 @@ bool search_for_fuzzy_match(buf_T *buf, pos_T *pos, char *pattern, int dir, pos_
|
||||
*ptr = ml_get_buf(buf, current_pos.lnum);
|
||||
// If ptr is end of line is reached, move to next line
|
||||
// or previous line based on direction
|
||||
if (**ptr != NUL) {
|
||||
if (*ptr != NULL && **ptr != NUL) {
|
||||
if (!whole_line) {
|
||||
*ptr += current_pos.col;
|
||||
// Try to find a fuzzy match in the current line starting from current position
|
||||
found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, ¤t_pos);
|
||||
// Try to find a fuzzy match in the current line starting
|
||||
// from current position
|
||||
found_new_match = fuzzy_match_str_in_line(ptr, pattern,
|
||||
len, ¤t_pos, score);
|
||||
if (found_new_match) {
|
||||
if (ctrl_x_mode_normal()) {
|
||||
if (strncmp(*ptr, pattern, (size_t)(*len)) == 0 && pattern[*len] == NUL) {
|
||||
@@ -4227,7 +4239,7 @@ search_line:
|
||||
const int add_r = ins_compl_add_infercase(aux, i, p_ic,
|
||||
curr_fname == curbuf->b_fname
|
||||
? NULL : curr_fname,
|
||||
dir, cont_s_ipos);
|
||||
dir, cont_s_ipos, 0);
|
||||
if (add_r == OK) {
|
||||
// if dir was BACKWARD then honor it just once
|
||||
dir = FORWARD;
|
||||
|
@@ -3483,7 +3483,7 @@ static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int
|
||||
? mb_strnicmp(p, pat, strlen(pat)) == 0
|
||||
: strncmp(p, pat, strlen(pat)) == 0)
|
||||
&& ins_compl_add_infercase(p, (int)strlen(p),
|
||||
p_ic, NULL, *dir, false) == OK) {
|
||||
p_ic, NULL, *dir, false, 0) == OK) {
|
||||
// if dir was BACKWARD then honor it just once
|
||||
*dir = FORWARD;
|
||||
}
|
||||
|
@@ -6932,35 +6932,6 @@ describe('builtin popupmenu', function()
|
||||
]])
|
||||
feed('o<BS><C-R>=Comp()<CR>')
|
||||
screen:expect_unchanged(true)
|
||||
|
||||
feed('<Esc>')
|
||||
command('set completeopt+=fuzzy,menu')
|
||||
feed('S hello helio hero h<C-X><C-P>')
|
||||
screen:expect([[
|
||||
hello helio hero h^ |
|
||||
{1:~ }{n: }{mn:h}{n:ero }{1: }|
|
||||
{1:~ }{n: }{mn:h}{n:elio }{1: }|
|
||||
{1:~ }{s: }{ms:h}{s:ello }{1: }|
|
||||
{1:~ }|*15
|
||||
{2:-- }{5:match 1 of 3} |
|
||||
]])
|
||||
|
||||
feed('<Esc>S hello helio hero h<C-X><C-P><C-P>')
|
||||
screen:expect([[
|
||||
hello helio hero h^ |
|
||||
{1:~ }{n: }{mn:h}{n:ero }{1: }|
|
||||
{1:~ }{s: }{ms:h}{s:elio }{1: }|
|
||||
{1:~ }{n: }{mn:h}{n:ello }{1: }|
|
||||
{1:~ }|*15
|
||||
{2:-- }{5:match 2 of 3} |
|
||||
]])
|
||||
|
||||
feed('<Esc>S/non_existing_folder<C-X><C-F>')
|
||||
screen:expect([[
|
||||
/non_existing_folder^ |
|
||||
{1:~ }|*18
|
||||
{2:-- }{6:Pattern not found} |
|
||||
]])
|
||||
feed('<C-E><Esc>')
|
||||
|
||||
command('hi PmenuMatchSel guibg=NONE')
|
||||
@@ -6981,6 +6952,41 @@ describe('builtin popupmenu', function()
|
||||
feed('<C-E><Esc>')
|
||||
end)
|
||||
|
||||
it('completefuzzycollect', function()
|
||||
exec([[
|
||||
set completefuzzycollect=keyword,files
|
||||
set completeopt=menu,menuone
|
||||
]])
|
||||
|
||||
feed('S hello helio hero h<C-X><C-P>')
|
||||
screen:expect([[
|
||||
hello helio hero hello^ |
|
||||
{1:~ }{n: hero }{1: }|
|
||||
{1:~ }{n: helio }{1: }|
|
||||
{1:~ }{s: hello }{1: }|
|
||||
{1:~ }|*15
|
||||
{2:-- }{5:match 1 of 3} |
|
||||
]])
|
||||
|
||||
feed('<Esc>S hello helio hero h<C-X><C-P><C-P>')
|
||||
screen:expect([[
|
||||
hello helio hero helio^ |
|
||||
{1:~ }{n: hero }{1: }|
|
||||
{1:~ }{s: helio }{1: }|
|
||||
{1:~ }{n: hello }{1: }|
|
||||
{1:~ }|*15
|
||||
{2:-- }{5:match 2 of 3} |
|
||||
]])
|
||||
|
||||
feed('<Esc>S/non_existing_folder<C-X><C-F>')
|
||||
screen:expect([[
|
||||
/non_existing_folder^ |
|
||||
{1:~ }|*18
|
||||
{2:-- }{6:Pattern not found} |
|
||||
]])
|
||||
feed('<C-E><Esc>')
|
||||
end)
|
||||
|
||||
-- oldtest: Test_pum_highlights_match_with_abbr()
|
||||
it('can highlight matched text with abbr', function()
|
||||
exec([[
|
||||
|
@@ -189,6 +189,9 @@ let test_values = {
|
||||
"\ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup',
|
||||
"\ " 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'],
|
||||
"\ " ['xxx', 'menu,,,longest,']],
|
||||
\ 'completefuzzycollect': [['', 'keyword', 'files', 'whole_line',
|
||||
\ 'keyword,whole_line', 'files,whole_line', 'keyword,files,whole_line'],
|
||||
\ ['xxx', 'keyword,,,whole_line,']],
|
||||
\ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'],
|
||||
\ ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr',
|
||||
\ 'abbr1234,kind,menu']],
|
||||
|
@@ -2763,7 +2763,7 @@ func Test_completefunc_first_call_complete_add()
|
||||
bwipe!
|
||||
endfunc
|
||||
|
||||
func Test_complete_fuzzy_match()
|
||||
func Test_complete_opt_fuzzy()
|
||||
func OnPumChange()
|
||||
let g:item = get(v:event, 'completed_item', {})
|
||||
let g:word = get(g:item, 'word', v:null)
|
||||
@@ -2819,8 +2819,66 @@ func Test_complete_fuzzy_match()
|
||||
call feedkeys("S\<C-x>\<C-o>fb\<C-n>", 'tx')
|
||||
call assert_equal('fooBaz', g:word)
|
||||
|
||||
" avoid breaking default completion behavior
|
||||
set completeopt=fuzzy,menu
|
||||
" test case for nosort option
|
||||
set cot=menuone,menu,noinsert,fuzzy,nosort
|
||||
" "fooBaz" should have a higher score when the leader is "fb".
|
||||
" With "nosort", "foobar" should still be shown first in the popup menu.
|
||||
call feedkeys("S\<C-x>\<C-o>fb", 'tx')
|
||||
call assert_equal('foobar', g:word)
|
||||
call feedkeys("S\<C-x>\<C-o>好", 'tx')
|
||||
call assert_equal("你好吗", g:word)
|
||||
|
||||
set cot+=noselect
|
||||
call feedkeys("S\<C-x>\<C-o>好", 'tx')
|
||||
call assert_equal(v:null, g:word)
|
||||
call feedkeys("S\<C-x>\<C-o>好\<C-N>", 'tx')
|
||||
call assert_equal('你好吗', g:word)
|
||||
|
||||
" "nosort" shouldn't enable fuzzy filtering when "fuzzy" isn't present.
|
||||
set cot=menuone,noinsert,nosort
|
||||
call feedkeys("S\<C-x>\<C-o>fooB\<C-Y>", 'tx')
|
||||
call assert_equal('fooBaz', getline('.'))
|
||||
|
||||
set cot=menuone,fuzzy,nosort
|
||||
func CompAnother()
|
||||
call complete(col('.'), [#{word: "do" }, #{word: "echo"}, #{word: "for (${1:expr1}, ${2:expr2}, ${3:expr3}) {\n\t$0\n}", abbr: "for" }, #{word: "foo"}])
|
||||
return ''
|
||||
endfunc
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
call assert_equal(2, g:selected)
|
||||
|
||||
set cot+=noinsert
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>f", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
call assert_equal(2, g:selected)
|
||||
|
||||
set cot=menu,menuone,noselect,fuzzy
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>\<C-N>\<C-N>", 'tx')
|
||||
call assert_equal("foo", g:word)
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>", 'tx')
|
||||
call assert_equal("foo", g:word)
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>\<C-P>", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
|
||||
" clean up
|
||||
set omnifunc=
|
||||
bw!
|
||||
set complete& completeopt&
|
||||
autocmd! AAAAA_Group
|
||||
augroup! AAAAA_Group
|
||||
delfunc OnPumChange
|
||||
delfunc Omni_test
|
||||
delfunc Comp
|
||||
unlet g:item
|
||||
unlet g:word
|
||||
unlet g:abbr
|
||||
endfunc
|
||||
|
||||
func Test_complete_fuzzy_collect()
|
||||
new
|
||||
redraw " need this to prevent NULL dereference in Nvim
|
||||
set completefuzzycollect=keyword,files,whole_line
|
||||
call setline(1, ['hello help hero h'])
|
||||
" Use "!" flag of feedkeys() so that ex_normal_busy is not set and
|
||||
" ins_compl_check_keys() is not skipped.
|
||||
@@ -2852,16 +2910,6 @@ func Test_complete_fuzzy_match()
|
||||
call feedkeys("A\<C-X>\<C-N>\<C-N>\<Esc>0", 'tx!')
|
||||
call assert_equal('你的 我的 我的', getline('.'))
|
||||
|
||||
" respect wrapscan
|
||||
set nowrapscan
|
||||
call setline(1, ["xyz", "yxz", ""])
|
||||
call cursor(3, 1)
|
||||
call feedkeys("Sy\<C-X>\<C-N>\<Esc>0", 'tx!')
|
||||
call assert_equal('y', getline('.'))
|
||||
set wrapscan
|
||||
call feedkeys("Sy\<C-X>\<C-N>\<Esc>0", 'tx!')
|
||||
call assert_equal('xyz', getline('.'))
|
||||
|
||||
" fuzzy on file
|
||||
call writefile([''], 'fobar', 'D')
|
||||
call writefile([''], 'foobar', 'D')
|
||||
@@ -2877,7 +2925,6 @@ func Test_complete_fuzzy_match()
|
||||
call assert_match('../testdir', getline('.'))
|
||||
|
||||
" can get completion from other buffer
|
||||
set completeopt=fuzzy,menu,menuone
|
||||
vnew
|
||||
call setline(1, ["completeness,", "compatibility", "Composite", "Omnipotent"])
|
||||
wincmd p
|
||||
@@ -2929,79 +2976,109 @@ func Test_complete_fuzzy_match()
|
||||
call assert_equal('你好 他好', getline('.'))
|
||||
|
||||
" issue #15526
|
||||
set completeopt=fuzzy,menuone,menu,noselect
|
||||
set completeopt=menuone,menu,noselect
|
||||
call setline(1, ['Text', 'ToText', ''])
|
||||
call cursor(3, 1)
|
||||
call feedkeys("STe\<C-X>\<C-N>x\<CR>\<Esc>0", 'tx!')
|
||||
call assert_equal('Tex', getline(line('.') - 1))
|
||||
|
||||
" test case for nosort option
|
||||
set cot=menuone,menu,noinsert,fuzzy,nosort
|
||||
" "fooBaz" should have a higher score when the leader is "fb".
|
||||
" With "nosort", "foobar" should still be shown first in the popup menu.
|
||||
call feedkeys("S\<C-x>\<C-o>fb", 'tx')
|
||||
call assert_equal('foobar', g:word)
|
||||
call feedkeys("S\<C-x>\<C-o>好", 'tx')
|
||||
call assert_equal("你好吗", g:word)
|
||||
|
||||
set cot+=noselect
|
||||
call feedkeys("S\<C-x>\<C-o>好", 'tx')
|
||||
call assert_equal(v:null, g:word)
|
||||
call feedkeys("S\<C-x>\<C-o>好\<C-N>", 'tx')
|
||||
call assert_equal('你好吗', g:word)
|
||||
|
||||
" "nosort" shouldn't enable fuzzy filtering when "fuzzy" isn't present.
|
||||
set cot=menuone,noinsert,nosort
|
||||
call feedkeys("S\<C-x>\<C-o>fooB\<C-Y>", 'tx')
|
||||
call assert_equal('fooBaz', getline('.'))
|
||||
|
||||
set cot=menuone,fuzzy,nosort
|
||||
func CompAnother()
|
||||
call complete(col('.'), [#{word: "do" }, #{word: "echo"}, #{word: "for (${1:expr1}, ${2:expr2}, ${3:expr3}) {\n\t$0\n}", abbr: "for" }, #{word: "foo"}])
|
||||
return ''
|
||||
endfunc
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
call assert_equal(2, g:selected)
|
||||
|
||||
set cot+=noinsert
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>f", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
call assert_equal(2, g:selected)
|
||||
|
||||
set cot=menu,menuone,noselect,fuzzy
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-N>\<C-N>\<C-N>\<C-N>", 'tx')
|
||||
call assert_equal("foo", g:word)
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>", 'tx')
|
||||
call assert_equal("foo", g:word)
|
||||
call feedkeys("i\<C-R>=CompAnother()\<CR>\<C-P>\<C-P>", 'tx')
|
||||
call assert_equal("for", g:abbr)
|
||||
|
||||
" clean up
|
||||
set omnifunc=
|
||||
bw!
|
||||
bw!
|
||||
set complete& completeopt&
|
||||
autocmd! AAAAA_Group
|
||||
augroup! AAAAA_Group
|
||||
delfunc OnPumChange
|
||||
delfunc Omni_test
|
||||
delfunc Comp
|
||||
delfunc CompAnother
|
||||
unlet g:item
|
||||
unlet g:word
|
||||
unlet g:selected
|
||||
unlet g:abbr
|
||||
set completeopt& cfc& cpt&
|
||||
endfunc
|
||||
|
||||
func Test_complete_fuzzy_with_completeslash()
|
||||
func Test_cfc_with_longest()
|
||||
new
|
||||
set completefuzzycollect=keyword,files,whole_line
|
||||
set completeopt=menu,menuone,longest,fuzzy
|
||||
|
||||
" keyword
|
||||
exe "normal ggdGShello helio think h\<C-X>\<C-N>\<ESC>"
|
||||
call assert_equal("hello helio think hel", getline('.'))
|
||||
exe "normal hello helio think h\<C-X>\<C-P>\<ESC>"
|
||||
call assert_equal("hello helio think hel", getline('.'))
|
||||
|
||||
" skip non-consecutive prefixes
|
||||
exe "normal ggdGShello helio heo\<C-X>\<C-N>\<ESC>"
|
||||
call assert_equal("hello helio heo", getline('.'))
|
||||
|
||||
" kdcit
|
||||
call writefile(['help'], 'test_keyword.txt', 'D')
|
||||
set complete=ktest_keyword.txt
|
||||
exe "normal ggdGSh\<C-N>\<ESC>"
|
||||
" auto insert help when only have one match
|
||||
call assert_equal("help", getline('.'))
|
||||
call writefile(['hello', 'help', 'think'], 'xtest_keyword.txt', 'D')
|
||||
set complete=kxtest_keyword.txt
|
||||
" auto insert hel
|
||||
exe "normal ggdGSh\<C-N>\<ESC>"
|
||||
call assert_equal("hel", getline('.'))
|
||||
|
||||
" line start with a space
|
||||
call writefile([' hello'], 'test_case1.txt', 'D')
|
||||
set complete=ktest_case1.txt
|
||||
exe "normal ggdGSh\<C-N>\<ESC>"
|
||||
call assert_equal("hello", getline('.'))
|
||||
|
||||
" multiple matches
|
||||
set complete=ktest_case2.txt
|
||||
call writefile([' hello help what'], 'test_case2.txt', 'D')
|
||||
exe "normal ggdGSh\<C-N>\<C-N>\<C-N>\<C-N>\<ESC>"
|
||||
call assert_equal("what", getline('.'))
|
||||
|
||||
" multiple lines of matches
|
||||
set complete=ktest_case3.txt
|
||||
call writefile([' hello help what', 'hola', ' hey'], 'test_case3.txt', 'D')
|
||||
exe "normal ggdGSh\<C-N>\<C-N>\<ESC>"
|
||||
call assert_equal("hey", getline('.'))
|
||||
exe "normal ggdGSh\<C-N>\<C-N>\<C-N>\<C-N>\<ESC>"
|
||||
call assert_equal("hola", getline('.'))
|
||||
|
||||
set complete=ktest_case4.txt
|
||||
call writefile([' auto int enum register', 'why'], 'test_case4.txt', 'D')
|
||||
exe "normal ggdGSe\<C-N>\<C-N>\<ESC>"
|
||||
call assert_equal("enum", getline('.'))
|
||||
set complete&
|
||||
|
||||
" file
|
||||
call writefile([''], 'hello', 'D')
|
||||
call writefile([''], 'helio', 'D')
|
||||
exe "normal ggdGS./h\<C-X>\<C-f>\<ESC>"
|
||||
call assert_equal('./hel', getline('.'))
|
||||
|
||||
" word
|
||||
call setline(1, ['what do you think', 'why i have that', ''])
|
||||
call cursor(3,1)
|
||||
call feedkeys("Sw\<C-X>\<C-l>\<C-N>\<Esc>0", 'tx!')
|
||||
call assert_equal('wh', getline('.'))
|
||||
|
||||
exe "normal ggdG"
|
||||
" auto complete when only one match
|
||||
exe "normal Shello\<CR>h\<C-X>\<C-N>\<esc>"
|
||||
call assert_equal('hello', getline('.'))
|
||||
exe "normal Sh\<C-N>\<C-P>\<esc>"
|
||||
call assert_equal('hello', getline('.'))
|
||||
|
||||
exe "normal Shello\<CR>h\<C-X>\<C-N>\<Esc>cch\<C-X>\<C-N>\<Esc>"
|
||||
call assert_equal('hello', getline('.'))
|
||||
|
||||
" continue search for new leader after insert common prefix
|
||||
exe "normal ohellokate\<CR>h\<C-X>\<C-N>k\<C-y>\<esc>"
|
||||
call assert_equal('hellokate', getline('.'))
|
||||
|
||||
bw!
|
||||
set completeopt&
|
||||
set completefuzzycollect&
|
||||
endfunc
|
||||
|
||||
func Test_completefuzzycollect_with_completeslash()
|
||||
CheckMSWindows
|
||||
|
||||
call writefile([''], 'fobar', 'D')
|
||||
let orig_shellslash = &shellslash
|
||||
set cpt&
|
||||
new
|
||||
set completeopt+=fuzzy
|
||||
set completefuzzycollect=files
|
||||
set noshellslash
|
||||
|
||||
" Test with completeslash unset
|
||||
@@ -3023,6 +3100,7 @@ func Test_complete_fuzzy_with_completeslash()
|
||||
" Reset and clean up
|
||||
let &shellslash = orig_shellslash
|
||||
set completeslash=
|
||||
set completefuzzycollect&
|
||||
%bw!
|
||||
endfunc
|
||||
|
||||
|
@@ -1501,22 +1501,6 @@ func Test_pum_highlights_match()
|
||||
call VerifyScreenDump(buf, 'Test_pum_highlights_09', {})
|
||||
call term_sendkeys(buf, "o\<BS>\<C-R>=Comp()\<CR>")
|
||||
call VerifyScreenDump(buf, 'Test_pum_highlights_09', {})
|
||||
|
||||
" issue #15095 wrong select
|
||||
call term_sendkeys(buf, "\<ESC>:set completeopt=fuzzy,menu\<CR>")
|
||||
call TermWait(buf)
|
||||
call term_sendkeys(buf, "S hello helio hero h\<C-X>\<C-P>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_highlights_10', {})
|
||||
|
||||
call term_sendkeys(buf, "\<ESC>S hello helio hero h\<C-X>\<C-P>\<C-P>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_highlights_11', {})
|
||||
|
||||
" issue #15357
|
||||
call term_sendkeys(buf, "\<ESC>S/non_existing_folder\<C-X>\<C-F>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_highlights_15', {})
|
||||
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||
|
||||
call term_sendkeys(buf, ":hi PmenuMatchSel ctermfg=14 ctermbg=NONE\<CR>")
|
||||
@@ -1530,7 +1514,34 @@ func Test_pum_highlights_match()
|
||||
|
||||
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||
call TermWait(buf)
|
||||
call StopVimInTerminal(buf)
|
||||
endfunc
|
||||
|
||||
func Test_pum_completefuzzycollect()
|
||||
CheckScreendump
|
||||
let lines =<< trim END
|
||||
set completefuzzycollect=keyword,files
|
||||
set completeopt=menu,menuone
|
||||
END
|
||||
call writefile(lines, 'Xscript', 'D')
|
||||
let buf = RunVimInTerminal('-S Xscript', {})
|
||||
|
||||
" issue #15095 wrong select
|
||||
call term_sendkeys(buf, "S hello helio hero h\<C-X>\<C-P>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_completefuzzycollect_01', {})
|
||||
|
||||
call term_sendkeys(buf, "\<ESC>S hello helio hero h\<C-X>\<C-P>\<C-P>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_completefuzzycollect_02', {})
|
||||
|
||||
" issue #15357
|
||||
call term_sendkeys(buf, "\<ESC>S/non_existing_folder\<C-X>\<C-F>")
|
||||
call TermWait(buf, 50)
|
||||
call VerifyScreenDump(buf, 'Test_pum_completefuzzycollect_03', {})
|
||||
call term_sendkeys(buf, "\<C-E>\<Esc>")
|
||||
|
||||
call TermWait(buf)
|
||||
call StopVimInTerminal(buf)
|
||||
endfunc
|
||||
|
||||
|
Reference in New Issue
Block a user