As I suspected, this is possible. We use complete_info()
to find the existing matches, ask the user to type his desired filter using getchar()
, and replace the completions with complete()
.
I mapped this to <C-/>
/<C-_>
. To use this, after getting a lot of completions (e.g. from <C-n>
or <C-x><C-i>
), invoke <C-/>
and start typing to search for the completion you need. You will see your search at the bottom of the screen (like the usual search prompt). You can see the completions being filtered as you type.
You can use all normal vim regex patterns here (like the magic
mode). Press backspace to delete the last character or <C-u>
to delete your whole search. Press <C-/>
to accept this filter and begin another search. Press Enter to leave the search and go back to the completion. The usual completion bindings <C-e>
, <C-y>
, <C-n>
, <C-p>
, <C-h>
, <C-l>
, <Up>
, <Down>
, <Esc>
also work, and automatically leave the completion search.
Some more subtleties about the implementation:
- We need to get the "startcol", that is, the start of the text being replaced, so that selecting the completions replaces that text, rather than appending to the end. We find this by
<C-n>
(to ensure some completion is selected), get the cursor position after the selection and finding the length of the selected completion by complete_info()
, and compensating the <C-n>
with a <C-p>
. The "startcol" is the cursor position after selection minus the length of the selected completion.
- Naively passing
<C-e>
back into the mapping may not work, because the popup menu state is reset when we call complete()
, so we have to save the value ourselves and restore it. Similarly, we should persist this state when we perform <C-/>
to refine the search. We find the state by performing <C-e>
and saving the result before starting the completion search.
- Only apply the mapping when pum is active.
This is the result:
function! Prepareearchpum ()
let g:compstartcol = col('.')
let g:compinf = complete_info()
if len(g:compinf.items) && (g:compinf.selected == -1)
return "\<C-n>\<C-r>=Prepareearchpum()\<CR>\<C-p>"
else
return ""
endif
endfunction
function! Searchpum ()
let words = []
for item in g:compinf.items
call add(words, item.word)
endfor
let filter = ""
if len(words)
let startcol = g:compstartcol - len(g:compinf.items[g:compinf.selected].word)
if exists('g:lastexittext')
let exittext = g:lastexittext
unlet g:lastexittext
else
let exitcol = col('.')
let exittext = getline('.')[startcol-1:exitcol-2]
endif
while 1
let matchwords = []
for word in words
if word =~ filter
call add (matchwords, word)
endif
endfor
if len(matchwords) == 0
call add (matchwords, exittext)
endif
call complete(startcol, matchwords)
echomsg '/' .. filter
try
let char = getchar()
catch /^Vim:Interrupt$/
return "\<C-c>"
endtry
if type(char) == 0
let char = nr2char(char)
endif
if char == "\<C-e>"
call complete(startcol, [exittext])
return "\<C-y>"
elseif char == "\<C-y>"
return "\<C-y>"
elseif char == "\<C-n>"
return "\<C-n>"
elseif char == "\<C-p>"
return "\<C-p>"
elseif char == "\<C-l>"
return "\<C-l>"
elseif char == "\<C-h>"
return "\<C-h>"
elseif char == "\<Up>"
return "\<Up>"
elseif char == "\<Down>"
return "\<Down>"
elseif char == "\<Esc>"
return "\<Esc>"
elseif char == "\<C-_>"
let g:lastexittext = exittext
call feedkeys ("\<C-_>")
return ""
elseif char == "\<CR>"
return ""
elseif char == "\<BS>"
let filter = filter[:-2]
elseif char == "\<C-u>"
let filter = ""
else
let filter = filter .. char
endif
endwhile
endif
endfunction
inoremap <expr> <C-_> pumvisible() ? Prepareearchpum () . '<C-e><C-r>=Searchpum()<CR>' : '<C-_>'