1
0
forked from aniani/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 #15296
fixes #15295
fixes #15294
closes: #16032

Signed-off-by: glepnir <glephunter@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
glepnir
2025-03-06 21:59:13 +01:00
committed by Christian Brabandt
parent 25e833f4ec
commit f31cfa29bf
21 changed files with 578 additions and 185 deletions

View File

@@ -132,6 +132,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 int 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 int compl_enter_selects = FALSE;
@@ -206,7 +213,7 @@ static int *compl_fuzzy_scores;
static pumitem_T *compl_match_array = NULL;
static int compl_match_arraysize;
static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup, int *user_hl);
static int ins_compl_add(char_u *str, int len, char_u *fname, char_u **cptext, typval_T *user_data, int cdir, int flags, int adup, int *user_hl, int score);
static void ins_compl_longest_match(compl_T *match);
static void ins_compl_del_pum(void);
static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir);
@@ -229,6 +236,7 @@ static void show_pum(int prev_w_wrow, int prev_w_leftcol);
static unsigned quote_meta(char_u *dest, char_u *str, int len);
static int ins_compl_has_multiple(void);
static void ins_compl_expand_multiple(char_u *str);
static void ins_compl_longest_insert(char_u *prefix);
#ifdef FEAT_SPELL
static void spell_back_to_badword(void);
@@ -686,7 +694,8 @@ ins_compl_add_infercase(
int icase,
char_u *fname,
int dir,
int cont_s_ipos) // next ^X<> will set initial_pos
int cont_s_ipos, // next ^X<> will set initial_pos
int score)
{
char_u *str = str_arg;
char_u *p;
@@ -745,11 +754,30 @@ ins_compl_add_infercase(
if (icase)
flags |= CP_ICASE;
res = ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE, NULL);
res = ins_compl_add(str, len, fname, NULL, NULL, dir, flags, FALSE, NULL, score);
vim_free(tofree);
return res;
}
/*
* Check if ctrl_x_mode has been configured in 'completefuzzycollect'
*/
static int
cfc_has_mode(void)
{
switch (ctrl_x_mode)
{
case CTRL_X_NORMAL:
return (cfc_flags & CFC_KEYWORD) != 0;
case CTRL_X_FILES:
return (cfc_flags & CFC_FILES) != 0;
case CTRL_X_WHOLE_LINE:
return (cfc_flags & CFC_WHOLELINE) != 0;
default:
return FALSE;
}
}
/*
* Add a match to the list of matches. The arguments are:
* str - text of the match to add
@@ -780,11 +808,13 @@ ins_compl_add(
int cdir,
int flags_arg,
int adup, // accept duplicate match
int *user_hl) // user abbr/kind hlattr
int *user_hl, // user abbr/kind hlattr
int score)
{
compl_T *match;
compl_T *match, *current, *prev;
int dir = (cdir == 0 ? compl_direction : cdir);
int flags = flags_arg;
int inserted = FALSE;
if (flags & CP_FAST)
fast_breakcheck();
@@ -846,6 +876,7 @@ ins_compl_add(
match->cp_flags = flags;
match->cp_user_abbr_hlattr = user_hl ? user_hl[0] : -1;
match->cp_user_kind_hlattr = user_hl ? user_hl[1] : -1;
match->cp_score = score;
if (cptext != NULL)
{
@@ -866,6 +897,37 @@ ins_compl_add(
// 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)
{
current = compl_first_match->cp_next;
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;
@@ -885,7 +947,7 @@ ins_compl_add(
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);
return OK;
@@ -987,9 +1049,7 @@ ins_compl_longest_match(compl_T *match)
compl_leader.length = match->cp_str.length;
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
ins_redraw(FALSE);
ins_compl_longest_insert(compl_leader.string);
// When the match isn't there (to avoid matching itself) remove it
// again after redrawing.
@@ -1037,9 +1097,7 @@ ins_compl_longest_match(compl_T *match)
compl_leader.length = (size_t)(p - compl_leader.string);
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1);
ins_redraw(FALSE);
ins_compl_longest_insert(compl_leader.string);
// When the match isn't there (to avoid matching itself) remove it
// again after redrawing.
@@ -1067,7 +1125,7 @@ ins_compl_add_matches(
for (i = 0; i < num_matches && add_r != FAIL; i++)
{
add_r = ins_compl_add(matches[i], -1, NULL, NULL, 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;
@@ -1298,6 +1356,7 @@ ins_compl_build_pum(void)
int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
int fuzzy_filter = (cur_cot_flags & COT_FUZZY) != 0;
int fuzzy_sort = fuzzy_filter && !(cur_cot_flags & COT_NOSORT);
compl_T *match_head = NULL;
compl_T *match_tail = NULL;
compl_T *match_next = NULL;
@@ -1644,7 +1703,7 @@ ins_compl_dictionaries(
if (count > 0) // avoid warning for using "files" uninit
{
ins_compl_files(count, files, thesaurus, flags,
&regmatch, buf, &dir);
(cfc_has_mode() ? NULL : &regmatch), buf, &dir);
if (flags != DICT_EXACT)
FreeWild(count, files);
}
@@ -1704,7 +1763,7 @@ thesaurus_add_words_in_line(
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;
}
@@ -1732,6 +1791,18 @@ ins_compl_files(
int i;
FILE *fp;
int add_r;
char_u *leader = NULL;
int leader_len = 0;
int in_fuzzy_collect = cfc_has_mode() && ctrl_x_mode_normal();
int score = 0;
int len = 0;
char_u *line_end = NULL;
if (in_fuzzy_collect)
{
leader = ins_compl_leader();
leader_len = ins_compl_leader_len();
}
for (i = 0; i < count && !got_int && !compl_interrupted; i++)
{
@@ -1752,30 +1823,56 @@ ins_compl_files(
while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp))
{
ptr = buf;
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf)))
if (regmatch != NULL)
{
ptr = regmatch->startp[0];
ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr)
: find_word_end(ptr);
add_r = ins_compl_add_infercase(regmatch->startp[0],
(int)(ptr - regmatch->startp[0]),
p_ic, files[i], *dir, FALSE);
if (thesaurus)
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf)))
{
// 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]);
ptr = regmatch->startp[0];
ptr = ctrl_x_mode_line_or_eval() ? find_line_end(ptr)
: find_word_end(ptr);
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;
}
}
else if (in_fuzzy_collect && leader_len > 0)
{
line_end = find_line_end(ptr);
while (ptr < line_end)
{
if (fuzzy_match_str_in_line(&ptr, leader, &len, NULL, &score))
{
char_u *end_ptr = ctrl_x_mode_line_or_eval()
? find_line_end(ptr) : find_word_end(ptr);
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;
}
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;
}
line_breakcheck();
ins_compl_check_keys(50, FALSE);
@@ -1888,6 +1985,7 @@ 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;
@@ -3101,7 +3199,7 @@ ins_compl_add_tv(typval_T *tv, int dir, int fast)
return FAIL;
}
status = ins_compl_add(word, -1, NULL, cptext,
&user_data, dir, flags, dup, user_hl);
&user_data, dir, flags, dup, user_hl, 0);
if (status != OK)
clear_tv(&user_data);
return status;
@@ -3196,7 +3294,7 @@ set_completion(colnr_T startcol, list_T *list)
compl_orig_text.length = (size_t)compl_length;
if (ins_compl_add(compl_orig_text.string,
(int)compl_orig_text.length, NULL, NULL, NULL, 0,
flags | CP_FAST, FALSE, NULL) != OK)
flags | CP_FAST, FALSE, NULL, 0) != OK)
return;
ctrl_x_mode = CTRL_X_EVAL;
@@ -3762,7 +3860,8 @@ get_next_tag_completion(void)
/*
* Compare function for qsort
*/
static int compare_scores(const void *a, const void *b)
static int
compare_scores(const void *a, const void *b)
{
int idx_a = *(const int *)a;
int idx_b = *(const int *)b;
@@ -3772,6 +3871,114 @@ 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_u *prefix)
{
ins_compl_delete();
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)
{
char_u *prefix = NULL;
int prefix_len = 0;
int i = 0;
int j = 0;
char_u *match_str = NULL;
char_u *prefix_ptr = NULL;
char_u *match_ptr = NULL;
char_u *leader = NULL;
size_t leader_len = 0;
compl_T *compl = NULL;
int more_candidates = FALSE;
compl_T *nn_compl = NULL;
if (compl_num_bests == 0)
return;
nn_compl = compl_first_match->cp_next->cp_next;
if (nn_compl && nn_compl != compl_first_match)
more_candidates = TRUE;
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.string);
compl_num_bests = 0;
}
compl_num_bests = 0;
return;
}
compl_best_matches = (compl_T **)alloc(compl_num_bests * sizeof(compl_T *));
if (compl_best_matches == NULL)
return;
while (compl != NULL && i < compl_num_bests)
{
compl_best_matches[i] = compl;
compl = compl->cp_next;
i++;
}
prefix = compl_best_matches[0]->cp_str.string;
prefix_len = (int)STRLEN(prefix);
for (i = 1; i < compl_num_bests; i++)
{
match_str = compl_best_matches[i]->cp_str.string;
prefix_ptr = prefix;
match_ptr = match_str;
j = 0;
while (j < prefix_len && *match_ptr != NUL && *prefix_ptr != NUL)
{
if (STRNCMP(prefix_ptr, match_ptr, mb_ptr2len(prefix_ptr)) != 0)
break;
MB_PTR_ADV(prefix_ptr);
MB_PTR_ADV(match_ptr);
j++;
}
if (j > 0)
prefix_len = j;
}
leader = ins_compl_leader();
if (leader != NULL)
leader_len = STRLEN(leader);
// skip non-consecutive prefixes
if (STRNCMP(prefix, leader, leader_len) != 0)
goto end;
prefix = vim_strnsave(compl_best_matches[0]->cp_str.string, prefix_len);
if (prefix != NULL)
{
ins_compl_longest_insert(prefix);
compl_cfc_longest_ins = TRUE;
vim_free(prefix);
}
end:
vim_free(compl_best_matches);
compl_best_matches = NULL;
compl_num_bests = 0;
}
/*
* Get the next set of filename matching "compl_pattern".
*/
@@ -3786,10 +3993,13 @@ get_next_filename_completion(void)
int score;
char_u *leader = ins_compl_leader();
size_t leader_len = ins_compl_leader_len();;
int in_fuzzy = ((get_cot_flags() & COT_FUZZY) != 0 && leader_len > 0);
char_u **sorted_matches;
int in_fuzzy_collect = (cfc_has_mode() && leader_len > 0);
int *fuzzy_indices_data;
char_u *last_sep = NULL;
int need_collect_bests = in_fuzzy_collect && compl_get_longest;
int max_score = 0;
int current_score = 0;
int dir = compl_direction;
#ifdef BACKSLASH_IN_FILENAME
char pathsep = (curbuf->b_p_csl[0] == 's') ?
@@ -3798,7 +4008,7 @@ 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')
@@ -3830,7 +4040,7 @@ get_next_filename_completion(void)
compl_pattern.length = 1;
}
else if (*(last_sep + 1) == '\0')
in_fuzzy = FALSE;
in_fuzzy_collect = FALSE;
else
{
// Split leader into path and file parts
@@ -3876,7 +4086,7 @@ get_next_filename_completion(void)
}
#endif
if (in_fuzzy)
if (in_fuzzy_collect)
{
ga_init2(&fuzzy_indices, sizeof(int), 10);
compl_fuzzy_scores = (int *)alloc(sizeof(int) * num_matches);
@@ -3899,16 +4109,30 @@ get_next_filename_completion(void)
// prevent qsort from deref NULL pointer
if (fuzzy_indices.ga_len > 0)
{
char_u *match = NULL;
fuzzy_indices_data = (int *)fuzzy_indices.ga_data;
qsort(fuzzy_indices_data, fuzzy_indices.ga_len, sizeof(int), compare_scores);
sorted_matches = (char_u **)alloc(sizeof(char_u *) * fuzzy_indices.ga_len);
for (i = 0; i < fuzzy_indices.ga_len; ++i)
sorted_matches[i] = vim_strsave(matches[fuzzy_indices_data[i]]);
{
match = matches[fuzzy_indices_data[i]];
current_score = compl_fuzzy_scores[fuzzy_indices_data[i]];
if (ins_compl_add(match, -1, NULL, NULL, 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)
{
@@ -3918,6 +4142,10 @@ get_next_filename_completion(void)
vim_free(compl_fuzzy_scores);
ga_clear(&fuzzy_indices);
if (compl_num_bests > 0 && compl_get_longest)
fuzzy_longest_match();
return;
}
if (num_matches > 0)
@@ -4076,8 +4304,9 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
int looped_around = FALSE;
char_u *ptr = NULL;
int len = 0;
int in_fuzzy = (get_cot_flags() & COT_FUZZY) != 0 && compl_length > 0;
int in_collect = (cfc_has_mode() && compl_length > 0);
char_u *leader = ins_compl_leader();
int score = 0;
// If 'infercase' is set, don't use 'smartcase' here
save_p_scs = p_scs;
@@ -4091,7 +4320,7 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
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;
looped_around = FALSE;
for (;;)
@@ -4100,15 +4329,17 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
++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.string);
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.string);
else
found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos,
NULL, compl_direction, compl_pattern.string, (int)compl_pattern.length,
@@ -4157,16 +4388,18 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos)
&& start_pos->col == st->cur_match_pos->col)
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);
if (ptr == NULL || (ins_compl_has_preinsert() && STRCMP(ptr, compl_pattern.string) == 0))
continue;
if (ins_compl_add_infercase(ptr, len, p_ic,
st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname,
0, cont_s_ipos) != NOTDONE)
st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname,
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;
}
@@ -4352,6 +4585,9 @@ ins_compl_get_exp(pos_T *ini)
&& !ctrl_x_mode_line_or_eval()))
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
@@ -5594,7 +5830,7 @@ ins_compl_start(void)
if (p_ic)
flags |= CP_ICASE;
if (compl_orig_text.string == NULL || ins_compl_add(compl_orig_text.string,
(int)compl_orig_text.length, NULL, NULL, NULL, 0, flags, FALSE, NULL) != OK)
-1, NULL, NULL, NULL, 0, flags, FALSE, NULL, 0) != OK)
{
VIM_CLEAR_STRING(compl_pattern);
VIM_CLEAR_STRING(compl_orig_text);

View File

@@ -514,6 +514,8 @@ EXTERN char_u *p_cpt; // 'complete'
EXTERN int p_confirm; // 'confirm'
#endif
EXTERN int p_cp; // 'compatible'
EXTERN char_u *p_cfc; // 'completefuzzycollect'
EXTERN unsigned cfc_flags; // flags from "completefuzzycollect"
EXTERN char_u *p_cia; // 'completeitemalign'
EXTERN unsigned cia_flags; // order flags of 'completeitemalign'
EXTERN char_u *p_cot; // 'completeopt'
@@ -533,6 +535,11 @@ EXTERN unsigned cot_flags; // flags from 'completeopt'
#define COT_FUZZY 0x100 // TRUE: fuzzy match enabled
#define COT_NOSORT 0x200 // TRUE: fuzzy match without qsort score
#define COT_PREINSERT 0x400 // TRUE: preinsert
#define CFC_KEYWORD 0x001
#define CFC_FILES 0x002
#define CFC_WHOLELINE 0x004
#ifdef BACKSLASH_IN_FILENAME
EXTERN char_u *p_csl; // 'completeslash'
#endif

View File

@@ -655,6 +655,10 @@ static struct vimoption options[] =
{(char_u *)0L, (char_u *)0L}
#endif
SCTX_INIT},
{"completefuzzycollect", "cfc", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
(char_u *)&p_cfc, PV_NONE, did_set_completefuzzycollect, NULL,
{(char_u *)"", (char_u *)0L}
SCTX_INIT},
{"completeitemalign", "cia", P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
(char_u *)&p_cia, PV_NONE, did_set_completeitemalign, NULL,
{(char_u *)"abbr,kind,menu", (char_u *)0L}

View File

@@ -120,6 +120,7 @@ static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax",
NULL};
static char *(p_fcl_values[]) = {"all", NULL};
#endif
static char *(p_cfc_values[]) = {"keyword", "files", "whole_line", NULL};
static char *(p_cot_values[]) = {"menu", "menuone", "longest", "preview", "popup", "popuphidden", "noinsert", "noselect", "fuzzy", "nosort", "preinsert", NULL};
#ifdef BACKSLASH_IN_FILENAME
static char *(p_csl_values[]) = {"slash", "backslash", NULL};
@@ -146,6 +147,7 @@ didset_string_options(void)
(void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, TRUE);
(void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, TRUE);
(void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, TRUE);
(void)opt_strings_flags(p_cfc, p_cfc_values, &cfc_flags, TRUE);
(void)opt_strings_flags(p_cot, p_cot_values, &cot_flags, TRUE);
#ifdef FEAT_SESSION
(void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, TRUE);
@@ -1646,6 +1648,17 @@ expand_set_completeopt(optexpand_T *args, int *numMatches, char_u ***matches)
matches);
}
/*
* The 'completefuzzycollect' option is changed.
*/
char *
did_set_completefuzzycollect(optset_T *args UNUSED)
{
if (opt_strings_flags(p_cfc, p_cfc_values, &cfc_flags, TRUE) != OK)
return e_invalid_argument;
return NULL;
}
/*
* The 'completeitemalign' option is changed.
*/

