2020-06-18 23:07:37 +08:00
|
|
|
"=============================================================================
|
|
|
|
" FILE: handler.vim
|
|
|
|
" AUTHOR: Shougo Matsushita <Shougo.Matsu at gmail.com>
|
|
|
|
" License: MIT license
|
|
|
|
"=============================================================================
|
|
|
|
|
|
|
|
function! deoplete#handler#_init() abort
|
|
|
|
augroup deoplete
|
|
|
|
autocmd!
|
|
|
|
autocmd InsertLeave * call s:on_insert_leave()
|
|
|
|
autocmd CompleteDone * call s:on_complete_done()
|
|
|
|
augroup END
|
|
|
|
|
|
|
|
for event in [
|
|
|
|
\ 'InsertEnter', 'InsertLeave',
|
|
|
|
\ 'BufReadPost', 'BufWritePost',
|
2022-04-23 12:09:55 +08:00
|
|
|
\ 'VimLeavePre', 'FileType',
|
2020-06-18 23:07:37 +08:00
|
|
|
\ ]
|
|
|
|
call s:define_on_event(event)
|
|
|
|
endfor
|
|
|
|
|
|
|
|
if deoplete#custom#_get_option('on_text_changed_i')
|
|
|
|
call s:define_completion_via_timer('TextChangedI')
|
|
|
|
endif
|
|
|
|
if deoplete#custom#_get_option('on_insert_enter')
|
|
|
|
call s:define_completion_via_timer('InsertEnter')
|
|
|
|
endif
|
|
|
|
if deoplete#custom#_get_option('refresh_always')
|
2022-04-23 12:09:55 +08:00
|
|
|
call s:define_completion_via_timer('TextChangedP')
|
2020-06-18 23:07:37 +08:00
|
|
|
endif
|
|
|
|
|
|
|
|
" Note: Vim 8 GUI(MacVim and Win32) is broken
|
|
|
|
" dummy timer call is needed before complete()
|
|
|
|
if !has('nvim') && has('gui_running')
|
|
|
|
\ && (has('gui_macvim') || has('win32'))
|
|
|
|
let s:dummy_timer = timer_start(200, {timer -> 0}, {'repeat': -1})
|
|
|
|
endif
|
|
|
|
|
|
|
|
if deoplete#util#has_yarp()
|
|
|
|
" To fix "RuntimeError: Event loop is closed" issue
|
|
|
|
" Note: Workaround
|
|
|
|
autocmd deoplete VimLeavePre * call s:kill_yarp()
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! deoplete#handler#_do_complete() abort
|
|
|
|
let context = g:deoplete#_context
|
|
|
|
let event = get(context, 'event', '')
|
|
|
|
if s:is_exiting() || v:insertmode !=# 'i' || s:check_input_method()
|
2022-04-23 12:09:55 +08:00
|
|
|
\ || !has_key(context, 'candidates')
|
2020-06-18 23:07:37 +08:00
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let prev = g:deoplete#_prev_completion
|
|
|
|
let prev.event = context.event
|
|
|
|
let prev.input = context.input
|
|
|
|
let prev.candidates = context.candidates
|
|
|
|
let prev.complete_position = context.complete_position
|
|
|
|
let prev.linenr = line('.')
|
2022-04-23 12:09:55 +08:00
|
|
|
let prev.time = context.time
|
|
|
|
|
|
|
|
if context.event ==# 'Manual'
|
|
|
|
let context.event = ''
|
|
|
|
endif
|
2020-06-18 23:07:37 +08:00
|
|
|
|
|
|
|
let auto_popup = deoplete#custom#_get_option(
|
|
|
|
\ 'auto_complete_popup') !=# 'manual'
|
|
|
|
|
|
|
|
" Enable auto refresh when popup is displayed
|
|
|
|
if deoplete#util#check_popup()
|
|
|
|
let auto_popup = v:true
|
|
|
|
endif
|
|
|
|
|
|
|
|
if auto_popup
|
2022-04-23 12:09:55 +08:00
|
|
|
" Note: completeopt must be changed before complete() and feedkeys()
|
|
|
|
call deoplete#mapping#_set_completeopt(g:deoplete#_context.is_async)
|
|
|
|
|
2020-06-18 23:07:37 +08:00
|
|
|
call feedkeys("\<Plug>_", 'i')
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! deoplete#handler#_check_omnifunc(context) abort
|
|
|
|
let prev = g:deoplete#_prev_completion
|
|
|
|
let blacklist = ['LanguageClient#complete']
|
|
|
|
if a:context.event ==# 'Manual'
|
|
|
|
\ || &l:omnifunc ==# ''
|
|
|
|
\ || index(blacklist, &l:omnifunc) >= 0
|
|
|
|
\ || prev.input ==# a:context.input
|
|
|
|
\ || s:check_input_method()
|
|
|
|
\ || deoplete#custom#_get_option('auto_complete_popup') ==# 'manual'
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
for filetype in a:context.filetypes
|
|
|
|
for pattern in deoplete#util#convert2list(
|
|
|
|
\ deoplete#custom#_get_filetype_option(
|
|
|
|
\ 'omni_patterns', filetype, ''))
|
|
|
|
if pattern !=# '' && a:context.input =~# '\%('.pattern.'\)$'
|
|
|
|
let g:deoplete#_context.candidates = []
|
|
|
|
|
|
|
|
let prev.event = a:context.event
|
|
|
|
let prev.input = a:context.input
|
|
|
|
let prev.candidates = []
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
call deoplete#mapping#_set_completeopt(v:true)
|
|
|
|
call feedkeys("\<C-x>\<C-o>", 'in')
|
2020-06-18 23:07:37 +08:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
endfor
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:completion_timer_start(event) abort
|
|
|
|
if exists('s:completion_timer')
|
|
|
|
call s:completion_timer_stop()
|
|
|
|
endif
|
|
|
|
|
|
|
|
let delay = deoplete#custom#_get_option('auto_complete_delay')
|
|
|
|
if delay > 0
|
|
|
|
let s:completion_timer = timer_start(
|
|
|
|
\ delay, {-> deoplete#handler#_completion_begin(a:event)})
|
|
|
|
else
|
|
|
|
call deoplete#handler#_completion_begin(a:event)
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
function! s:completion_timer_stop() abort
|
|
|
|
if !exists('s:completion_timer')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
call timer_stop(s:completion_timer)
|
|
|
|
unlet s:completion_timer
|
|
|
|
endfunction
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
function! deoplete#handler#_check_prev_completion(event) abort
|
2020-06-18 23:07:37 +08:00
|
|
|
let prev = g:deoplete#_prev_completion
|
|
|
|
if a:event ==# 'Async' || a:event ==# 'Update' || mode() !=# 'i'
|
|
|
|
\ || empty(get(prev, 'candidates', []))
|
|
|
|
\ || s:check_input_method()
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let input = deoplete#util#get_input(a:event)
|
|
|
|
let complete_str = matchstr(input, '\w\+$')
|
|
|
|
let min_pattern_length = deoplete#custom#_get_option('min_pattern_length')
|
|
|
|
if prev.linenr != line('.') || len(complete_str) < min_pattern_length
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let mode = deoplete#custom#_get_option('prev_completion_mode')
|
|
|
|
let candidates = copy(prev.candidates)
|
|
|
|
|
|
|
|
if mode ==# 'filter' || mode ==# 'length'
|
|
|
|
let input = input[prev.complete_position :]
|
|
|
|
let escaped_input = escape(input, '~\.^$[]*')
|
|
|
|
let pattern = substitute(escaped_input, '\w', '\\w*\0', 'g')
|
2022-04-23 12:09:55 +08:00
|
|
|
call filter(candidates, { _, val -> val.word =~? pattern })
|
2020-06-18 23:07:37 +08:00
|
|
|
if mode ==# 'length'
|
2022-04-23 12:09:55 +08:00
|
|
|
call filter(candidates, { _, val -> len(val.word) > len(input) })
|
2020-06-18 23:07:37 +08:00
|
|
|
endif
|
|
|
|
elseif mode ==# 'mirror'
|
|
|
|
" pass
|
|
|
|
else
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let g:deoplete#_filtered_prev = {
|
|
|
|
\ 'complete_position': prev.complete_position,
|
|
|
|
\ 'candidates': candidates,
|
|
|
|
\ }
|
2022-04-23 12:09:55 +08:00
|
|
|
return 1
|
2020-06-18 23:07:37 +08:00
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! deoplete#handler#_async_timer_start() abort
|
|
|
|
let delay = deoplete#custom#_get_option('auto_refresh_delay')
|
|
|
|
if delay <= 0
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
call timer_start(max([20, delay]), {-> deoplete#auto_complete()})
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! deoplete#handler#_completion_begin(event) abort
|
|
|
|
call deoplete#custom#_update_cache()
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
let auto_popup = deoplete#custom#_get_option(
|
|
|
|
\ 'auto_complete_popup') !=# 'manual'
|
|
|
|
let prev_input = get(g:deoplete#_context, 'input', '')
|
|
|
|
let cur_input = deoplete#util#get_input(a:event)
|
|
|
|
|
|
|
|
let check_back_space = auto_popup
|
|
|
|
\ && cur_input !=# prev_input
|
|
|
|
\ && len(cur_input) + 1 ==# len(prev_input)
|
|
|
|
\ && stridx(prev_input, cur_input) == 0
|
|
|
|
let refresh_backspace = deoplete#custom#_get_option('refresh_backspace')
|
|
|
|
|
|
|
|
if s:is_skip(a:event) || (check_back_space && !refresh_backspace)
|
2020-06-18 23:07:37 +08:00
|
|
|
let g:deoplete#_context.candidates = []
|
2022-04-23 12:09:55 +08:00
|
|
|
let g:deoplete#_context.input = cur_input
|
2020-06-18 23:07:37 +08:00
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
if auto_popup && deoplete#handler#_check_prev_completion(a:event)
|
|
|
|
call feedkeys("\<Plug>+", 'i')
|
|
|
|
endif
|
2020-06-18 23:07:37 +08:00
|
|
|
|
|
|
|
if a:event !=# 'Update' && a:event !=# 'Async'
|
|
|
|
call deoplete#init#_prev_completion()
|
|
|
|
endif
|
|
|
|
|
|
|
|
call deoplete#util#rpcnotify(
|
|
|
|
\ 'deoplete_auto_completion_begin', {'event': a:event})
|
2022-04-23 12:09:55 +08:00
|
|
|
|
|
|
|
" For <BS> popup flicker
|
|
|
|
if check_back_space && empty(v:completed_item)
|
|
|
|
call feedkeys("\<Plug>_", 'i')
|
|
|
|
endif
|
2020-06-18 23:07:37 +08:00
|
|
|
endfunction
|
|
|
|
function! s:is_skip(event) abort
|
|
|
|
if a:event ==# 'TextChangedP' && !empty(v:completed_item)
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
" Note: The check is needed for <C-y> mapping
|
|
|
|
if s:is_skip_prev_text(a:event)
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
if s:is_skip_text(a:event)
|
|
|
|
" Close the popup
|
|
|
|
if deoplete#util#check_popup()
|
|
|
|
call feedkeys("\<Plug>_", 'i')
|
|
|
|
endif
|
|
|
|
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
" Check nofile buffers
|
|
|
|
if &l:buftype =~# 'nofile' && bufname('%') !=# '[Command Line]'
|
|
|
|
let nofile_complete_filetypes = deoplete#custom#_get_option(
|
|
|
|
\ 'nofile_complete_filetypes')
|
|
|
|
if index(nofile_complete_filetypes, &l:filetype) < 0
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
|
2020-06-18 23:07:37 +08:00
|
|
|
let auto_complete = deoplete#custom#_get_option('auto_complete')
|
|
|
|
|
|
|
|
if &paste
|
|
|
|
\ || (a:event !=# 'Manual' && a:event !=# 'Update' && !auto_complete)
|
|
|
|
\ || v:insertmode !=# 'i'
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
function! s:is_skip_prev_text(event) abort
|
|
|
|
let input = deoplete#util#get_input(a:event)
|
|
|
|
|
|
|
|
" Note: Use g:deoplete#_context is needed instead of
|
|
|
|
" g:deoplete#_prev_completion
|
|
|
|
let prev_input = get(g:deoplete#_context, 'input', '')
|
|
|
|
if input ==# prev_input
|
|
|
|
\ && input !=# ''
|
|
|
|
\ && a:event !=# 'Manual'
|
|
|
|
\ && a:event !=# 'Async'
|
|
|
|
\ && a:event !=# 'Update'
|
|
|
|
\ && a:event !=# 'TextChangedP'
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
" Note: It fixes insert first candidate automatically problem
|
|
|
|
if a:event ==# 'Update' && prev_input !=# '' && input !=# prev_input
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
return 0
|
|
|
|
endfunction
|
|
|
|
function! s:is_skip_text(event) abort
|
|
|
|
let input = deoplete#util#get_input(a:event)
|
2022-04-23 12:09:55 +08:00
|
|
|
if !has('nvim') && iconv(iconv(input, 'utf-8', 'utf-16'),
|
|
|
|
\ 'utf-16', 'utf-8') !=# input
|
|
|
|
" In Vim8, invalid bytes brokes nvim-yarp.
|
|
|
|
return 1
|
|
|
|
endif
|
2020-06-18 23:07:37 +08:00
|
|
|
|
|
|
|
let lastchar = matchstr(input, '.$')
|
|
|
|
let skip_multibyte = deoplete#custom#_get_option('skip_multibyte')
|
|
|
|
if skip_multibyte && len(lastchar) != strwidth(lastchar)
|
|
|
|
\ && empty(get(b:, 'eskk', []))
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
|
|
|
let displaywidth = strdisplaywidth(input) + 1
|
|
|
|
let is_virtual = virtcol('.') >= displaywidth
|
|
|
|
if &l:formatoptions =~# '[tca]' && &l:textwidth > 0
|
|
|
|
\ && displaywidth >= &l:textwidth
|
|
|
|
if &l:formatoptions =~# '[ta]'
|
|
|
|
\ || !empty(filter(deoplete#util#get_syn_names(),
|
2022-04-23 12:09:55 +08:00
|
|
|
\ { _, val -> val ==# 'Comment' }))
|
2020-06-18 23:07:37 +08:00
|
|
|
\ || is_virtual
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
if a:event =~# '^TextChanged' && s:matched_indentkeys(input) !=# ''
|
|
|
|
call deoplete#util#indent_current_line()
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
|
2020-06-18 23:07:37 +08:00
|
|
|
let skip_chars = deoplete#custom#_get_option('skip_chars')
|
|
|
|
|
|
|
|
return (a:event !=# 'Manual' && input !=# ''
|
|
|
|
\ && index(skip_chars, input[-1:]) >= 0)
|
|
|
|
endfunction
|
|
|
|
function! s:check_input_method() abort
|
|
|
|
return exists('*getimstatus') && getimstatus()
|
|
|
|
endfunction
|
2022-04-23 12:09:55 +08:00
|
|
|
function! s:matched_indentkeys(input) abort
|
|
|
|
if &l:indentexpr ==# ''
|
|
|
|
" Disable auto indent
|
|
|
|
return ''
|
|
|
|
endif
|
|
|
|
|
|
|
|
" Note: check the last word
|
|
|
|
let checkstr = matchstr(a:input, '\w\+$')
|
|
|
|
|
|
|
|
for word in filter(map(split(&l:indentkeys, ','),
|
|
|
|
\ { _, val -> matchstr(val, 'e\|=\zs.*') }),
|
|
|
|
\ { _, val -> val !=# '' && val =~# '\h\w*' })
|
|
|
|
|
|
|
|
if word ==# 'e'
|
|
|
|
let word = 'else'
|
|
|
|
endif
|
|
|
|
|
|
|
|
let lastpos = len(a:input) - len(word)
|
|
|
|
if checkstr ==# word || (word =~# '^\W\+$' &&
|
|
|
|
\ lastpos >= 0 && strridx(a:input, word) == lastpos)
|
|
|
|
return word
|
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
|
|
|
|
return ''
|
|
|
|
endfunction
|
2020-06-18 23:07:37 +08:00
|
|
|
|
|
|
|
function! s:define_on_event(event) abort
|
|
|
|
if !exists('##' . a:event)
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
execute 'autocmd deoplete' a:event
|
|
|
|
\ '* call deoplete#send_event('.string(a:event).')'
|
|
|
|
endfunction
|
|
|
|
function! s:define_completion_via_timer(event) abort
|
|
|
|
if !exists('##' . a:event)
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
execute 'autocmd deoplete' a:event
|
|
|
|
\ '* call s:completion_timer_start('.string(a:event).')'
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:on_insert_leave() abort
|
|
|
|
call deoplete#mapping#_restore_completeopt()
|
|
|
|
let g:deoplete#_context = {}
|
|
|
|
call deoplete#init#_prev_completion()
|
|
|
|
|
|
|
|
if &cpoptions =~# '$'
|
|
|
|
" If 'cpoptions' includes '$' with popup, redraw problem exists.
|
|
|
|
redraw
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:on_complete_done() abort
|
|
|
|
if get(v:completed_item, 'word', '') ==# ''
|
2022-04-23 12:09:55 +08:00
|
|
|
\ || !has_key(g:deoplete#_context, 'complete_str')
|
2020-06-18 23:07:37 +08:00
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
call deoplete#handler#_skip_next_completion()
|
|
|
|
|
2022-04-23 12:09:55 +08:00
|
|
|
let max_used = 100
|
|
|
|
let g:deoplete#_recently_used = insert(
|
|
|
|
\ g:deoplete#_recently_used,
|
|
|
|
\ tolower(v:completed_item.word),
|
|
|
|
\ )
|
|
|
|
let min_pattern_length = deoplete#custom#_get_option('min_pattern_length')
|
|
|
|
if len(g:deoplete#_context['complete_str']) > min_pattern_length
|
|
|
|
let g:deoplete#_recently_used = insert(
|
|
|
|
\ g:deoplete#_recently_used,
|
|
|
|
\ tolower(g:deoplete#_context['complete_str']),
|
|
|
|
\ )
|
|
|
|
endif
|
|
|
|
let g:deoplete#_recently_used = deoplete#util#uniq(
|
|
|
|
\ g:deoplete#_recently_used)[: max_used]
|
|
|
|
|
2020-06-18 23:07:37 +08:00
|
|
|
let user_data = get(v:completed_item, 'user_data', '')
|
|
|
|
if type(user_data) !=# v:t_string || user_data ==# ''
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
try
|
|
|
|
call s:substitute_suffix(json_decode(user_data))
|
|
|
|
catch /.*/
|
|
|
|
endtry
|
|
|
|
endfunction
|
|
|
|
function! s:substitute_suffix(user_data) abort
|
|
|
|
if !deoplete#custom#_get_option('complete_suffix')
|
|
|
|
\ || !has_key(a:user_data, 'old_suffix')
|
|
|
|
\ || !has_key(a:user_data, 'new_suffix')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
let old_suffix = a:user_data.old_suffix
|
|
|
|
let new_suffix = a:user_data.new_suffix
|
|
|
|
|
|
|
|
let next_text = deoplete#util#get_next_input('CompleteDone')
|
|
|
|
if stridx(next_text, old_suffix) != 0
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let next_text = new_suffix . next_text[len(old_suffix):]
|
|
|
|
call setline('.', deoplete#util#get_input('CompleteDone') . next_text)
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! deoplete#handler#_skip_next_completion() abort
|
|
|
|
if !exists('g:deoplete#_context')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let input = deoplete#util#get_input('CompleteDone')
|
|
|
|
if input !~# '[/.]$'
|
|
|
|
let g:deoplete#_context.input = input
|
|
|
|
endif
|
|
|
|
call deoplete#mapping#_restore_completeopt()
|
|
|
|
call deoplete#init#_prev_completion()
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:is_exiting() abort
|
|
|
|
return exists('v:exiting') && v:exiting != v:null
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:kill_yarp() abort
|
|
|
|
if !exists('g:deoplete#_yarp')
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
if g:deoplete#_yarp.job_is_dead
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
|
|
|
|
let job = g:deoplete#_yarp.job
|
|
|
|
if !has('nvim') && !exists('g:yarp_jobstart')
|
|
|
|
" Get job object from vim-hug-neovim-rpc
|
|
|
|
let job = g:_neovim_rpc_jobs[job].job
|
|
|
|
endif
|
|
|
|
|
|
|
|
if has('nvim')
|
|
|
|
call jobstop(job)
|
|
|
|
else
|
|
|
|
call job_stop(job, 'kill')
|
|
|
|
endif
|
|
|
|
|
|
|
|
let g:deoplete#_yarp.job_is_dead = 1
|
|
|
|
endfunction
|