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>
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 883a67f..6afe32f 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.1. Last change: 2025 Mar 03
+*options.txt* For Vim version 9.1. Last change: 2025 Mar 06
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -2104,6 +2104,18 @@
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
+ *'completefuzzycollect'* *'cfc'*
+'completefuzzycollect' 'cfc' string (default: empty)
+ 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
@@ -2123,7 +2135,12 @@
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
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 49a849e..6aee799 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -129,6 +129,7 @@
'cdpath' options.txt /*'cdpath'*
'cedit' options.txt /*'cedit'*
'cf' options.txt /*'cf'*
+'cfc' options.txt /*'cfc'*
'cfu' options.txt /*'cfu'*
'ch' options.txt /*'ch'*
'character' intro.txt /*'character'*
@@ -162,6 +163,7 @@
'compatible' options.txt /*'compatible'*
'complete' options.txt /*'complete'*
'completefunc' options.txt /*'completefunc'*
+'completefuzzycollect' options.txt /*'completefuzzycollect'*
'completeitemalign' options.txt /*'completeitemalign'*
'completeopt' options.txt /*'completeopt'*
'completepopup' options.txt /*'completepopup'*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 5edadce..f12bcd4 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt* For Vim version 9.1. Last change: 2025 Mar 03
+*version9.txt* For Vim version 9.1. Last change: 2025 Mar 06
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -41708,6 +41708,8 @@
Options: ~
+'completefuzzycollect' Enable fuzzy collection of candiates for (some)
+ |ins-completion| modes
'completeitemalign' Order of |complete-items| in Insert mode completion
popup
'eventignorewin' autocommand events that are ignored in a window
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 996e141..2c28ee1 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -848,6 +848,8 @@
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")
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index 5cab4ae..e83c9b2 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -2,7 +2,7 @@
" Language: Vim script
" Maintainer: Hirohito Higashi <h.east.727 ATMARK gmail.com>
" Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2025 Feb 27
+" Last Change: 2025 Mar 06
" Former Maintainer: Charles E. Campbell
" DO NOT CHANGE DIRECTLY.
@@ -42,13 +42,13 @@
" vimOptions are caught only when contained in a vimSet {{{2
" GEN_SYN_VIM: vimOption normal, START_STR='syn keyword vimOption contained', END_STR='skipwhite nextgroup=vimSetEqual,vimSetMod'
syn keyword vimOption contained al aleph ari allowrevins ambw ambiwidth arab arabic arshape arabicshape acd autochdir ai autoindent ar autoread asd autoshelldir aw autowrite awa autowriteall bg background bs backspace bk backup bkc backupcopy bdir backupdir bex backupext bsk backupskip bdlay balloondelay beval ballooneval bevalterm balloonevalterm bexpr balloonexpr bo belloff bin binary bomb brk breakat bri breakindent briopt breakindentopt bsdir browsedir bh bufhidden bl buflisted bt buftype cmp casemap cdh cdhome cd cdpath cedit ccv charconvert cin cindent cink cinkeys cino cinoptions cinsd cinscopedecls cinw cinwords cb clipboard ch cmdheight cwh cmdwinheight cc colorcolumn co columns com comments cms commentstring cp compatible cpt complete cfu completefunc skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained cia completeitemalign cot completeopt cpp completepopup csl completeslash cocu concealcursor cole conceallevel cf confirm ci copyindent cpo cpoptions cm cryptmethod cspc cscopepathcomp csprg cscopeprg csqf cscopequickfix csre cscoperelative cst cscopetag csto cscopetagorder csverb cscopeverbose crb cursorbind cuc cursorcolumn cul cursorline culopt cursorlineopt debug def define deco delcombine dict dictionary diff dex diffexpr dip diffopt dg digraph dir directory dy display ead eadirection ed edcompatible emo emoji enc encoding eof endoffile eol endofline ea equalalways ep equalprg eb errorbells ef errorfile efm errorformat ek esckeys ei eventignore eiw eventignorewin et expandtab ex exrc fenc fileencoding fencs fileencodings ff fileformat skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained ffs fileformats fic fileignorecase ft filetype fcs fillchars ffu findfunc fixeol fixendofline fcl foldclose fdc foldcolumn fen foldenable fde foldexpr fdi foldignore fdl foldlevel fdls foldlevelstart fmr foldmarker fdm foldmethod fml foldminlines fdn foldnestmax fdo foldopen fdt foldtext fex formatexpr flp formatlistpat fo formatoptions fp formatprg fs fsync gd gdefault gfm grepformat gp grepprg gcr guicursor gfn guifont gfs guifontset gfw guifontwide ghr guiheadroom gli guiligatures go guioptions guipty gtl guitablabel gtt guitabtooltip hf helpfile hh helpheight hlg helplang hid hidden hl highlight hi history hk hkmap hkp hkmapp hls hlsearch icon iconstring ic ignorecase imaf imactivatefunc imak imactivatekey imc imcmdline imd imdisable skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained imi iminsert ims imsearch imsf imstatusfunc imst imstyle inc include inex includeexpr is incsearch inde indentexpr indk indentkeys inf infercase im insertmode isf isfname isi isident isk iskeyword isp isprint js joinspaces jop jumpoptions key kmp keymap km keymodel kpc keyprotocol kp keywordprg lmap langmap lm langmenu lnr langnoremap lrm langremap ls laststatus lz lazyredraw lbr linebreak lines lsp linespace lisp lop lispoptions lw lispwords list lcs listchars lpl loadplugins luadll magic mef makeef menc makeencoding mp makeprg mps matchpairs mat matchtime mco maxcombine mfd maxfuncdepth mmd maxmapdepth mm maxmem mmp maxmempattern mmt maxmemtot mis menuitems mopt messagesopt msm mkspellmem ml modeline mle modelineexpr mls modelines skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained ma modifiable mod modified more mouse mousef mousefocus mh mousehide mousem mousemodel mousemev mousemoveevent mouses mouseshape mouset mousetime mzq mzquantum mzschemedll mzschemegcdll nf nrformats nu number nuw numberwidth ofu omnifunc odev opendevice opfunc operatorfunc pp packpath para paragraphs paste pt pastetoggle pex patchexpr pm patchmode pa path perldll pi preserveindent pvh previewheight pvp previewpopup pvw previewwindow pdev printdevice penc printencoding pexpr printexpr pfn printfont pheader printheader pmbcs printmbcharset pmbfn printmbfont popt printoptions prompt ph pumheight pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape ro readonly rdt redrawtime skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote sr shiftround sw shiftwidth shm shortmess sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab sms smoothscroll sts softtabstop skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack tcldll term tbidi termbidi tenc termencoding tgc termguicolors twk termwinkey twsl termwinscroll tws termwinsize twt termwintype terse ta textauto tx textmode tw textwidth tsr thesaurus tsrfu thesaurusfunc top tildeop to timeout tm timeoutlen title titlelen titleold titlestring tb toolbar tbis toolbariconsize ttimeout ttm ttimeoutlen tbi ttybuiltin skipwhite nextgroup=vimSetEqual,vimSetMod
-syn keyword vimOption contained tf ttyfast ttym ttymouse tsl ttyscroll tty ttytype udir undodir udf undofile ul undolevels ur undoreload uc updatecount ut updatetime vsts varsofttabstop vts vartabstop vbs verbose vfile verbosefile vdir viewdir vop viewoptions vi viminfo vif viminfofile ve virtualedit vb visualbell warn wiv weirdinvert ww whichwrap wc wildchar wcm wildcharm wig wildignore wic wildignorecase wmnu wildmenu wim wildmode wop wildoptions wak winaltkeys wcr wincolor wi window wfb winfixbuf wfh winfixheight wfw winfixwidth wh winheight wmh winminheight wmw winminwidth winptydll wiw winwidth wrap wm wrapmargin ws wrapscan write wa writeany wb writebackup wd writedelay xtermcodes skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained cfc completefuzzycollect cia completeitemalign cot completeopt cpp completepopup csl completeslash cocu concealcursor cole conceallevel cf confirm ci copyindent cpo cpoptions cm cryptmethod cspc cscopepathcomp csprg cscopeprg csqf cscopequickfix csre cscoperelative cst cscopetag csto cscopetagorder csverb cscopeverbose crb cursorbind cuc cursorcolumn cul cursorline culopt cursorlineopt debug def define deco delcombine dict dictionary diff dex diffexpr dip diffopt dg digraph dir directory dy display ead eadirection ed edcompatible emo emoji enc encoding eof endoffile eol endofline ea equalalways ep equalprg eb errorbells ef errorfile efm errorformat ek esckeys ei eventignore eiw eventignorewin et expandtab ex exrc fenc fileencoding skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained fencs fileencodings ff fileformat ffs fileformats fic fileignorecase ft filetype fcs fillchars ffu findfunc fixeol fixendofline fcl foldclose fdc foldcolumn fen foldenable fde foldexpr fdi foldignore fdl foldlevel fdls foldlevelstart fmr foldmarker fdm foldmethod fml foldminlines fdn foldnestmax fdo foldopen fdt foldtext fex formatexpr flp formatlistpat fo formatoptions fp formatprg fs fsync gd gdefault gfm grepformat gp grepprg gcr guicursor gfn guifont gfs guifontset gfw guifontwide ghr guiheadroom gli guiligatures go guioptions guipty gtl guitablabel gtt guitabtooltip hf helpfile hh helpheight hlg helplang hid hidden hl highlight hi history hk hkmap hkp hkmapp hls hlsearch icon iconstring ic ignorecase imaf imactivatefunc imak imactivatekey skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained imc imcmdline imd imdisable imi iminsert ims imsearch imsf imstatusfunc imst imstyle inc include inex includeexpr is incsearch inde indentexpr indk indentkeys inf infercase im insertmode isf isfname isi isident isk iskeyword isp isprint js joinspaces jop jumpoptions key kmp keymap km keymodel kpc keyprotocol kp keywordprg lmap langmap lm langmenu lnr langnoremap lrm langremap ls laststatus lz lazyredraw lbr linebreak lines lsp linespace lisp lop lispoptions lw lispwords list lcs listchars lpl loadplugins luadll magic mef makeef menc makeencoding mp makeprg mps matchpairs mat matchtime mco maxcombine mfd maxfuncdepth mmd maxmapdepth mm maxmem mmp maxmempattern mmt maxmemtot mis menuitems mopt messagesopt msm mkspellmem ml modeline skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained mle modelineexpr mls modelines ma modifiable mod modified more mouse mousef mousefocus mh mousehide mousem mousemodel mousemev mousemoveevent mouses mouseshape mouset mousetime mzq mzquantum mzschemedll mzschemegcdll nf nrformats nu number nuw numberwidth ofu omnifunc odev opendevice opfunc operatorfunc pp packpath para paragraphs paste pt pastetoggle pex patchexpr pm patchmode pa path perldll pi preserveindent pvh previewheight pvp previewpopup pvw previewwindow pdev printdevice penc printencoding pexpr printexpr pfn printfont pheader printheader pmbcs printmbcharset pmbfn printmbfont popt printoptions prompt ph pumheight pw pumwidth pythondll pythonhome pythonthreedll pythonthreehome pyx pyxversion qftf quickfixtextfunc qe quoteescape skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained ro readonly rdt redrawtime re regexpengine rnu relativenumber remap rop renderoptions report rs restorescreen ri revins rl rightleft rlc rightleftcmd rubydll ru ruler ruf rulerformat rtp runtimepath scr scroll scb scrollbind scf scrollfocus sj scrolljump so scrolloff sbo scrollopt sect sections secure sel selection slm selectmode ssop sessionoptions sh shell shcf shellcmdflag sp shellpipe shq shellquote srr shellredir ssl shellslash stmp shelltemp st shelltype sxe shellxescape sxq shellxquote sr shiftround sw shiftwidth shm shortmess sn shortname sbr showbreak sc showcmd sloc showcmdloc sft showfulltag sm showmatch smd showmode stal showtabline ss sidescroll siso sidescrolloff scl signcolumn scs smartcase si smartindent sta smarttab skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained sms smoothscroll sts softtabstop spell spc spellcapcheck spf spellfile spl spelllang spo spelloptions sps spellsuggest sb splitbelow spk splitkeep spr splitright sol startofline stl statusline su suffixes sua suffixesadd swf swapfile sws swapsync swb switchbuf smc synmaxcol syn syntax tcl tabclose tal tabline tpm tabpagemax ts tabstop tbs tagbsearch tc tagcase tfu tagfunc tl taglength tr tagrelative tag tags tgst tagstack tcldll term tbidi termbidi tenc termencoding tgc termguicolors twk termwinkey twsl termwinscroll tws termwinsize twt termwintype terse ta textauto tx textmode tw textwidth tsr thesaurus tsrfu thesaurusfunc top tildeop to timeout tm timeoutlen title titlelen titleold titlestring tb toolbar tbis toolbariconsize ttimeout skipwhite nextgroup=vimSetEqual,vimSetMod
+syn keyword vimOption contained ttm ttimeoutlen tbi ttybuiltin tf ttyfast ttym ttymouse tsl ttyscroll tty ttytype udir undodir udf undofile ul undolevels ur undoreload uc updatecount ut updatetime vsts varsofttabstop vts vartabstop vbs verbose vfile verbosefile vdir viewdir vop viewoptions vi viminfo vif viminfofile ve virtualedit vb visualbell warn wiv weirdinvert ww whichwrap wc wildchar wcm wildcharm wig wildignore wic wildignorecase wmnu wildmenu wim wildmode wop wildoptions wak winaltkeys wcr wincolor wi window wfb winfixbuf wfh winfixheight wfw winfixwidth wh winheight wmh winminheight wmw winminwidth winptydll wiw winwidth wrap wm wrapmargin ws wrapscan write wa writeany wb writebackup wd writedelay xtermcodes skipwhite nextgroup=vimSetEqual,vimSetMod
" vimOptions: These are the turn-off setting variants {{{2
" GEN_SYN_VIM: vimOption turn-off, START_STR='syn keyword vimOption contained', END_STR=''
diff --git a/src/insexpand.c b/src/insexpand.c
index 1616a0f..9769c46 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -132,6 +132,13 @@
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 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 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 @@
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,12 +754,31 @@
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
* len - length of "str". If -1, then the length of "str" is
@@ -780,11 +808,13 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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);
}
@@ -1704,7 +1763,7 @@
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 @@
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 @@
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;
}
- 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;
+ }
}
line_breakcheck();
ins_compl_check_keys(50, FALSE);
@@ -1888,6 +1985,7 @@
{
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 @@
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 @@
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 @@
/*
* 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;
@@ -3773,6 +3872,114 @@
}
/*
+ * 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".
*/
static void
@@ -3786,10 +3993,13 @@
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 @@
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 @@
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 @@
}
#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 @@
// 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 @@
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 @@
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 @@
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 @@
++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 @@
&& 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 @@
&& !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 @@
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);
diff --git a/src/option.h b/src/option.h
index 182ab26..5714e0c 100644
--- a/src/option.h
+++ b/src/option.h
@@ -514,6 +514,8 @@
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 @@
#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
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 8326537..a8d7972 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -655,6 +655,10 @@
{(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}
diff --git a/src/optionstr.c b/src/optionstr.c
index e2970b6..5b863bc 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -120,6 +120,7 @@
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 @@
(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);
@@ -1647,6 +1649,17 @@
}
/*
+ * 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.
*/
char *
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index 9dfbd91..8d96164 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -24,7 +24,7 @@
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);
diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro
index 11b9d05..6fd26ba 100644
--- a/src/proto/optionstr.pro
+++ b/src/proto/optionstr.pro
@@ -43,6 +43,7 @@
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);
diff --git a/src/proto/search.pro b/src/proto/search.pro
index f450d09..e9be453 100644
--- a/src/proto/search.pro
+++ b/src/proto/search.pro
@@ -42,7 +42,9 @@
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 : */
diff --git a/src/search.c b/src/search.c
index 3519c32..67082a7 100644
--- a/src/search.c
+++ b/src/search.c
@@ -53,7 +53,6 @@
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 @@
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 @@
}
/*
- * 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 @@
*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 @@
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 @@
*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, ¤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())
diff --git a/src/spell.c b/src/spell.c
index 2581a5e..87256f9 100644
--- a/src/spell.c
+++ b/src/spell.c
@@ -4219,7 +4219,7 @@
? 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;
}
diff --git a/src/testdir/dumps/Test_pum_completefuzzycollect_01.dump b/src/testdir/dumps/Test_pum_completefuzzycollect_01.dump
new file mode 100644
index 0000000..78b3312
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completefuzzycollect_01.dump
@@ -0,0 +1,20 @@
+| +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|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
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@27
diff --git a/src/testdir/dumps/Test_pum_completefuzzycollect_02.dump b/src/testdir/dumps/Test_pum_completefuzzycollect_02.dump
new file mode 100644
index 0000000..c57ee7d
--- /dev/null
+++ b/src/testdir/dumps/Test_pum_completefuzzycollect_02.dump
@@ -0,0 +1,20 @@
+| +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|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
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |3| +0#0000000&@27
diff --git a/src/testdir/dumps/Test_pum_highlights_15.dump b/src/testdir/dumps/Test_pum_completefuzzycollect_03.dump
similarity index 100%
rename from src/testdir/dumps/Test_pum_highlights_15.dump
rename to src/testdir/dumps/Test_pum_completefuzzycollect_03.dump
diff --git a/src/testdir/dumps/Test_pum_highlights_10.dump b/src/testdir/dumps/Test_pum_highlights_10.dump
deleted file mode 100644
index 790b028..0000000
--- a/src/testdir/dumps/Test_pum_highlights_10.dump
+++ /dev/null
@@ -1,20 +0,0 @@
-| +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
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@27
diff --git a/src/testdir/dumps/Test_pum_highlights_11.dump b/src/testdir/dumps/Test_pum_highlights_11.dump
deleted file mode 100644
index ef75a89..0000000
--- a/src/testdir/dumps/Test_pum_highlights_11.dump
+++ /dev/null
@@ -1,20 +0,0 @@
-| +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
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|-+2#0000000&@1| |K|e|y|w|o|r|d| |L|o|c|a|l| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |2| |o|f| |3| +0#0000000&@27
diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim
index 9506532..b3a89a0 100644
--- a/src/testdir/gen_opt_test.vim
+++ b/src/testdir/gen_opt_test.vim
@@ -157,6 +157,9 @@
\ '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']],
diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim
index 1bef953..345e365 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -2731,7 +2731,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
" Reset and clean up
let &shellslash = orig_shellslash
set completeslash=
+ set completefuzzycollect&
%bw!
endfunc
diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim
index aa4e3e4..e599a8d 100644
--- a/src/testdir/test_popup.vim
+++ b/src/testdir/test_popup.vim
@@ -1495,22 +1495,6 @@
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 @@
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
diff --git a/src/version.c b/src/version.c
index 790fc7d..a7ce796 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1178,
+/**/
1177,
/**/
1176,