diff options
Diffstat (limited to 'vim/bundle/YouCompleteMe/python/ycm/client')
12 files changed, 1328 insertions, 0 deletions
diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/__init__.py b/vim/bundle/YouCompleteMe/python/ycm/client/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/__init__.py diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/base_request.py b/vim/bundle/YouCompleteMe/python/ycm/client/base_request.py new file mode 100644 index 0000000..ffc3fac --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/base_request.py @@ -0,0 +1,244 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 + +import requests +import urllib.parse +import json +from future.utils import native +from base64 import b64decode, b64encode +from retries import retries +from requests_futures.sessions import FuturesSession +from ycm.unsafe_thread_pool_executor import UnsafeThreadPoolExecutor +from ycm import vimsupport +from ycmd.utils import ToBytes +from ycmd.hmac_utils import CreateRequestHmac, CreateHmac, SecureBytesEqual +from ycmd.responses import ServerError, UnknownExtraConf + +_HEADERS = {'content-type': 'application/json'} +_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' + + +class BaseRequest( object ): + + def __init__( self ): + pass + + + def Start( self ): + pass + + + def Done( self ): + return True + + + def Response( self ): + return {} + + # This method blocks + # |timeout| is num seconds to tolerate no response from server before giving + # up; see Requests docs for details (we just pass the param along). + @staticmethod + def GetDataFromHandler( handler, timeout = _DEFAULT_TIMEOUT_SEC ): + return JsonFromFuture( BaseRequest._TalkToHandlerAsync( '', + handler, + 'GET', + timeout ) ) + + + # This is the blocking version of the method. See below for async. + # |timeout| is num seconds to tolerate no response from server before giving + # up; see Requests docs for details (we just pass the param along). + @staticmethod + def PostDataToHandler( data, handler, timeout = _DEFAULT_TIMEOUT_SEC ): + return JsonFromFuture( BaseRequest.PostDataToHandlerAsync( data, + handler, + timeout ) ) + + + # This returns a future! Use JsonFromFuture to get the value. + # |timeout| is num seconds to tolerate no response from server before giving + # up; see Requests docs for details (we just pass the param along). + @staticmethod + def PostDataToHandlerAsync( data, handler, timeout = _DEFAULT_TIMEOUT_SEC ): + return BaseRequest._TalkToHandlerAsync( data, handler, 'POST', timeout ) + + + # This returns a future! Use JsonFromFuture to get the value. + # |method| is either 'POST' or 'GET'. + # |timeout| is num seconds to tolerate no response from server before giving + # up; see Requests docs for details (we just pass the param along). + @staticmethod + def _TalkToHandlerAsync( data, + handler, + method, + timeout = _DEFAULT_TIMEOUT_SEC ): + def SendRequest( data, handler, method, timeout ): + request_uri = _BuildUri( handler ) + if method == 'POST': + sent_data = _ToUtf8Json( data ) + return BaseRequest.session.post( + request_uri, + data = sent_data, + headers = BaseRequest._ExtraHeaders( method, + request_uri, + sent_data ), + timeout = timeout ) + if method == 'GET': + return BaseRequest.session.get( + request_uri, + headers = BaseRequest._ExtraHeaders( method, request_uri ), + timeout = timeout ) + + @retries( 5, delay = 0.5, backoff = 1.5 ) + def DelayedSendRequest( data, handler, method ): + request_uri = _BuildUri( handler ) + if method == 'POST': + sent_data = _ToUtf8Json( data ) + return requests.post( + request_uri, + data = sent_data, + headers = BaseRequest._ExtraHeaders( method, + request_uri, + sent_data ) ) + if method == 'GET': + return requests.get( + request_uri, + headers = BaseRequest._ExtraHeaders( method, request_uri ) ) + + if not _CheckServerIsHealthyWithCache(): + return _EXECUTOR.submit( DelayedSendRequest, data, handler, method ) + + return SendRequest( data, handler, method, timeout ) + + + @staticmethod + def _ExtraHeaders( method, request_uri, request_body = None ): + if not request_body: + request_body = bytes( b'' ) + headers = dict( _HEADERS ) + headers[ _HMAC_HEADER ] = b64encode( + CreateRequestHmac( ToBytes( method ), + ToBytes( urllib.parse.urlparse( request_uri ).path ), + request_body, + BaseRequest.hmac_secret ) ) + return headers + + session = FuturesSession( executor = _EXECUTOR ) + server_location = '' + hmac_secret = '' + + +def BuildRequestData( include_buffer_data = True ): + line, column = vimsupport.CurrentLineAndColumn() + filepath = vimsupport.GetCurrentBufferFilepath() + request_data = { + 'line_num': line + 1, + 'column_num': column + 1, + 'filepath': filepath + } + + if include_buffer_data: + request_data[ 'file_data' ] = vimsupport.GetUnsavedAndCurrentBufferData() + + return request_data + + +def JsonFromFuture( future ): + response = future.result() + _ValidateResponseObject( response ) + if response.status_code == requests.codes.server_error: + raise MakeServerException( response.json() ) + + # We let Requests handle the other status types, we only handle the 500 + # error code. + response.raise_for_status() + + if response.text: + return response.json() + return None + + +def HandleServerException( exception ): + serialized_exception = str( exception ) + + # We ignore the exception about the file already being parsed since it comes + # up often and isn't something that's actionable by the user. + if 'already being parsed' in serialized_exception: + return + vimsupport.PostMultiLineNotice( serialized_exception ) + + +def _ToUtf8Json( data ): + return ToBytes( json.dumps( data ) if data else None ) + + +def _ValidateResponseObject( response ): + our_hmac = CreateHmac( response.content, BaseRequest.hmac_secret ) + their_hmac = ToBytes( b64decode( response.headers[ _HMAC_HEADER ] ) ) + if not SecureBytesEqual( our_hmac, their_hmac ): + raise RuntimeError( 'Received invalid HMAC for response!' ) + return True + + +def _BuildUri( handler ): + return native( ToBytes( urllib.parse.urljoin( BaseRequest.server_location, + handler ) ) ) + + +SERVER_HEALTHY = False + + +def _CheckServerIsHealthyWithCache(): + global SERVER_HEALTHY + + def _ServerIsHealthy(): + request_uri = _BuildUri( 'healthy' ) + response = requests.get( request_uri, + headers = BaseRequest._ExtraHeaders( + 'GET', request_uri, bytes( b'' ) ) ) + _ValidateResponseObject( response ) + response.raise_for_status() + return response.json() + + if SERVER_HEALTHY: + return True + + try: + SERVER_HEALTHY = _ServerIsHealthy() + return SERVER_HEALTHY + except: + return False + + +def MakeServerException( data ): + if data[ 'exception' ][ 'TYPE' ] == UnknownExtraConf.__name__: + return UnknownExtraConf( data[ 'exception' ][ 'extra_conf_file' ] ) + + return ServerError( '{0}: {1}'.format( data[ 'exception' ][ 'TYPE' ], + data[ 'message' ] ) ) diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/command_request.py b/vim/bundle/YouCompleteMe/python/ycm/client/command_request.py new file mode 100644 index 0000000..6c5bc66 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/command_request.py @@ -0,0 +1,153 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 requests.exceptions import ReadTimeout + +from ycmd.responses import ServerError +from ycm.client.base_request import ( BaseRequest, BuildRequestData, + HandleServerException ) +from ycm import vimsupport +from ycmd.utils import ToUnicode + + +def _EnsureBackwardsCompatibility( arguments ): + if arguments and arguments[ 0 ] == 'GoToDefinitionElseDeclaration': + arguments[ 0 ] = 'GoTo' + return arguments + + +class CommandRequest( BaseRequest ): + def __init__( self, arguments, completer_target = None ): + super( CommandRequest, self ).__init__() + self._arguments = _EnsureBackwardsCompatibility( arguments ) + self._completer_target = ( completer_target if completer_target + else 'filetype_default' ) + self._response = None + + + def Start( self ): + request_data = BuildRequestData() + request_data.update( { + 'completer_target': self._completer_target, + 'command_arguments': self._arguments + } ) + try: + self._response = self.PostDataToHandler( request_data, + 'run_completer_command' ) + except ( ServerError, ReadTimeout ) as e: + HandleServerException( e ) + + + def Response( self ): + return self._response + + + def RunPostCommandActionsIfNeeded( self ): + if not self.Done() or self._response is None: + return + + # If not a dictionary or a list, the response is necessarily a + # scalar: boolean, number, string, etc. In this case, we print + # it to the user. + if not isinstance( self._response, ( dict, list ) ): + return self._HandleBasicResponse() + + if 'fixits' in self._response: + return self._HandleFixitResponse() + + if 'message' in self._response: + return self._HandleMessageResponse() + + if 'detailed_info' in self._response: + return self._HandleDetailedInfoResponse() + + # The only other type of response we understand is GoTo, and that is the + # only one that we can't detect just by inspecting the response (it should + # either be a single location or a list) + return self._HandleGotoResponse() + + + def _HandleGotoResponse( self ): + if isinstance( self._response, list ): + vimsupport.SetQuickFixList( + [ _BuildQfListItem( x ) for x in self._response ], + focus = True, + autoclose = True ) + else: + vimsupport.JumpToLocation( self._response[ 'filepath' ], + self._response[ 'line_num' ], + self._response[ 'column_num' ] ) + + + def _HandleFixitResponse( self ): + if not len( self._response[ 'fixits' ] ): + vimsupport.EchoText( "No fixits found for current line" ) + else: + chunks = self._response[ 'fixits' ][ 0 ][ 'chunks' ] + try: + vimsupport.ReplaceChunks( chunks ) + except RuntimeError as e: + vimsupport.PostMultiLineNotice( str( e ) ) + + + def _HandleBasicResponse( self ): + vimsupport.EchoText( self._response ) + + + def _HandleMessageResponse( self ): + vimsupport.EchoText( self._response[ 'message' ] ) + + + def _HandleDetailedInfoResponse( self ): + vimsupport.WriteToPreviewWindow( self._response[ 'detailed_info' ] ) + + +def SendCommandRequest( arguments, completer ): + request = CommandRequest( arguments, completer ) + # This is a blocking call. + request.Start() + request.RunPostCommandActionsIfNeeded() + return request.Response() + + +def _BuildQfListItem( goto_data_item ): + qf_item = {} + if 'filepath' in goto_data_item: + qf_item[ 'filename' ] = ToUnicode( goto_data_item[ 'filepath' ] ) + if 'description' in goto_data_item: + qf_item[ 'text' ] = ToUnicode( goto_data_item[ 'description' ] ) + if 'line_num' in goto_data_item: + qf_item[ 'lnum' ] = goto_data_item[ 'line_num' ] + if 'column_num' in goto_data_item: + # ycmd returns columns 1-based, and QuickFix lists require "byte offsets". + # See :help getqflist and equivalent comment in + # vimsupport.ConvertDiagnosticsToQfList. + # + # When the Vim help says "byte index", it really means "1-based column + # number" (which is somewhat confusing). :help getqflist states "first + # column is 1". + qf_item[ 'col' ] = goto_data_item[ 'column_num' ] + + return qf_item diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/completer_available_request.py b/vim/bundle/YouCompleteMe/python/ycm/client/completer_available_request.py new file mode 100644 index 0000000..3a480ca --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/completer_available_request.py @@ -0,0 +1,58 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 requests.exceptions import ReadTimeout + +from ycm.client.base_request import ( BaseRequest, BuildRequestData, + HandleServerException ) +from ycmd.responses import ServerError + + +class CompleterAvailableRequest( BaseRequest ): + def __init__( self, filetypes ): + super( CompleterAvailableRequest, self ).__init__() + self.filetypes = filetypes + self._response = None + + + def Start( self ): + request_data = BuildRequestData() + request_data.update( { 'filetypes': self.filetypes } ) + try: + self._response = self.PostDataToHandler( request_data, + 'semantic_completion_available' ) + except ( ServerError, ReadTimeout ) as e: + HandleServerException( e ) + + + def Response( self ): + return self._response + + +def SendCompleterAvailableRequest( filetypes ): + request = CompleterAvailableRequest( filetypes ) + # This is a blocking call. + request.Start() + return request.Response() diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/completion_request.py b/vim/bundle/YouCompleteMe/python/ycm/client/completion_request.py new file mode 100644 index 0000000..bf16912 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/completion_request.py @@ -0,0 +1,109 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 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 + + +class CompletionRequest( BaseRequest ): + def __init__( self, request_data ): + super( CompletionRequest, self ).__init__() + self.request_data = request_data + + + def Start( self ): + self._response_future = self.PostDataToHandlerAsync( self.request_data, + 'completions', + TIMEOUT_SECONDS ) + + + def Done( self ): + return self._response_future.done() + + + def RawResponse( self ): + if not self._response_future: + return [] + try: + response = JsonFromFuture( self._response_future ) + + errors = response[ 'errors' ] if 'errors' in response else [] + for e in errors: + HandleServerException( MakeServerException( e ) ) + + return JsonFromFuture( self._response_future )[ 'completions' ] + except ( ServerError, ReadTimeout ) as e: + HandleServerException( e ) + return [] + + + def Response( self ): + return _ConvertCompletionDatasToVimDatas( self.RawResponse() ) + + +def ConvertCompletionDataToVimData( completion_data ): + # see :h complete-items for a description of the dictionary fields + vim_data = { + 'word' : '', + 'dup' : 1, + 'empty' : 1, + } + + if ( 'extra_data' in completion_data and + 'doc_string' in completion_data[ 'extra_data' ] ): + doc_string = completion_data[ 'extra_data' ][ 'doc_string' ] + else: + doc_string = "" + + if 'insertion_text' in completion_data: + vim_data[ 'word' ] = completion_data[ 'insertion_text' ] + if 'menu_text' in completion_data: + vim_data[ 'abbr' ] = completion_data[ 'menu_text' ] + if 'extra_menu_info' in completion_data: + vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ] + if 'kind' in completion_data: + kind = ToUnicode( completion_data[ 'kind' ] ) + if kind: + vim_data[ 'kind' ] = kind[ 0 ].lower() + if 'detailed_info' in completion_data: + vim_data[ 'info' ] = completion_data[ 'detailed_info' ] + if doc_string: + vim_data[ 'info' ] += '\n' + doc_string + elif doc_string: + vim_data[ 'info' ] = doc_string + + return vim_data + + +def _ConvertCompletionDatasToVimDatas( response_data ): + return [ ConvertCompletionDataToVimData( x ) + for x in response_data ] diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/event_notification.py b/vim/bundle/YouCompleteMe/python/ycm/client/event_notification.py new file mode 100644 index 0000000..b9a60d1 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/event_notification.py @@ -0,0 +1,89 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 requests.exceptions import ReadTimeout + +from ycm import vimsupport +from ycmd.responses import UnknownExtraConf, ServerError +from ycm.client.base_request import ( BaseRequest, BuildRequestData, + JsonFromFuture, HandleServerException ) + + +class EventNotification( BaseRequest ): + def __init__( self, event_name, extra_data = None ): + super( EventNotification, self ).__init__() + self._event_name = event_name + self._extra_data = extra_data + self._cached_response = None + + + def Start( self ): + request_data = BuildRequestData() + if self._extra_data: + request_data.update( self._extra_data ) + request_data[ 'event_name' ] = self._event_name + + self._response_future = self.PostDataToHandlerAsync( request_data, + 'event_notification' ) + + + def Done( self ): + return self._response_future.done() + + + def Response( self ): + if self._cached_response: + return self._cached_response + + 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 ) + + return self._cached_response if self._cached_response else [] + + +def SendEventNotificationAsync( event_name, extra_data = None ): + event = EventNotification( event_name, 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/vim/bundle/YouCompleteMe/python/ycm/client/omni_completion_request.py b/vim/bundle/YouCompleteMe/python/ycm/client/omni_completion_request.py new file mode 100644 index 0000000..2bcb291 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/omni_completion_request.py @@ -0,0 +1,71 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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.completion_request import CompletionRequest + + +class OmniCompletionRequest( CompletionRequest ): + def __init__( self, omni_completer, request_data ): + super( OmniCompletionRequest, self ).__init__( request_data ) + self._omni_completer = omni_completer + + + def Start( self ): + self._results = self._omni_completer.ComputeCandidates( self.request_data ) + + + def Done( self ): + return True + + + def RawResponse( self ): + return _ConvertVimDatasToCompletionDatas( self._results ) + + + def Response( self ): + return self._results + + +def ConvertVimDataToCompletionData( vim_data ): + # see :h complete-items for a description of the dictionary fields + completion_data = {} + + if 'word' in vim_data: + completion_data[ 'insertion_text' ] = vim_data[ 'word' ] + if 'abbr' in vim_data: + completion_data[ 'menu_text' ] = vim_data[ 'abbr' ] + if 'menu' in vim_data: + completion_data[ 'extra_menu_info' ] = vim_data[ 'menu' ] + if 'kind' in vim_data: + completion_data[ 'kind' ] = [ vim_data[ 'kind' ] ] + if 'info' in vim_data: + completion_data[ 'detailed_info' ] = vim_data[ 'info' ] + + return completion_data + + +def _ConvertVimDatasToCompletionDatas( response_data ): + return [ ConvertVimDataToCompletionData( x ) + for x in response_data ] diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/tests/__init__.py b/vim/bundle/YouCompleteMe/python/ycm/client/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/tests/__init__.py diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/tests/command_request_test.py b/vim/bundle/YouCompleteMe/python/ycm/client/tests/command_request_test.py new file mode 100644 index 0000000..dfc55e0 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/tests/command_request_test.py @@ -0,0 +1,283 @@ +# 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 <http://www.gnu.org/licenses/>. + +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.test_utils import ExtendedMock, MockVimModule +MockVimModule() + +import json +from mock import patch, call +from nose.tools import ok_ +from ycm.client.command_request import CommandRequest + + +class GoToResponse_QuickFix_test( object ): + """This class tests the generation of QuickFix lists for GoTo responses which + return multiple locations, such as the Python completer and JavaScript + completer. It mostly proves that we use 1-based indexing for the column + number.""" + + def setUp( self ): + self._request = CommandRequest( [ 'GoToTest' ] ) + + + def tearDown( self ): + self._request = None + + + def GoTo_EmptyList_test( self ): + self._CheckGoToList( [], [] ) + + + def GoTo_SingleItem_List_test( self ): + self._CheckGoToList( [ { + 'filepath': 'dummy_file', + 'line_num': 10, + 'column_num': 1, + 'description': 'this is some text', + } ], [ { + 'filename': 'dummy_file', + 'text': 'this is some text', + 'lnum': 10, + 'col': 1 + } ] ) + + + def GoTo_MultiItem_List_test( self ): + self._CheckGoToList( [ { + 'filepath': 'dummy_file', + 'line_num': 10, + 'column_num': 1, + 'description': 'this is some other text', + }, { + 'filepath': 'dummy_file2', + 'line_num': 1, + 'column_num': 21, + 'description': 'this is some text', + } ], [ { + 'filename': 'dummy_file', + 'text': 'this is some other text', + 'lnum': 10, + 'col': 1 + }, { + 'filename': 'dummy_file2', + 'text': 'this is some text', + 'lnum': 1, + 'col': 21 + } ] ) + + + @patch( 'ycm.vimsupport.VariableExists', return_value = True ) + @patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' ) + @patch( 'vim.command', new_callable = ExtendedMock ) + @patch( 'vim.eval', new_callable = ExtendedMock ) + def _CheckGoToList( self, + completer_response, + expected_qf_list, + vim_eval, + vim_command, + set_fitting_height, + variable_exists ): + self._request._response = completer_response + + self._request.RunPostCommandActionsIfNeeded() + + vim_eval.assert_has_exact_calls( [ + call( 'setqflist( {0} )'.format( json.dumps( expected_qf_list ) ) ) + ] ) + vim_command.assert_has_exact_calls( [ + call( 'botright copen' ), + call( 'au WinLeave <buffer> q' ), + call( 'doautocmd User YcmQuickFixOpened' ) + ] ) + set_fitting_height.assert_called_once_with() + + +class Response_Detection_test( object ): + + def BasicResponse_test( self ): + def _BasicResponseTest( command, response ): + with patch( 'vim.command' ) as vim_command: + request = CommandRequest( [ command ] ) + request._response = response + request.RunPostCommandActionsIfNeeded() + vim_command.assert_called_with( "echom '{0}'".format( response ) ) + + tests = [ + [ 'AnythingYouLike', True ], + [ 'GoToEvenWorks', 10 ], + [ 'FixItWorks', 'String!' ], + [ 'and8434fd andy garbag!', 10.3 ], + ] + + for test in tests: + yield _BasicResponseTest, test[ 0 ], test[ 1 ] + + + def FixIt_Response_Empty_test( self ): + # Ensures we recognise and handle fixit responses which indicate that there + # are no fixits available + def EmptyFixItTest( command ): + with patch( 'ycm.vimsupport.ReplaceChunks' ) as replace_chunks: + with patch( 'ycm.vimsupport.EchoText' ) as echo_text: + request = CommandRequest( [ command ] ) + request._response = { + 'fixits': [] + } + request.RunPostCommandActionsIfNeeded() + + echo_text.assert_called_with( 'No fixits found for current line' ) + replace_chunks.assert_not_called() + + for test in [ 'FixIt', 'Refactor', 'GoToHell', 'any_old_garbade!!!21' ]: + yield EmptyFixItTest, test + + + def FixIt_Response_test( self ): + # Ensures we recognise and handle fixit responses with some dummy chunk data + def FixItTest( command, response, chunks ): + with patch( 'ycm.vimsupport.ReplaceChunks' ) as replace_chunks: + with patch( 'ycm.vimsupport.EchoText' ) as echo_text: + request = CommandRequest( [ command ] ) + request._response = response + request.RunPostCommandActionsIfNeeded() + + replace_chunks.assert_called_with( chunks ) + echo_text.assert_not_called() + + basic_fixit = { + 'fixits': [ { + 'chunks': [ { + 'dummy chunk contents': True + } ] + } ] + } + basic_fixit_chunks = basic_fixit[ 'fixits' ][ 0 ][ 'chunks' ] + + multi_fixit = { + 'fixits': [ { + 'chunks': [ { + 'dummy chunk contents': True + } ] + }, { + 'additional fixits are ignored currently': True + } ] + } + multi_fixit_first_chunks = multi_fixit[ 'fixits' ][ 0 ][ 'chunks' ] + + tests = [ + [ 'AnythingYouLike', basic_fixit, basic_fixit_chunks ], + [ 'GoToEvenWorks', basic_fixit, basic_fixit_chunks ], + [ 'FixItWorks', basic_fixit, basic_fixit_chunks ], + [ 'and8434fd andy garbag!', basic_fixit, basic_fixit_chunks ], + [ 'additional fixits ignored', multi_fixit, multi_fixit_first_chunks ], + ] + + for test in tests: + yield FixItTest, test[ 0 ], test[ 1 ], test[ 2 ] + + + def Message_Response_test( self ): + # Ensures we correctly recognise and handle responses with a message to show + # to the user + + def MessageTest( command, message ): + with patch( 'ycm.vimsupport.EchoText' ) as echo_text: + request = CommandRequest( [ command ] ) + request._response = { 'message': message } + request.RunPostCommandActionsIfNeeded() + echo_text.assert_called_with( message ) + + tests = [ + [ '___________', 'This is a message' ], + [ '', 'this is also a message' ], + [ 'GetType', 'std::string' ], + ] + + for test in tests: + yield MessageTest, test[ 0 ], test[ 1 ] + + + def Detailed_Info_test( self ): + # Ensures we correctly detect and handle detailed_info responses which are + # used to display information in the preview window + + def DetailedInfoTest( command, info ): + with patch( 'ycm.vimsupport.WriteToPreviewWindow' ) as write_to_preview: + request = CommandRequest( [ command ] ) + request._response = { 'detailed_info': info } + request.RunPostCommandActionsIfNeeded() + write_to_preview.assert_called_with( info ) + + tests = [ + [ '___________', 'This is a message' ], + [ '', 'this is also a message' ], + [ 'GetDoc', 'std::string\netc\netc' ], + ] + + for test in tests: + yield DetailedInfoTest, test[ 0 ], test[ 1 ] + + + def GoTo_Single_test( self ): + # Ensures we handle any unknown type of response as a GoTo response + + def GoToTest( command, response ): + with patch( 'ycm.vimsupport.JumpToLocation' ) as jump_to_location: + request = CommandRequest( [ command ] ) + request._response = response + request.RunPostCommandActionsIfNeeded() + jump_to_location.assert_called_with( + response[ 'filepath' ], + response[ 'line_num' ], + response[ 'column_num' ] ) + + def GoToListTest( command, response ): + # Note: the detail of these called are tested by + # GoToResponse_QuickFix_test, so here we just check that the right call is + # made + with patch( 'ycm.vimsupport.SetQuickFixList' ) as set_qf_list: + request = CommandRequest( [ command ] ) + request._response = response + request.RunPostCommandActionsIfNeeded() + ok_( set_qf_list.called ) + + basic_goto = { + 'filepath': 'test', + 'line_num': 10, + 'column_num': 100, + } + + tests = [ + [ GoToTest, 'AnythingYouLike', basic_goto ], + [ GoToTest, 'GoTo', basic_goto ], + [ GoToTest, 'FindAThing', basic_goto ], + [ GoToTest, 'FixItGoto', basic_goto ], + [ GoToListTest, 'AnythingYouLike', [ basic_goto ] ], + [ GoToListTest, 'GoTo', [] ], + [ GoToListTest, 'FixItGoto', [ basic_goto, basic_goto ] ], + ] + + for test in tests: + yield test[ 0 ], test[ 1 ], test[ 2 ] diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/tests/completion_request_test.py b/vim/bundle/YouCompleteMe/python/ycm/client/tests/completion_request_test.py new file mode 100644 index 0000000..acf0252 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/tests/completion_request_test.py @@ -0,0 +1,186 @@ +# Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. + +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 nose.tools import eq_ +from ycm.test_utils import MockVimModule +vim_mock = MockVimModule() + +from .. import completion_request + + +class ConvertCompletionResponseToVimDatas_test( object ): + """ This class tests the + completion_request._ConvertCompletionResponseToVimDatas method """ + + def _Check( self, completion_data, expected_vim_data ): + vim_data = completion_request.ConvertCompletionDataToVimData( + completion_data ) + + try: + eq_( expected_vim_data, vim_data ) + except: + print( "Expected:\n'{0}'\nwhen parsing:\n'{1}'\nBut found:\n'{2}'".format( + expected_vim_data, + completion_data, + vim_data ) ) + raise + + + def All_Fields_test( self ): + self._Check( { + 'insertion_text': 'INSERTION TEXT', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': { + 'doc_string': 'DOC STRING', + }, + }, { + 'word' : 'INSERTION TEXT', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'dup' : 1, + 'empty': 1, + } ) + + + def Just_Detailed_Info_test( self ): + self._Check( { + 'insertion_text': 'INSERTION TEXT', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + }, { + 'word' : 'INSERTION TEXT', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO', + 'dup' : 1, + 'empty': 1, + } ) + + + def Just_Doc_String_test( self ): + self._Check( { + 'insertion_text': 'INSERTION TEXT', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'extra_data': { + 'doc_string': 'DOC STRING', + }, + }, { + 'word' : 'INSERTION TEXT', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DOC STRING', + 'dup' : 1, + 'empty': 1, + } ) + + + def Extra_Info_No_Doc_String_test( self ): + self._Check( { + 'insertion_text': 'INSERTION TEXT', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'extra_data': { + }, + }, { + 'word' : 'INSERTION TEXT', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'dup' : 1, + 'empty': 1, + } ) + + + def Extra_Info_No_Doc_String_With_Detailed_Info_test( self ): + self._Check( { + 'insertion_text': 'INSERTION TEXT', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': { + }, + }, { + 'word' : 'INSERTION TEXT', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO', + 'dup' : 1, + 'empty': 1, + } ) + + + def Empty_Insertion_Text_test( self ): + self._Check( { + 'insertion_text': '', + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': { + 'doc_string': 'DOC STRING', + }, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'dup' : 1, + 'empty': 1, + } ) + + + def No_Insertion_Text_test( self ): + self._Check( { + 'menu_text': 'MENU TEXT', + 'extra_menu_info': 'EXTRA MENU INFO', + 'kind': 'K', + 'detailed_info': 'DETAILED INFO', + 'extra_data': { + 'doc_string': 'DOC STRING', + }, + }, { + 'word' : '', + 'abbr' : 'MENU TEXT', + 'menu' : 'EXTRA MENU INFO', + 'kind' : 'k', + 'info' : 'DETAILED INFO\nDOC STRING', + 'dup' : 1, + 'empty': 1, + } ) diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/tests/omni_completion_request_tests.py b/vim/bundle/YouCompleteMe/python/ycm/client/tests/omni_completion_request_tests.py new file mode 100644 index 0000000..02fb2e9 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/tests/omni_completion_request_tests.py @@ -0,0 +1,81 @@ +# 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 <http://www.gnu.org/licenses/>. + +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 mock import MagicMock +from nose.tools import eq_ +from hamcrest import assert_that, has_entries + +from ycm.client.omni_completion_request import OmniCompletionRequest + + +def BuildOmnicompletionRequest( results ): + omni_completer = MagicMock() + omni_completer.ComputeCandidates = MagicMock( return_value = results ) + + request = OmniCompletionRequest( omni_completer, None ) + request.Start() + + return request + + +def Done_AlwaysTrue_test(): + request = BuildOmnicompletionRequest( [] ) + + eq_( request.Done(), True ) + + +def Response_FromOmniCompleter_test(): + results = [ { "word": "test" } ] + request = BuildOmnicompletionRequest( results ) + + eq_( request.Response(), results ) + + +def RawResponse_ConvertedFromOmniCompleter_test(): + vim_results = [ + { "word": "WORD", "abbr": "ABBR", "menu": "MENU", + "kind": "KIND", "info": "INFO" }, + { "word": "WORD2", "abbr": "ABBR2", "menu": "MENU2", + "kind": "KIND2", "info": "INFO" }, + { "word": "WORD", "abbr": "ABBR", }, + { }, + ] + expected_results = [ + has_entries( { "insertion_text": "WORD", "menu_text": "ABBR", + "extra_menu_info": "MENU", "kind": [ "KIND" ], + "detailed_info": "INFO" } ), + has_entries( { "insertion_text": "WORD2", "menu_text": "ABBR2", + "extra_menu_info": "MENU2", "kind": [ "KIND2" ], + "detailed_info": "INFO" } ), + has_entries( { "insertion_text": "WORD", "menu_text": "ABBR", } ), + has_entries( { } ), + ] + request = BuildOmnicompletionRequest( vim_results ) + + results = request.RawResponse() + + eq_( len( results ), len( expected_results ) ) + for result, expected_result in zip( results, expected_results ): + assert_that( result, expected_result ) diff --git a/vim/bundle/YouCompleteMe/python/ycm/client/ycmd_keepalive.py b/vim/bundle/YouCompleteMe/python/ycm/client/ycmd_keepalive.py new file mode 100644 index 0000000..fca59f4 --- /dev/null +++ b/vim/bundle/YouCompleteMe/python/ycm/client/ycmd_keepalive.py @@ -0,0 +1,54 @@ +# Copyright (C) 2013 Google Inc. +# +# 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 <http://www.gnu.org/licenses/>. + +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 + +import time +from threading import Thread +from ycm.client.base_request import BaseRequest + + +# This class can be used to keep the ycmd server alive for the duration of the +# life of the client. By default, ycmd shuts down if it doesn't see a request in +# a while. +class YcmdKeepalive( object ): + def __init__( self, ping_interval_seconds = 60 * 10 ): + self._keepalive_thread = Thread( target = self._ThreadMain ) + self._keepalive_thread.daemon = True + self._ping_interval_seconds = ping_interval_seconds + + + def Start( self ): + self._keepalive_thread.start() + + + def _ThreadMain( self ): + while True: + time.sleep( self._ping_interval_seconds ) + + # We don't care if there's an intermittent problem in contacting the + # server; it's fine to just skip this ping. + try: + BaseRequest.GetDataFromHandler( 'healthy' ) + except: + pass |