Skip to content

Commit c6cff63

Browse files
committed
improve hierarchy
add commands: 1. reopen hierarchy window. 2. Add a caller to current node manually. 3. PageUp and PageDown. 4. Close callers. 5. Remove callers.
1 parent b5fe27b commit c6cff63

File tree

5 files changed

+178
-21
lines changed

5 files changed

+178
-21
lines changed

autoload/youcompleteme.vim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,6 +1767,10 @@ silent! nnoremap <silent> <plug>(YCMTypeHierarchy)
17671767
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'type' )<cr>
17681768
silent! nnoremap <silent> <plug>(YCMCallHierarchy)
17691769
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'call' )<cr>
1770+
silent! nnoremap <silent> <plug>(YCMResumeHierarchy)
1771+
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'resume' )<cr>
1772+
silent! nnoremap <silent> <plug>(YCMAddCallHierarchy)
1773+
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'addcall' )<cr>
17701774
17711775
" This is basic vim plugin boilerplate
17721776
let &cpo = s:save_cpo

autoload/youcompleteme/hierarchy.vim

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,35 @@ function! youcompleteme#hierarchy#StartRequest( kind )
4141
endif
4242

4343
call youcompleteme#symbol#InitSymbolProperties()
44-
py3 ycm_state.ResetCurrentHierarchy()
4544
py3 from ycm.client.command_request import GetRawCommandResponse
45+
46+
if a:kind == 'resume'
47+
if s:lines_and_handles != v:null
48+
call s:SetUpMenu()
49+
endif
50+
return
51+
endif
52+
53+
if a:kind == 'addcall'
54+
let handle = s:lines_and_handles[ s:select - 1 ][ 1 ]
55+
let lines_and_handles = py3eval(
56+
\ 'ycm_state.AddCurrentHierarchy( ' .
57+
\ 'vimsupport.GetIntValue( "handle" ), ' .
58+
\ 'GetRawCommandResponse( ' .
59+
\ '[ "CallHierarchy" ], False ))' )
60+
let s:lines_and_handles = lines_and_handles
61+
call s:SetUpMenu()
62+
return
63+
endif
64+
65+
py3 ycm_state.ResetCurrentHierarchy()
4666
if a:kind == 'call'
4767
let lines_and_handles = py3eval(
4868
\ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' .
4969
\ '[ "CallHierarchy" ], False ), ' .
5070
\ 'vim.eval( "a:kind" ) )' )
5171
else
52-
let lines_and_handles = py3eval(
72+
let lines_and_handles = py3eval(
5373
\ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' .
5474
\ '[ "TypeHierarchy" ], False ), ' .
5575
\ 'vim.eval( "a:kind" ) )' )
@@ -59,10 +79,29 @@ function! youcompleteme#hierarchy#StartRequest( kind )
5979
let s:kind = a:kind
6080
let s:select = 1
6181
call s:SetUpMenu()
82+
else
83+
let s:lines_and_handles = v:null
84+
let s:select = -1
85+
let s:kind = ''
86+
endif
87+
endfunction
88+
89+
function! s:RedrawMenu()
90+
let pos = popup_getpos( s:popup_id )
91+
call win_execute( s:popup_id,
92+
\ 'call cursor( [' . string( s:select ) . ', 1 ] )' )
93+
call win_execute( s:popup_id,
94+
\ 'set cursorline cursorlineopt&' )
95+
if s:select < pos.firstline
96+
call win_execute( s:popup_id, "normal z\<CR>" )
97+
endif
98+
if s:select >= (pos.firstline + pos.core_height )
99+
call win_execute( s:popup_id, ':normal z-' )
62100
endif
63101
endfunction
64102

65103
function! s:MenuFilter( winid, key )
104+
let pos = popup_getpos( s:popup_id )
66105
if a:key == "\<S-Tab>"
67106
" Root changes if we're showing super-tree of a sub-tree of the root
68107
" (indicated by the handle being positive)
@@ -81,6 +120,20 @@ function! s:MenuFilter( winid, key )
81120
\ [ s:select - 1, 'resolve_down', will_change_root ] )
82121
return 1
83122
endif
123+
if a:key == "c"
124+
let will_change_root = 0
125+
call popup_close(
126+
\ s:popup_id,
127+
\ [ s:select - 1, 'resolve_close', will_change_root ] )
128+
return 1
129+
endif
130+
if a:key == "d"
131+
let will_change_root = 0
132+
call popup_close(
133+
\ s:popup_id,
134+
\ [ s:select - 1, 'resolve_remove', will_change_root ] )
135+
return 1
136+
endif
84137
if a:key == "\<CR>"
85138
call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] )
86139
return 1
@@ -90,21 +143,31 @@ function! s:MenuFilter( winid, key )
90143
if s:select < 1
91144
let s:select = 1
92145
endif
93-
call win_execute( s:popup_id,
94-
\ 'call cursor( [' . string( s:select ) . ', 1 ] )' )
95-
call win_execute( s:popup_id,
96-
\ 'set cursorline cursorlineopt&' )
146+
call s:RedrawMenu()
147+
return 1
148+
endif
149+
if a:key == "\<PageUp>" || a:key == "\<kPageUp>"
150+
let s:select -= pos.core_height
151+
if s:select < 1
152+
let s:select = 1
153+
endif
154+
call s:RedrawMenu()
97155
return 1
98156
endif
99157
if a:key == "\<Down>" || a:key == "\<C-n>" || a:key == "\<C-j>" || a:key == "j"
100158
let s:select += 1
101159
if s:select > len( s:lines_and_handles )
102160
let s:select = len( s:lines_and_handles )
103161
endif
104-
call win_execute( s:popup_id,
105-
\ 'call cursor( [' . string( s:select ) . ', 1 ] )' )
106-
call win_execute( s:popup_id,
107-
\ 'set cursorline cursorlineopt&' )
162+
call s:RedrawMenu()
163+
return 1
164+
endif
165+
if a:key == "\<PageDown>" || a:key == "\<kPageDown>"
166+
let s:select += pos.core_height
167+
if s:select > len( s:lines_and_handles )
168+
let s:select = len( s:lines_and_handles )
169+
endif
170+
call s:RedrawMenu()
108171
return 1
109172
endif
110173
if index( s:ingored_keys, a:key ) >= 0
@@ -125,14 +188,15 @@ function! s:MenuCallback( winid, result )
125188
call s:ResolveItem( selection, 'down', a:result[ 2 ] )
126189
elseif operation == 'resolve_up'
127190
call s:ResolveItem( selection, 'up', a:result[ 2 ] )
191+
elseif operation == 'resolve_close'
192+
call s:ResolveItem( selection, 'close', a:result[ 2 ] )
193+
elseif operation == 'resolve_remove'
194+
call s:ResolveItem( selection, 'remove', a:result[ 2 ] )
128195
else
129196
if operation == 'jump'
130197
let handle = s:lines_and_handles[ selection ][ 1 ]
131198
py3 ycm_state.JumpToHierarchyItem( vimsupport.GetIntValue( "handle" ) )
132199
endif
133-
py3 ycm_state.ResetCurrentHierarchy()
134-
let s:kind = ''
135-
let s:select = 1
136200
endif
137201
endfunction
138202

