Skip to content

Commit

Permalink
Rewrite completion system
Browse files Browse the repository at this point in the history
  • Loading branch information
micbou committed May 22, 2017
1 parent 263bd88 commit cb83e99
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 235 deletions.
209 changes: 106 additions & 103 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ set cpo&vim

" This needs to be called outside of a function
let s:script_folder_path = escape( expand( '<sfile>:p:h' ), '\' )
let s:omnifunc_mode = 0

let s:old_cursor_position = []
let s:cursor_moved = 0
let s:force_semantic = 0
let s:default_completion = {
\ 'start_column': -1,
\ 'candidates': []
\ }
let s:completion = s:default_completion
let s:previous_allowed_buffer_number = 0
let s:pollers = {
\ 'completion': {
\ 'id': -1,
\ 'wait_milliseconds': 10
\ },
\ 'file_parse_response': {
\ 'id': -1,
\ 'wait_milliseconds': 100
Expand Down Expand Up @@ -100,7 +106,6 @@ function! youcompleteme#Enable()
autocmd BufEnter * call s:OnBufferEnter()
autocmd BufUnload * call s:OnBufferUnload()
autocmd InsertLeave * call s:OnInsertLeave()
autocmd InsertEnter * call s:OnInsertEnter()
autocmd VimLeave * call s:OnVimLeave()
autocmd CompleteDone * call s:OnCompleteDone()
augroup END
Expand All @@ -119,6 +124,7 @@ function! youcompleteme#EnableCursorMovedAutocommands()
autocmd CursorMoved * call s:OnCursorMovedNormalMode()
autocmd TextChanged * call s:OnTextChangedNormalMode()
autocmd TextChangedI * call s:OnTextChangedInsertMode()
autocmd InsertCharPre * call s:OnInsertCharPre()
augroup END
endfunction

Expand Down Expand Up @@ -221,15 +227,20 @@ function! s:SetUpKeyMappings()
imap <Nul> <C-Space>
endif

" <c-x><c-o> trigger omni completion, <c-p> deselects the first completion
" candidate that vim selects by default
silent! exe 'inoremap <unique> ' . invoke_key . ' <C-X><C-O><C-P>'
silent! exe 'inoremap <unique> <silent> ' . invoke_key .
\ ' <C-R>=<SID>InvokeSemanticCompletion()<CR>'
endif

if !empty( g:ycm_key_detailed_diagnostics )
silent! exe 'nnoremap <unique> ' . g:ycm_key_detailed_diagnostics .
\ ' :YcmShowDetailedDiagnostic<cr>'
\ ' :YcmShowDetailedDiagnostic<CR>'
endif

" Update completions when deleting a character in insert mode.
for key in [ "<BS>", "<C-h>" ]
silent! exe 'inoremap <unique> <expr> ' . key .
\ ' <SID>OnDeleteChar( "\' . key . '" )'
endfor
endfunction


Expand Down Expand Up @@ -406,6 +417,11 @@ function! s:SetUpCompleteopt()
endfunction


function! s:SetCompleteFunc()
let &completefunc = 'youcompleteme#CompleteFunc'
endfunction


function! s:OnVimLeave()
exec s:python_command "ycm_state.OnVimLeave()"
endfunction
Expand All @@ -423,7 +439,6 @@ function! s:OnFileTypeSet()

call s:SetUpCompleteopt()
call s:SetCompleteFunc()
call s:SetOmnicompleteFunc()

exec s:python_command "ycm_state.OnBufferVisit()"
call s:OnFileReadyToParse( 1 )
Expand All @@ -437,7 +452,6 @@ function! s:OnBufferEnter()

call s:SetUpCompleteopt()
call s:SetCompleteFunc()
call s:SetOmnicompleteFunc()

exec s:python_command "ycm_state.OnBufferVisit()"
" Last parse may be outdated because of changes from other buffers. Force a
Expand Down Expand Up @@ -504,24 +518,10 @@ function! s:PollFileParseResponse( ... )
endfunction


function! s:SetCompleteFunc()
let &completefunc = 'youcompleteme#Complete'
let &l:completefunc = 'youcompleteme#Complete'
endfunction


function! s:SetOmnicompleteFunc()
if s:Pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
let &omnifunc = 'youcompleteme#OmniComplete'
let &l:omnifunc = 'youcompleteme#OmniComplete'

" If we don't have native filetype support but the omnifunc is set to YCM's
" omnifunc because the previous file the user was editing DID have native
" support, we remove our omnifunc.
elseif &omnifunc == 'youcompleteme#OmniComplete'
let &omnifunc = ''
let &l:omnifunc = ''
endif
function! s:OnInsertCharPre()
" The TextChangedI event is not triggered when the completion menu is open. We
" solve this by closing the completion menu just before inserting a character.
call s:StopCompletion()
endfunction


Expand All @@ -548,24 +548,27 @@ function! s:OnTextChangedInsertMode()
return
endif

call s:IdentifierFinishedOperations()

" We have to make sure we correctly leave semantic mode even when the user
" inserts something like a "operator[]" candidate string which fails
" CurrentIdentifierFinished check.
if s:force_semantic && !s:Pyeval( 'base.LastEnteredCharIsIdentifierChar()' )
let s:force_semantic = 0
endif

" Immediately call previous completion to avoid flickers.
call s:Complete()

exec s:python_command "ycm_state.OnCursorMoved()"
call s:UpdateCursorMoved()

call s:IdentifierFinishedOperations()
if g:ycm_autoclose_preview_window_after_completion
call s:ClosePreviewWindowIfNeeded()
endif

if g:ycm_auto_trigger || s:omnifunc_mode
if g:ycm_auto_trigger || s:force_semantic
call s:InvokeCompletion()
endif

" We have to make sure we correctly leave omnifunc mode even when the user
" inserts something like a "operator[]" candidate string which fails
" CurrentIdentifierFinished check.
if s:omnifunc_mode && !s:Pyeval( 'base.LastEnteredCharIsIdentifierChar()')
let s:omnifunc_mode = 0
endif
endfunction


Expand All @@ -574,7 +577,8 @@ function! s:OnInsertLeave()
return
endif

let s:omnifunc_mode = 0
let s:force_semantic = 0
let s:completion = s:default_completion
call s:OnFileReadyToParse()
exec s:python_command "ycm_state.OnInsertLeave()"
if g:ycm_autoclose_preview_window_after_completion ||
Expand All @@ -584,22 +588,6 @@ function! s:OnInsertLeave()
endfunction


function! s:OnInsertEnter()
if !s:AllowedToCompleteInCurrentBuffer()
return
endif

let s:old_cursor_position = []
endfunction


function! s:UpdateCursorMoved()
let current_position = getpos('.')
let s:cursor_moved = current_position != s:old_cursor_position
let s:old_cursor_position = current_position
endfunction


function! s:ClosePreviewWindowIfNeeded()
let current_buffer_name = bufname('')

Expand All @@ -621,7 +609,8 @@ function! s:IdentifierFinishedOperations()
return
endif
exec s:python_command "ycm_state.OnCurrentIdentifierFinished()"
let s:omnifunc_mode = 0
let s:force_semantic = 0
let s:completion = s:default_completion
endfunction


Expand Down Expand Up @@ -664,28 +653,68 @@ endfunction


function! s:InvokeCompletion()
if &completefunc != "youcompleteme#Complete"
if s:InsideCommentOrStringAndShouldStop() || s:OnBlankLine()
call s:StopCompletion()
return
endif

if s:InsideCommentOrStringAndShouldStop() || s:OnBlankLine()
return
exec s:python_command "ycm_state.SendCompletionRequest(" .
\ "vimsupport.GetBoolValue( 's:force_semantic' ) )"

call s:PollCompletion()
endfunction


function! s:InvokeSemanticCompletion()
let s:force_semantic = 1
exec s:python_command "ycm_state.SendCompletionRequest( True )"

call s:PollCompletion()
" Since this function is called in a mapping through the expression register
" <C-R>=, its return value is inserted (see :h c_CTRL-R_=).
return ''
endfunction


function! s:StopCompletion()
call timer_stop( s:pollers.completion.id )
if pumvisible()
call feedkeys( "\<C-e>", 'n' )
endif
endfunction

" This is tricky. First, having 'refresh' set to 'always' in the dictionary
" that our completion function returns makes sure that our completion function
" is called on every keystroke. Second, when the sequence of characters the
" user typed produces no results in our search an infinite loop can occur. The
" problem is that our feedkeys call triggers the OnCursorMovedI event which we
" are tied to. We prevent this infinite loop from starting by making sure that
" the user has moved the cursor since the last time we provided completion
" results.
if !s:cursor_moved

function! s:OnDeleteChar( char )
" We can't use s:StopCompletion here as we don't want to throw away the
" completed text before deleting the character.
call timer_stop( s:pollers.completion.id )
if pumvisible()
return "\<C-y>" . a:char
endif
return a:char
endfunction


function! s:PollCompletion( ... )
if !s:Pyeval( 'ycm_state.CompletionRequestReady()' )
let s:pollers.completion.id = timer_start(
\ s:pollers.completion.wait_milliseconds,
\ function( 's:PollCompletion' ) )
return
endif

let response = s:Pyeval( 'ycm_state.GetCompletionResponse()' )
let s:completion = {
\ 'start_column': response.completion_start_column,
\ 'candidates': response.completions
\ }
call s:Complete()
endfunction


function! s:Complete()
" <c-x><c-u> invokes the user's completion function (which we have set to
" youcompleteme#Complete), and <c-p> tells Vim to select the previous
" youcompleteme#CompleteFunc), and <c-p> tells Vim to select the previous
" completion candidate. This is necessary because by default, Vim selects the
" first candidate when completion is invoked, and selecting a candidate
" automatically replaces the current text with it. Calling <c-p> forces Vim to
Expand All @@ -695,43 +724,17 @@ function! s:InvokeCompletion()
endfunction


" This is our main entry point. This is what vim calls to get completions.
function! youcompleteme#Complete( findstart, base )
" After the user types one character after the call to the omnifunc, the
" completefunc will be called because of our mapping that calls the
" completefunc on every keystroke. Therefore we need to delegate the call we
" 'stole' back to the omnifunc
if s:omnifunc_mode
return youcompleteme#OmniComplete( a:findstart, a:base )
endif

function! youcompleteme#CompleteFunc( findstart, base )
if a:findstart
" InvokeCompletion has this check but we also need it here because of random
" Vim bugs and unfortunate interactions with the autocommands of other
" plugins
if !s:cursor_moved
" for vim, -2 means not found but don't trigger an error message
" see :h complete-functions
if s:completion.start_column > col( '.' ) ||
\ empty( s:completion.candidates )
" For vim, -2 means not found but don't trigger an error message.
" See :h complete-functions.
return -2
endif

exec s:python_command "ycm_state.CreateCompletionRequest()"
return s:Pyeval( 'base.CompletionStartColumn()' )
else
return s:Pyeval( 'ycm_state.GetCompletions()' )
endif
endfunction


function! youcompleteme#OmniComplete( findstart, base )
if a:findstart
let s:omnifunc_mode = 1
exec s:python_command "ycm_state.CreateCompletionRequest(" .
\ "force_semantic = True )"
return s:Pyeval( 'base.CompletionStartColumn()' )
else
return s:Pyeval( 'ycm_state.GetCompletions()' )
return s:completion.start_column - 1
endif
return s:completion.candidates
endfunction


Expand Down
8 changes: 0 additions & 8 deletions python/ycm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from future.utils import iteritems
from ycm import vimsupport
from ycmd import user_options_store
from ycmd import request_wrap
from ycmd import identifier_utils

YCM_VAR_PREFIX = 'ycm_'
Expand Down Expand Up @@ -57,13 +56,6 @@ def LoadJsonDefaultsIntoVim():
vimsupport.SetVariableValue( new_key, value )


def CompletionStartColumn():
return ( request_wrap.CompletionStartColumn(
vimsupport.CurrentLineContents(),
vimsupport.CurrentColumn() + 1,
vimsupport.CurrentFiletypes()[ 0 ] ) - 1 )


def CurrentIdentifierFinished():
current_column = vimsupport.CurrentColumn()
previous_char_index = current_column - 1
Expand Down
Loading

0 comments on commit cb83e99

Please sign in to comment.