Skip to content

Commit

Permalink
Resend request when extra conf is loaded or ignored
Browse files Browse the repository at this point in the history
When the client sends a request to the server, if an extra conf file is found
that is not already white/blacklisted, the server stops processing the request
and tells the client that an unknown extra conf file has been found. The client
then asks the user if that file should be loaded or not. Depending on the
user's answer, the client sends a request to the server to load or ignore the
extra conf file. Finally, the server loads the file or adds it to the
blacklist. However, the initial request was not processed by the server and
should be sent again.
  • Loading branch information
micbou committed Apr 22, 2018
1 parent e777234 commit a24d97c
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 111 deletions.
3 changes: 3 additions & 0 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,9 @@ function! s:PollFileParseResponse( ... )
endif

exec s:python_command "ycm_state.HandleFileParseRequest()"
if s:Pyeval( "ycm_state.ShouldResendFileParseRequest()" )
call s:OnFileReadyToParse( 1 )
endif
endfunction


Expand Down
4 changes: 4 additions & 0 deletions python/ycm/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def NeedsReparse( self ):
return self._parse_tick != self._ChangedTick()


def ShouldResendParseRequest( self ):
return self._parse_request.ShouldResend()


def UpdateDiagnostics( self, force=False ):
if force or not self._async_diags:
self.UpdateWithNewDiagnostics( self._parse_request.Response() )
Expand Down
42 changes: 25 additions & 17 deletions python/ycm/client/base_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
class BaseRequest( object ):

def __init__( self ):
pass
self._should_resend = False


def Start( self ):
Expand All @@ -58,15 +58,22 @@ def Response( self ):
return {}


@staticmethod
def HandleFuture( future, display_message = True, truncate_message = False ):
def ShouldResend( self ):
return self._should_resend