View File

@@ -24,7 +24,7 @@ void compl_status_clear(void);
int has_compl_option(int dict_opt);
int vim_is_ctrl_x_key(int c);
int ins_compl_accept_char(int c);
int ins_compl_add_infercase(char_u *str_arg, int len, int icase, char_u *fname, int dir, int cont_s_ipos);
int ins_compl_add_infercase(char_u *str_arg, int len, int icase, char_u *fname, int dir, int cont_s_ipos, int score);
int ins_compl_has_shown_match(void);
int ins_compl_long_shown_match(void);
unsigned int get_cot_flags(void);

View File

@@ -43,6 +43,7 @@ int expand_set_complete(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_completeopt(optset_T *args);
int expand_set_completeopt(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_completeitemalign(optset_T *args);
char *did_set_completefuzzycollect(optset_T *args);
char *did_set_completepopup(optset_T *args);
char *did_set_completeslash(optset_T *args);
int expand_set_completeslash(optexpand_T *args, int *numMatches, char_u ***matches);

View File

@@ -42,7 +42,9 @@ void f_matchfuzzy(typval_T *argvars, typval_T *rettv);
void f_matchfuzzypos(typval_T *argvars, typval_T *rettv);
int fuzzy_match_str(char_u *str, char_u *pat);
garray_T *fuzzy_match_str_with_pos(char_u *str, char_u *pat);
int search_for_fuzzy_match(buf_T *buf, pos_T *pos, char_u *pattern, int dir, pos_T *start_pos, int *len, char_u **ptr, int whole_line);
int search_for_fuzzy_match(buf_T *buf, pos_T *pos, char_u *pattern, int dir, pos_T *start_pos, int *len, char_u **ptr, int *score);
void fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count);
int fuzzymatches_to_strmatches(fuzmatch_str_T *fuzmatch, char_u ***matches, int count, int funcsort);
int fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos, int *score);
/* vim: set ft=c : */