@@ -208,6 +272,7 @@ function! s:SetUpMenu()
208272
\ . "\t"
209273
\ .. trunc_desc
210274
call add( menu_lines, { 'text': line, 'props': props } )
275+
211276
endfor
212277
call win_execute( s:popup_id,
213278
\ 'setlocal tabstop=' . tabstop )

doc/youcompleteme.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,13 +2162,20 @@ are supported:
21622162
- Call hierarchy '<Plug>(YCMCallHierarchy)': Display callees and callers of
21632163
the symbol under cursor. Expand down to callers and up to callees.
21642164

2165+
- Resume hierarchy '<Plug>(YCMResumeHierarchy)': Reopen the Hierarchy window.
2166+
2167+
- Add hierarchy '<Plug>(YCMAddCallHierarchy)': Add a caller to current node
2168+
manually.
2169+
21652170
Take a look at this Image: asciicast [85] for brief demo.
21662171

21672172
Hierarchy UI can be initiated by mapping something to the indicated plug
21682173
mappings, for example:
21692174
>
21702175
nmap <leader>yth <Plug>(YCMTypeHierarchy)
21712176
nmap <leader>ych <Plug>(YCMCallHierarchy)
2177+
nmap <leader>ycr <Plug>(YCMResumeHierarchy)
2178+
nmap <leader>yca <Plug>(YCMAddCallHierarchy)
21722179
<
21732180
This opens a "modal" popup showing the current element in the hierarchy tree.
21742181
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,
21812188
invisible, parent links. '<S-Tab>' on that row will show these by setting the
21822189
display root to the selected item.
21832190

