-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Enhancements for Neovim #1079
Enhancements for Neovim #1079
Changes from all commits
3777ca2
5040cfe
1cc325e
5ebfe98
24ec726
21060ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,11 @@ | |
" This is basic vim plugin boilerplate | ||
let s:save_cpo = &cpo | ||
set cpo&vim | ||
|
||
let s:is_nvim = has( 'neovim' ) | ||
if s:is_nvim | ||
let s:channel_id = pyeval( 'vim.channel_id' ) | ||
endif | ||
|
||
" This needs to be called outside of a function | ||
let s:script_folder_path = escape( expand( '<sfile>:p:h' ), '\' ) | ||
|
@@ -120,6 +125,11 @@ function! s:SetUpPython() | |
|
||
py from ycm.youcompleteme import YouCompleteMe | ||
py ycm_state = YouCompleteMe( user_options_store.GetAll() ) | ||
|
||
if s:is_nvim | ||
call send_event( s:channel_id, 'ycm_setup', 0 ) | ||
endif | ||
|
||
return 1 | ||
endfunction | ||
|
||
|
@@ -164,9 +174,15 @@ function! s:SetUpKeyMappings() | |
let invoke_key = '<Nul>' | ||
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>' | ||
if s:is_nvim | ||
silent! exe 'inoremap ' . invoke_key . | ||
\ ' <C-R>=youcompleteme#BeginCompletion()<cr>' | ||
else | ||
" <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>' | ||
endif | ||
|
||
endif | ||
|
||
if !empty( g:ycm_key_detailed_diagnostics ) | ||
|
@@ -285,7 +301,7 @@ function! s:SetUpCpoptions() | |
|
||
" This prevents the display of "Pattern not found" & similar messages during | ||
" completion. This is only available since Vim 7.4.314 | ||
if pyeval( 'vimsupport.VimVersionAtLeast("7.4.314")' ) | ||
if !s:is_nvim && pyeval( 'vimsupport.VimVersionAtLeast("7.4.314")' ) | ||
set shortmess+=c | ||
endif | ||
endfunction | ||
|
@@ -328,7 +344,9 @@ endfunction | |
|
||
|
||
function! s:OnVimLeave() | ||
py ycm_state.OnVimLeave() | ||
if !s:is_nvim | ||
py ycm_state.OnVimLeave() | ||
endif | ||
endfunction | ||
|
||
|
||
|
@@ -344,7 +362,14 @@ function! s:OnBufferVisit() | |
|
||
call s:SetUpCompleteopt() | ||
call s:SetCompleteFunc() | ||
py ycm_state.OnBufferVisit() | ||
|
||
if s:is_nvim | ||
call send_event( s:channel_id, | ||
\ 'buffer_visit', | ||
\ youcompleteme#BuildRequestData( 0 ) ) | ||
else | ||
py ycm_state.OnBufferVisit() | ||
endif | ||
call s:OnFileReadyToParse() | ||
endfunction | ||
|
||
|
@@ -354,7 +379,11 @@ function! s:OnBufferUnload( deleted_buffer_file ) | |
return | ||
endif | ||
|
||
py ycm_state.OnBufferUnload( vim.eval( 'a:deleted_buffer_file' ) ) | ||
if s:is_nvim | ||
call send_event( s:channel_id, 'buffer_unload', a:deleted_buffer_file ) | ||
else | ||
py ycm_state.OnBufferUnload( vim.eval( 'a:deleted_buffer_file' ) ) | ||
endif | ||
endfunction | ||
|
||
|
||
|
@@ -373,15 +402,21 @@ function! s:OnFileReadyToParse() | |
" happen for special buffers. | ||
call s:SetUpYcmChangedTick() | ||
|
||
" Order is important here; we need to extract any done diagnostics before | ||
" reparsing the file again. If we sent the new parse request first, then | ||
" the response would always be pending when we called | ||
" UpdateDiagnosticNotifications. | ||
call s:UpdateDiagnosticNotifications() | ||
if !s:is_nvim | ||
" Order is important here; we need to extract any done diagnostics before | ||
" reparsing the file again. If we sent the new parse request first, then | ||
" the response would always be pending when we called | ||
" UpdateDiagnosticNotifications. | ||
call s:UpdateDiagnosticNotifications() | ||
endif | ||
|
||
let buffer_changed = b:changedtick != b:ycm_changedtick.file_ready_to_parse | ||
if buffer_changed | ||
py ycm_state.OnFileReadyToParse() | ||
if s:is_nvim | ||
call s:BeginCompilation() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first glance, this strikes me as wrong. ycmd expects to see a OnFileReadyToParse event and you're not sending it when the user is using neovim. I don't want to have two models of ycmd calling behavior: one when the user is using neovim and one for plain vim. That would be difficult to maintain and reason about. Given a sequence of equivalent keyboard presses in vim and neovim, both vim and neovim should call ycmd in the same way. The sequence of HTTP calls should be the same. There really isn't any good reason for it to not be the same since neovim support is just a different client. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does send the event, the only difference is that it will pack the request data before calling python. I agree that it's strange, so I will remove this as the performance impact is likely to be small |
||
else | ||
py ycm_state.OnFileReadyToParse() | ||
endif | ||
endif | ||
let b:ycm_changedtick.file_ready_to_parse = b:changedtick | ||
endfunction | ||
|
@@ -410,7 +445,10 @@ function! s:OnCursorMovedInsertMode() | |
return | ||
endif | ||
|
||
py ycm_state.OnCursorMoved() | ||
if !s:is_nvim | ||
py ycm_state.OnCursorMoved() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar here; why isn't this event being sent to ycmd? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 I didn't see an event being sent to ycmd in this case, just an update of the line/column number in the python class. I will fix to keep the current behavior |
||
endif | ||
|
||
call s:UpdateCursorMoved() | ||
|
||
" Basically, we need to only trigger the completion menu when the user has | ||
|
@@ -430,7 +468,11 @@ function! s:OnCursorMovedInsertMode() | |
endif | ||
|
||
if g:ycm_auto_trigger || s:omnifunc_mode | ||
call s:InvokeCompletion() | ||
if s:is_nvim | ||
call youcompleteme#BeginCompletion() | ||
else | ||
call s:InvokeCompletion() | ||
endif | ||
endif | ||
|
||
" We have to make sure we correctly leave omnifunc mode even when the user | ||
|
@@ -459,7 +501,11 @@ function! s:OnInsertLeave() | |
|
||
let s:omnifunc_mode = 0 | ||
call s:OnFileReadyToParse() | ||
py ycm_state.OnInsertLeave() | ||
|
||
if !s:is_nvim | ||
py ycm_state.OnCursorMoved() | ||
endif | ||
|
||
if g:ycm_autoclose_preview_window_after_completion || | ||
\ g:ycm_autoclose_preview_window_after_insertion | ||
call s:ClosePreviewWindowIfNeeded() | ||
|
@@ -812,6 +858,114 @@ endfunction | |
command! YcmDiags call s:ShowDiagnostics() | ||
|
||
|
||
function! s:BeginCompilation() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't have both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
if b:changedtick == get(b:, 'last_changedtick', -1) | ||
return | ||
endif | ||
|
||
let b:last_changedtick = b:changedtick | ||
call send_event( s:channel_id, | ||
\ 'begin_compilation', | ||
\ youcompleteme#BuildRequestData( 1 ) ) | ||
endfunction | ||
|
||
|
||
function youcompleteme#GetCurrentBufferFilepath() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm strongly against adding more vimscript code to YCM. I've been moving logic out of vimscript and into Python because it's both easier to test and maintain that; pulling logic back into vimscript is a major regression. Is there really no way to easily run Python code inside the neovim process? If true, IMO you're setting back Vim plugin development then. If the only way to talk to Python in neovim is with costly communication overhead, that's a major issue. I've talked to several developers of popular Vim plugins and everyone hates writing (and especially maintaining) VimScript. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the Neovim-specific performance issues are fixed by the first commit of this PR, the same which moves request building code to vimscript. This didn't match with my initial assumptions that the cause was the amount of data being transfered for each event, so I did some profiling to get to the real problem. It seems the slowdown is caused by the synchronization code I added to support multithreading in the python client, not by IPC overhead. Here's the profiler output for this PR
and here for the master branch:
All I did was open nvim with a 20k loc C file I bet YCM will run much better once I reimplement the client it using greenlets + twisted, which I was already planning to do. In case you are wondering, I've posted the profiler output here because it may be interesting to you, considering that ycmd is implemented as a multithreaded python web server(the performance may improve if requests are served with green threads) |
||
let filepath = expand( '%:p' ) | ||
|
||
if empty( filepath ) | ||
" Buffers that have just been created by a command like :enew don't have | ||
" any buffer name so we use the buffer number for that. | ||
let filepath = getcwd() . '/' . bufnr( '%' ) | ||
endif | ||
|
||
return filepath | ||
endfunction | ||
|
||
|
||
function youcompleteme#BuildRequestData( include_buffer_data ) | ||
let pos = getpos( '.' ) | ||
let filepath = youcompleteme#GetCurrentBufferFilepath() | ||
|
||
let data = { | ||
\ 'line_num': pos[ 1 ], | ||
\ 'column_num': pos[ 2 ], | ||
\ 'filepath': filepath | ||
\ } | ||
|
||
if a:include_buffer_data | ||
let data.file_data = s:GetUnsavedAndCurrentBufferData( filepath ) | ||
endif | ||
|
||
return data | ||
endfunction | ||
|
||
|
||
function s:GetUnsavedAndCurrentBufferData( filepath ) | ||
let file_data = {} | ||
let file_data[ a:filepath ] = { | ||
\ 'filetypes': split( &filetype, ',' ), | ||
\ 'contents': join( getline( 1, '$' ), "\n" ), | ||
\ } | ||
return file_data | ||
endfunction | ||
|
||
|
||
let s:next_completion_id = 1 | ||
let s:current_completion_id = 0 | ||
|
||
|
||
function! youcompleteme#BeginCompletion() | ||
let s:current_completion_id = s:next_completion_id | ||
let s:next_completion_id += 1 | ||
|
||
let data = youcompleteme#BuildRequestData( 1 ) | ||
let data.id = s:current_completion_id | ||
let data.position = s:GetCompletionPosition() | ||
|
||
call send_event( s:channel_id, 'begin_completion', data ) | ||
return '' | ||
endfunction | ||
|
||
|
||
function! youcompleteme#EndCompletion( data, result ) | ||
if mode() != 'i' | ||
" Not in insert mode, ignore | ||
return | ||
endif | ||
|
||
let completion_id = a:data.id | ||
if s:current_completion_id != completion_id | ||
" Completion expired | ||
return | ||
endif | ||
|
||
let completion_pos = a:data.position | ||
let current_pos = s:GetCompletionPosition() | ||
if current_pos[ 0 ] != completion_pos[ 0 ] | ||
\ || current_pos[ 1 ] != completion_pos[ 1 ] | ||
" Completion position changed | ||
return | ||
endif | ||
|
||
if empty( a:result ) | ||
return | ||
endif | ||
|
||
call complete( completion_pos[ 1 ], a:result ) | ||
" Deselect the first match | ||
call feedkeys( "\<C-P>", 'n' ) | ||
endfunction | ||
|
||
|
||
function! s:GetCompletionPosition() | ||
" The completion position is the start of the current identifier | ||
let pos = searchpos( '\i\@!', 'bn', line( '.' ) ) | ||
let pos[ 1 ] += 1 | ||
return pos | ||
endfunction | ||
|
||
|
||
" This is basic vim plugin boilerplate | ||
let &cpo = s:save_cpo | ||
unlet s:save_cpo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should abstract this behind a function like
OnBufferVisitInternal
or whatever. Then that would check foris_nvim
and callsend_event
orycm_state.OnBufferVisit
.Do something similar in other places where possible. I'd prefer to have the logic that picks
send_event
over python calls encapsulated.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