"=============================================================================
" 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()
    autocmd InsertLeave * call s:completion_timer_stop()
  augroup END

  for event in ['InsertEnter', 'BufWritePost', 'DirChanged']
    call s:define_on_event(event)
  endfor

  call s:define_completion_via_timer('TextChangedI')
  if g:deoplete#enable_on_insert_enter
    call s:define_completion_via_timer('InsertEnter')
  endif
  if g:deoplete#enable_refresh_always
    call s:define_completion_via_timer('InsertCharPre')
  endif

  " Note: Vim 8 GUI is broken
  " dummy timer call is needed before complete()
  if !has('nvim') && has('gui_running')
    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', '')
  let modes = (event ==# 'InsertEnter') ? ['n', 'i'] : ['i']
  if s:is_exiting() || index(modes, mode()) < 0
    call s:completion_timer_stop()
    return
  endif

  if empty(get(context, 'candidates', []))
        \ || deoplete#util#get_input(context.event) !=# context.input
    return
  endif

  let prev = g:deoplete#_prev_completion
  let prev.event = context.event
  let prev.candidates = context.candidates
  let prev.complete_position = getpos('.')

  if context.event ==# 'Manual'
    let context.event = ''
  elseif !exists('g:deoplete#_saved_completeopt')
    call deoplete#mapping#_set_completeopt()
  endif

  if g:deoplete#complete_method ==# 'complete'
    call feedkeys("\<Plug>_", 'i')
  elseif g:deoplete#complete_method ==# 'completefunc'
    let &l:completefunc = 'deoplete#mapping#_completefunc'
    call feedkeys("\<C-x>\<C-u>", 'in')
  elseif g:deoplete#complete_method ==# 'omnifunc'
    let &l:omnifunc = 'deoplete#mapping#_completefunc'
    call feedkeys("\<C-x>\<C-o>", 'in')
  endif
endfunction

function! s:completion_timer_start(event) abort
  if exists('s:completion_timer')
    call s:completion_timer_stop()
  endif

  let delay = max([20, g:deoplete#auto_complete_delay])
  let s:completion_timer = timer_start(
        \ delay, {-> s:completion_begin(a:event)})
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

function! deoplete#handler#_async_timer_start() abort
  if exists('s:async_timer')
    call deoplete#handler#_async_timer_stop()
  endif

  let s:async_timer = { 'event': 'Async', 'changedtick': b:changedtick }
  let s:async_timer.id = timer_start(
        \ max([20, g:deoplete#auto_refresh_delay]),
        \ function('s:completion_async'), {'repeat': -1})
endfunction
function! deoplete#handler#_async_timer_stop() abort
  if exists('s:async_timer')
    call timer_stop(s:async_timer.id)
    unlet s:async_timer
  endif
endfunction
function! s:completion_async(timer) abort
  if mode() !=# 'i' || s:is_exiting()
    call deoplete#handler#_async_timer_stop()
    return
  endif

  call s:completion_begin(s:async_timer.event)
endfunction

function! s:completion_begin(event) abort
  if s:is_skip(a:event)
    call deoplete#mapping#_restore_completeopt()
    let g:deoplete#_context.candidates = []
    return
  endif

  let context = deoplete#init#_context(a:event, [])
  if s:check_omnifunc(context)
    return
  endif

  call deoplete#util#rpcnotify(
        \ 'deoplete_auto_completion_begin', context)
endfunction
function! s:is_skip(event) abort
  if s:is_skip_text(a:event)
    return 1
  endif

  let disable_auto_complete =
        \ deoplete#util#get_simple_buffer_config(
        \   'b:deoplete_disable_auto_complete',
        \   'g:deoplete#disable_auto_complete')

  if &paste
        \ || (a:event !=# 'Manual' && a:event !=# 'Async'
        \     && disable_auto_complete)
        \ || (&l:completefunc !=# '' && &l:buftype =~# 'nofile')
        \ || (a:event !=# 'InsertEnter' && mode() !=# 'i')
    return 1
  endif

  return 0
endfunction
function! s:is_skip_text(event) abort
  let context = g:deoplete#_context
  let input = deoplete#util#get_input(a:event)

  if has_key(context, 'input')
        \ && a:event !=# 'Manual'
        \ && a:event !=# 'Async'
        \ && input ==# context.input
    return 1
  endif

  let displaywidth = strdisplaywidth(input) + 1
  if &l:formatoptions =~# '[tca]' && &l:textwidth > 0
        \     && displaywidth >= &l:textwidth
    if &l:formatoptions =~# '[ta]'
          \ || !empty(filter(deoplete#util#get_syn_names(),
          \                  "v:val ==# 'Comment'"))
      return 1
    endif
  endif

  let skip_chars = deoplete#util#get_simple_buffer_config(
        \   'b:deoplete_skip_chars', 'g:deoplete#skip_chars')

  return (!pumvisible() && virtcol('.') != displaywidth)
        \ || (a:event !=# 'Manual' && input !=# ''
        \     && index(skip_chars, input[-1:]) >= 0)
endfunction
function! s:check_omnifunc(context) abort
  let prev = g:deoplete#_prev_completion
  let blacklist = ['LanguageClient#complete']
  if prev.event ==# 'Manual'
        \ || &l:omnifunc ==# ''
        \ || index(blacklist, &l:omnifunc) >= 0
        \ || prev.complete_position ==# getpos('.')
    return
  endif

  for filetype in a:context.filetypes
    for pattern in deoplete#util#convert2list(
          \ deoplete#util#get_buffer_config(filetype,
          \ 'b:deoplete_omni_patterns',
          \ 'g:deoplete#omni_patterns',
          \ 'g:deoplete#_omni_patterns'))
      if pattern !=# '' && a:context.input =~# '\%('.pattern.'\)$'
        let g:deoplete#_context.candidates = []

        let prev.event = a:context.event
        let prev.candidates = []
        let prev.complete_position = getpos('.')

        call deoplete#mapping#_set_completeopt()
        call feedkeys("\<C-x>\<C-o>", 'in')
        return 1
      endif
    endfor
  endfor
endfunction

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 = {}
  let g:deoplete#_prev_completion = {
        \ 'complete_position': [],
        \ 'candidates': [],
        \ 'event': '',
        \ }
endfunction

function! s:on_complete_done() abort
  if get(v:completed_item, 'word', '') ==# ''
    return
  endif

  let word = v:completed_item.word
  if !has_key(g:deoplete#_rank, word)
    let g:deoplete#_rank[word] = 1
  else
    let g:deoplete#_rank[word] += 1
  endif
endfunction

function! deoplete#handler#_skip_next_completion() abort
  if !exists('g:deoplete#_context')
    return
  endif

  let input = deoplete#util#get_input('CompleteDone')
  if input[-1:] !=# '/'
    let g:deoplete#_context.input = input
  endif
endfunction

function! s:is_exiting() abort
  return exists('v:exiting') && v:exiting != v:null
endfunction

function! s:kill_yarp() abort
  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
