Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve call hierarchy #4245

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,10 @@ silent! nnoremap <silent> <plug>(YCMTypeHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'type' )<cr>
silent! nnoremap <silent> <plug>(YCMCallHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'call' )<cr>
silent! nnoremap <silent> <plug>(YCMResumeHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'resume' )<cr>
silent! nnoremap <silent> <plug>(YCMAddCallHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'addcall' )<cr>

" This is basic vim plugin boilerplate
let &cpo = s:save_cpo
Expand Down
91 changes: 78 additions & 13 deletions autoload/youcompleteme/hierarchy.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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" ) )' )
Expand All @@ -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\<CR>" )
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 == "\<S-Tab>"
" Root changes if we're showing super-tree of a sub-tree of the root
" (indicated by the handle being positive)
Expand All @@ -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 == "\<CR>"
call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] )
return 1
Expand All @@ -90,21 +143,31 @@ 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 == "\<PageUp>" || a:key == "\<kPageUp>"
let s:select -= pos.core_height
if s:select < 1
let s:select = 1
endif
call s:RedrawMenu()
return 1
endif
if a:key == "\<Down>" || a:key == "\<C-n>" || a:key == "\<C-j>" || a:key == "j"
let s:select += 1
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 == "\<PageDown>" || a:key == "\<kPageDown>"
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
Expand All @@ -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

Expand Down Expand Up @@ -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 )
Expand Down
22 changes: 22 additions & 0 deletions doc/youcompleteme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2162,13 +2162,20 @@ are supported:
- Call hierarchy '<Plug>(YCMCallHierarchy)': Display callees and callers of
the symbol under cursor. Expand down to callers and up to callees.

- Resume hierarchy '<Plug>(YCMResumeHierarchy)': Reopen the Hierarchy window.

- Add hierarchy '<Plug>(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
mappings, for example:
>
nmap <leader>yth <Plug>(YCMTypeHierarchy)
nmap <leader>ych <Plug>(YCMCallHierarchy)
nmap <leader>ycr <Plug>(YCMResumeHierarchy)
nmap <leader>yca <Plug>(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
Expand All @@ -2181,6 +2188,16 @@ inheritance where a "child" of the current root may actually have other,
invisible, parent links. '<S-Tab>' on that row will show these by setting the
display root to the selected item.

When YCMCallHierarchy cannot find the actual caller, '<Plug>(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 '<Plug>(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:

- '<Tab>': Drill into the hierarchy at the selected item: expand and show
Expand All @@ -2191,6 +2208,11 @@ When the hierarchy is displayed, the following keys are intercepted:
- '<CR>': Jump to the symbol currently selected.
- '<Down>', '<C-n>', '<C-j>', 'j': Select the next item
- '<Up>', '<C-p>', '<C-k>', 'k'; Select the previous item
- '<PageUp>': Select the item on the previous page.
- '<PageDown>': Select the item on the next page.
- '<c>': Close the current item, in other words, remove the caller of the
item under the cursorline.
- '<d>': 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
Expand Down
63 changes: 56 additions & 7 deletions python/ycm/hierarchy_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -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' ):
Expand Down Expand Up @@ -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 []
Expand All @@ -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 ):
Expand Down
19 changes: 18 additions & 1 deletion python/ycm/youcompleteme.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 )

Expand All @@ -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 ),
Expand Down
Loading