2191+
When YCMCallHierarchy cannot find the actual caller, '<Plug>(YCMAddCallHierarchy)'
2192+
will be very useful. For example, when tracking the caller of callback functions
2193+
in C language, YCMCallHierarchy may not be able to find the true caller; instead,
2194+
it may trace related registration functions or initialization functions. The
2195+
relevant code passes the callback function to a function pointer, resulting in
2196+
a call stack that may not be what you are looking for. In this case, you can
2197+
manually find the function that calls the function pointer, which is the true
2198+
caller, and use '<Plug>(YCMAddCallHierarchy)' to manually add the true caller
2199+
to the call stack, thus extending the call stack.
2200+
21842201
When the hierarchy is displayed, the following keys are intercepted:
21852202

21862203
- '<Tab>': 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:
21912208
- '<CR>': Jump to the symbol currently selected.
21922209
- '<Down>', '<C-n>', '<C-j>', 'j': Select the next item
21932210
- '<Up>', '<C-p>', '<C-k>', 'k'; Select the previous item
2211+
- '<PageUp>': Select the item on the previous page.
2212+
- '<PageDown>': Select the item on the next page.
2213+
- '<c>': Close the current item, in other words, remove the caller of the
2214+
item under the cursorline.
2215+
- '<d>': Remove the current item.
21942216
- Any other key: closes the popup without jumping to any location
21952217

21962218
**Note:** you might think the call hierarchy tree is inverted, but we think

python/ycm/hierarchy_tree.py

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222

2323
class HierarchyNode:
24-
def __init__( self, data, distance : int ):
24+
def __init__( self, data, distance : int, parent ):
2525
self._references : Optional[ List[ int ] ] = None
2626
self._data = data
2727
self._distance_from_root = distance
28-
28+
self._parent = parent
2929

