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,
-							&regmatch, buf, &dir);
+			    (cfc_has_mode() ? NULL : &regmatch), 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, &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())
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,