GoTo commands work again

This commit is contained in:
Strahinja Val Markovic 2013-09-27 13:52:04 -07:00
parent 13b86563f0
commit fe0c0a1607
14 changed files with 149 additions and 79 deletions

View File

@ -585,33 +585,27 @@ endfunction
command! YcmDebugInfo call s:DebugInfo() command! YcmDebugInfo call s:DebugInfo()
function! s:CompleterCommand(...) function! s:CompleterCommand(...)
" CompleterCommand will call the OnUserCommand function of a completer. " 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 " 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 " 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 " 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:omni" " no completer was specified this throws an error. You can use
" to select the omni completer or "ft=ycm:ident" to select the identifier " "ft=ycm:ident" to select the identifier completer.
" completer. The remaining arguments will passed to the completer. " The remaining arguments will be passed to the completer.
let arguments = copy(a:000) let arguments = copy(a:000)
let completer = '' let completer = ''
if a:0 > 0 && strpart(a:1, 0, 3) == 'ft=' if a:0 > 0 && strpart(a:1, 0, 3) == 'ft='
if a:1 == 'ft=ycm:omni' if a:1 == 'ft=ycm:ident'
let completer = 'omni'
elseif a:1 == 'ft=ycm:ident'
let completer = 'identifier' let completer = 'identifier'
endif endif
let arguments = arguments[1:] let arguments = arguments[1:]
endif endif
py << EOF py ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ),
response = ycm_state.SendCommandRequest( vim.eval( 'l:arguments' ), \ vim.eval( 'l:completer' ) )
vim.eval( 'l:completer' ) )
if not response.Valid():
vimsupport.PostVimMessage( 'No native completer found for current buffer. ' +
'Use ft=... as the first argument to specify a completer.')
EOF
endfunction endfunction
@ -625,6 +619,8 @@ function! youcompleteme#OpenGoToList()
endfunction endfunction
command! -nargs=* YcmCompleter call s:CompleterCommand(<f-args>)
" TODO: Make this work again " TODO: Make this work again
" command! -nargs=* -complete=custom,youcompleteme#SubCommandsComplete " command! -nargs=* -complete=custom,youcompleteme#SubCommandsComplete
" \ YcmCompleter call s:CompleterCommand(<f-args>) " \ YcmCompleter call s:CompleterCommand(<f-args>)

View File

