Răsfoiți Sursa

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 luni în urmă
părinte
comite
8888bbea1e

+ 2 - 2
autoload/youcompleteme/hierarchy.vim

@@ -64,7 +64,7 @@ endfunction
 function! s:MenuFilter( winid, key )
   if a:key == "\<S-Tab>"
     " 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
     call popup_close(
           \ s:popup_id,
@@ -73,7 +73,7 @@ function! s:MenuFilter( winid, key )
   endif
   if a:key == "\<Tab>"
     " 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
     call popup_close(
           \ s:popup_id,

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

@@ -167,7 +167,7 @@ class BaseRequest:
       else:
         headers = BaseRequest._ExtraHeaders( method, request_uri )
         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 )
       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 ):
   try:
     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
 # 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
 
 DEFAULT_BUFFER_COMMAND = 'same-buffer'
@@ -28,7 +30,11 @@ def _EnsureBackwardsCompatibility( arguments ):
 
 
 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__()
     self._arguments = _EnsureBackwardsCompatibility( arguments )
     self._command = arguments and arguments[ 0 ]
@@ -38,10 +44,13 @@ class CommandRequest( BaseRequest ):
     self._response_future = None
     self._silent = silent
     self._bufnr = extra_data.pop( 'bufnr', None ) if extra_data else None
+    self._location = location
 
 
   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 )
     else:
       self._request_data = BuildRequestData()
@@ -206,10 +215,14 @@ class CommandRequest( BaseRequest ):
                                      modifiers )
 
 
-def SendCommandRequestAsync( arguments, extra_data = None, silent = True ):
+def SendCommandRequestAsync( arguments,
+                             extra_data = None,
+                             silent = True,
+                             location = None ):
   request = CommandRequest( arguments,
                             extra_data = extra_data,
-                            silent = silent )
+                            silent = silent,
+                            location = location )
   request.Start()
   # Don't block
   return request

+ 26 - 20
python/ycm/hierarchy_tree.py

@@ -69,27 +69,16 @@ class HierarchyTree:
 
   def UpdateHierarchy( self, handle : int, items, direction : str ):
     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:
-      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 ):
@@ -195,3 +184,20 @@ class HierarchyTree:
       node._data,
       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 ):
   realpath = os.path.realpath( filename )
   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():
@@ -163,7 +163,7 @@ def GetCurrentBufferFilepath():
 def BufferIsVisible( buffer_number ):
   if buffer_number < 0:
     return False
-  window_number = GetIntValue( f"bufwinnr({ buffer_number })" )
+  window_number = GetIntValue( f"bufwinnr( { buffer_number } )" )
   return window_number != -1
 
 
@@ -259,7 +259,7 @@ def VisibleRangeOfBufferOverlaps( bufnr, expanded_range ):
 
 
 def CaptureVimCommand( command ):
-  return vim.eval( f"execute( '{EscapeForVim(command)}', 'silent!' )" )
+  return vim.eval( f"execute( '{ EscapeForVim( command ) }', 'silent!' )" )
 
 
 def GetSignsInBuffer( buffer_number ):
@@ -345,7 +345,7 @@ def GetTextProperties( buffer_number ):
     else:
       properties = []
       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 } }} )' )
         properties.extend(
           DiagnosticProperty(
@@ -818,9 +818,9 @@ def PresentDialog( message, choices, default_choice_index = 0 ):
       [Y]es, (N)o, May(b)e:"""
   message = EscapeForVim( ToUnicode( message ) )
   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:
     return GetIntValue( to_eval ) - 1
   except KeyboardInterrupt:
@@ -1258,7 +1258,7 @@ def OpenFileInPreviewWindow( filename, modifiers ):
   """ Open the supplied filename in the preview window """
   if modifiers:
     modifiers = ' ' + modifiers
-  vim.command( f'silent!{modifiers} pedit! { filename }' )
+  vim.command( f'silent!{ modifiers } pedit! { filename }' )
 
 
 def WriteToPreviewWindow( message, modifiers ):
@@ -1353,7 +1353,7 @@ def OpenFilename( filename, options = {} ):
 
   # Open the file.
   try:
-    vim.command( f'{ options.get( "mods", "") }'
+    vim.command( f'{ options.get( "mods", "" ) }'
                  f'{ size }'
                  f'{ command } '
                  f'{ filename }' )

+ 22 - 6
python/ycm/youcompleteme.py

@@ -119,10 +119,22 @@ class YouCompleteMe:
 
 
   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 ):
@@ -471,7 +483,10 @@ class YouCompleteMe:
     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(
       arguments,
       False,
@@ -483,7 +498,8 @@ class YouCompleteMe:
     self._command_requests[ request_id ] = SendCommandRequestAsync(
       final_arguments,
       extra_data,
-      silent )
+      silent,
+      location = location )
     return request_id