diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim
index 386e4edb..6d1ecd5d 100644
--- a/autoload/youcompleteme.vim
+++ b/autoload/youcompleteme.vim
@@ -41,6 +41,10 @@ let s:pollers = {
\ 'server_ready': {
\ 'id': -1,
\ 'wait_milliseconds': 100
+ \ },
+ \ 'receive_messages': {
+ \ 'id': -1,
+ \ 'wait_milliseconds': 100
\ }
\ }
@@ -71,6 +75,29 @@ function! s:Pyeval( eval_string )
endfunction
+function! s:StartMessagePoll()
+ if s:pollers.receive_messages.id < 0
+ let s:pollers.receive_messages.id = timer_start(
+ \ s:pollers.receive_messages.wait_milliseconds,
+ \ function( 's:ReceiveMessages' ) )
+ endif
+endfunction
+
+
+function! s:ReceiveMessages( timer_id )
+ let poll_again = s:Pyeval( 'ycm_state.OnPeriodicTick()' )
+
+ if poll_again
+ let s:pollers.receive_messages.id = timer_start(
+ \ s:pollers.receive_messages.wait_milliseconds,
+ \ function( 's:ReceiveMessages' ) )
+ else
+ " Don't poll again until we open another buffer
+ let s:pollers.receive_messages.id = -1
+ endif
+endfunction
+
+
function! youcompleteme#Enable()
call s:SetUpBackwardsCompatibility()
@@ -451,6 +478,7 @@ function! s:OnFileTypeSet()
call s:SetUpCompleteopt()
call s:SetCompleteFunc()
+ call s:StartMessagePoll()
exec s:python_command "ycm_state.OnBufferVisit()"
call s:OnFileReadyToParse( 1 )
@@ -464,6 +492,7 @@ function! s:OnBufferEnter()
call s:SetUpCompleteopt()
call s:SetCompleteFunc()
+ call s:StartMessagePoll()
exec s:python_command "ycm_state.OnBufferVisit()"
" Last parse may be outdated because of changes from other buffers. Force a
@@ -801,6 +830,10 @@ endfunction
function! s:RestartServer()
exec s:python_command "ycm_state.RestartServer()"
+
+ call timer_stop( s:pollers.receive_messages.id )
+ let s:pollers.receive_messages.id = -1
+
call timer_stop( s:pollers.server_ready.id )
let s:pollers.server_ready.id = timer_start(
\ s:pollers.server_ready.wait_milliseconds,
@@ -828,11 +861,11 @@ endfunction
function! s:CompleterCommand(...)
- " CompleterCommand will call the OnUserCommand function of a completer.
- " If the first arguments is of the form "ft=..." it can be used to specify the
- " completer to use (for example "ft=cpp"). Else the native filetype completer
- " of the current buffer is used. If no native filetype completer is found and
- " no completer was specified this throws an error. You can use
+ " CompleterCommand will call the OnUserCommand function of a completer. If
+ " the first arguments is of the form "ft=..." it can be used to specify the
+ " completer to use (for example "ft=cpp"). Else the native filetype
+ " completer of the current buffer is used. If no native filetype completer
+ " is found and no completer was specified this throws an error. You can use
" "ft=ycm:ident" to select the identifier completer.
" The remaining arguments will be passed to the completer.
let arguments = copy(a:000)
diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py
index 111a140a..f87cbc9b 100644
--- a/python/ycm/buffer.py
+++ b/python/ycm/buffer.py
@@ -27,17 +27,23 @@ from ycm.client.event_notification import EventNotification
from ycm.diagnostic_interface import DiagnosticInterface
+DIAGNOSTIC_UI_FILETYPES = set( [ 'cpp', 'cs', 'c', 'objc', 'objcpp',
+ 'typescript' ] )
+DIAGNOSTIC_UI_ASYNC_FILETYPES = set( [ 'java' ] )
+
+
# Emulates Vim buffer
# Used to store buffer related information like diagnostics, latest parse
# request. Stores buffer change tick at the parse request moment, allowing
# to effectively determine whether reparse is needed for the buffer.
class Buffer( object ):
- def __init__( self, bufnr, user_options ):
+ def __init__( self, bufnr, user_options, async_diags ):
self.number = bufnr
self._parse_tick = 0
self._handled_tick = 0
self._parse_request = None
+ self._async_diags = async_diags
self._diag_interface = DiagnosticInterface( bufnr, user_options )
@@ -60,9 +66,18 @@ class Buffer( object ):
return self._parse_tick != self._ChangedTick()
- def UpdateDiagnostics( self ):
- self._diag_interface.UpdateWithNewDiagnostics(
- self._parse_request.Response() )
+ def UpdateDiagnostics( self, force=False ):
+ if force or not self._async_diags:
+ self.UpdateWithNewDiagnostics( self._parse_request.Response() )
+ else:
+ # We need to call the response method, because it might throw an exception
+ # or require extra config confirmation, even if we don't actually use the
+ # diagnostics.
+ self._parse_request.Response()
+
+
+ def UpdateWithNewDiagnostics( self, diagnostics ):
+ self._diag_interface.UpdateWithNewDiagnostics( diagnostics )
def PopulateLocationList( self ):
@@ -105,5 +120,11 @@ class BufferDict( dict ):
def __missing__( self, key ):
# Python does not allow to return assignment operation result directly
- new_value = self[ key ] = Buffer( key, self._user_options )
+ new_value = self[ key ] = Buffer(
+ key,
+ self._user_options,
+ any( [ x in DIAGNOSTIC_UI_ASYNC_FILETYPES
+ for x in
+ vimsupport.GetBufferFiletypes( key ) ] ) )
+
return new_value
diff --git a/python/ycm/client/messages_request.py b/python/ycm/client/messages_request.py
new file mode 100644
index 00000000..12c87ad8
--- /dev/null
+++ b/python/ycm/client/messages_request.py
@@ -0,0 +1,97 @@
+# Copyright (C) 2017 YouCompleteMe contributors
+#
+# This file is part of YouCompleteMe.
+#
+# YouCompleteMe is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# YouCompleteMe is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with YouCompleteMe. If not, see .
+
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+# Not installing aliases from python-future; it's unreliable and slow.
+from builtins import * # noqa
+
+from ycm.vimsupport import PostVimMessage
+
+from ycm.client.base_request import ( BaseRequest, BuildRequestData,
+ JsonFromFuture, HandleServerException )
+
+import logging
+
+_logger = logging.getLogger( __name__ )
+
+# Looooong poll
+TIMEOUT_SECONDS = 60
+
+
+class MessagesPoll( BaseRequest ):
+ def __init__( self ):
+ super( MessagesPoll, self ).__init__()
+ self._request_data = BuildRequestData()
+ self._response_future = None
+
+
+ def _SendRequest( self ):
+ self._response_future = self.PostDataToHandlerAsync(
+ self._request_data,
+ 'receive_messages',
+ timeout = TIMEOUT_SECONDS )
+ return
+
+
+ def Poll( self, diagnostics_handler ):
+ """This should be called regularly to check for new messages in this buffer.
+ Returns True if Poll should be called again in a while. Returns False when
+ the completer or server indicated that further polling should not be done
+ for the requested file."""
+
+ if self._response_future is None:
+ # First poll
+ self._SendRequest()
+ return True
+
+ if not self._response_future.done():
+ # Nothing yet...
+ return True
+
+ with HandleServerException( display = False ):
+ response = JsonFromFuture( self._response_future )
+
+ poll_again = _HandlePollResponse( response, diagnostics_handler )
+ if poll_again:
+ self._SendRequest()
+ return True
+
+ return False
+
+
+def _HandlePollResponse( response, diagnostics_handler ):
+ if isinstance( response, list ):
+ for notification in response:
+ if 'message' in notification:
+ PostVimMessage( notification[ 'message' ],
+ warning = False,
+ truncate = True )
+ elif 'diagnostics' in notification:
+ diagnostics_handler.UpdateWithNewDiagnosticsForFile(
+ notification[ 'filepath' ],
+ notification[ 'diagnostics' ] )
+ elif response is False:
+ # Don't keep polling for this file
+ return False
+ # else any truthy response means "nothing to see here; poll again in a
+ # while"
+
+ # Start the next poll (only if the last poll didn't raise an exception)
+ return True
diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py
index 6c2b2a60..969b2ea3 100644
--- a/python/ycm/diagnostic_interface.py
+++ b/python/ycm/diagnostic_interface.py
@@ -123,7 +123,8 @@ class DiagnosticInterface( object ):
def _UpdateLocationList( self ):
- vimsupport.SetLocationList(
+ vimsupport.SetLocationListForBuffer(
+ self._bufnr,
vimsupport.ConvertDiagnosticsToQfList( self._diagnostics ) )
diff --git a/python/ycm/tests/client/messages_request_test.py b/python/ycm/tests/client/messages_request_test.py
new file mode 100644
index 00000000..40f0c700
--- /dev/null
+++ b/python/ycm/tests/client/messages_request_test.py
@@ -0,0 +1,142 @@
+# Copyright (C) 2017 YouCompleteMe Contributors
+#
+# This file is part of YouCompleteMe.
+#
+# YouCompleteMe is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# YouCompleteMe is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with YouCompleteMe. If not, see .
+
+
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+# Not installing aliases from python-future; it's unreliable and slow.
+from builtins import * # noqa
+
+from ycm.tests.test_utils import MockVimModule
+MockVimModule()
+
+from hamcrest import assert_that, equal_to
+from mock import patch, call
+
+from ycm.client.messages_request import _HandlePollResponse
+from ycm.tests.test_utils import ExtendedMock
+
+
+def HandlePollResponse_NoMessages_test():
+ assert_that( _HandlePollResponse( True, None ), equal_to( True ) )
+
+ # Other non-False responses mean the same thing
+ assert_that( _HandlePollResponse( '', None ), equal_to( True ) )
+ assert_that( _HandlePollResponse( 1, None ), equal_to( True ) )
+ assert_that( _HandlePollResponse( {}, None ), equal_to( True ) )
+
+
+def HandlePollResponse_PollingNotSupported_test():
+ assert_that( _HandlePollResponse( False, None ), equal_to( False ) )
+
+ # 0 is not False
+ assert_that( _HandlePollResponse( 0, None ), equal_to( True ) )
+
+
+@patch( 'ycm.client.messages_request.PostVimMessage',
+ new_callable = ExtendedMock )
+def HandlePollResponse_SingleMessage_test( post_vim_message ):
+ assert_that( _HandlePollResponse( [ { 'message': 'this is a message' } ] ,
+ None ),
+ equal_to( True ) )
+
+ post_vim_message.assert_has_exact_calls( [
+ call( 'this is a message', warning=False, truncate=True )
+ ] )
+
+
+@patch( 'ycm.client.messages_request.PostVimMessage',
+ new_callable = ExtendedMock )
+def HandlePollResponse_MultipleMessages_test( post_vim_message ):
+ assert_that( _HandlePollResponse( [ { 'message': 'this is a message' },
+ { 'message': 'this is another one' } ] ,
+ None ),
+ equal_to( True ) )
+
+ post_vim_message.assert_has_exact_calls( [
+ call( 'this is a message', warning=False, truncate=True ),
+ call( 'this is another one', warning=False, truncate=True )
+ ] )
+
+
+def HandlePollResponse_SingleDiagnostic_test():
+ diagnostics_handler = ExtendedMock()
+ messages = [
+ { 'filepath': 'foo', 'diagnostics': [ 'PLACEHOLDER' ] },
+ ]
+ assert_that( _HandlePollResponse( messages, diagnostics_handler ),
+ equal_to( True ) )
+ diagnostics_handler.UpdateWithNewDiagnosticsForFile.assert_has_exact_calls( [
+ call( 'foo', [ 'PLACEHOLDER' ] )
+ ] )
+
+
+def HandlePollResponse_MultipleDiagnostics_test():
+ diagnostics_handler = ExtendedMock()
+ messages = [
+ { 'filepath': 'foo', 'diagnostics': [ 'PLACEHOLDER1' ] },
+ { 'filepath': 'bar', 'diagnostics': [ 'PLACEHOLDER2' ] },
+ { 'filepath': 'baz', 'diagnostics': [ 'PLACEHOLDER3' ] },
+ { 'filepath': 'foo', 'diagnostics': [ 'PLACEHOLDER4' ] },
+ ]
+ assert_that( _HandlePollResponse( messages, diagnostics_handler ),
+ equal_to( True ) )
+ diagnostics_handler.UpdateWithNewDiagnosticsForFile.assert_has_exact_calls( [
+ call ( 'foo', [ 'PLACEHOLDER1' ] ),
+ call ( 'bar', [ 'PLACEHOLDER2' ] ),
+ call ( 'baz', [ 'PLACEHOLDER3' ] ),
+ call ( 'foo', [ 'PLACEHOLDER4' ] )
+ ] )
+
+
+@patch( 'ycm.client.messages_request.PostVimMessage',
+ new_callable = ExtendedMock )
+def HandlePollResponse_MultipleMessagesAndDiagnostics_test( post_vim_message ):
+ diagnostics_handler = ExtendedMock()
+ messages = [
+ { 'filepath': 'foo', 'diagnostics': [ 'PLACEHOLDER1' ] },
+ { 'message': 'On the first day of Christmas, my VimScript gave to me' },
+ { 'filepath': 'bar', 'diagnostics': [ 'PLACEHOLDER2' ] },
+ { 'message': 'A test file in a Command-T' },
+ { 'filepath': 'baz', 'diagnostics': [ 'PLACEHOLDER3' ] },
+ { 'message': 'On the second day of Christmas, my VimScript gave to me' },
+ { 'filepath': 'foo', 'diagnostics': [ 'PLACEHOLDER4' ] },
+ { 'message': 'Two popup menus, and a test file in a Command-T' },
+ ]
+ assert_that( _HandlePollResponse( messages, diagnostics_handler ),
+ equal_to( True ) )
+ diagnostics_handler.UpdateWithNewDiagnosticsForFile.assert_has_exact_calls( [
+ call ( 'foo', [ 'PLACEHOLDER1' ] ),
+ call ( 'bar', [ 'PLACEHOLDER2' ] ),
+ call ( 'baz', [ 'PLACEHOLDER3' ] ),
+ call ( 'foo', [ 'PLACEHOLDER4' ] )
+ ] )
+
+ post_vim_message.assert_has_exact_calls( [
+ call( 'On the first day of Christmas, my VimScript gave to me',
+ warning=False,
+ truncate=True ),
+ call( 'A test file in a Command-T', warning=False, truncate=True ),
+ call( 'On the second day of Christmas, my VimScript gave to me',
+ warning=False,
+ truncate=True ),
+ call( 'Two popup menus, and a test file in a Command-T',
+ warning=False,
+ truncate=True ),
+ ] )
diff --git a/python/ycm/tests/mock_utils.py b/python/ycm/tests/mock_utils.py
new file mode 100644
index 00000000..40ffd3ae
--- /dev/null
+++ b/python/ycm/tests/mock_utils.py
@@ -0,0 +1,113 @@
+# Copyright (C) 2017 YouCompleteMe contributors
+#
+# This file is part of YouCompleteMe.
+#
+# YouCompleteMe is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# YouCompleteMe is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with YouCompleteMe. If not, see .
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+# Not installing aliases from python-future; it's unreliable and slow.
+from builtins import * # noqa
+
+import mock
+import requests
+
+
+class FakeResponse( object ):
+ """A fake version of a requests response object, just about suitable for
+ mocking a server response. Not usually used directly. See
+ MockServerResponse* methods"""
+ def __init__( self, response, exception ):
+ self._json = response
+ self._exception = exception
+ self.status_code = requests.codes.ok
+ self.text = not exception
+
+ def json( self ):
+ if self._exception:
+ return None
+ return self._json
+
+
+ def raise_for_status( self ):
+ if self._exception:
+ raise self._exception
+
+
+class FakeFuture( object ):
+ """A fake version of a future response object, just about suitable for
+ mocking a server response as generated by PostDataToHandlerAsync.
+ Not usually used directly. See MockAsyncServerResponse* methods"""
+ def __init__( self, done, response = None, exception = None ):
+ self._done = done
+
+ if not done:
+ self._result = None
+ else:
+ self._result = FakeResponse( response, exception )
+
+
+ def done( self ):
+ return self._done
+
+
+ def result( self ):
+ return self._result
+
+
+def MockAsyncServerResponseDone( response ):
+ """Return a fake future object that is complete with the supplied response
+ message. Suitable for mocking a response future within a client request. For
+ example:
+
+ server_message = {
+ 'message': 'this message came from the server'
+ }
+
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseDone( [] ) ) as mock_future:
+ ycm.OnPeriodicTick() # Uses ycm._message_poll_request ...
+ """
+ return mock.MagicMock( wraps = FakeFuture( True, response ) )
+
+
+def MockAsyncServerResponseInProgress():
+ """Return a fake future object that is incomplete. Suitable for mocking a
+ response future within a client request. For example:
+
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseInProgress() ):
+ ycm.OnPeriodicTick() # Uses ycm._message_poll_request ...
+ """
+ return mock.MagicMock( wraps = FakeFuture( False ) )
+
+
+def MockAsyncServerResponseException( exception ):
+ """Return a fake future object that is complete, but raises an exception.
+ Suitable for mocking a response future within a client request. For example:
+
+ exception = RuntimeError( 'Check client handles exception' )
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseException( exception ) ):
+ ycm.OnPeriodicTick() # Uses ycm._message_poll_request ...
+ """
+ return mock.MagicMock( wraps = FakeFuture( True, None, exception ) )
+
+
+# TODO: In future, implement MockServerResponse and MockServerResponseException
+# for synchronous cases when such test cases are needed.
diff --git a/python/ycm/tests/vimsupport_test.py b/python/ycm/tests/vimsupport_test.py
index 7c36c6a0..0da1ce18 100644
--- a/python/ycm/tests/vimsupport_test.py
+++ b/python/ycm/tests/vimsupport_test.py
@@ -39,6 +39,74 @@ import os
import json
+@patch( 'vim.eval', new_callable = ExtendedMock )
+def SetLocationListForBuffer_Current_test( vim_eval ):
+ diagnostics = [ {
+ 'bufnr': 3,
+ 'filename': 'some_filename',
+ 'lnum': 5,
+ 'col': 22,
+ 'type': 'E',
+ 'valid': 1
+ } ]
+ current_buffer = VimBuffer( '/test', number = 3, window = 7 )
+ with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+ vimsupport.SetLocationListForBuffer( 3, diagnostics )
+
+ # We asked for the buffer which is current, so we use winnr 0
+ vim_eval.assert_has_exact_calls( [
+ call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+ ] )
+
+
+@patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ 8, 1 ] )
+def SetLocationListForBuffer_NotCurrent_test( vim_eval ):
+ diagnostics = [ {
+ 'bufnr': 3,
+ 'filename': 'some_filename',
+ 'lnum': 5,
+ 'col': 22,
+ 'type': 'E',
+ 'valid': 1
+ } ]
+ current_buffer = VimBuffer( '/test', number = 3, window = 7 )
+ other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
+ with MockVimBuffers( [ current_buffer, other_buffer ],
+ current_buffer,
+ ( 1, 1 ) ):
+ vimsupport.SetLocationListForBuffer( 1, diagnostics )
+
+ # We asked for a buffer which is not current, so we find the window
+ vim_eval.assert_has_exact_calls( [
+ call( 'bufwinnr(1)' ), # returns 8 due to side_effect
+ call( 'setloclist( 8, {0} )'.format( json.dumps( diagnostics ) ) )
+ ] )
+
+
+@patch( 'vim.eval', new_callable = ExtendedMock, side_effect = [ -1, 1 ] )
+def SetLocationListForBuffer_NotVisible_test( vim_eval ):
+ diagnostics = [ {
+ 'bufnr': 3,
+ 'filename': 'some_filename',
+ 'lnum': 5,
+ 'col': 22,
+ 'type': 'E',
+ 'valid': 1
+ } ]
+ current_buffer = VimBuffer( '/test', number = 3, window = 7 )
+ other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
+ with MockVimBuffers( [ current_buffer, other_buffer ],
+ current_buffer,
+ ( 1, 1 ) ):
+ vimsupport.SetLocationListForBuffer( 1, diagnostics )
+
+ # We asked for a buffer which is not current, so we find the window
+ vim_eval.assert_has_exact_calls( [
+ call( 'bufwinnr(1)' ), # returns -1 due to side_effect
+ call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+ ] )
+
+
@patch( 'vim.eval', new_callable = ExtendedMock )
def SetLocationList_test( vim_eval ):
diagnostics = [ {
@@ -49,9 +117,36 @@ def SetLocationList_test( vim_eval ):
'type': 'E',
'valid': 1
} ]
- vimsupport.SetLocationList( diagnostics )
- vim_eval.assert_called_once_with(
- 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+ current_buffer = VimBuffer( '/test', number = 3, window = 7 )
+ with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
+ vimsupport.SetLocationList( diagnostics )
+
+ vim_eval.assert_has_calls( [
+ call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) ),
+ ] )
+
+
+@patch( 'vim.eval', new_callable = ExtendedMock )
+def SetLocationList_NotCurrent_test( vim_eval ):
+ diagnostics = [ {
+ 'bufnr': 3,
+ 'filename': 'some_filename',
+ 'lnum': 5,
+ 'col': 22,
+ 'type': 'E',
+ 'valid': 1
+ } ]
+ current_buffer = VimBuffer( '/test', number = 3, window = 7 )
+ other_buffer = VimBuffer( '/notcurrent', number = 1, window = 8 )
+ with MockVimBuffers( [ current_buffer, other_buffer ],
+ current_buffer,
+ ( 1, 1 ) ):
+ vimsupport.SetLocationList( diagnostics )
+
+ # This version does not check the current buffer and just sets the current win
+ vim_eval.assert_has_exact_calls( [
+ call( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) ),
+ ] )
@patch( 'ycm.vimsupport.VariableExists', return_value = True )
diff --git a/python/ycm/tests/youcompleteme_test.py b/python/ycm/tests/youcompleteme_test.py
index 9f80f488..33e6c688 100644
--- a/python/ycm/tests/youcompleteme_test.py
+++ b/python/ycm/tests/youcompleteme_test.py
@@ -38,6 +38,12 @@ from ycm.tests import ( MakeUserOptions, StopServer, test_utils,
WaitUntilReady, YouCompleteMeInstance )
from ycm.client.base_request import _LoadExtraConfFile
from ycmd.responses import ServerError
+from ycm.tests.mock_utils import ( MockAsyncServerResponseDone,
+ MockAsyncServerResponseInProgress,
+ MockAsyncServerResponseException )
+
+
+from ycm import buffer as ycm_buffer_module
@YouCompleteMeInstance()
@@ -394,11 +400,11 @@ def YouCompleteMe_ShowDiagnostics_FiletypeNotSupported_test( ycm,
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
-@patch( 'ycm.vimsupport.SetLocationList', new_callable = ExtendedMock )
+@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
- ycm, set_location_list, post_vim_message, *args ):
+ ycm, set_location_list_for_window, post_vim_message, *args ):
- current_buffer = VimBuffer( 'buffer', filetype = 'cpp' )
+ current_buffer = VimBuffer( 'buffer', filetype = 'cpp', window = 99 )
with MockVimBuffers( [ current_buffer ], current_buffer ):
with patch( 'ycm.client.event_notification.EventNotification.Response',
return_value = {} ):
@@ -410,7 +416,7 @@ def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
call( 'Diagnostics refreshed', warning = False ),
call( 'No warnings or errors detected.', warning = False )
] )
- set_location_list.assert_called_once_with( [] )
+ set_location_list_for_window.assert_called_once_with( 0, [] )
@YouCompleteMeInstance( { 'log_level': 'debug',
@@ -419,9 +425,9 @@ def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
-@patch( 'ycm.vimsupport.SetLocationList', new_callable = ExtendedMock )
+@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
- ycm, set_location_list, post_vim_message, *args ):
+ ycm, set_location_list_for_window, post_vim_message, *args ):
diagnostic = {
'kind': 'ERROR',
@@ -433,7 +439,10 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
}
}
- current_buffer = VimBuffer( 'buffer', filetype = 'cpp', number = 3 )
+ current_buffer = VimBuffer( 'buffer',
+ filetype = 'cpp',
+ number = 3,
+ window = 99 )
with MockVimBuffers( [ current_buffer ], current_buffer ):
with patch( 'ycm.client.event_notification.EventNotification.Response',
return_value = [ diagnostic ] ):
@@ -444,7 +453,7 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
warning = False ),
call( 'Diagnostics refreshed', warning = False )
] )
- set_location_list.assert_called_once_with( [ {
+ set_location_list_for_window.assert_called_once_with( 0, [ {
'bufnr': 3,
'lnum': 19,
'col': 2,
@@ -458,10 +467,14 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
-@patch( 'ycm.vimsupport.SetLocationList', new_callable = ExtendedMock )
+@patch( 'ycm.vimsupport.SetLocationListForWindow', new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.OpenLocationList', new_callable = ExtendedMock )
def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
- ycm, open_location_list, set_location_list, post_vim_message, *args ):
+ ycm,
+ open_location_list,
+ set_location_list_for_window,
+ post_vim_message,
+ *args ):
diagnostic = {
'kind': 'ERROR',
@@ -473,7 +486,10 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
}
}
- current_buffer = VimBuffer( 'buffer', filetype = 'cpp', number = 3 )
+ current_buffer = VimBuffer( 'buffer',
+ filetype = 'cpp',
+ number = 3,
+ window = 99 )
with MockVimBuffers( [ current_buffer ], current_buffer ):
with patch( 'ycm.client.event_notification.EventNotification.Response',
return_value = [ diagnostic ] ):
@@ -484,7 +500,7 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
warning = False ),
call( 'Diagnostics refreshed', warning = False )
] )
- set_location_list.assert_called_once_with( [ {
+ set_location_list_for_window.assert_called_once_with( 0, [ {
'bufnr': 3,
'lnum': 19,
'col': 2,
@@ -641,3 +657,361 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
call( 'sign place 2 name=YcmWarning line=3 buffer=5' ),
call( 'try | exec "sign unplace 1 buffer=5" | catch /E158/ | endtry' )
] )
+
+
+@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
+ 'always_populate_location_list': 1 } )
+@patch.object( ycm_buffer_module,
+ 'DIAGNOSTIC_UI_ASYNC_FILETYPES',
+ [ 'ycmtest' ] )
+@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
+ return_value = True )
+@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
+def YouCompleteMe_AsyncDiagnosticUpdate_SingleFile_test( ycm,
+ post_vim_message,
+ *args ):
+
+ # This test simulates asynchronous diagnostic updates associated with a single
+ # file (e.g. Translation Unit), but where the actual errors refer to other
+ # open files and other non-open files. This is not strictly invalid, nor is it
+ # completely normal, but it is supported and does work.
+
+ # Contrast with the next test which sends the diagnostics filewise, which is
+ # what the language server protocol will do.
+
+ diagnostics = [
+ {
+ 'kind': 'ERROR',
+ 'text': 'error text in current buffer',
+ 'location': {
+ 'filepath': '/current',
+ 'line_num': 1,
+ 'column_num': 1
+ },
+ },
+ {
+ 'kind': 'ERROR',
+ 'text': 'error text in hidden buffer',
+ 'location': {
+ 'filepath': '/has_diags',
+ 'line_num': 4,
+ 'column_num': 2
+ },
+ },
+ {
+ 'kind': 'ERROR',
+ 'text': 'error text in buffer not open in Vim',
+ 'location': {
+ 'filepath': '/not_open',
+ 'line_num': 8,
+ 'column_num': 4
+ },
+ },
+ ]
+
+ current_buffer = VimBuffer( '/current',
+ filetype = 'ycmtest',
+ number = 1,
+ window = 10 )
+ buffers = [
+ current_buffer,
+ VimBuffer( '/no_diags',
+ filetype = 'ycmtest',
+ number = 2,
+ window = 9 ),
+ VimBuffer( '/has_diags',
+ filetype = 'ycmtest',
+ number = 3,
+ window = 8 ),
+ ]
+
+ # Register each buffer internally with YCM
+ for current in buffers:
+ with MockVimBuffers( buffers, current, ( 1, 1 ) ):
+ ycm.OnFileReadyToParse()
+
+ with patch( 'ycm.vimsupport.SetLocationListForWindow',
+ new_callable = ExtendedMock ) as set_location_list_for_window:
+ with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+ ycm.UpdateWithNewDiagnosticsForFile( '/current', diagnostics )
+
+ # We update the diagnostic on the current cursor position
+ post_vim_message.assert_has_exact_calls( [
+ call( "error text in current buffer", truncate = True, warning = False ),
+ ] )
+
+ # Ensure we included all the diags though
+ set_location_list_for_window.assert_has_exact_calls( [
+ call( 0, [
+ {
+ 'lnum': 1,
+ 'col': 1,
+ 'bufnr': 1,
+ 'valid': 1,
+ 'type': 'E',
+ 'text': 'error text in current buffer',
+ },
+ {
+ 'lnum': 4,
+ 'col': 2,
+ 'bufnr': 3,
+ 'valid': 1,
+ 'type': 'E',
+ 'text': 'error text in hidden buffer',
+ },
+ {
+ 'lnum': 8,
+ 'col': 4,
+ 'bufnr': -1, # sic: Our mocked bufnr function actually returns -1,
+ # even though YCM is passing "create if needed".
+ # FIXME? we shouldn't do that, and we should pass
+ # filename instead
+ 'valid': 1,
+ 'type': 'E',
+ 'text': 'error text in buffer not open in Vim'
+ }
+ ] )
+ ] )
+
+
+@YouCompleteMeInstance( { 'echo_current_diagnostic': 1,
+ 'always_populate_location_list': 1 } )
+@patch.object( ycm_buffer_module,
+ 'DIAGNOSTIC_UI_ASYNC_FILETYPES',
+ [ 'ycmtest' ] )
+@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
+ return_value = True )
+@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
+def YouCompleteMe_AsyncDiagnosticUpdate_PerFile_test( ycm,
+ post_vim_message,
+ *args ):
+
+ # This test simulates asynchronous diagnostic updates which are delivered per
+ # file, including files which are open and files which are not.
+
+ # Ordered to ensure that the calls to update are deterministic
+ diagnostics_per_file = [
+ ( '/current', [ {
+ 'kind': 'ERROR',
+ 'text': 'error text in current buffer',
+ 'location': {
+ 'filepath': '/current',
+ 'line_num': 1,
+ 'column_num': 1
+ }, }, ] ),
+ ( '/has_diags', [ {
+ 'kind': 'ERROR',
+ 'text': 'error text in hidden buffer',
+ 'location': {
+ 'filepath': '/has_diags',
+ 'line_num': 4,
+ 'column_num': 2
+ }, }, ] ),
+ ( '/not_open', [ {
+ 'kind': 'ERROR',
+ 'text': 'error text in buffer not open in Vim',
+ 'location': {
+ 'filepath': '/not_open',
+ 'line_num': 8,
+ 'column_num': 4
+ }, }, ] )
+ ]
+
+ current_buffer = VimBuffer( '/current',
+ filetype = 'ycmtest',
+ number = 1,
+ window = 10 )
+ buffers = [
+ current_buffer,
+ VimBuffer( '/no_diags',
+ filetype = 'ycmtest',
+ number = 2,
+ window = 9 ),
+ VimBuffer( '/has_diags',
+ filetype = 'ycmtest',
+ number = 3,
+ window = 8 ),
+ ]
+
+ # Register each buffer internally with YCM
+ for current in buffers:
+ with MockVimBuffers( buffers, current, ( 1, 1 ) ):
+ ycm.OnFileReadyToParse()
+
+ with patch( 'ycm.vimsupport.SetLocationListForWindow',
+ new_callable = ExtendedMock ) as set_location_list_for_window:
+ with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+ for filename, diagnostics in diagnostics_per_file:
+ ycm.UpdateWithNewDiagnosticsForFile( filename, diagnostics )
+
+ # We update the diagnostic on the current cursor position
+ post_vim_message.assert_has_exact_calls( [
+ call( "error text in current buffer", truncate = True, warning = False ),
+ ] )
+
+ # Ensure we included all the diags though
+ set_location_list_for_window.assert_has_exact_calls( [
+ call( 0, [
+ {
+ 'lnum': 1,
+ 'col': 1,
+ 'bufnr': 1,
+ 'valid': 1,
+ 'type': 'E',
+ 'text': 'error text in current buffer',
+ },
+ ] ),
+
+ call( 8, [
+ {
+ 'lnum': 4,
+ 'col': 2,
+ 'bufnr': 3,
+ 'valid': 1,
+ 'type': 'E',
+ 'text': 'error text in hidden buffer',
+ },
+ ] )
+ ] )
+
+
+@YouCompleteMeInstance()
+def YouCompleteMe_OnPeriodicTick_ServerNotRunning_test( ycm, *args ):
+ with patch.object( ycm, 'IsServerAlive', return_value = False ):
+ assert_that( ycm.OnPeriodicTick(), equal_to( False ) )
+
+
+@YouCompleteMeInstance()
+def YouCompleteMe_OnPeriodicTick_ServerNotReady_test( ycm, *args ):
+ with patch.object( ycm, 'IsServerAlive', return_value = True ):
+ with patch.object( ycm, 'IsServerReady', return_value = False ):
+ assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
+
+
+@YouCompleteMeInstance()
+@patch.object( ycm_buffer_module,
+ 'DIAGNOSTIC_UI_ASYNC_FILETYPES',
+ [ 'ycmtest' ] )
+@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
+ return_value = True )
+@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
+@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
+def YouCompelteMe_OnPeriodicTick_DontRetry_test( ycm,
+ post_data_to_handler_async,
+ *args ):
+
+ current_buffer = VimBuffer( '/current',
+ filetype = 'ycmtest',
+ number = 1,
+ window = 10 )
+ buffers = [ current_buffer ]
+
+ # Create the request and make the first poll; we expect no response
+ with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+ assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
+ post_data_to_handler_async.assert_called()
+
+ assert ycm._message_poll_request is not None
+ post_data_to_handler_async.reset_mock()
+
+ # OK that sent the request, now poll to check if it is complete (say it is
+ # not)
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseInProgress() ) as mock_future:
+ poll_again = ycm.OnPeriodicTick()
+ mock_future.done.assert_called()
+ mock_future.result.assert_not_called()
+ assert_that( poll_again, equal_to( True ) )
+
+ # Poll again, but return a response (telling us to stop polling)
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseDone( False ) ) \
+ as mock_future:
+ poll_again = ycm.OnPeriodicTick()
+ mock_future.done.assert_called()
+ mock_future.result.assert_called()
+ post_data_to_handler_async.assert_not_called()
+ # We reset and don't poll anymore
+ assert_that( ycm._message_poll_request is None )
+ assert_that( poll_again, equal_to( False ) )
+
+
+
+@YouCompleteMeInstance()
+@patch.object( ycm_buffer_module,
+ 'DIAGNOSTIC_UI_ASYNC_FILETYPES',
+ [ 'ycmtest' ] )
+@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
+ return_value = True )
+@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
+@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
+def YouCompelteMe_OnPeriodicTick_Exception_test( ycm,
+ post_data_to_handler_async,
+ *args ):
+
+ current_buffer = VimBuffer( '/current',
+ filetype = 'ycmtest',
+ number = 1,
+ window = 10 )
+ buffers = [ current_buffer ]
+
+ # Create the request and make the first poll; we expect no response
+ with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+ assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
+ post_data_to_handler_async.assert_called()
+
+ post_data_to_handler_async.reset_mock()
+
+ # Poll again, but return an exception response
+ mock_response = MockAsyncServerResponseException( RuntimeError( 'test' ) )
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = mock_response ) as mock_future:
+ assert_that( ycm.OnPeriodicTick(), equal_to( False ) )
+ mock_future.done.assert_called()
+ mock_future.result.assert_called()
+ post_data_to_handler_async.assert_not_called()
+ # We reset and don't poll anymore
+ assert_that( ycm._message_poll_request is None )
+
+
+@YouCompleteMeInstance()
+@patch.object( ycm_buffer_module,
+ 'DIAGNOSTIC_UI_ASYNC_FILETYPES',
+ [ 'ycmtest' ] )
+@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
+ return_value = True )
+@patch( 'ycm.client.base_request._ValidateResponseObject', return_value = True )
+@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync' )
+@patch( 'ycm.client.messages_request._HandlePollResponse' )
+def YouCompelteMe_OnPeriodicTick_ValidResponse_test( ycm,
+ handle_poll_response,
+ post_data_to_handler_async,
+ *args ):
+
+ current_buffer = VimBuffer( '/current',
+ filetype = 'ycmtest',
+ number = 1,
+ window = 10 )
+ buffers = [ current_buffer ]
+
+ # Create the request and make the first poll; we expect no response
+ with MockVimBuffers( buffers, current_buffer, ( 1, 1 ) ):
+ assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
+ post_data_to_handler_async.assert_called()
+
+ post_data_to_handler_async.reset_mock()
+
+ # Poll again, and return a _proper_ response (finally!).
+ # Note, _HandlePollResponse is tested independently (for simplicity)
+ with patch.object( ycm._message_poll_request,
+ '_response_future',
+ new = MockAsyncServerResponseDone( [] ) ) as mock_future:
+ assert_that( ycm.OnPeriodicTick(), equal_to( True ) )
+ handle_poll_response.assert_called()
+ mock_future.done.assert_called()
+ mock_future.result.assert_called()
+ post_data_to_handler_async.assert_called() # Poll again!
+ assert_that( ycm._message_poll_request is not None )
diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py
index 7070d55f..13c8ed8a 100644
--- a/python/ycm/vimsupport.py
+++ b/python/ycm/vimsupport.py
@@ -235,9 +235,47 @@ def LineAndColumnNumbersClamped( line_num, column_num ):
def SetLocationList( diagnostics ):
+ """Set the location list for the current window to the supplied diagnostics"""
+ SetLocationListForWindow( 0, diagnostics )
+
+
+def GetWindowNumberForBufferDiagnostics( buffer_number ):
+ """Return an appropriate window number to use for displaying diagnostics
+ associated with the buffer number supplied. Always returns a valid window
+ number or 0 meaning the current window."""
+
+ # Location lists are associated with _windows_ not _buffers_. This makes a lot
+ # of sense, but YCM associates diagnostics with _buffers_, because it is the
+ # buffer that actually gets parsed.
+ #
+ # The heuristic we use is to determine any open window for a specified buffer,
+ # and set that. If there is no such window on the current tab page, we use the
+ # current window (by passing 0 as the window number)
+
+ if buffer_number == vim.current.buffer.number:
+ return 0
+
+ window_number = GetIntValue( "bufwinnr({0})".format( buffer_number ) )
+
+ if window_number < 0:
+ return 0
+
+ return window_number
+
+
+def SetLocationListForBuffer( buffer_number, diagnostics ):
+ """Populate the location list of an apppropriate window for the supplied
+ buffer number. See SetLocationListForWindow for format of diagnostics."""
+ return SetLocationListForWindow(
+ GetWindowNumberForBufferDiagnostics( buffer_number ),
+ diagnostics )
+
+
+def SetLocationListForWindow( window_number, diagnostics ):
"""Populate the location list with diagnostics. Diagnostics should be in
qflist format; see ":h setqflist" for details."""
- vim.eval( 'setloclist( 0, {0} )'.format( json.dumps( diagnostics ) ) )
+ vim.eval( 'setloclist( {0}, {1} )'.format( window_number,
+ json.dumps( diagnostics ) ) )
def OpenLocationList( focus = False, autoclose = False ):
diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py
index 2b958303..84df240d 100644
--- a/python/ycm/youcompleteme.py
+++ b/python/ycm/youcompleteme.py
@@ -32,7 +32,9 @@ import vim
from subprocess import PIPE
from tempfile import NamedTemporaryFile
from ycm import base, paths, vimsupport
-from ycm.buffer import BufferDict
+from ycm.buffer import ( BufferDict,
+ DIAGNOSTIC_UI_FILETYPES,
+ DIAGNOSTIC_UI_ASYNC_FILETYPES )
from ycmd import utils
from ycmd import server_utils
from ycmd.request_wrap import RequestWrap
@@ -50,6 +52,7 @@ from ycm.client.debug_info_request import ( SendDebugInfoRequest,
from ycm.client.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import SendEventNotificationAsync
from ycm.client.shutdown_request import SendShutdownRequest
+from ycm.client.messages_request import MessagesPoll
def PatchNoProxy():
@@ -97,8 +100,6 @@ CORE_OUTDATED_MESSAGE = (
'YCM core library too old; PLEASE RECOMPILE by running the install.py '
'script. See the documentation for more details.' )
SERVER_IDLE_SUICIDE_SECONDS = 1800 # 30 minutes
-DIAGNOSTIC_UI_FILETYPES = set( [ 'cpp', 'cs', 'c', 'objc', 'objcpp',
- 'typescript' ] )
CLIENT_LOGFILE_FORMAT = 'ycm_'
SERVER_LOGFILE_FORMAT = 'ycmd_{port}_{std}_'
@@ -136,6 +137,7 @@ class YouCompleteMe( object ):
self._user_notified_about_crash = False
self._filetypes_with_keywords_loaded = set()
self._server_is_ready_with_cache = False
+ self._message_poll_request = None
hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
options_dict = dict( self._user_options )
@@ -364,6 +366,59 @@ class YouCompleteMe( object ):
return self.CurrentBuffer().NeedsReparse()
+ def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ):
+ bufnr = vimsupport.GetBufferNumberForFilename( filepath )
+ if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ):
+ # Note: We only update location lists, etc. for visible buffers, because
+ # otherwise we defualt to using the curren location list and the results
+ # are that non-visible buffer errors clobber visible ones.
+ self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics )
+ else:
+ # The project contains errors in file "filepath", but that file is not
+ # open in any buffer. This happens for Language Server Protocol-based
+ # completers, as they return diagnostics for the entire "project"
+ # asynchronously (rather than per-file in the response to the parse
+ # request).
+ #
+ # There are a number of possible approaches for
+ # this, but for now we simply ignore them. Other options include:
+ # - Use the QuickFix list to report project errors?
+ # - Use a special buffer for project errors
+ # - Put them in the location list of whatever the "current" buffer is
+ # - Store them in case the buffer is opened later
+ # - add a :YcmProjectDiags command
+ # - Add them to errror/warning _counts_ but not any actual location list
+ # or other
+ # - etc.
+ #
+ # However, none of those options are great, and lead to their own
+ # complexities. So for now, we just ignore these diagnostics for files not
+ # open in any buffer.
+ pass
+
+
+ def OnPeriodicTick( self ):
+ if not self.IsServerAlive():
+ # Server has died. We'll reset when the server is started again.
+ return False
+ elif not self.IsServerReady():
+ # Try again in a jiffy
+ return True
+
+ if not self._message_poll_request:
+ self._message_poll_request = MessagesPoll()
+
+ if not self._message_poll_request.Poll( self ):
+ # Don't poll again until some event which might change the server's mind
+ # about whether to provide messages for the current buffer (e.g. buffer
+ # visit, file ready to parse, etc.)
+ self._message_poll_request = None
+ return False
+
+ # Poll again in a jiffy
+ return True
+
+
def OnFileReadyToParse( self ):
if not self.IsServerAlive():
self.NotifyUserIfServerCrashed()
@@ -534,7 +589,8 @@ class YouCompleteMe( object ):
def DiagnosticUiSupportedForCurrentFiletype( self ):
- return any( [ x in DIAGNOSTIC_UI_FILETYPES
+ return any( [ x in DIAGNOSTIC_UI_FILETYPES or
+ x in DIAGNOSTIC_UI_ASYNC_FILETYPES
for x in vimsupport.CurrentFiletypes() ] )
@@ -566,7 +622,9 @@ class YouCompleteMe( object ):
self.NativeFiletypeCompletionUsable() ):
if self.ShouldDisplayDiagnostics():
- current_buffer.UpdateDiagnostics()
+ # Forcefuly update the location list, etc. from the parse request when
+ # doing something like :YcmDiags
+ current_buffer.UpdateDiagnostics( block is True )
else:
# YCM client has a hard-coded list of filetypes which are known
# to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()