3030
def ToRootLocation( self, subindex : int ):
3131
if location := self._data.get( 'root_location' ):
@@ -70,8 +70,8 @@ def SetRootNode( self, items, kind : str ):
7070
if items:
7171
assert len( items ) == 1
7272
self._root_node_indices = [ 0 ]
73-
self._down_nodes.append( HierarchyNode( items[ 0 ], 0 ) )
74-
self._up_nodes.append( HierarchyNode( items[ 0 ], 0 ) )
73+
self._down_nodes.append( HierarchyNode( items[ 0 ], 0, None ) )
74+
self._up_nodes.append( HierarchyNode( items[ 0 ], 0, None ) )
7575
self._kind = kind
7676
return self.HierarchyToLines()
7777
return []
@@ -80,15 +80,64 @@ def SetRootNode( self, items, kind : str ):
8080
def UpdateHierarchy( self, handle : int, items, direction : str ):
8181
current_index = handle_to_index( handle )
8282
nodes = self._down_nodes if direction == 'down' else self._up_nodes
83+
node = nodes[ current_index ]
8384
if items:
8485
nodes.extend( [
8586
HierarchyNode( item,
86-
nodes[ current_index ]._distance_from_root + 1 )
87+
node._distance_from_root + 1, node )
8788
for item in items ] )
88-
nodes[ current_index ]._references = list(
89+
node._references = list(
8990
range( len( nodes ) - len( items ), len( nodes ) ) )
9091
else:
91-
nodes[ current_index ]._references = []
92+
node._references = []
93+
94+
95+
def AddNodes( self, handle : int, items ):
96+
if not items:
97+
return
98+
current_index = handle_to_index( handle )
99+
nodes = self._down_nodes
100+
node = nodes[ current_index ]
101+
nodes.extend( [
102+
HierarchyNode( item, node._distance_from_root + 1, node )
103+
for item in items ] )
104+
new_refs = list( range( len( nodes ) - len( items ), len( nodes ) ) )
105+
if node._references:
106+
node._references.extend( new_refs)
107+
else:
108+
node._references = new_refs
109+
110+
111+
def RemoveNode( self, handle : int ):
112+
current_index = handle_to_index( handle )
113+
nodes = self._down_nodes
114+
node = nodes[ current_index ]
115+
self._CloseNode( node )
116+
if node._parent:
117+
node._parent._references.remove( current_index )
118+
if len( node._parent._references ) == 0:
119+
node._parent._references = None
120+
nodes[ current_index ] = None
121+
return True
122+
else:
123+
return False
124+
125+
126+
def _CloseNode( self, node: HierarchyNode ):
127+
nodes = self._down_nodes
128+
if node._references:
129+
for subindex in node._references:
130+
if nodes[ subindex ]:
131+
self._CloseNode( nodes[ subindex ])
132+
nodes[ subindex ] = None
133+
node._references = None
134+
135+
136+
def CloseNode( self, handle : int):
137+
current_index = handle_to_index( handle )
138+
nodes = self._down_nodes
139+
node = nodes[ current_index ]
140+
self._CloseNode( node )
92141

93142

94143
def Reset( self ):

python/ycm/youcompleteme.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,14 @@ def InitializeCurrentHierarchy( self, items, kind ):
118118

119119

120120
def UpdateCurrentHierarchy( self, handle : int, direction : str ):
121-
if not self._current_hierarchy.UpdateChangesRoot( handle, direction ):
121+
if direction == 'close':
122+
self._current_hierarchy.CloseNode( handle )
123+
return self._current_hierarchy.HierarchyToLines(), 0
124+
elif direction == 'remove':
125+
ret = self._current_hierarchy.RemoveNode( handle )
126+
offset = -1 if ret else 0
127+
return self._current_hierarchy.HierarchyToLines(), offset
128+
elif not self._current_hierarchy.UpdateChangesRoot( handle, direction ):
122129
items = self._ResolveHierarchyItem( handle, direction )
123130
self._current_hierarchy.UpdateHierarchy( handle, items, direction )
124131

@@ -143,6 +150,16 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ):
143150
return self.UpdateCurrentHierarchy( handle, direction )
144151

145152

153+
def AddCurrentHierarchy( self, handle : int, items ):
154+
self._current_hierarchy.AddNodes( handle, items )
155+
return self._current_hierarchy.HierarchyToLines()
156+
157+
158+
def RemoveCurrentHierarchy( self, handle : int ):
159+
self._current_hierarchy.RemoveNode( handle )
160+
return self._current_hierarchy.HierarchyToLines()
161+
162+
146163
def _ResolveHierarchyItem( self, handle : int, direction : str ):
147164
return GetRawCommandResponse(
148165
self._current_hierarchy.ResolveArguments( handle, direction ),

0 commit comments

Comments
 (0)