Skip to main content
1 of 7

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().

One more subtlety is that 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.

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 command 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 Enter to go back to the completion. The usual completion bindings <C-e>, <C-y>, <C-n>, <C-p>, <C-h>, <C-l>, <Up>, <Down> also work, and automatically leaves the completion search.

function! Prepareearchpum ()
    let g:compstartcol = col('.')
    let g:compinf = complete_info()
    return ""
    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)
        while 1
            let matchwords = []
            for word in words
                if word =~ filter
                    call add (matchwords, word)
                    endif
                endfor
                
            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 == "\<CR>"
                return ""
            elseif char == "\<C-u>"
                let filter = ""
            elseif char == "\<C-e>"
                return "\<C-e>"
            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 == "\<BS>"
                if len(filter) == 1
                    let filter = ""
                else
                    let filter = filter[:len(filter) - 2]
                    endif
            else
                let filter = filter .. char
                endif
            endwhile
        endif
    endfunction

inoremap <C-_> <C-n><C-r>=Prepareearchpum()<CR><C-p><C-r>=Searchpum()<CR>