View File

@@ -53,7 +53,6 @@ static int fuzzy_match_str_compare(const void *s1, const void *s2);
static void fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz);
static int fuzzy_match_func_compare(const void *s1, const void *s2);
static void fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz);
static int fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos);
#define SEARCH_STAT_DEF_TIMEOUT 40L
#define SEARCH_STAT_DEF_MAX_COUNT 99
@@ -3867,7 +3866,7 @@ search_line:
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;
@@ -5221,20 +5220,31 @@ fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED)
}
/*
* 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.
* 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.
*/
static int
fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos)
int
fuzzy_match_str_in_line(
char_u **ptr,
char_u *pat,
int *len,
pos_T *current_pos,
int *score)
{
char_u *str = *ptr;
char_u *strBegin = str;
char_u *end = NULL;
char_u *start = NULL;
int found = FALSE;
int result;
char save_end;
if (str == NULL || pat == NULL)
@@ -5253,15 +5263,16 @@ fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos)
*end = NUL;
// Perform fuzzy match
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;
}
@@ -5292,13 +5303,14 @@ search_for_fuzzy_match(
pos_T *start_pos,
int *len,
char_u **ptr,
int whole_line)
int *score)
{
pos_T current_pos = *pos;
pos_T circly_end;
int found_new_match = FALSE;
int looped_around = FALSE;
int whole_line = ctrl_x_mode_whole_line();
if (whole_line)
current_pos.lnum += dir;
@@ -5324,13 +5336,15 @@ search_for_fuzzy_match(
*ptr = ml_get_buf(buf, current_pos.lnum, FALSE);
// 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, &current_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, &current_pos, score);
if (found_new_match)
{
if (ctrl_x_mode_normal())

View File

@@ -4219,7 +4219,7 @@ dump_word(
? 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;
}

View File

@@ -1,7 +1,7 @@
| +0&#ffffff0|h|e|l@1|o| |h|e|l|i|o| |h|e|r|o| |h|e|l@1|o> @51
|~+0#4040ff13&| @15| +0#0000001#ffd7ff255|h+0#0000e05&|e+0#0000001&|r|o| @10| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#ffd7ff255|h+0#0000e05&|e+0#0000001&|l|i|o| @9| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#e0e0e08|h+0#00e0e07&|e+0#0000001&|l@1|o| @9| +0#4040ff13#ffffff0@41
|~+0#4040ff13&| @15| +0#0000001#ffd7ff255|h|e|r|o| @10| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#ffd7ff255|h|e|l|i|o| @9| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#e0e0e08|h|e|l@1|o| @9| +0#4040ff13#ffffff0@41
|~| @73
|~| @73
|~| @73

View File

@@ -1,7 +1,7 @@
| +0&#ffffff0|h|e|l@1|o| |h|e|l|i|o| |h|e|r|o| |h|e|l|i|o> @51
|~+0#4040ff13&| @15| +0#0000001#ffd7ff255|h+0#0000e05&|e+0#0000001&|r|o| @10| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#e0e0e08|h+0#00e0e07&|e+0#0000001&|l|i|o| @9| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#ffd7ff255|h+0#0000e05&|e+0#0000001&|l@1|o| @9| +0#4040ff13#ffffff0@41
|~+0#4040ff13&| @15| +0#0000001#ffd7ff255|h|e|r|o| @10| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#e0e0e08|h|e|l|i|o| @9| +0#4040ff13#ffffff0@41
|~| @15| +0#0000001#ffd7ff255|h|e|l@1|o| @9| +0#4040ff13#ffffff0@41
|~| @73
|~| @73
|~| @73

View File

@@ -157,6 +157,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']],

View File

@@ -2731,7 +2731,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)
@@ -2787,8 +2787,65 @@ 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
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.
@@ -2820,16 +2877,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')
@@ -2845,7 +2892,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
@@ -2897,79 +2943,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
@@ -2991,6 +3067,7 @@ func Test_complete_fuzzy_with_completeslash()
" Reset and clean up
let &shellslash = orig_shellslash
set completeslash=
set completefuzzycollect&
%bw!
endfunc

View File

@@ -1495,22 +1495,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>")
@@ -1524,7 +1508,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

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1178,
/**/
1177,
/**/