@ -24,6 +24,11 @@ from ycm import vimsupport
HEADERS = {'content-type': 'application/json'} HEADERS = {'content-type': 'application/json'}
class ServerError( Exception ):
def __init__( self, message ):
super( ServerError, self ).__init__( message )
class BaseRequest( object ): class BaseRequest( object ):
def __init__( self ): def __init__( self ):
pass pass
@ -46,8 +51,13 @@ class BaseRequest( object ):
response = requests.post( _BuildUri( handler ), response = requests.post( _BuildUri( handler ),
data = json.dumps( data ), data = json.dumps( data ),
headers = HEADERS ) 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() response.raise_for_status()
if response.text: if response.text:
return response.json() return response.json()
return None return None

View File

@ -17,26 +17,23 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# 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 time import time
from ycm.client.base_request import BaseRequest, BuildRequestData from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError
from ycm import vimsupport
from ycm.utils import ToUtf8IfNeeded
class CommandRequest( BaseRequest ): class CommandRequest( BaseRequest ):
class ServerResponse( object ):
def __init__( self ):
pass
def Valid( self ):
return True
def __init__( self, arguments, completer_target = None ): def __init__( self, arguments, completer_target = None ):
super( CommandRequest, self ).__init__() super( CommandRequest, self ).__init__()
self._arguments = arguments self._arguments = arguments
self._completer_target = ( completer_target if completer_target self._completer_target = ( completer_target if completer_target
else 'filetype_default' ) else 'filetype_default' )
# TODO: Handle this case. self._is_goto_command = (
# if completer_target == 'omni': True if arguments and arguments[ 0 ].startswith( 'GoTo' ) else False )
# completer = SERVER_STATE.GetOmniCompleter() self._response = None
def Start( self ): def Start( self ):
request_data = BuildRequestData() request_data = BuildRequestData()
@ -44,34 +41,51 @@ class CommandRequest( BaseRequest ):
'completer_target': self._completer_target, 'completer_target': self._completer_target,
'command_arguments': self._arguments 'command_arguments': self._arguments
} ) } )
self._response = self.PostDataToHandler( request_data, try:
'run_completer_command' ) self._response = self.PostDataToHandler( request_data,
'run_completer_command' )
except ServerError as e:
vimsupport.PostVimMessage( e )
def Response( self ): def Response( self ):
# TODO: Call vimsupport.JumpToLocation if the user called a GoTo command... return self._response
# we may want to have specific subclasses of CommandRequest so that a
# GoToRequest knows it needs to jump after the data comes back.
#
# Also need to run the following on GoTo data:
#
# CAREFUL about line/column number 0-based/1-based confusion!
#
# defs = []
# defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
# 'lnum': definition.line,
# 'col': definition.column + 1,
# 'text': definition.description.encode( 'utf-8' ) } )
# vim.eval( 'setqflist( %s )' % repr( defs ) )
# vim.eval( 'youcompleteme#OpenGoToList()' )
return self.ServerResponse()
def SendCommandRequest( self, arguments, completer ): def RunPostCommandActionsIfNeeded( self ):
request = CommandRequest( self, arguments, completer ) if not self._is_goto_command or not self.Done() or not self._response:
return
if isinstance( self._response, list ):
defs = [ _BuildQfListItem( x ) for x in self._response ]
vim.eval( 'setqflist( %s )' % repr( defs ) )
vim.eval( 'youcompleteme#OpenGoToList()' )
else:
vimsupport.JumpToLocation( self._response[ 'filepath' ],
self._response[ 'line_num' ] + 1,
self._response[ 'column_num' ] )
def SendCommandRequest( arguments, completer ):
request = CommandRequest( arguments, completer )
request.Start() request.Start()
while not request.Done(): while not request.Done():
time.sleep( 0.1 ) time.sleep( 0.1 )
request.RunPostCommandActionsIfNeeded()
return request.Response() return request.Response()
def _BuildQfListItem( goto_data_item ):
qf_item = {}
if 'filepath' in goto_data_item:
qf_item[ 'filename' ] = ToUtf8IfNeeded( goto_data_item[ 'filepath' ] )
if 'description' in goto_data_item:
qf_item[ 'text' ] = ToUtf8IfNeeded( goto_data_item[ 'description' ] )
if 'line_num' in goto_data_item:
qf_item[ 'lnum' ] = goto_data_item[ 'line_num' ] + 1
if 'column_num' in goto_data_item:
qf_item[ 'col' ] = goto_data_item[ 'column_num' ]
return qf_item

View File

@ -30,6 +30,7 @@ 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 ): # def ShouldComplete( self ):
# return ( self._do_filetype_completion or # return ( self._do_filetype_completion or
# self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) ) # self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )

View File

@ -198,7 +198,7 @@ class Completer( object ):
'\n'.join( subcommands ) + '\n'.join( subcommands ) +
'\nSee the docs for information on what they do.' ) '\nSee the docs for information on what they do.' )
else: else:
return 'No supported subcommands' return 'This Completer has no supported subcommands.'
def FilterAndSortCandidates( self, candidates, query ): def FilterAndSortCandidates( self, candidates, query ):

View File

@ -152,7 +152,8 @@ class ClangCompleter( Completer ):
elif command == 'GoToDefinitionElseDeclaration': elif command == 'GoToDefinitionElseDeclaration':
return self._GoToDefinitionElseDeclaration( request_data ) return self._GoToDefinitionElseDeclaration( request_data )
elif command == 'ClearCompilationFlagCache': elif command == 'ClearCompilationFlagCache':
self._ClearCompilationFlagCache( request_data ) return self._ClearCompilationFlagCache( request_data )
raise ValueError( self.UserCommandsHelpMessage() )
def _LocationForGoTo( self, goto_function, request_data ): def _LocationForGoTo( self, goto_function, request_data ):
@ -170,7 +171,7 @@ class ClangCompleter( Completer ):
files = self.GetUnsavedFilesVector( request_data ) files = self.GetUnsavedFilesVector( request_data )
line = request_data[ 'line_num' ] + 1 line = request_data[ 'line_num' ] + 1
column = request_data[ 'start_column' ] + 1 column = request_data[ 'column_num' ] + 1
return getattr( self.completer, goto_function )( return getattr( self.completer, goto_function )(
ToUtf8IfNeeded( filename ), ToUtf8IfNeeded( filename ),
line, line,
@ -180,7 +181,7 @@ class ClangCompleter( Completer ):
def _GoToDefinition( self, request_data ): def _GoToDefinition( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation' ) location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to definition.' ) raise RuntimeError( 'Can\'t jump to definition.' )
@ -190,7 +191,7 @@ class ClangCompleter( Completer ):
def _GoToDeclaration( self, request_data ): def _GoToDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDeclarationLocation' ) location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to declaration.' ) raise RuntimeError( 'Can\'t jump to declaration.' )
@ -200,9 +201,9 @@ class ClangCompleter( Completer ):
def _GoToDefinitionElseDeclaration( self, request_data ): def _GoToDefinitionElseDeclaration( self, request_data ):
location = self._LocationForGoTo( 'GetDefinitionLocation' ) location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
if not location or not location.IsValid(): if not location or not location.IsValid():
location = self._LocationForGoTo( 'GetDeclarationLocation' ) location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to definition or declaration.' ) raise RuntimeError( 'Can\'t jump to definition or declaration.' )
@ -325,20 +326,7 @@ class ClangCompleter( Completer ):
source, source,
list( flags ) ) ) list( flags ) ) )
# TODO: Make this work again
# TODO: make these functions module-local
# def CompletionDataToDict( completion_data ):
# # see :h complete-items for a description of the dictionary fields
# return {
# 'word' : completion_data.TextToInsertInBuffer(),
# 'abbr' : completion_data.MainCompletionText(),
# 'menu' : completion_data.ExtraMenuInfo(),
# 'kind' : completion_data.kind_,
# 'info' : completion_data.DetailedInfoForPreviewWindow(),
# 'dup' : 1,
# }
# def DiagnosticToDict( diagnostic ): # def DiagnosticToDict( diagnostic ):
# # see :h getqflist for a description of the dictionary fields # # see :h getqflist for a description of the dictionary fields
# return { # return {

