瀏覽代碼

Fix re-rooting of call/type hierarchies

Previously, when changing direction of hierarchy and re-rooting the
hierarchy tree, we would retain the old node as the new root.
That works most of the time, but fails in the face of multiple
inheritance and switching to supertypes. In that, problematic, case, the
resulting hierarchy will be missing some supertypes.
Worse, if a call hierarchy contains a node with multiple locations,
re-rooting to that node behaved in a way that defies explanation.

The solution is to throw away everything and start a new hierarchy
completely every time a user requests re-rooting.

This trades some performance for correctness, but this is not at all
performance sensitive.
Boris Staletic 9 月之前
父節點
當前提交
8888bbea1e

+ 2 - 2
autoload/youcompleteme/hierarchy.vim

@@ -64,7 +64,7 @@ endfunction
 function! s:MenuFilter( winid, key )
 function! s:MenuFilter( winid, key )
   if a:key == "\<S-Tab>"
   if a:key == "\<S-Tab>"
     " Root changes if we're showing super-tree of a sub-tree of the root
     " Root changes if we're showing super-tree of a sub-tree of the root
-    " (indeicated by the handle being positive)
+    " (indicated by the handle being positive)
     let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0
     let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0
     call popup_close(
     call popup_close(
           \ s:popup_id,
           \ s:popup_id,
@@ -73,7 +73,7 @@ function! s:MenuFilter( winid, key )
   endif
   endif
   if a:key == "\<Tab>"
   if a:key == "\<Tab>"
     " Root changes if we're showing sub-tree of a super-tree of the root
     " Root changes if we're showing sub-tree of a super-tree of the root
-    " (indeicated by the handle being negative)
+    " (indicated by the handle being negative)
     let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] < 0
     let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] < 0
     call popup_close(
     call popup_close(
           \ s:popup_id,
           \ s:popup_id,

+ 16 - 1
python/ycm/client/base_request.py

@@ -167,7 +167,7 @@ class BaseRequest:
       else:
       else:
         headers = BaseRequest._ExtraHeaders( method, request_uri )
         headers = BaseRequest._ExtraHeaders( method, request_uri )
         if payload:
         if payload:
-          request_uri += ToBytes( f'?{urlencode( payload )}' )
+          request_uri += ToBytes( f'?{ urlencode( payload ) }' )
 
 
         _logger.debug( 'GET %s (%s)\n%s', request_uri, payload, headers )
         _logger.debug( 'GET %s (%s)\n%s', request_uri, payload, headers )
       return urlopen(
       return urlopen(
@@ -249,6 +249,21 @@ def BuildRequestData( buffer_number = None ):
   }
   }
 
 
 
 
+def BuildRequestDataForLocation( location ):
+  file, line, column = location
+  current_buffer = vim.current.buffer
+  current_filepath = vimsupport.GetBufferFilepath( current_buffer )
+  file_data = vimsupport.GetUnsavedAndSpecifiedBufferData( current_buffer,
+                                                           current_filepath )
+  return {
+    'filepath': file,
+    'line_num': line,
+    'column_num': column,
+    'working_dir': GetCurrentDirectory(),
+    'file_data': file_data
+  }
+
+
 def _JsonFromFuture( future ):
 def _JsonFromFuture( future ):
   try:
   try:
     response = future.result()
     response = future.result()

+ 18 - 5
python/ycm/client/command_request.py

@@ -15,7 +15,9 @@
 # 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/>.
 
 
-from ycm.client.base_request import BaseRequest, BuildRequestData
+from ycm.client.base_request import ( BaseRequest,
+                                      BuildRequestData,
+                                      BuildRequestDataForLocation )
 from ycm import vimsupport
 from ycm import vimsupport
 
 
 DEFAULT_BUFFER_COMMAND = 'same-buffer'
 DEFAULT_BUFFER_COMMAND = 'same-buffer'
@@ -28,7 +30,11 @@ def _EnsureBackwardsCompatibility( arguments ):
 
 
 
 
 class CommandRequest( BaseRequest ):
 class CommandRequest( BaseRequest ):
-  def __init__( self, arguments, extra_data = None, silent = False ):
+  def __init__( self,
+                arguments,
+                extra_data = None,
+                silent = False,
+                location = None ):
     super( CommandRequest, self ).__init__()
     super( CommandRequest, self ).__init__()
     self._arguments = _EnsureBackwardsCompatibility( arguments )
     self._arguments = _EnsureBackwardsCompatibility( arguments )
     self._command = arguments and arguments[ 0 ]
     self._command = arguments and arguments[ 0 ]
@@ -38,10 +44,13 @@ class CommandRequest( BaseRequest ):
     self._response_future = None
     self._response_future = None
     self._silent = silent
     self._silent = silent
     self._bufnr = extra_data.pop( 'bufnr', None ) if extra_data else None
     self._bufnr = extra_data.pop( 'bufnr', None ) if extra_data else None
+    self._location = location
 
 
 
 
   def Start( self ):
   def Start( self ):
-    if self._bufnr is not None:
+    if self._location is not None:
+      self._request_data = BuildRequestDataForLocation( self._location )
+    elif self._bufnr is not None:
       self._request_data = BuildRequestData( self._bufnr )
       self._request_data = BuildRequestData( self._bufnr )
     else:
     else:
       self._request_data = BuildRequestData()
       self._request_data = BuildRequestData()
@@ -206,10 +215,14 @@ class CommandRequest( BaseRequest ):
                                      modifiers )
                                      modifiers )
 
 
 
 
