Jelajahi Sumber

GoTo commands work again

Strahinja Val Markovic 11 tahun lalu
induk
melakukan
fe0c0a1607

+ 9 - 13
autoload/youcompleteme.vim

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

+ 10 - 0
python/ycm/client/base_request.py

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

+ 47 - 33
python/ycm/client/command_request.py

@@ -17,26 +17,23 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
+import vim
 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 ServerResponse( object ):
-    def __init__( self ):
-      pass
-
-    def Valid( self ):
-      return True
-
   def __init__( self, arguments, completer_target = None ):
     super( CommandRequest, self ).__init__()
     self._arguments = arguments
     self._completer_target = ( completer_target if completer_target
                                else 'filetype_default' )
-    # TODO: Handle this case.
-    # if completer_target == 'omni':
-    #   completer = SERVER_STATE.GetOmniCompleter()
+    self._is_goto_command = (
+        True if arguments and arguments[ 0 ].startswith( 'GoTo' ) else False )
+    self._response = None
+
 
   def Start( self ):
     request_data = BuildRequestData()
@@ -44,34 +41,51 @@ class CommandRequest( BaseRequest ):
       'completer_target': self._completer_target,
       'command_arguments': self._arguments
     } )
-    self._response = self.PostDataToHandler( request_data,
-                                             'run_completer_command' )
+    try:
+      self._response = self.PostDataToHandler( request_data,
+                                              'run_completer_command' )
+    except ServerError as e:
+      vimsupport.PostVimMessage( e )
 
 
   def Response( self ):
-    # TODO: Call vimsupport.JumpToLocation if the user called a GoTo command...
-    # 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 ):
-  request = CommandRequest( self, arguments, completer )
+    return self._response
+
+
+  def RunPostCommandActionsIfNeeded( self ):
+    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()
   while not request.Done():
     time.sleep( 0.1 )
 
+  request.RunPostCommandActionsIfNeeded()
   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

+ 1 - 0
python/ycm/client/completion_request.py

@@ -30,6 +30,7 @@ class CompletionRequest( BaseRequest ):
     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 ) )

+ 1 - 1
python/ycm/completers/completer.py

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

+ 8 - 20
python/ycm/completers/cpp/clang_completer.py

@@ -152,7 +152,8 @@ class ClangCompleter( Completer ):
     elif command == 'GoToDefinitionElseDeclaration':
       return self._GoToDefinitionElseDeclaration( request_data )
     elif command == 'ClearCompilationFlagCache':
-      self._ClearCompilationFlagCache( request_data )
+      return self._ClearCompilationFlagCache( request_data )
+    raise ValueError( self.UserCommandsHelpMessage() )
 
 
   def _LocationForGoTo( self, goto_function, request_data ):
@@ -170,7 +171,7 @@ class ClangCompleter( Completer ):
 
     files = self.GetUnsavedFilesVector( request_data )
     line = request_data[ 'line_num' ] + 1
-    column = request_data[ 'start_column' ] + 1
+    column = request_data[ 'column_num' ] + 1
     return getattr( self.completer, goto_function )(
         ToUtf8IfNeeded( filename ),
         line,
@@ -180,7 +181,7 @@ class ClangCompleter( Completer ):
 
 
   def _GoToDefinition( self, request_data ):
-    location = self._LocationForGoTo( 'GetDefinitionLocation' )
+    location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
     if not location or not location.IsValid():
       raise RuntimeError( 'Can\'t jump to definition.' )
 
@@ -190,7 +191,7 @@ class ClangCompleter( Completer ):
 
 
   def _GoToDeclaration( self, request_data ):
-    location = self._LocationForGoTo( 'GetDeclarationLocation' )
+    location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
     if not location or not location.IsValid():
       raise RuntimeError( 'Can\'t jump to declaration.' )
 
@@ -200,9 +201,9 @@ class ClangCompleter( Completer ):
 
 
   def _GoToDefinitionElseDeclaration( self, request_data ):
-    location = self._LocationForGoTo( 'GetDefinitionLocation' )
+    location = self._LocationForGoTo( 'GetDefinitionLocation', request_data )
     if not location or not location.IsValid():
-      location = self._LocationForGoTo( 'GetDeclarationLocation' )
+      location = self._LocationForGoTo( 'GetDeclarationLocation', request_data )
     if not location or not location.IsValid():
       raise RuntimeError( 'Can\'t jump to definition or declaration.' )
 
@@ -325,20 +326,7 @@ class ClangCompleter( Completer ):
                                                     source,
                                                     list( flags ) ) )
 
-
-# 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,
-#   }
-
-
+# TODO: Make this work again
 # def DiagnosticToDict( diagnostic ):
 #   # see :h getqflist for a description of the dictionary fields
 #   return {

+ 1 - 0
python/ycm/completers/cs/cs_completer.py

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

+ 4 - 0
python/ycm/completers/general/general_completer_store.py

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

+ 6 - 5
python/ycm/completers/python/jedi_completer.py

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

+ 8 - 0
python/ycm/server/responses.py

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

+ 7 - 3
python/ycm/server/server_state.py

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

+ 35 - 0
python/ycm/server/tests/basic_test.py

@@ -120,6 +120,41 @@ def GetCompletions_UltiSnipsCompleter_Works_test():
        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 )
 def FiletypeCompletionAvailable_Works_test():
   app = TestApp( ycmd.app )

+ 13 - 5
python/ycm/server/ycmd.py

@@ -37,6 +37,7 @@ import bottle
 from bottle import run, request, response
 import server_state
 from ycm import user_options_store
+from ycm.server.responses import BuildExceptionResponse
 import argparse
 
 # 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' ]
 
   if completer_target == 'identifier':
-    completer = SERVER_STATE.GetGeneralCompleter()
+    completer = SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
   else:
-    completer = SERVER_STATE.GetFiletypeCompleter()
+    completer = SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
 
-  return _JsonResponse(
-      completer.OnUserCommand( request_data[ 'command_arguments' ],
-                               request_data ) )
+  return _JsonResponse( completer.OnUserCommand(
+      request_data[ 'command_arguments' ],
+      request_data ) )
 
 
 @app.post( '/get_completions' )
@@ -123,6 +124,13 @@ def FiletypeCompletionAvailable():
       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 ):
   response.set_header( 'Content-Type', 'application/json' )
   return json.dumps( data )

+ 1 - 1
python/ycm/vimsupport.py

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