View File

@ -95,6 +95,7 @@ class CsharpCompleter( ThreadedCompleter ):
'GoToDeclaration', 'GoToDeclaration',
'GoToDefinitionElseDeclaration' ]: 'GoToDefinitionElseDeclaration' ]:
return self._GoToDefinition( request_data ) return self._GoToDefinition( request_data )
raise ValueError( self.UserCommandsHelpMessage() )
def DebugInfo( self ): def DebugInfo( self ):

View File

@ -51,6 +51,10 @@ class GeneralCompleterStore( Completer ):
return set() return set()
def GetIdentifierCompleter( self ):
return self._identifier_completer
def ShouldUseNow( self, request_data ): def ShouldUseNow( self, request_data ):
self._current_query_completers = [] self._current_query_completers = []

View File

@ -88,6 +88,7 @@ class JediCompleter( ThreadedCompleter ):
return self._GoToDeclaration( request_data ) return self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration': elif command == 'GoToDefinitionElseDeclaration':
return self._GoToDefinitionElseDeclaration( request_data ) return self._GoToDefinitionElseDeclaration( request_data )
raise ValueError( self.UserCommandsHelpMessage() )
def _GoToDefinition( self, request_data ): def _GoToDefinition( self, request_data ):
@ -107,8 +108,8 @@ class JediCompleter( ThreadedCompleter ):
def _GoToDefinitionElseDeclaration( self, request_data ): def _GoToDefinitionElseDeclaration( self, request_data ):
definitions = self._GetDefinitionsList() or \ definitions = ( self._GetDefinitionsList( request_data ) or
self._GetDefinitionsList( request_data, declaration = True ) self._GetDefinitionsList( request_data, declaration = True ) )
if definitions: if definitions:
return self._BuildGoToResponse( definitions ) return self._BuildGoToResponse( definitions )
else: else:
@ -141,7 +142,7 @@ class JediCompleter( ThreadedCompleter ):
raise RuntimeError( 'Builtin modules cannot be displayed.' ) raise RuntimeError( 'Builtin modules cannot be displayed.' )
else: else:
return responses.BuildGoToResponse( definition.module_path, return responses.BuildGoToResponse( definition.module_path,
definition.line -1, definition.line - 1,
definition.column ) definition.column )
else: else:
# multiple definitions # multiple definitions
@ -149,11 +150,11 @@ class JediCompleter( ThreadedCompleter ):
for definition in definition_list: for definition in definition_list:
if definition.in_builtin_module(): if definition.in_builtin_module():
defs.append( responses.BuildDescriptionOnlyGoToResponse( defs.append( responses.BuildDescriptionOnlyGoToResponse(
'Builting ' + definition.description ) ) 'Builtin ' + definition.description ) )
else: else:
defs.append( defs.append(
responses.BuildGoToResponse( definition.module_path, responses.BuildGoToResponse( definition.module_path,
definition.line -1, definition.line - 1,
definition.column, definition.column,
definition.description ) ) definition.description ) )
return defs return defs