-def SendCommandRequestAsync( arguments, extra_data = None, silent = True ):
+def SendCommandRequestAsync( arguments,
+                             extra_data = None,
+                             silent = True,
+                             location = None ):
   request = CommandRequest( arguments,
   request = CommandRequest( arguments,
                             extra_data = extra_data,
                             extra_data = extra_data,
-                            silent = silent )
+                            silent = silent,
+                            location = location )
   request.Start()
   request.Start()
   # Don't block
   # Don't block
   return request
   return request

+ 26 - 20
python/ycm/hierarchy_tree.py

@@ -69,27 +69,16 @@ class HierarchyTree:
 
 
   def UpdateHierarchy( self, handle : int, items, direction : str ):
   def UpdateHierarchy( self, handle : int, items, direction : str ):
     current_index = handle_to_index( handle )
     current_index = handle_to_index( handle )
-    if ( ( handle >= 0 and direction == 'down' ) or
-         ( handle <= 0 and direction == 'up' ) ):
-      nodes = self._down_nodes if direction == 'down' else self._up_nodes
-      if items:
-        nodes.extend( [
-          HierarchyNode( item,
-                         nodes[ current_index ]._distance_from_root + 1 )
-          for item in items ] )
-        nodes[ current_index ]._references = list(
-            range( len( nodes ) - len( items ), len( nodes ) ) )
-      else:
-        nodes[ current_index ]._references = []
+    nodes = self._down_nodes if direction == 'down' else self._up_nodes
+    if items:
+      nodes.extend( [
+        HierarchyNode( item,
+                       nodes[ current_index ]._distance_from_root + 1 )
+        for item in items ] )
+      nodes[ current_index ]._references = list(
+          range( len( nodes ) - len( items ), len( nodes ) ) )
     else:
     else:
-      if direction == 'up':
-        current_node = self._down_nodes[ current_index ]
-      else:
-        current_node = self._up_nodes[ current_index ]
-      old_kind = self._kind
-      self.Reset()
-      self.SetRootNode( [ current_node._data ], old_kind )
-      self.UpdateHierarchy( 0, items, direction )
+      nodes[ current_index ]._references = []
 
 
 
 
   def Reset( self ):
   def Reset( self ):
@@ -195,3 +184,20 @@ class HierarchyTree:
       node._data,
       node._data,
       direction
       direction
     ]
     ]
