0
0
mirror of https://github.com/vim/vim.git synced 2025-07-04 23:07:33 -04:00

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
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
21 changed files with 578 additions and 185 deletions

View File

@ -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 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
*'completefuzzycollect'* *'cfc'*
'completefuzzycollect' 'cfc' string (default: 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 @@ A jump table for the options with a short description can be found at |Q_op|.
fuzzy Enable |fuzzy-matching| for completion candidates. This
allows for more flexible and intuitive matching, where
characters can be skipped and matches can be found even
if the exact sequence is not typed.
if the exact sequence is not typed. Note: This option
does not affect the collection of candidate list, it only
controls how completion candidates are reduced from the
list of alternatives. If you want to use |fuzzy-matching|
to gather more alternatives for your candidate list,
see |'completefuzzycollect'|.
longest Only insert the longest common text of the matches. If
the menu is displayed you can use CTRL-L to add more

View File

@ -129,6 +129,7 @@ $quote eval.txt /*$quote*
'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 @@ $quote eval.txt /*$quote*
'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'*

View File

@ -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 @@ Commands: ~
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

View File

@ -848,6 +848,8 @@ endif
if has("insert_expand")
call <SID>AddOption("complete", gettext("specifies how Insert mode completion works for CTRL-N and CTRL-P"))
call append("$", "\t" .. s:local_to_buffer)
call <SID>OptionL("cfc")
call <SID>AddOption("completefuzzycollect", gettext("using fuzzy collect for defaule completion mode"))
call <SID>OptionL("cpt")
call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion"))
call <SID>OptionL("cot")

View File

@ -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 @@ syn keyword vimStdPlugin contained Arguments Asm Break Cfilter Clear Continue Di
" 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=''

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,
/**/