Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[READY] Rewrite completion system #2657

Merged
merged 2 commits into from
Jun 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,19 @@ Default: `['<S-TAB>', '<Up>']`
let g:ycm_key_list_previous_completion = ['<S-TAB>', '<Up>']
```

### The `g:ycm_key_list_stop_completion` option

This option controls the key mappings used to close the completion menu. This is
useful when the menu is blocking the view, when you need to insert the `<TAB>`
character, or when you want to expand a snippet from [UltiSnips][] and navigate
through it.

Default: `['<C-y>']`

```viml
let g:ycm_key_list_stop_completion = ['<C-y>']
```

### The `g:ycm_key_invoke_completion` option

This option controls the key mapping used to invoke the completion menu for
Expand Down
224 changes: 122 additions & 102 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ 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:completion_stopped = 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 +107,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 +125,10 @@ function! youcompleteme#EnableCursorMovedAutocommands()
autocmd CursorMoved * call s:OnCursorMovedNormalMode()
autocmd TextChanged * call s:OnTextChangedNormalMode()
autocmd TextChangedI * call s:OnTextChangedInsertMode()
" The TextChangedI event is not triggered when inserting a character while
" the completion menu is open. We handle this by closing the completion menu
" just before inserting a character.
autocmd InsertCharPre * call s:OnInsertChar()
augroup END
endfunction

Expand Down Expand Up @@ -206,13 +216,19 @@ function! s:SetUpKeyMappings()
\ ' pumvisible() ? "\<C-n>" : "\' . key .'"'
endfor


for key in g:ycm_key_list_previous_completion
" This selects the previous candidate for shift-tab (default)
exe 'inoremap <expr>' . key .
\ ' pumvisible() ? "\<C-p>" : "\' . key .'"'
endfor

for key in g:ycm_key_list_stop_completion
" When selecting a candidate and closing the completion menu with the <C-y>
" key, the menu will automatically be reopened because of the TextChangedI
" event. We define a command to prevent that.
exe 'inoremap <expr>' . key . ' <SID>StopCompletion( "\' . key . '" )'
endfor

if !empty( g:ycm_key_invoke_completion )
let invoke_key = g:ycm_key_invoke_completion

Expand All @@ -221,15 +237,22 @@ 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

" The TextChangedI event is not triggered when deleting a character while the
" completion menu is open. We handle this by closing the completion menu on
" the keys that delete 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 +429,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 +451,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 +464,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 @@ -502,24 +528,30 @@ function! s:PollFileParseResponse( ... )
endfunction


function! s:SetCompleteFunc()
let &completefunc = 'youcompleteme#Complete'
let &l:completefunc = 'youcompleteme#Complete'
function! s:OnInsertChar()
call timer_stop( s:pollers.completion.id )
if pumvisible()
call feedkeys( "\<C-e>", 'n' )
endif
endfunction


function! s:SetOmnicompleteFunc()
if s:Pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
let &omnifunc = 'youcompleteme#OmniComplete'
let &l:omnifunc = 'youcompleteme#OmniComplete'
function! s:OnDeleteChar( key )
call timer_stop( s:pollers.completion.id )
if pumvisible()
return "\<C-y>" . a:key
endif
return a:key
endfunction


" 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 = ''
function! s:StopCompletion( key )
call timer_stop( s:pollers.completion.id )
if pumvisible()
let s:completion_stopped = 1
return "\<C-y>"
endif
return a:key
endfunction


Expand All @@ -546,23 +578,34 @@ function! s:OnTextChangedInsertMode()
return
endif

exec s:python_command "ycm_state.OnCursorMoved()"
call s:UpdateCursorMoved()
if s:completion_stopped
let s:completion_stopped = 0
let s:completion = s:default_completion
return
endif

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

" 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

if g:ycm_auto_trigger || s:omnifunc_mode
if &completefunc == "youcompleteme#CompleteFunc" &&
\ ( g:ycm_auto_trigger || s:force_semantic ) &&
\ !s:InsideCommentOrStringAndShouldStop() &&
\ !s:OnBlankLine()
" Immediately call previous completion to avoid flickers.
call s:Complete()
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
exec s:python_command "ycm_state.OnCursorMoved()"

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

Expand All @@ -572,7 +615,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 @@ -582,22 +626,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 @@ -619,7 +647,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 @@ -662,28 +691,45 @@ endfunction


function! s:InvokeCompletion()
if &completefunc != "youcompleteme#Complete"
return
endif
exec s:python_command "ycm_state.SendCompletionRequest(" .
\ "vimsupport.GetBoolValue( 's:force_semantic' ) )"

call s:PollCompletion()
endfunction

if s:InsideCommentOrStringAndShouldStop() || s:OnBlankLine()
return
endif

" 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: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_=). We don't want to
" insert anything so we return an empty string.
return ''
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 @@ -693,43 +739,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
Loading