View File

@ -76,3 +76,11 @@ def BuildDiagnosticData( filepath,
'text': text, 'text': text,
'kind': kind 'kind': kind
} }
def BuildExceptionResponse( error_message, traceback ):
return {
'message': error_message,
'traceback': traceback
}

View File

@ -74,12 +74,16 @@ class ServerState( object ):
if completer: if completer:
return completer return completer
return None raise ValueError( 'No semantic completer exists for filetypes: {}'.format(
current_filetypes ) )
def FiletypeCompletionAvailable( self, filetypes ): def FiletypeCompletionAvailable( self, filetypes ):
return bool( self.GetFiletypeCompleter( filetypes ) ) try:
self.GetFiletypeCompleter( filetypes )
return True
except:
return False
def FiletypeCompletionUsable( self, filetypes ): def FiletypeCompletionUsable( self, filetypes ):
return ( self.CurrentFiletypeCompletionEnabled( filetypes ) and return ( self.CurrentFiletypeCompletionEnabled( filetypes ) and

View File

@ -120,6 +120,41 @@ def GetCompletions_UltiSnipsCompleter_Works_test():
app.post_json( '/get_completions', completion_data ).json ) app.post_json( '/get_completions', completion_data ).json )
@with_setup( Setup )
def RunCompleterCommand_GoTo_Jedi_ZeroBasedLineAndColumn_test():
app = TestApp( ycmd.app )
contents = """
def foo():
pass
foo()
"""
goto_data = {
'completer_target': 'filetype_default',
'command_arguments': ['GoToDefinition'],
'line_num': 4,
'column_num': 0,
'filetypes': ['python'],
'filepath': '/foo.py',
'line_value': contents,
'file_data': {
'/foo.py': {
'contents': contents,
'filetypes': ['python']
}
}
}
# 0-based line and column!
eq_( {
'filepath': '/foo.py',
'line_num': 1,
'column_num': 4
},
app.post_json( '/run_completer_command', goto_data ).json )
@with_setup( Setup ) @with_setup( Setup )
def FiletypeCompletionAvailable_Works_test(): def FiletypeCompletionAvailable_Works_test():
app = TestApp( ycmd.app ) app = TestApp( ycmd.app )

View File

@ -37,6 +37,7 @@ import bottle
from bottle import run, request, response from bottle import run, request, response
import server_state import server_state
from ycm import user_options_store from ycm import user_options_store
from ycm.server.responses import BuildExceptionResponse
import argparse import argparse
# num bytes for the request body buffer; request.json only works if the request # num bytes for the request body buffer; request.json only works if the request
@ -71,13 +72,13 @@ def RunCompleterCommand():
completer_target = request_data[ 'completer_target' ] completer_target = request_data[ 'completer_target' ]
if completer_target == 'identifier': if completer_target == 'identifier':
completer = SERVER_STATE.GetGeneralCompleter() completer = SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
else: else:
completer = SERVER_STATE.GetFiletypeCompleter() completer = SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
return _JsonResponse( return _JsonResponse( completer.OnUserCommand(
completer.OnUserCommand( request_data[ 'command_arguments' ], request_data[ 'command_arguments' ],
request_data ) ) request_data ) )
@app.post( '/get_completions' ) @app.post( '/get_completions' )
@ -123,6 +124,13 @@ def FiletypeCompletionAvailable():
request.json[ 'filetypes' ] ) ) request.json[ 'filetypes' ] ) )
# The type of the param is Bottle.HTTPError
@app.error( 500 )
def ErrorHandler( httperror ):
return _JsonResponse( BuildExceptionResponse( str( httperror.exception ),
httperror.traceback ) )
def _JsonResponse( data ): def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' ) response.set_header( 'Content-Type', 'application/json' )
return json.dumps( data ) return json.dumps( data )

View File

@ -105,7 +105,7 @@ def PostVimMessage( message ):
# non-GUI thread which *sometimes* crashes Vim because Vim is not thread-safe. # non-GUI thread which *sometimes* crashes Vim because Vim is not thread-safe.
# A consistent crash should force us to notice the error. # A consistent crash should force us to notice the error.
vim.command( "echohl WarningMsg | echomsg '{0}' | echohl None" vim.command( "echohl WarningMsg | echomsg '{0}' | echohl None"
.format( EscapeForVim( message ) ) ) .format( EscapeForVim( str( message ) ) ) )
def PresentDialog( message, choices, default_choice_index = 0 ): def PresentDialog( message, choices, default_choice_index = 0 ):