+
+
+  def HandleToLocation( self, handle : int ):
+    node_index = handle_to_index( handle )
+
+    if handle >= 0:
+      node = self._down_nodes[ node_index ]
+    else:
+      node = self._up_nodes[ node_index ]
+
+    location_index = handle_to_location_index( handle )
+    return node.ToLocation( location_index )
+
+
+  def UpdateChangesRoot( self, handle : int, direction : str ):
+    return ( ( handle < 0 and direction == 'down' ) or
+             ( handle > 0 and direction == 'up'   ) )

+ 10 - 10
python/ycm/vimsupport.py

@@ -152,8 +152,8 @@ def GetUnsavedAndSpecifiedBufferData( included_buffer, included_filepath ):
 def GetBufferNumberForFilename( filename, create_buffer_if_needed = False ):
 def GetBufferNumberForFilename( filename, create_buffer_if_needed = False ):
   realpath = os.path.realpath( filename )
   realpath = os.path.realpath( filename )
   return MADEUP_FILENAME_TO_BUFFER_NUMBER.get( realpath, GetIntValue(
   return MADEUP_FILENAME_TO_BUFFER_NUMBER.get( realpath, GetIntValue(
-      f"bufnr('{ EscapeForVim( realpath ) }', "
-             f"{ int( create_buffer_if_needed ) })" ) )
+      f"bufnr( '{ EscapeForVim( realpath ) }', "
+             f"{ int( create_buffer_if_needed ) } )" ) )
 
 
 
 
 def GetCurrentBufferFilepath():
 def GetCurrentBufferFilepath():
@@ -163,7 +163,7 @@ def GetCurrentBufferFilepath():
 def BufferIsVisible( buffer_number ):
 def BufferIsVisible( buffer_number ):
   if buffer_number < 0:
   if buffer_number < 0:
     return False
     return False
-  window_number = GetIntValue( f"bufwinnr({ buffer_number })" )
+  window_number = GetIntValue( f"bufwinnr( { buffer_number } )" )
   return window_number != -1
   return window_number != -1
 
 
 
 
@@ -259,7 +259,7 @@ def VisibleRangeOfBufferOverlaps( bufnr, expanded_range ):
 
 
 
 
 def CaptureVimCommand( command ):
 def CaptureVimCommand( command ):
-  return vim.eval( f"execute( '{EscapeForVim(command)}', 'silent!' )" )
+  return vim.eval( f"execute( '{ EscapeForVim( command ) }', 'silent!' )" )
 
 
 
 
 def GetSignsInBuffer( buffer_number ):
 def GetSignsInBuffer( buffer_number ):
@@ -345,7 +345,7 @@ def GetTextProperties( buffer_number ):
     else:
     else:
       properties = []
       properties = []
       for line_number in range( len( vim.buffers[ buffer_number ] ) ):
       for line_number in range( len( vim.buffers[ buffer_number ] ) ):
