Event and completion request are now async

This results in a much snappier Vim.
This commit is contained in:
Strahinja Val Markovic 2013-10-01 16:21:17 -07:00
parent e08dd4ab33
commit 9d0a6c96d7
8 changed files with 60 additions and 32 deletions

View File

@ -40,6 +40,8 @@ function! youcompleteme#Enable()
py import sys py import sys
py import vim py import vim
exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )' exe 'python sys.path.insert( 0, "' . s:script_folder_path . '/../python" )'
py from ycm import utils
py utils.AddThirdPartyFoldersToSysPath()
py from ycm import base py from ycm import base
py from ycm import vimsupport py from ycm import vimsupport
py from ycm import user_options_store py from ycm import user_options_store
@ -497,13 +499,11 @@ python << EOF
def GetCompletions( query ): def GetCompletions( query ):
request = ycm_state.GetCurrentCompletionRequest() request = ycm_state.GetCurrentCompletionRequest()
request.Start( query ) request.Start( query )
results_ready = False while not request.Done():
while not results_ready:
results_ready = request.Done()
if bool( int( vim.eval( 'complete_check()' ) ) ): if bool( int( vim.eval( 'complete_check()' ) ) ):
return { 'words' : [], 'refresh' : 'always'} return { 'words' : [], 'refresh' : 'always'}
results = base.AdjustCandidateInsertionText( request.Results() ) results = base.AdjustCandidateInsertionText( request.Response() )
return { 'words' : results, 'refresh' : 'always' } return { 'words' : results, 'refresh' : 'always' }
EOF EOF

View File

@ -20,6 +20,7 @@
import vim import vim
import json import json
import requests import requests
from requests_futures.sessions import FuturesSession
from ycm import vimsupport from ycm import vimsupport
HEADERS = {'content-type': 'application/json'} HEADERS = {'content-type': 'application/json'}
@ -46,22 +47,22 @@ class BaseRequest( object ):
return {} return {}
# This is the blocking version of the method. See below for async.
@staticmethod @staticmethod
def PostDataToHandler( data, handler ): def PostDataToHandler( data, handler ):
response = requests.post( _BuildUri( handler ), return JsonFromFuture( BaseRequest.PostDataToHandlerAsync( data,
data = json.dumps( data ), handler ) )
headers = HEADERS )
if response.status_code == requests.codes.server_error:
raise ServerError( response.json()[ 'message' ] )
# We let Requests handle the other status types, we only handle the 500
# error code.
response.raise_for_status()
if response.text: # This returns a future! Use JsonFromFuture to get the value.
return response.json() @staticmethod
return None def PostDataToHandlerAsync( data, handler ):
return BaseRequest.session.post( _BuildUri( handler ),
data = json.dumps( data ),
headers = HEADERS )
session = FuturesSession( max_workers = 4 )
server_location = 'http://localhost:6666' server_location = 'http://localhost:6666'
@ -83,6 +84,20 @@ def BuildRequestData( start_column = None, query = None ):
return request_data return request_data
def JsonFromFuture( future ):
response = future.result()
if response.status_code == requests.codes.server_error:
raise ServerError( response.json()[ 'message' ] )
# 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 _BuildUri( handler ): def _BuildUri( handler ):
return ''.join( [ BaseRequest.server_location, '/', handler ] ) return ''.join( [ BaseRequest.server_location, '/', handler ] )

View File

@ -18,7 +18,6 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import vim import vim
import time
from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError
from ycm import vimsupport from ycm import vimsupport
from ycm.utils import ToUtf8IfNeeded from ycm.utils import ToUtf8IfNeeded
@ -70,10 +69,8 @@ class CommandRequest( BaseRequest ):
def SendCommandRequest( arguments, completer ): def SendCommandRequest( arguments, completer ):
request = CommandRequest( arguments, completer ) request = CommandRequest( arguments, completer )
# This is a blocking call.
request.Start() request.Start()
while not request.Done():
time.sleep( 0.1 )
request.RunPostCommandActionsIfNeeded() request.RunPostCommandActionsIfNeeded()
return request.Response() return request.Response()

View File

@ -19,7 +19,8 @@
from ycm import base from ycm import base
from ycm import vimsupport from ycm import vimsupport
from ycm.client.base_request import BaseRequest, BuildRequestData from ycm.client.base_request import ( BaseRequest, BuildRequestData,
JsonFromFuture )
class CompletionRequest( BaseRequest ): class CompletionRequest( BaseRequest ):
@ -30,27 +31,26 @@ class CompletionRequest( BaseRequest ):
self._request_data = BuildRequestData( self._completion_start_column ) self._request_data = BuildRequestData( self._completion_start_column )
# TODO: Do we need this anymore?
# def ShouldComplete( self ):
# return ( self._do_filetype_completion or
# self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )
def CompletionStartColumn( self ): def CompletionStartColumn( self ):
return self._completion_start_column return self._completion_start_column
def Start( self, query ): def Start( self, query ):
self._request_data[ 'query' ] = query self._request_data[ 'query' ] = query
self._response = self.PostDataToHandler( self._request_data, self._response_future = self.PostDataToHandlerAsync( self._request_data,
'get_completions' ) 'get_completions' )
def Results( self ): def Done( self ):
if not self._response: return self._response_future.done()
def Response( self ):
if not self._response_future:
return [] return []
try: try:
return [ _ConvertCompletionDataToVimData( x ) for x in self._response ] return [ _ConvertCompletionDataToVimData( x )
for x in JsonFromFuture( self._response_future ) ]
except Exception as e: except Exception as e:
vimsupport.PostVimMessage( str( e ) ) vimsupport.PostVimMessage( str( e ) )
return [] return []

View File

@ -41,7 +41,7 @@ class EventNotification( BaseRequest ):
# quietly to the Vim message log because nothing bad will happen if the # quietly to the Vim message log because nothing bad will happen if the
# server misses some events and we don't want to annoy the user. # server misses some events and we don't want to annoy the user.
try: try:
self.PostDataToHandler( request_data, 'event_notification' ) self.PostDataToHandlerAsync( request_data, 'event_notification' )
except: except:
vimsupport.EchoText( traceback.format_exc() ) vimsupport.EchoText( traceback.format_exc() )

View File

@ -30,6 +30,9 @@ sys.path.insert( 0, os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ), os.path.dirname( os.path.abspath( __file__ ) ),
'../..' ) ) '../..' ) )
from ycm import utils
utils.AddThirdPartyFoldersToSysPath()
import logging import logging
import json import json
import bottle import bottle

View File

@ -52,3 +52,15 @@ def TerminateProcess( pid ):
ctypes.windll.kernel32.CloseHandle( handle ) ctypes.windll.kernel32.CloseHandle( handle )
else: else:
os.kill( pid, signal.SIGTERM ) os.kill( pid, signal.SIGTERM )
def AddThirdPartyFoldersToSysPath():
path_to_third_party = os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'../../third_party' )
for folder in os.listdir( path_to_third_party ):
sys.path.insert( 0, os.path.realpath( os.path.join( path_to_third_party,
folder ) ) )

View File

@ -165,6 +165,7 @@ class YouCompleteMe( object ):
def OnVimLeave( self ): def OnVimLeave( self ):
# TODO: There should be a faster way of shutting down the server
self._server_popen.terminate() self._server_popen.terminate()
os.remove( self._temp_options_filename ) os.remove( self._temp_options_filename )