def HandleFuture( self,
future,
display_message = True,
truncate_message = False ):
"""Get the server response from a |future| object and catch any exception
while doing so. If an exception is raised because of a unknown
.ycm_extra_conf.py file, load the file or ignore it after asking the user.
For other exceptions, log the exception and display its message to the user
on the Vim status line. Unset the |display_message| parameter to hide the
message from the user. Set the |truncate_message| parameter to avoid
hit-enter prompts from this message."""
An identical request should be sent again to the server. For other
exceptions, log the exception and display its message to the user on the Vim
status line. Unset the |display_message| parameter to hide the message from
the user. Set the |truncate_message| parameter to avoid hit-enter prompts
from this message."""
try:
try:
return _JsonFromFuture( future )
Expand All @@ -75,6 +82,7 @@ def HandleFuture( future, display_message = True, truncate_message = False ):
_LoadExtraConfFile( e.extra_conf_file )
else:
_IgnoreExtraConfFile( e.extra_conf_file )
self._should_resend = True
except BaseRequest.Requests().exceptions.ConnectionError:
# We don't display this exception to the user since it is likely to happen
# for each subsequent request (typically if the server crashed) and we
Expand All @@ -93,12 +101,12 @@ def HandleFuture( future, display_message = True, truncate_message = False ):
# up; see Requests docs for details (we just pass the param along).
# See the HandleFuture method for the |display_message| and |truncate_message|
# parameters.
@staticmethod
def GetDataFromHandler( handler,
def GetDataFromHandler( self,
handler,
timeout = _READ_TIMEOUT_SEC,
display_message = True,
truncate_message = False ):
return BaseRequest.HandleFuture(
return self.HandleFuture(
BaseRequest._TalkToHandlerAsync( '', handler, 'GET', timeout ),
display_message,
truncate_message )
Expand All @@ -109,13 +117,13 @@ def GetDataFromHandler( handler,
# up; see Requests docs for details (we just pass the param along).
# See the HandleFuture method for the |display_message| and |truncate_message|
# parameters.
@staticmethod
def PostDataToHandler( data,
def PostDataToHandler( self,
data,
handler,
timeout = _READ_TIMEOUT_SEC,
display_message = True,
truncate_message = False ):
return BaseRequest.HandleFuture(
return self.HandleFuture(
BaseRequest.PostDataToHandlerAsync( data, handler, timeout ),
display_message,
truncate_message )
Expand Down Expand Up @@ -243,13 +251,13 @@ def _JsonFromFuture( future ):


def _LoadExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'load_extra_conf_file' )
BaseRequest().PostDataToHandler( { 'filepath': filepath },
'load_extra_conf_file' )


def _IgnoreExtraConfFile( filepath ):
BaseRequest.PostDataToHandler( { 'filepath': filepath },
'ignore_extra_conf_file' )
BaseRequest().PostDataToHandler( { 'filepath': filepath },
'ignore_extra_conf_file' )


def DisplayServerException( exception, truncate_message = False ):
Expand Down
2 changes: 1 addition & 1 deletion python/ycm/client/ycmd_keepalive.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ def _ThreadMain( self ):
while True:
time.sleep( self._ping_interval_seconds )

BaseRequest.GetDataFromHandler( 'healthy', display_message = False )
BaseRequest().GetDataFromHandler( 'healthy', display_message = False )
4 changes: 2 additions & 2 deletions python/ycm/omni_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,6 @@ def FilterAndSortCandidatesInner( self, candidates, sort_property, query ):
'query': query
}

response = BaseRequest.PostDataToHandler( request_data,
'filter_and_sort_candidates' )
response = BaseRequest().PostDataToHandler( request_data,
'filter_and_sort_candidates' )
return response if response is not None else []
2 changes: 1 addition & 1 deletion python/ycm/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def MakeUserOptions( custom_options = {} ):


def _IsReady():
return BaseRequest.GetDataFromHandler( 'ready' )
return BaseRequest().GetDataFromHandler( 'ready' )


def WaitUntilReady( timeout = 5 ):
Expand Down
191 changes: 105 additions & 86 deletions python/ycm/tests/event_notification_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def ErrorResponse( *args ):
call( ERROR_TEXT, truncate = True )
] )

ok_( not ycm.ShouldResendFileParseRequest() )

# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
Expand All @@ -134,6 +136,8 @@ def ErrorResponse( *args ):
call( ERROR_TEXT, truncate = True )
] )

ok_( not ycm.ShouldResendFileParseRequest() )


@YouCompleteMeInstance()
def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test(
Expand All @@ -154,107 +158,115 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test(
test_utils.VIM_SIGNS,
contains()
)
ok_( not ycm.ShouldResendFileParseRequest() )


@patch( 'ycm.client.base_request._LoadExtraConfFile',
new_callable = ExtendedMock )
@patch( 'ycm.client.base_request._IgnoreExtraConfFile',
new_callable = ExtendedMock )
@YouCompleteMeInstance()
def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
ycm, ignore_extra_conf, load_extra_conf ):
ycm ):

# This test validates the behaviour of YouCompleteMe.HandleFileParseRequest
# in combination with YouCompleteMe.OnFileReadyToParse when the completer
# raises the (special) UnknownExtraConf exception

FILE_NAME = 'a_file'
MESSAGE = ( 'Found ' + FILE_NAME + '. Load? \n\n(Question can be '
'turned off with options, see YCM docs)' )

def UnknownExtraConfResponse( *args ):
raise UnknownExtraConf( FILE_NAME )

with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( UnknownExtraConfResponse ):

# When the user accepts the extra conf, we load it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 0,
new_callable = ExtendedMock ) as present_dialog:
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
load_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
] )

# Subsequent calls don't re-raise the warning
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
load_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
] )

# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
load_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
call( FILE_NAME ),
] )

# When the user rejects the extra conf, we reject it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 1,
new_callable = ExtendedMock ) as present_dialog:
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
ignore_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
] )

# Subsequent calls don't re-raise the warning
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
ignore_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
] )

# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
ignore_extra_conf.assert_has_exact_calls( [
call( FILE_NAME ),
call( FILE_NAME ),
] )
with patch( 'ycm.client.base_request.BaseRequest.PostDataToHandler',
new_callable = ExtendedMock ) as post_data_to_handler:
with MockArbitraryBuffer( 'javascript' ):
with MockEventNotification( UnknownExtraConfResponse ):

# When the user accepts the extra conf, we load it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 0,
new_callable = ExtendedMock ) as present_dialog:
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'load_extra_conf_file' )
] )

# Subsequent calls don't re-raise the warning
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'load_extra_conf_file' )
] )

ok_( ycm.ShouldResendFileParseRequest() )

# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'load_extra_conf_file' ),
call( { 'filepath': FILE_NAME }, 'load_extra_conf_file' )
] )

ok_( ycm.ShouldResendFileParseRequest() )

post_data_to_handler.reset_mock()

# When the user rejects the extra conf, we reject it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 1,
new_callable = ExtendedMock ) as present_dialog:
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'ignore_extra_conf_file' )
] )

# Subsequent calls don't re-raise the warning
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE )
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'ignore_extra_conf_file' )
] )

ok_( ycm.ShouldResendFileParseRequest() )

# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()

present_dialog.assert_has_exact_calls( [
PresentDialog_Confirm_Call( MESSAGE ),
PresentDialog_Confirm_Call( MESSAGE ),
] )
post_data_to_handler.assert_has_exact_calls( [
call( { 'filepath': FILE_NAME }, 'ignore_extra_conf_file' ),
call( { 'filepath': FILE_NAME }, 'ignore_extra_conf_file' )
] )

ok_( ycm.ShouldResendFileParseRequest() )


@YouCompleteMeInstance()
Expand Down Expand Up @@ -302,6 +314,8 @@ def DiagnosticResponse( *args ):
eq_( ycm.GetErrorCount(), 1 )
eq_( ycm.GetWarningCount(), 0 )

ok_( not ycm.ShouldResendFileParseRequest() )

# New identical requests should result in the same diagnostics.
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
Expand All @@ -315,6 +329,8 @@ def DiagnosticResponse( *args ):
eq_( ycm.GetErrorCount(), 1 )
eq_( ycm.GetWarningCount(), 0 )

ok_( not ycm.ShouldResendFileParseRequest() )


def _Check_FileReadyToParse_Diagnostic_Warning( ycm ):
# Tests Vim sign placement/unplacement and error/warning count python API
Expand Down Expand Up @@ -353,6 +369,8 @@ def DiagnosticResponse( *args ):
eq_( ycm.GetErrorCount(), 0 )
eq_( ycm.GetWarningCount(), 1 )

ok_( not ycm.ShouldResendFileParseRequest() )


def _Check_FileReadyToParse_Diagnostic_Clean( ycm ):
# Tests Vim sign unplacement and error/warning count python API
Expand All @@ -368,6 +386,7 @@ def _Check_FileReadyToParse_Diagnostic_Clean( ycm ):
)
eq_( ycm.GetErrorCount(), 0 )
eq_( ycm.GetWarningCount(), 0 )
ok_( not ycm.ShouldResendFileParseRequest() )


@patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
Expand Down
Loading

0 comments on commit a24d97c

Please sign in to comment.