Skip to content

Instantly share code, notes, and snippets.

@ganwell
Last active June 28, 2019 09:12
Show Gist options
  • Save ganwell/62f0d3e04c2110dd2f4b0928e720e4a1 to your computer and use it in GitHub Desktop.
Save ganwell/62f0d3e04c2110dd2f4b0928e720e4a1 to your computer and use it in GitHub Desktop.
Completion-as-you-time with timer: not simple anymore, I had to workaround many vim/neovim bugs (works with neovim v0.4)
" Set the complete-options that are best for auto-completion as you type:
" * menuone: show the (pop-up-)menu even when there is only one entry
" * preview: show the documentation for the current completion if available
" * noinsert: only insert a completion if the user presses enter
" * also do not set noselect/longest, since we want vim to select the first entry, so
" enter is sufficient to insert the completion
set completeopt=menuone,preview,noinsert
" Enable dictionary completions
set complete+=k
" This is the workaround for the workaround for the workaround
set lazyredraw
" By default we do a keyword-completion.
let s:completion_keys = "\<C-n>"
" These variables pass the timer between functions.
let s:rtimer = -1
let s:otimer = -1
let s:ntimer = -1
" If the pop-up-menu is not visible (!pumvisible()), and the cursor is at a
" keyword-character (regex \K), we feed the keys that activate the current completion.
" If the completion is an omni-completion (<C-x><C-o>), we start a timer that shows the
" keyword-completion if the omni-completion failed. If it is a keyword-completion, we
" check that the current word is at least two characters long, because the dictionary
" completion only works with two or more characters.
function! s:OpenCompletion(timer)
let s:otimer = -1
if !pumvisible()
let l:col = col('.')
let l:line = getline('.')
if s:completion_keys == "\<C-x>\<C-o>"
if match(l:line[l:col - 2], '\K') == 0
call feedkeys(s:completion_keys, 'n')
if s:ntimer == -1
let s:ntimer = timer_start(0, function('s:NextCompletionFirst'))
endif
endif
else
let l:begin = l:line[l:col - 3 : l:col - 1]
if strchars(l:begin) < 2
let l:begin = l:line[l:col - 4 : l:col - 1]
endif
if match(l:begin, '\K\k') == 0
call feedkeys(s:completion_keys, 'n')
endif
endif
endif
endfunction
function! s:StartCompletion()
if !pumvisible()
if s:otimer == -1
let s:otimer = timer_start(0, function('s:OpenCompletion'))
endif
endif
endfunction
" Call OpenCompletion() for every character we type.
autocmd InsertCharPre * call s:StartCompletion()
" The workaround for the workaround (this is all so racey)
function! s:FixSelection()
if pumvisible()
if !has_key(v:event['completed_item'], 'word')
call feedkeys("\<C-e>" . s:completion_keys, 'n')
endif
endif
endfunction
autocmd CompleteChanged * call s:FixSelection()
" If there is no language-based semantic-completion, we do the keyword-completion.
function! s:ChangeCompletionType()
if &omnifunc == ""
let s:completion_keys = "\<C-n>"
else
let s:completion_keys = "\<C-x>\<C-o>"
endif
endfunction
" Change the completion type as we change buffers.
autocmd BufEnter * call s:ChangeCompletionType()
" Stop the repeating the timer once the completion is done.
autocmd CompleteDone * call timer_stop(s:rtimer)
" I don't like ignorecase for completions, remove these lines if you prefer ignorecase.
autocmd InsertEnter * setlocal noignorecase
autocmd InsertLeave * setlocal ignorecase
" If two characters before the cursor are a keyword-characters, we show the
" keyword-completion.
function! s:NextCompletion()
if !pumvisible()
let l:curpos = col('.')
if match(getline('.')[l:curpos - 3:curpos - 1] . v:char, '\K\k') == 0
call feedkeys("\<C-e>\<C-n>", 'n')
endif
endif
endfunction
" If the pop-up-menu is not visible, we have exhausted the semantic-completion, we stop
" the timer and check if should display the keyword-completion.
function! s:NextCompletionRepeat(timer)
if !pumvisible()
call timer_stop(a:timer)
call s:NextCompletion()
endif
endfunction
" The same as above but for the initial completion. After the initial completion, we
" check the pop-up-menu every 250ms.
function! s:NextCompletionFirst(timer)
let s:ntimer = -1
if !pumvisible()
call s:NextCompletion()
else
call timer_stop(s:rtimer)
let s:rtimer = timer_start(250, function('s:NextCompletionRepeat'), {'repeat' : -1})
endif
endfunction
" Not treating underscore as a keyword-character results in better
" dictionary-completions.
function! CompleteAsYouTypeSetUnderscoreIsKeyword()
redir => l:is_syntax_iskeyword_set
silent! syntax iskeyword
redir END
if l:is_syntax_iskeyword_set =~ 'syntax iskeyword not set'
exec 'syntax iskeyword ' . &iskeyword
endif
setlocal iskeyword-=_
endfunction
function! s:ToggleUnderscoreIsKeyword()
if &iskeyword =~ "_"
call CompleteAsYouTypeSetUnderscoreIsKeyword()
echo "setlocal iskeyword-=_"
else
setlocal iskeyword+=_
echo "setlocal iskeyword+=_"
endif
endfunction
" Bind toggling underscore_is_keyword to :Tu
command Tu call s:ToggleUnderscoreIsKeyword()
" If you dislike case-insensitive-completion add this to your ftplugin/python.vim
PythonJedi import jedi; jedi.settings.case_insensitive_completion = False
" Use an augroup for the languages you want underscore not being a keyword during completion (ftplugin/python.vim)
augroup CompleteAsYouTypeGroup
autocmd!
autocmd InsertEnter * call CompleteAsYouTypeSetUnderscoreIsKeyword()
autocmd InsertLeave * setlocal iskeyword+=_
augroup END
" If you have the habit to paste into the terminal, instead of into vim use bracketed paste
Plugin 'ConradIrwin/vim-bracketed-paste'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment