diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index 1fae1ea8..d158c633 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -23,6 +23,8 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa +import contextlib +import logging import requests import urllib.parse import json @@ -41,6 +43,7 @@ _EXECUTOR = UnsafeThreadPoolExecutor( max_workers = 30 ) # Setting this to None seems to screw up the Requests/urllib3 libs. _DEFAULT_TIMEOUT_SEC = 30 _HMAC_HEADER = 'x-ycm-hmac' +_logger = logging.getLogger( __name__ ) class BaseRequest( object ): @@ -193,7 +196,32 @@ def JsonFromFuture( future ): return None -def HandleServerException( exception, truncate = False ): +@contextlib.contextmanager +def HandleServerException( display = True, truncate = False ): + try: + yield + except UnknownExtraConf as e: + if vimsupport.Confirm( str( e ) ): + _LoadExtraConfFile( e.extra_conf_file ) + else: + _IgnoreExtraConfFile( e.extra_conf_file ) + except Exception as e: + _logger.exception( 'Error while handling server response' ) + if display: + DisplayServerException( e, truncate ) + + +def _LoadExtraConfFile( filepath ): + BaseRequest.PostDataToHandler( { 'filepath': filepath }, + 'load_extra_conf_file' ) + + +def _IgnoreExtraConfFile( filepath ): + BaseRequest.PostDataToHandler( { 'filepath': filepath }, + 'ignore_extra_conf_file' ) + + +def DisplayServerException( exception, truncate = False ): serialized_exception = str( exception ) # We ignore the exception about the file already being parsed since it comes diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index fd60f262..59747ccf 100644 --- a/python/ycm/client/command_request.py +++ b/python/ycm/client/command_request.py @@ -23,9 +23,6 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from requests.exceptions import ReadTimeout - -from ycmd.responses import ServerError from ycm.client.base_request import ( BaseRequest, BuildRequestData, HandleServerException ) from ycm import vimsupport @@ -53,11 +50,9 @@ class CommandRequest( BaseRequest ): 'completer_target': self._completer_target, 'command_arguments': self._arguments } ) - try: + with HandleServerException(): self._response = self.PostDataToHandler( request_data, 'run_completer_command' ) - except ( ServerError, ReadTimeout ) as e: - HandleServerException( e ) def Response( self ): diff --git a/python/ycm/client/completer_available_request.py b/python/ycm/client/completer_available_request.py index 3a480cac..37d53d88 100644 --- a/python/ycm/client/completer_available_request.py +++ b/python/ycm/client/completer_available_request.py @@ -23,11 +23,8 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from requests.exceptions import ReadTimeout - from ycm.client.base_request import ( BaseRequest, BuildRequestData, HandleServerException ) -from ycmd.responses import ServerError class CompleterAvailableRequest( BaseRequest ): @@ -40,11 +37,9 @@ class CompleterAvailableRequest( BaseRequest ): def Start( self ): request_data = BuildRequestData() request_data.update( { 'filetypes': self.filetypes } ) - try: + with HandleServerException(): self._response = self.PostDataToHandler( request_data, 'semantic_completion_available' ) - except ( ServerError, ReadTimeout ) as e: - HandleServerException( e ) def Response( self ): diff --git a/python/ycm/client/completion_request.py b/python/ycm/client/completion_request.py index f4440b64..4bf284c6 100644 --- a/python/ycm/client/completion_request.py +++ b/python/ycm/client/completion_request.py @@ -23,13 +23,10 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from requests.exceptions import ReadTimeout - from ycmd.utils import ToUnicode from ycm.client.base_request import ( BaseRequest, JsonFromFuture, HandleServerException, MakeServerException ) -from ycmd.responses import ServerError TIMEOUT_SECONDS = 0.5 @@ -53,16 +50,15 @@ class CompletionRequest( BaseRequest ): def RawResponse( self ): if not self._response_future: return [] - try: + with HandleServerException( truncate = True ): response = JsonFromFuture( self._response_future ) errors = response[ 'errors' ] if 'errors' in response else [] for e in errors: - HandleServerException( MakeServerException( e ) ) + with HandleServerException( truncate = True ): + raise MakeServerException( e ) - return JsonFromFuture( self._response_future )[ 'completions' ] - except ( ServerError, ReadTimeout ) as e: - HandleServerException( e, truncate = True ) + return response[ 'completions' ] return [] diff --git a/python/ycm/client/debug_info_request.py b/python/ycm/client/debug_info_request.py new file mode 100755 index 00000000..cf6599a7 --- /dev/null +++ b/python/ycm/client/debug_info_request.py @@ -0,0 +1,52 @@ +# Copyright (C) 2016 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 +from future import standard_library +standard_library.install_aliases() +from builtins import * # noqa + +from ycm.client.base_request import ( BaseRequest, BuildRequestData, + HandleServerException ) + + +class DebugInfoRequest( BaseRequest ): + def __init__( self ): + super( DebugInfoRequest, self ).__init__() + self._response = None + + + def Start( self ): + request_data = BuildRequestData() + with HandleServerException( display = False ): + self._response = self.PostDataToHandler( request_data, 'debug_info' ) + + + def Response( self ): + if not self._response: + return 'Server errored, no debug info from server' + return self._response + + +def SendDebugInfoRequest(): + request = DebugInfoRequest() + # This is a blocking call. + request.Start() + return request.Response() diff --git a/python/ycm/client/event_notification.py b/python/ycm/client/event_notification.py index bd73f31f..fa5df75e 100644 --- a/python/ycm/client/event_notification.py +++ b/python/ycm/client/event_notification.py @@ -23,10 +23,6 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from requests.exceptions import ReadTimeout - -from ycm import vimsupport -from ycmd.responses import UnknownExtraConf, ServerError from ycm.client.base_request import ( BaseRequest, BuildRequestData, JsonFromFuture, HandleServerException ) @@ -61,16 +57,8 @@ class EventNotification( BaseRequest ): if not self._response_future or self._event_name != 'FileReadyToParse': return [] - try: - try: - self._cached_response = JsonFromFuture( self._response_future ) - except UnknownExtraConf as e: - if vimsupport.Confirm( str( e ) ): - _LoadExtraConfFile( e.extra_conf_file ) - else: - _IgnoreExtraConfFile( e.extra_conf_file ) - except ( ServerError, ReadTimeout ) as e: - HandleServerException( e ) + with HandleServerException( truncate = True ): + self._cached_response = JsonFromFuture( self._response_future ) return self._cached_response if self._cached_response else [] @@ -80,13 +68,3 @@ def SendEventNotificationAsync( event_name, extra_data = None ): event = EventNotification( event_name, filepath, extra_data ) event.Start() - - -def _LoadExtraConfFile( filepath ): - BaseRequest.PostDataToHandler( { 'filepath': filepath }, - 'load_extra_conf_file' ) - - -def _IgnoreExtraConfFile( filepath ): - BaseRequest.PostDataToHandler( { 'filepath': filepath }, - 'ignore_extra_conf_file' ) diff --git a/python/ycm/client/shutdown_request.py b/python/ycm/client/shutdown_request.py index 1c2b952c..31fd6c53 100644 --- a/python/ycm/client/shutdown_request.py +++ b/python/ycm/client/shutdown_request.py @@ -23,9 +23,7 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from requests.exceptions import ReadTimeout - -from ycm.client.base_request import BaseRequest +from ycm.client.base_request import BaseRequest, HandleServerException TIMEOUT_SECONDS = 0.1 @@ -36,12 +34,8 @@ class ShutdownRequest( BaseRequest ): def Start( self ): - try: - self.PostDataToHandler( {}, - 'shutdown', - TIMEOUT_SECONDS ) - except ReadTimeout: - pass + with HandleServerException( display = False ): + self.PostDataToHandler( {}, 'shutdown', TIMEOUT_SECONDS ) def SendShutdownRequest(): diff --git a/python/ycm/omni_completer.py b/python/ycm/omni_completer.py index df3a51cc..2424ddd7 100644 --- a/python/ycm/omni_completer.py +++ b/python/ycm/omni_completer.py @@ -26,7 +26,6 @@ from builtins import * # noqa import vim from ycm import vimsupport from ycmd import utils -from ycmd.responses import ServerError from ycmd.completers.completer import Completer from ycm.client.base_request import BaseRequest, HandleServerException @@ -115,9 +114,7 @@ class OmniCompleter( Completer ): 'query': query } - try: + with HandleServerException(): return BaseRequest.PostDataToHandler( request_data, 'filter_and_sort_candidates' ) - except ServerError as e: - HandleServerException( e ) - return candidates + return candidates diff --git a/python/ycm/tests/event_notification_test.py b/python/ycm/tests/event_notification_test.py index d12892eb..ee010fde 100644 --- a/python/ycm/tests/event_notification_test.py +++ b/python/ycm/tests/event_notification_test.py @@ -128,13 +128,13 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test( # The first call raises a warning post_vim_message.assert_has_exact_calls( [ - call( ERROR_TEXT, truncate = False ) + call( ERROR_TEXT, truncate = True ) ] ) # Subsequent calls don't re-raise the warning ycm.HandleFileParseRequest() post_vim_message.assert_has_exact_calls( [ - call( ERROR_TEXT, truncate = False ) + call( ERROR_TEXT, truncate = True ) ] ) # But it does if a subsequent event raises again @@ -142,8 +142,8 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test( ok_( ycm.FileParseRequestReady() ) ycm.HandleFileParseRequest() post_vim_message.assert_has_exact_calls( [ - call( ERROR_TEXT, truncate = False ), - call( ERROR_TEXT, truncate = False ) + call( ERROR_TEXT, truncate = True ), + call( ERROR_TEXT, truncate = True ) ] ) @@ -159,9 +159,9 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_NonNative_test( vim_command.assert_not_called() -@patch( 'ycm.client.event_notification._LoadExtraConfFile', +@patch( 'ycm.client.base_request._LoadExtraConfFile', new_callable = ExtendedMock ) -@patch( 'ycm.client.event_notification._IgnoreExtraConfFile', +@patch( 'ycm.client.base_request._IgnoreExtraConfFile', new_callable = ExtendedMock ) @YouCompleteMeInstance() def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test( diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index b6e15e42..0116ff0f 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -38,16 +38,17 @@ from ycm import base, paths, vimsupport from ycmd import utils from ycmd import server_utils from ycmd.request_wrap import RequestWrap -from ycmd.responses import ServerError from ycm.diagnostic_interface import DiagnosticInterface from ycm.omni_completer import OmniCompleter from ycm import syntax_parse from ycm.client.ycmd_keepalive import YcmdKeepalive -from ycm.client.base_request import BaseRequest, BuildRequestData +from ycm.client.base_request import ( BaseRequest, BuildRequestData, + HandleServerException ) from ycm.client.completer_available_request import SendCompleterAvailableRequest from ycm.client.command_request import SendCommandRequest from ycm.client.completion_request import ( CompletionRequest, ConvertCompletionDataToVimData ) +from ycm.client.debug_info_request import SendDebugInfoRequest from ycm.client.omni_completion_request import OmniCompletionRequest from ycm.client.event_notification import ( SendEventNotificationAsync, EventNotification ) @@ -302,13 +303,10 @@ class YouCompleteMe( object ): def GetDefinedSubcommands( self ): if self.IsServerAlive(): - try: + with HandleServerException(): return BaseRequest.PostDataToHandler( BuildRequestData(), - 'defined_subcommands' ) - except ServerError: - return [] - else: - return [] + 'defined_subcommands' ) + return [] def GetCurrentCompletionRequest( self ): @@ -636,14 +634,13 @@ class YouCompleteMe( object ): def ShowDetailedDiagnostic( self ): if not self.IsServerAlive(): return - try: - debug_info = BaseRequest.PostDataToHandler( BuildRequestData(), - 'detailed_diagnostic' ) - if 'message' in debug_info: - vimsupport.PostVimMessage( debug_info[ 'message' ], + with HandleServerException(): + detailed_diagnostic = BaseRequest.PostDataToHandler( + BuildRequestData(), 'detailed_diagnostic' ) + + if 'message' in detailed_diagnostic: + vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ], warning = False ) - except ServerError as e: - vimsupport.PostVimMessage( str( e ) ) def DebugInfo( self ): @@ -651,8 +648,7 @@ class YouCompleteMe( object ): if self._client_logfile: debug_info += 'Client logfile: {0}\n'.format( self._client_logfile ) if self.IsServerAlive(): - debug_info += BaseRequest.PostDataToHandler( BuildRequestData(), - 'debug_info' ) + debug_info += SendDebugInfoRequest() else: debug_info += 'Server crashed, no debug info from server' debug_info += '\nServer running at: {0}\n'.format(