diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 3ca046b20..606663129 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -1767,6 +1767,10 @@ silent! nnoremap (YCMTypeHierarchy) \ call youcompleteme#hierarchy#StartRequest( 'type' ) silent! nnoremap (YCMCallHierarchy) \ call youcompleteme#hierarchy#StartRequest( 'call' ) +silent! nnoremap (YCMResumeHierarchy) + \ call youcompleteme#hierarchy#StartRequest( 'resume' ) +silent! nnoremap (YCMAddCallHierarchy) + \ call youcompleteme#hierarchy#StartRequest( 'addcall' ) " This is basic vim plugin boilerplate let &cpo = s:save_cpo diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index d5b3563bd..439db41ee 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -41,15 +41,35 @@ function! youcompleteme#hierarchy#StartRequest( kind ) endif call youcompleteme#symbol#InitSymbolProperties() - py3 ycm_state.ResetCurrentHierarchy() py3 from ycm.client.command_request import GetRawCommandResponse + + if a:kind == 'resume' + if s:lines_and_handles != v:null + call s:SetUpMenu() + endif + return + endif + + if a:kind == 'addcall' + let handle = s:lines_and_handles[ s:select - 1 ][ 1 ] + let lines_and_handles = py3eval( + \ 'ycm_state.AddCurrentHierarchy( ' . + \ 'vimsupport.GetIntValue( "handle" ), ' . + \ 'GetRawCommandResponse( ' . + \ '[ "CallHierarchy" ], False ))' ) + let s:lines_and_handles = lines_and_handles + call s:SetUpMenu() + return + endif + + py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' let lines_and_handles = py3eval( \ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' . \ '[ "CallHierarchy" ], False ), ' . \ 'vim.eval( "a:kind" ) )' ) else - let lines_and_handles = py3eval( + let lines_and_handles = py3eval( \ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' . \ '[ "TypeHierarchy" ], False ), ' . \ 'vim.eval( "a:kind" ) )' ) @@ -59,10 +79,29 @@ function! youcompleteme#hierarchy#StartRequest( kind ) let s:kind = a:kind let s:select = 1 call s:SetUpMenu() + else + let s:lines_and_handles = v:null + let s:select = -1 + let s:kind = '' + endif +endfunction + +function! s:RedrawMenu() + let pos = popup_getpos( s:popup_id ) + call win_execute( s:popup_id, + \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + call win_execute( s:popup_id, + \ 'set cursorline cursorlineopt&' ) + if s:select < pos.firstline + call win_execute( s:popup_id, "normal z\" ) + endif + if s:select >= (pos.firstline + pos.core_height ) + call win_execute( s:popup_id, ':normal z-' ) endif endfunction function! s:MenuFilter( winid, key ) + let pos = popup_getpos( s:popup_id ) if a:key == "\" " Root changes if we're showing super-tree of a sub-tree of the root " (indicated by the handle being positive) @@ -81,6 +120,20 @@ function! s:MenuFilter( winid, key ) \ [ s:select - 1, 'resolve_down', will_change_root ] ) return 1 endif + if a:key == "c" + let will_change_root = 0 + call popup_close( + \ s:popup_id, + \ [ s:select - 1, 'resolve_close', will_change_root ] ) + return 1 + endif + if a:key == "d" + let will_change_root = 0 + call popup_close( + \ s:popup_id, + \ [ s:select - 1, 'resolve_remove', will_change_root ] ) + return 1 + endif if a:key == "\" call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] ) return 1 @@ -90,10 +143,15 @@ function! s:MenuFilter( winid, key ) if s:select < 1 let s:select = 1 endif - call win_execute( s:popup_id, - \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) - call win_execute( s:popup_id, - \ 'set cursorline cursorlineopt&' ) + call s:RedrawMenu() + return 1 + endif + if a:key == "\" || a:key == "\" + let s:select -= pos.core_height + if s:select < 1 + let s:select = 1 + endif + call s:RedrawMenu() return 1 endif if a:key == "\" || a:key == "\" || a:key == "\" || a:key == "j" @@ -101,10 +159,15 @@ function! s:MenuFilter( winid, key ) if s:select > len( s:lines_and_handles ) let s:select = len( s:lines_and_handles ) endif - call win_execute( s:popup_id, - \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) - call win_execute( s:popup_id, - \ 'set cursorline cursorlineopt&' ) + call s:RedrawMenu() + return 1 + endif + if a:key == "\" || a:key == "\" + let s:select += pos.core_height + if s:select > len( s:lines_and_handles ) + let s:select = len( s:lines_and_handles ) + endif + call s:RedrawMenu() return 1 endif if index( s:ingored_keys, a:key ) >= 0 @@ -125,14 +188,15 @@ function! s:MenuCallback( winid, result ) call s:ResolveItem( selection, 'down', a:result[ 2 ] ) elseif operation == 'resolve_up' call s:ResolveItem( selection, 'up', a:result[ 2 ] ) + elseif operation == 'resolve_close' + call s:ResolveItem( selection, 'close', a:result[ 2 ] ) + elseif operation == 'resolve_remove' + call s:ResolveItem( selection, 'remove', a:result[ 2 ] ) else if operation == 'jump' let handle = s:lines_and_handles[ selection ][ 1 ] py3 ycm_state.JumpToHierarchyItem( vimsupport.GetIntValue( "handle" ) ) endif - py3 ycm_state.ResetCurrentHierarchy() - let s:kind = '' - let s:select = 1 endif endfunction @@ -208,6 +272,7 @@ function! s:SetUpMenu() \ . "\t" \ .. trunc_desc call add( menu_lines, { 'text': line, 'props': props } ) + endfor call win_execute( s:popup_id, \ 'setlocal tabstop=' . tabstop ) diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index 88ed26a46..3643d019b 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -2162,6 +2162,11 @@ are supported: - Call hierarchy '(YCMCallHierarchy)': Display callees and callers of the symbol under cursor. Expand down to callers and up to callees. +- Resume hierarchy '(YCMResumeHierarchy)': Reopen the Hierarchy window. + +- Add hierarchy '(YCMAddCallHierarchy)': Add a caller to current node + manually. + Take a look at this Image: asciicast [85] for brief demo. Hierarchy UI can be initiated by mapping something to the indicated plug @@ -2169,6 +2174,8 @@ mappings, for example: > nmap yth (YCMTypeHierarchy) nmap ych (YCMCallHierarchy) + nmap ycr (YCMResumeHierarchy) + nmap yca (YCMAddCallHierarchy) < This opens a "modal" popup showing the current element in the hierarchy tree. The current tree root is aligned to the left and child and parent nodes are @@ -2181,6 +2188,16 @@ inheritance where a "child" of the current root may actually have other, invisible, parent links. '' on that row will show these by setting the display root to the selected item. +When YCMCallHierarchy cannot find the actual caller, '(YCMAddCallHierarchy)' +will be very useful. For example, when tracking the caller of callback functions +in C language, YCMCallHierarchy may not be able to find the true caller; instead, +it may trace related registration functions or initialization functions. The +relevant code passes the callback function to a function pointer, resulting in +a call stack that may not be what you are looking for. In this case, you can +manually find the function that calls the function pointer, which is the true +caller, and use '(YCMAddCallHierarchy)' to manually add the true caller +to the call stack, thus extending the call stack. + When the hierarchy is displayed, the following keys are intercepted: - '': Drill into the hierarchy at the selected item: expand and show @@ -2191,6 +2208,11 @@ When the hierarchy is displayed, the following keys are intercepted: - '': Jump to the symbol currently selected. - '', '', '', 'j': Select the next item - '', '', '', 'k'; Select the previous item +- '': Select the item on the previous page. +- '': Select the item on the next page. +- '': Close the current item, in other words, remove the caller of the + item under the cursorline. +- '': Remove the current item. - Any other key: closes the popup without jumping to any location **Note:** you might think the call hierarchy tree is inverted, but we think diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 2485ce47e..0f1caad2a 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -21,11 +21,11 @@ class HierarchyNode: - def __init__( self, data, distance : int ): + def __init__( self, data, distance : int, parent ): self._references : Optional[ List[ int ] ] = None self._data = data self._distance_from_root = distance - + self._parent = parent def ToRootLocation( self, subindex : int ): if location := self._data.get( 'root_location' ): @@ -70,8 +70,8 @@ def SetRootNode( self, items, kind : str ): if items: assert len( items ) == 1 self._root_node_indices = [ 0 ] - self._down_nodes.append( HierarchyNode( items[ 0 ], 0 ) ) - self._up_nodes.append( HierarchyNode( items[ 0 ], 0 ) ) + self._down_nodes.append( HierarchyNode( items[ 0 ], 0, None ) ) + self._up_nodes.append( HierarchyNode( items[ 0 ], 0, None ) ) self._kind = kind return self.HierarchyToLines() return [] @@ -80,15 +80,64 @@ def SetRootNode( self, items, kind : str ): def UpdateHierarchy( self, handle : int, items, direction : str ): current_index = handle_to_index( handle ) nodes = self._down_nodes if direction == 'down' else self._up_nodes + node = nodes[ current_index ] if items: nodes.extend( [ HierarchyNode( item, - nodes[ current_index ]._distance_from_root + 1 ) + node._distance_from_root + 1, node ) for item in items ] ) - nodes[ current_index ]._references = list( + node._references = list( range( len( nodes ) - len( items ), len( nodes ) ) ) else: - nodes[ current_index ]._references = [] + node._references = [] + + + def AddNodes( self, handle : int, items ): + if not items: + return + current_index = handle_to_index( handle ) + nodes = self._down_nodes + node = nodes[ current_index ] + nodes.extend( [ + HierarchyNode( item, node._distance_from_root + 1, node ) + for item in items ] ) + new_refs = list( range( len( nodes ) - len( items ), len( nodes ) ) ) + if node._references: + node._references.extend( new_refs) + else: + node._references = new_refs + + + def RemoveNode( self, handle : int ): + current_index = handle_to_index( handle ) + nodes = self._down_nodes + node = nodes[ current_index ] + self._CloseNode( node ) + if node._parent: + node._parent._references.remove( current_index ) + if len( node._parent._references ) == 0: + node._parent._references = None + nodes[ current_index ] = None + return True + else: + return False + + + def _CloseNode( self, node: HierarchyNode ): + nodes = self._down_nodes + if node._references: + for subindex in node._references: + if nodes[ subindex ]: + self._CloseNode( nodes[ subindex ]) + nodes[ subindex ] = None + node._references = None + + + def CloseNode( self, handle : int): + current_index = handle_to_index( handle ) + nodes = self._down_nodes + node = nodes[ current_index ] + self._CloseNode( node ) def Reset( self ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 9e60ae87b..c30075678 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -118,7 +118,14 @@ def InitializeCurrentHierarchy( self, items, kind ): def UpdateCurrentHierarchy( self, handle : int, direction : str ): - if not self._current_hierarchy.UpdateChangesRoot( handle, direction ): + if direction == 'close': + self._current_hierarchy.CloseNode( handle ) + return self._current_hierarchy.HierarchyToLines(), 0 + elif direction == 'remove': + ret = self._current_hierarchy.RemoveNode( handle ) + offset = -1 if ret else 0 + return self._current_hierarchy.HierarchyToLines(), offset + elif not self._current_hierarchy.UpdateChangesRoot( handle, direction ): items = self._ResolveHierarchyItem( handle, direction ) self._current_hierarchy.UpdateHierarchy( handle, items, direction ) @@ -143,6 +150,16 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): return self.UpdateCurrentHierarchy( handle, direction ) + def AddCurrentHierarchy( self, handle : int, items ): + self._current_hierarchy.AddNodes( handle, items ) + return self._current_hierarchy.HierarchyToLines() + + + def RemoveCurrentHierarchy( self, handle : int ): + self._current_hierarchy.RemoveNode( handle ) + return self._current_hierarchy.HierarchyToLines() + + def _ResolveHierarchyItem( self, handle : int, direction : str ): return GetRawCommandResponse( self._current_hierarchy.ResolveArguments( handle, direction ),