-        vim_props =  vim.eval( f'prop_list( {line_number + 1}, '
+        vim_props =  vim.eval( f'prop_list( { line_number + 1 }, '
                                f'{{ "bufnr": { buffer_number } }} )' )
                                f'{{ "bufnr": { buffer_number } }} )' )
         properties.extend(
         properties.extend(
           DiagnosticProperty(
           DiagnosticProperty(
@@ -818,9 +818,9 @@ def PresentDialog( message, choices, default_choice_index = 0 ):
       [Y]es, (N)o, May(b)e:"""
       [Y]es, (N)o, May(b)e:"""
   message = EscapeForVim( ToUnicode( message ) )
   message = EscapeForVim( ToUnicode( message ) )
   choices = EscapeForVim( ToUnicode( '\n'.join( choices ) ) )
   choices = EscapeForVim( ToUnicode( '\n'.join( choices ) ) )
-  to_eval = ( f"confirm('{ message }', "
-                      f"'{ choices }', "
-                      f"{ default_choice_index + 1 })" )
+  to_eval = ( f"confirm( '{ message }', "
+                       f"'{ choices }', "
+                       f"{ default_choice_index + 1 } )" )
   try:
   try:
     return GetIntValue( to_eval ) - 1
     return GetIntValue( to_eval ) - 1
   except KeyboardInterrupt:
   except KeyboardInterrupt:
@@ -1258,7 +1258,7 @@ def OpenFileInPreviewWindow( filename, modifiers ):
   """ Open the supplied filename in the preview window """
   """ Open the supplied filename in the preview window """
   if modifiers:
   if modifiers:
     modifiers = ' ' + modifiers
     modifiers = ' ' + modifiers
-  vim.command( f'silent!{modifiers} pedit! { filename }' )
+  vim.command( f'silent!{ modifiers } pedit! { filename }' )
 
 
 
 
 def WriteToPreviewWindow( message, modifiers ):
 def WriteToPreviewWindow( message, modifiers ):
@@ -1353,7 +1353,7 @@ def OpenFilename( filename, options = {} ):
 
 
   # Open the file.
   # Open the file.
   try:
   try:
-    vim.command( f'{ options.get( "mods", "") }'
+    vim.command( f'{ options.get( "mods", "" ) }'
                  f'{ size }'
                  f'{ size }'
                  f'{ command } '
                  f'{ command } '
                  f'{ filename }' )
                  f'{ filename }' )

+ 22 - 6
python/ycm/youcompleteme.py

@@ -119,10 +119,22 @@ class YouCompleteMe:
 
 
 
 
   def UpdateCurrentHierarchy( self, handle : int, direction : str ):
   def UpdateCurrentHierarchy( self, handle : int, direction : str ):
-    items = self._ResolveHierarchyItem( handle, direction )
-    self._current_hierarchy.UpdateHierarchy( handle, items, direction )
-    offset = len( items ) if items is not None and direction == 'up' else 0
-    return self._current_hierarchy.HierarchyToLines(), offset
+    if not self._current_hierarchy.UpdateChangesRoot( handle, direction ):
+      items = self._ResolveHierarchyItem( handle, direction )
+      self._current_hierarchy.UpdateHierarchy( handle, items, direction )
+      offset = len( items ) if items is not None and direction == 'up' else 0
+      return self._current_hierarchy.HierarchyToLines(), offset
+    else:
+      location = self._current_hierarchy.HandleToLocation( handle )
+      kind = self._current_hierarchy._kind
+      self._current_hierarchy.Reset()
+      request_id = self.SendCommandRequestAsync(
+        [ f'{ kind.title() }Hierarchy' ],
+        silent = False,
+        location = location
+      )
+      handle = self.InitializeCurrentHierarchy( request_id, kind )[ 0 ][ 1 ]
+      return self.UpdateCurrentHierarchy( handle, direction )
 
 
 
 
   def _ResolveHierarchyItem( self, handle : int, direction : str ):
   def _ResolveHierarchyItem( self, handle : int, direction : str ):
@@ -471,7 +483,10 @@ class YouCompleteMe:
     return GetCommandResponse( final_arguments, extra_data )
     return GetCommandResponse( final_arguments, extra_data )
 
 
 
 
-  def SendCommandRequestAsync( self, arguments, silent = True ):
+  def SendCommandRequestAsync( self,
+                               arguments,
+                               silent = True,
+                               location = None ):
     final_arguments, extra_data = self._GetCommandRequestArguments(
     final_arguments, extra_data = self._GetCommandRequestArguments(
       arguments,
       arguments,
       False,
       False,
@@ -483,7 +498,8 @@ class YouCompleteMe:
     self._command_requests[ request_id ] = SendCommandRequestAsync(
     self._command_requests[ request_id ] = SendCommandRequestAsync(
       final_arguments,
       final_arguments,
       extra_data,
       extra_data,
-      silent )
+      silent,
+      location = location )
     return request_id
     return request_id