From dbcca3d0762a0cc4d42838e497ae72a69ca44966 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sat, 10 Feb 2024 17:05:39 +0100 Subject: [PATCH 01/34] Preliminary type/call hierarchy support --- autoload/youcompleteme/hierarchy.vim | 109 +++++++++++++++++++++++++++ python/ycm/client/command_request.py | 6 +- python/ycm/youcompleteme.py | 55 +++++++++++++- third_party/ycmd | 2 +- 4 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 autoload/youcompleteme/hierarchy.vim diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim new file mode 100644 index 0000000000..66489e7547 --- /dev/null +++ b/autoload/youcompleteme/hierarchy.vim @@ -0,0 +1,109 @@ +" Copyright (C) 2021 YouCompleteMe contributors +" +" This file is part of YouCompleteMe. +" +" YouCompleteMe is free software: you can redistribute it and/or modify +" it under the terms of the GNU General Public License as published by +" the Free Software Foundation, either version 3 of the License, or +" (at your option) any later version. +" +" YouCompleteMe is distributed in the hope that it will be useful, +" but WITHOUT ANY WARRANTY; without even the implied warranty of +" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +" GNU General Public License for more details. +" +" You should have received a copy of the GNU General Public License +" along with YouCompleteMe. If not, see . + +" This is basic vim plugin boilerplate +let s:save_cpo = &cpoptions +set cpoptions&vim + +scriptencoding utf-8 + +let s:popup_id = -1 +let s:lines_and_handles = v:none +let s:select = -1 +let s:kind = '' +let s:direction = '' + +function! youcompleteme#hierarchy#StartRequest( kind, direction ) + py3 ycm_state.ResetCurrentHierarchy() + if a:kind == 'call' + let lines_and_handles = py3eval( 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( [ "CallHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + else + let lines_and_handles = py3eval( 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( [ "TypeHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + endif + if len( lines_and_handles ) + let s:lines_and_handles = lines_and_handles + let s:kind = a:kind + let s:direction = a:direction + let s:select = 1 + call s:SetupMenu() + endif +endfunction + +function! s:MenuFilter( winid, key ) + if a:key == "\" + call popup_close( s:popup_id, [ s:select - 1, 'resolve' ] ) + return 1 + endif + if a:key == "\" + call popup_close( s:popup_id, [ s:select - 1, 'cancel' ] ) + return 1 + endif + if a:key == "\" + call popup_close( s:popup_id, [ s:select - 1, 'jump' ] ) + return 1 + endif + if a:key == "\" + let s:select -= 1 + if s:select < 1 + let s:select = 1 + endif + call win_execute( s:popup_id, 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + return 1 + endif + if a:key == "\" + 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 ] )' ) + return 1 + endif + return 0 +endfunction + +function! s:MenuCallback( winid, result ) + let operation = result[ 1 ] + if operation == 'resolve' + call s:ResolveItem( a:result ) + else + if operation == 'jump' + let handle = s:lines_and_handles[ a:result ][ 1 ] + py3 ycm_state.JumpToHierarchyItem( vimsupport.GetIntValue( "handle" ) ) + endif + py3 ycm_state.ResetCurrentHierarchy() + let s:kind = '' + let s:direction = '' + let s:select = 1 + endif +endfunction + +function! s:SetupMenu() + let menu_lines = [] + for line_and_item in s:lines_and_handles + call add( menu_lines, line_and_item[ 0 ] ) + endfor + let s:popup_id = popup_menu( menu_lines, #{ filter: funcref( 's:MenuFilter' ), callback: funcref( 's:MenuCallback' ) } ) + call win_execute( s:popup_id, 'call cursor( [' . string( s:select ) . ', 1 ] )' ) +endfunction + +function! s:ResolveItem( choice ) + let handle = s:lines_and_handles[ a:choice ][ 1 ] + if py3eval( 'ycm_state.ItemNeedsResolving( vimsupport.GetIntValue( "handle" ) )' ) + let s:lines_and_handles = py3eval( 'ycm_state.UpdateCurrentHierarchy( vimsupport.GetIntValue( "handle" ) )' ) + endif + call s:SetupMenu() +endfunction diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index ff29963ff3..b34c341886 100644 --- a/python/ycm/client/command_request.py +++ b/python/ycm/client/command_request.py @@ -218,12 +218,14 @@ def SendCommandRequestAsync( arguments, extra_data = None, silent = True ): def SendCommandRequest( arguments, modifiers, buffer_command = DEFAULT_BUFFER_COMMAND, - extra_data = None ): + extra_data = None, + skip_post_command_action = False ): request = SendCommandRequestAsync( arguments, extra_data = extra_data, silent = False ) # Block here to get the response - request.RunPostCommandActionsIfNeeded( modifiers, buffer_command ) + if not skip_post_command_action: + request.RunPostCommandActionsIfNeeded( modifiers, buffer_command ) return request.Response() diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index dd9e30a144..72db571e4b 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2018 YouCompleteMe contributors +# Copyright (C) 2011-2024 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -29,6 +29,7 @@ from ycmd.request_wrap import RequestWrap from ycm.omni_completer import OmniCompleter from ycm import syntax_parse +from ycm.hierarchy_tree import HierarchyTree from ycm.client.ycmd_keepalive import YcmdKeepalive from ycm.client.base_request import BaseRequest, BuildRequestData from ycm.client.completer_available_request import SendCompleterAvailableRequest @@ -108,6 +109,55 @@ def __init__( self, default_options = {} ): self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start() + self._current_hierarchy = HierarchyTree() + + + def InitializeCurrentHierarchy( self, items, kind, direction ): + return self._current_hierarchy.SetRootNode( items, kind, direction ) + + + def UpdateCurrentHierarchy( self, handle : int ): + items = self.ResolveHierarchyItem( handle ) + self._current_hierarchy.UpdateHierarchy( handle, items ) + return self._current_hierarchy.HierarchyToLines() + + + def ResolveHierarchyItem( self, handle : int ): + node_data = self._current_hierarchy._nodes[ handle ]._data + kind = self._current_hierarchy._kind + direction = self._current_hierarchy._direction + try: + item = node_data[ 'from' ] + except KeyError: + try: + item = node_data[ 'to' ] + except KeyError: + item = node_data + + return SendCommandRequest( + [ f'Resolve{ kind.title() }HierarchyItem', item, direction ], + '', + self._user_options[ 'goto_buffer_command' ], + extra_data = None, + skip_post_command_action = True + ) + + + def ItemNeedsResolving( self, handle : int ): + item = self._current_hierarchy._nodes[ handle ] + return item._references is None + + + def ResetCurrentHierarchy( self ): + self._current_hierarchy.Reset() + + + def JumpToHierarchyItem( self, handle : int ): + item = self._current_hierarchy._nodes[ handle ] + file, line, column = item.ToLocation() + line += 1 + column += 1 + vimsupport.JumpToLocation( file, line, column, '', self._user_options[ 'goto_buffer_command' ] ) def _SetUpServer( self ): @@ -419,7 +469,8 @@ def SendCommandRequest( self, final_arguments, modifiers, self._user_options[ 'goto_buffer_command' ], - extra_data ) + extra_data, + 'Hierarchy' in arguments[ 0 ] ) def GetCommandResponse( self, arguments ): diff --git a/third_party/ycmd b/third_party/ycmd index b63d2e86c6..7475c55565 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit b63d2e86c62d39c20284badfd4bfed6e8797d134 +Subproject commit 7475c55565495d00a7edd0d9b5c336a887fb6249 From 93833dca8b31fee5ff4a18ef5b4f753bd0f81c91 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 11 Feb 2024 10:36:53 +0100 Subject: [PATCH 02/34] Clean up API --- autoload/youcompleteme/hierarchy.vim | 37 ++++++--- python/ycm/hierarchy_tree.py | 119 +++++++++++++++++++++++++++ python/ycm/youcompleteme.py | 34 +++----- 3 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 python/ycm/hierarchy_tree.py diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 66489e7547..d4a328f135 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -30,9 +30,15 @@ let s:direction = '' function! youcompleteme#hierarchy#StartRequest( kind, direction ) py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' - let lines_and_handles = py3eval( 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( [ "CallHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + let lines_and_handles = py3eval( + \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . + \ '[ "CallHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), ' . + \ 'vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) else - let lines_and_handles = py3eval( 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( [ "TypeHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + let lines_and_handles = py3eval( + \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . + \ '[ "TypeHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), ' . + \ 'vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) endif if len( lines_and_handles ) let s:lines_and_handles = lines_and_handles @@ -61,7 +67,8 @@ 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, + \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) return 1 endif if a:key == "\" @@ -69,19 +76,21 @@ 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, + \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) return 1 endif return 0 endfunction function! s:MenuCallback( winid, result ) - let operation = result[ 1 ] + let operation = a:result[ 1 ] + let selection = a:result[ 0 ] if operation == 'resolve' - call s:ResolveItem( a:result ) + call s:ResolveItem( selection ) else if operation == 'jump' - let handle = s:lines_and_handles[ a:result ][ 1 ] + let handle = s:lines_and_handles[ selection ][ 1 ] py3 ycm_state.JumpToHierarchyItem( vimsupport.GetIntValue( "handle" ) ) endif py3 ycm_state.ResetCurrentHierarchy() @@ -96,14 +105,20 @@ function! s:SetupMenu() for line_and_item in s:lines_and_handles call add( menu_lines, line_and_item[ 0 ] ) endfor - let s:popup_id = popup_menu( menu_lines, #{ filter: funcref( 's:MenuFilter' ), callback: funcref( 's:MenuCallback' ) } ) - call win_execute( s:popup_id, 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + let s:popup_id = popup_menu( menu_lines, #{ + \ filter: funcref( 's:MenuFilter' ), + \ callback: funcref( 's:MenuCallback' ) } ) + call win_execute( s:popup_id, + \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) endfunction function! s:ResolveItem( choice ) let handle = s:lines_and_handles[ a:choice ][ 1 ] - if py3eval( 'ycm_state.ItemNeedsResolving( vimsupport.GetIntValue( "handle" ) )' ) - let s:lines_and_handles = py3eval( 'ycm_state.UpdateCurrentHierarchy( vimsupport.GetIntValue( "handle" ) )' ) + if py3eval( + \ 'ycm_state.ShouldResolveItem( vimsupport.GetIntValue( "handle" ) )' ) + let s:lines_and_handles = py3eval( + \ 'ycm_state.UpdateCurrentHierarchy( ' . + \ 'vimsupport.GetIntValue( "handle" ) )' ) endif call s:SetupMenu() endfunction diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py new file mode 100644 index 0000000000..9705ca99b6 --- /dev/null +++ b/python/ycm/hierarchy_tree.py @@ -0,0 +1,119 @@ +# Copyright (C) 2011-2024 YouCompleteMe contributors +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + +from typing import Optional, List +from ycm import vimsupport +import os + + +class HierarchyNode: + def __init__( self, data, distance : int ): + self._references : Optional[List[int]] = None + self._data = data + self._distance_from_root = distance + + + def ToLocation( self, subindex : int ): + location = self._data[ 'locations' ][ subindex ] + line = location[ 'line_num' ] + column = location[ 'column_num' ] + file = location[ 'filepath' ] + return file, line, column + + + +class HierarchyTree: + def __init__( self ): + self._nodes : List[HierarchyNode] = [] + self._kind : str = '' + self._direction : str = '' + + def SetRootNode( self, items, kind : str, direction : str ): + if items: + self._root_node_indices = list( range( 0, len( items ) ) ) + self._nodes.append( HierarchyNode( items[ 0 ], 0 ) ) + self._kind = kind + self._direction = direction + return self.HierarchyToLines( is_root_node = True ) + return [] + + + def UpdateHierarchy( self, handle : int, items ): + current_index = handle // 1000000 + if items: + self._nodes.extend( [ + HierarchyNode( item, + self._nodes[ current_index ]._distance_from_root + 1 ) + for item in items ] ) + self._nodes[ current_index ]._references = list( + range( len( self._nodes ) - len( items ), + len( self._nodes ) ) ) + else: + self._nodes[ current_index ]._references = [] + + + def Reset( self ): + self._nodes = [] + + + def _HierarchyToLinesHelper( self, refs, is_root_node = False ): + partial_result = [] + for i in refs: + next_node = self._nodes[ i ] + indent = 2 * next_node._distance_from_root + can_expand = next_node._references is None + symbol = '+' if can_expand else '-' + name = next_node._data[ 'name' ] + kind = next_node._data[ 'kind' ] + partial_result.extend( [ + ( ' ' * indent + symbol + kind + ': ' + name + '\t' + + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + + str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], + i * 1000000 + j ) + for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) + if next_node._references: + partial_result.extend( + self._HierarchyToLinesHelper( next_node._references, is_root_node ) ) + return partial_result + + def HierarchyToLines( self, is_root_node = False ): + lines = [] + for i in self._root_node_indices: + lines.extend( self._HierarchyToLinesHelper( [ i ], is_root_node ) ) + return lines + + + def JumpToItem( self, handle : int, command ): + node_index = handle // 1000000 + location_index = handle % 1000000 + node = self._nodes[ node_index ] + file, line, column = node.ToLocation( location_index ) + vimsupport.JumpToLocation( file, line, column, '', command ) + + + def ShouldResolveItem( self, handle : int ): + node_index = handle // 1000000 + return self._nodes[ node_index ]._references is None + + + def ResolveArguments( self, handle : int ): + node_index = handle // 1000000 + return [ + f'Resolve{ self._kind.title() }HierarchyItem', + self._nodes[ node_index ]._data, + self._direction + ] diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 72db571e4b..0bc990591f 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -116,26 +116,15 @@ def InitializeCurrentHierarchy( self, items, kind, direction ): return self._current_hierarchy.SetRootNode( items, kind, direction ) - def UpdateCurrentHierarchy( self, handle : int ): - items = self.ResolveHierarchyItem( handle ) + def UpdateCurrentHierarchy( self, handle ): + items = self._ResolveHierarchyItem( handle ) self._current_hierarchy.UpdateHierarchy( handle, items ) return self._current_hierarchy.HierarchyToLines() - def ResolveHierarchyItem( self, handle : int ): - node_data = self._current_hierarchy._nodes[ handle ]._data - kind = self._current_hierarchy._kind - direction = self._current_hierarchy._direction - try: - item = node_data[ 'from' ] - except KeyError: - try: - item = node_data[ 'to' ] - except KeyError: - item = node_data - + def _ResolveHierarchyItem( self, handle ): return SendCommandRequest( - [ f'Resolve{ kind.title() }HierarchyItem', item, direction ], + self._current_hierarchy.ResolveArguments( handle ), '', self._user_options[ 'goto_buffer_command' ], extra_data = None, @@ -143,21 +132,18 @@ def ResolveHierarchyItem( self, handle : int ): ) - def ItemNeedsResolving( self, handle : int ): - item = self._current_hierarchy._nodes[ handle ] - return item._references is None + def ShouldResolveItem( self, handle ): + return self._current_hierarchy.ShouldResolveItem( handle ) def ResetCurrentHierarchy( self ): self._current_hierarchy.Reset() - def JumpToHierarchyItem( self, handle : int ): - item = self._current_hierarchy._nodes[ handle ] - file, line, column = item.ToLocation() - line += 1 - column += 1 - vimsupport.JumpToLocation( file, line, column, '', self._user_options[ 'goto_buffer_command' ] ) + def JumpToHierarchyItem( self, handle ): + self._current_hierarchy.JumpToItem( + handle, + self._user_options[ 'goto_buffer_command' ] ) def _SetUpServer( self ): From 705803aec13ccddc91442122bd84e81e7b4b2798 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 11 Feb 2024 13:26:27 +0100 Subject: [PATCH 03/34] Allow both directions at the same time - me is happy --- autoload/youcompleteme/hierarchy.vim | 40 ++++++---- python/ycm/hierarchy_tree.py | 115 +++++++++++++++++---------- python/ycm/youcompleteme.py | 21 ++--- 3 files changed, 109 insertions(+), 67 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index d4a328f135..7f34fc343c 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -25,33 +25,39 @@ let s:popup_id = -1 let s:lines_and_handles = v:none let s:select = -1 let s:kind = '' -let s:direction = '' -function! youcompleteme#hierarchy#StartRequest( kind, direction ) +function! youcompleteme#hierarchy#StartRequest( kind ) py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' let lines_and_handles = py3eval( \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . - \ '[ "CallHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), ' . - \ 'vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + \ '[ "CallHierarchy" ], "", False, 0, 0 ), ' . + \ 'vim.eval( "a:kind" ) )' ) else let lines_and_handles = py3eval( \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . - \ '[ "TypeHierarchy", vim.eval( "a:direction" ) ], "", False, 0, 0 ), ' . - \ 'vim.eval( "a:kind" ), vim.eval( "a:direction" ) )' ) + \ '[ "TypeHierarchy" ], "", False, 0, 0 ), ' . + \ 'vim.eval( "a:kind" ) )' ) endif if len( lines_and_handles ) let s:lines_and_handles = lines_and_handles let s:kind = a:kind - let s:direction = a:direction let s:select = 1 call s:SetupMenu() endif endfunction function! s:MenuFilter( winid, key ) + if a:key == "\" + if s:lines_and_handles[ s:select - 1 ][ 1 ] <= 0 " TODO: switching root not impl + call popup_close( s:popup_id, [ s:select - 1, 'resolve_up' ] ) + endif + return 1 + endif if a:key == "\" - call popup_close( s:popup_id, [ s:select - 1, 'resolve' ] ) + if s:lines_and_handles[ s:select - 1 ][ 1 ] >= 0 " TODO: switching root not impl + call popup_close( s:popup_id, [ s:select - 1, 'resolve_down' ] ) + endif return 1 endif if a:key == "\" @@ -86,8 +92,10 @@ endfunction function! s:MenuCallback( winid, result ) let operation = a:result[ 1 ] let selection = a:result[ 0 ] - if operation == 'resolve' - call s:ResolveItem( selection ) + if operation == 'resolve_down' + call s:ResolveItem( selection, 'down' ) + elseif operation == 'resolve_up' + call s:ResolveItem( selection, 'up' ) else if operation == 'jump' let handle = s:lines_and_handles[ selection ][ 1 ] @@ -95,7 +103,6 @@ function! s:MenuCallback( winid, result ) endif py3 ycm_state.ResetCurrentHierarchy() let s:kind = '' - let s:direction = '' let s:select = 1 endif endfunction @@ -112,13 +119,16 @@ function! s:SetupMenu() \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) endfunction -function! s:ResolveItem( choice ) +function! s:ResolveItem( choice, direction ) let handle = s:lines_and_handles[ a:choice ][ 1 ] if py3eval( - \ 'ycm_state.ShouldResolveItem( vimsupport.GetIntValue( "handle" ) )' ) - let s:lines_and_handles = py3eval( + \ 'ycm_state.ShouldResolveItem( vimsupport.GetIntValue( "handle" ), vim.eval( "a:direction" ) )' ) + let lines_and_handles_with_offset = py3eval( \ 'ycm_state.UpdateCurrentHierarchy( ' . - \ 'vimsupport.GetIntValue( "handle" ) )' ) + \ 'vimsupport.GetIntValue( "handle" ), ' . + \ 'vim.eval( "a:direction" ) )' ) + let s:select += lines_and_handles_with_offset[ 1 ] + let s:lines_and_handles = lines_and_handles_with_offset[ 0 ] endif call s:SetupMenu() endfunction diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 9705ca99b6..74bf872949 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -1,4 +1,4 @@ -# Copyright (C) 2011-2024 YouCompleteMe contributors +# Copyright (C) 2024 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -38,82 +38,113 @@ def ToLocation( self, subindex : int ): class HierarchyTree: def __init__( self ): - self._nodes : List[HierarchyNode] = [] + self._up_nodes : List[HierarchyNode] = [] + self._down_nodes : List[HierarchyNode] = [] self._kind : str = '' - self._direction : str = '' - def SetRootNode( self, items, kind : str, direction : str ): + def SetRootNode( self, items, kind : str ): if items: - self._root_node_indices = list( range( 0, len( items ) ) ) - self._nodes.append( HierarchyNode( items[ 0 ], 0 ) ) + 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._kind = kind - self._direction = direction - return self.HierarchyToLines( is_root_node = True ) + return self.HierarchyToLines() return [] - def UpdateHierarchy( self, handle : int, items ): - current_index = handle // 1000000 + def UpdateHierarchy( self, handle : int, items, direction : str ): + current_index = abs( handle ) // 1000000 + nodes = self._down_nodes if direction == 'down' else self._up_nodes if items: - self._nodes.extend( [ + nodes.extend( [ HierarchyNode( item, - self._nodes[ current_index ]._distance_from_root + 1 ) + nodes[ current_index ]._distance_from_root + 1 ) for item in items ] ) - self._nodes[ current_index ]._references = list( - range( len( self._nodes ) - len( items ), - len( self._nodes ) ) ) + nodes[ current_index ]._references = list( + range( len( nodes ) - len( items ), + len( nodes ) ) ) else: - self._nodes[ current_index ]._references = [] + nodes[ current_index ]._references = [] def Reset( self ): - self._nodes = [] + self._down_nodes = [] + self._up_nodes = [] + self._kind = '' - def _HierarchyToLinesHelper( self, refs, is_root_node = False ): + def _HierarchyToLinesHelper( self, refs, use_down_nodes ): partial_result = [] + nodes = self._down_nodes if use_down_nodes else self._up_nodes for i in refs: - next_node = self._nodes[ i ] + next_node = nodes[ i ] indent = 2 * next_node._distance_from_root - can_expand = next_node._references is None + if i == 0: + can_expand = self._down_nodes[ 0 ]._references is None or self._up_nodes[ 0 ]._references is None + else: + can_expand = next_node._references is None symbol = '+' if can_expand else '-' name = next_node._data[ 'name' ] kind = next_node._data[ 'kind' ] - partial_result.extend( [ - ( ' ' * indent + symbol + kind + ': ' + name + '\t' + - os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], - i * 1000000 + j ) - for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) + if use_down_nodes: + partial_result.extend( [ + ( ' ' * indent + symbol + kind + ': ' + name + '\t' + + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + + str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], + ( i * 1000000 + j ) ) + for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) + else: + partial_result.extend( [ + ( ' ' * indent + symbol + kind + ': ' + name + '\t' + + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + + str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], + ( i * 1000000 + j ) * -1 ) + for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) if next_node._references: partial_result.extend( - self._HierarchyToLinesHelper( next_node._references, is_root_node ) ) + self._HierarchyToLinesHelper( next_node._references, use_down_nodes ) ) return partial_result - def HierarchyToLines( self, is_root_node = False ): - lines = [] - for i in self._root_node_indices: - lines.extend( self._HierarchyToLinesHelper( [ i ], is_root_node ) ) - return lines + def HierarchyToLines( self ): + down_lines = self._HierarchyToLinesHelper( [ 0 ], True ) + up_lines = self._HierarchyToLinesHelper( [ 0 ], False ) + up_lines.reverse() + return up_lines + down_lines[ 1: ] def JumpToItem( self, handle : int, command ): - node_index = handle // 1000000 - location_index = handle % 1000000 - node = self._nodes[ node_index ] + node_index = abs( handle ) // 1000000 + location_index = abs( handle ) % 1000000 + if handle >= 0: + node = self._down_nodes[ node_index ] + else: + node = self._up_nodes[ node_index ] file, line, column = node.ToLocation( location_index ) vimsupport.JumpToLocation( file, line, column, '', command ) - def ShouldResolveItem( self, handle : int ): - node_index = handle // 1000000 - return self._nodes[ node_index ]._references is None + def ShouldResolveItem( self, handle : int, direction : str ): + node_index = abs( handle ) // 1000000 + if direction == 'down': + node = self._down_nodes[ node_index ] + else: + node = self._up_nodes[ node_index ] + return node._references is None - def ResolveArguments( self, handle : int ): - node_index = handle // 1000000 + def ResolveArguments( self, handle : int, direction : str ): + node_index = abs( handle ) // 1000000 + if self._kind == 'call': + direction = 'outgoing' if direction == 'up' else 'incoming' + else: + direction = 'supertypes' if direction == 'up' else 'subtypes' + if handle >= 0: + node = self._down_nodes[ node_index ] + else: + node = self._up_nodes[ node_index ] return [ f'Resolve{ self._kind.title() }HierarchyItem', - self._nodes[ node_index ]._data, - self._direction + node._data, + direction ] diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 0bc990591f..150c3de8e4 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -112,19 +112,20 @@ def __init__( self, default_options = {} ): self._current_hierarchy = HierarchyTree() - def InitializeCurrentHierarchy( self, items, kind, direction ): - return self._current_hierarchy.SetRootNode( items, kind, direction ) + def InitializeCurrentHierarchy( self, items, kind ): + return self._current_hierarchy.SetRootNode( items, kind ) - def UpdateCurrentHierarchy( self, handle ): - items = self._ResolveHierarchyItem( handle ) - self._current_hierarchy.UpdateHierarchy( handle, items ) - return self._current_hierarchy.HierarchyToLines() + 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 - def _ResolveHierarchyItem( self, handle ): + def _ResolveHierarchyItem( self, handle : int, direction : str ): return SendCommandRequest( - self._current_hierarchy.ResolveArguments( handle ), + self._current_hierarchy.ResolveArguments( handle, direction ), '', self._user_options[ 'goto_buffer_command' ], extra_data = None, @@ -132,8 +133,8 @@ def _ResolveHierarchyItem( self, handle ): ) - def ShouldResolveItem( self, handle ): - return self._current_hierarchy.ShouldResolveItem( handle ) + def ShouldResolveItem( self, handle : int, direction : str ): + return self._current_hierarchy.ShouldResolveItem( handle, direction ) def ResetCurrentHierarchy( self ): From 729806cd4bac8426c54f95c287d59efad32bfac4 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 11 Feb 2024 15:56:54 +0100 Subject: [PATCH 04/34] Support changing the current hierarchy root node --- autoload/youcompleteme/hierarchy.vim | 26 +++++++------ python/ycm/hierarchy_tree.py | 57 ++++++++++++++++++---------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 7f34fc343c..ea61c57391 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -49,23 +49,21 @@ endfunction function! s:MenuFilter( winid, key ) if a:key == "\" - if s:lines_and_handles[ s:select - 1 ][ 1 ] <= 0 " TODO: switching root not impl - call popup_close( s:popup_id, [ s:select - 1, 'resolve_up' ] ) - endif + let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0 + call popup_close( s:popup_id, [ s:select - 1, 'resolve_up', will_change_root ] ) return 1 endif if a:key == "\" - if s:lines_and_handles[ s:select - 1 ][ 1 ] >= 0 " TODO: switching root not impl - call popup_close( s:popup_id, [ s:select - 1, 'resolve_down' ] ) - endif + let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] < 0 + call popup_close( s:popup_id, [ s:select - 1, 'resolve_down', will_change_root ] ) return 1 endif if a:key == "\" - call popup_close( s:popup_id, [ s:select - 1, 'cancel' ] ) + call popup_close( s:popup_id, [ s:select - 1, 'cancel', v:none ] ) return 1 endif if a:key == "\" - call popup_close( s:popup_id, [ s:select - 1, 'jump' ] ) + call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] ) return 1 endif if a:key == "\" @@ -93,9 +91,9 @@ function! s:MenuCallback( winid, result ) let operation = a:result[ 1 ] let selection = a:result[ 0 ] if operation == 'resolve_down' - call s:ResolveItem( selection, 'down' ) + call s:ResolveItem( selection, 'down', a:result[ 2 ] ) elseif operation == 'resolve_up' - call s:ResolveItem( selection, 'up' ) + call s:ResolveItem( selection, 'up', a:result[ 2 ] ) else if operation == 'jump' let handle = s:lines_and_handles[ selection ][ 1 ] @@ -119,7 +117,7 @@ function! s:SetupMenu() \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) endfunction -function! s:ResolveItem( choice, direction ) +function! s:ResolveItem( choice, direction, will_change_root ) let handle = s:lines_and_handles[ a:choice ][ 1 ] if py3eval( \ 'ycm_state.ShouldResolveItem( vimsupport.GetIntValue( "handle" ), vim.eval( "a:direction" ) )' ) @@ -127,8 +125,12 @@ function! s:ResolveItem( choice, direction ) \ 'ycm_state.UpdateCurrentHierarchy( ' . \ 'vimsupport.GetIntValue( "handle" ), ' . \ 'vim.eval( "a:direction" ) )' ) - let s:select += lines_and_handles_with_offset[ 1 ] let s:lines_and_handles = lines_and_handles_with_offset[ 0 ] + if a:will_change_root + let s:select = 1 + indexof( s:lines_and_handles, { i, v -> v[0][0] =~ "[-+]" } ) + else + let s:select += lines_and_handles_with_offset[ 1 ] + endif endif call s:SetupMenu() endfunction diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 74bf872949..80b5630361 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -55,17 +55,27 @@ def SetRootNode( self, items, kind : str ): def UpdateHierarchy( self, handle : int, items, direction : str ): current_index = abs( handle ) // 1000000 - 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 ) ) ) + 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 = [] else: - nodes[ current_index ]._references = [] + 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 ) def Reset( self ): @@ -81,7 +91,8 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): next_node = nodes[ i ] indent = 2 * next_node._distance_from_root if i == 0: - can_expand = self._down_nodes[ 0 ]._references is None or self._up_nodes[ 0 ]._references is None + can_expand = ( self._down_nodes[ 0 ]._references is None or + self._up_nodes[ 0 ]._references is None ) else: can_expand = next_node._references is None symbol = '+' if can_expand else '-' @@ -89,21 +100,22 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): kind = next_node._data[ 'kind' ] if use_down_nodes: partial_result.extend( [ - ( ' ' * indent + symbol + kind + ': ' + name + '\t' + + ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], + str( l[ 'line_num' ] ) + 3 * '\t' + l[ 'description' ], ( i * 1000000 + j ) ) for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) else: partial_result.extend( [ - ( ' ' * indent + symbol + kind + ': ' + name + '\t' + + ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + '\t' + l[ 'description' ], + str( l[ 'line_num' ] ) + 3 * '\t' + l[ 'description' ], ( i * 1000000 + j ) * -1 ) for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) if next_node._references: partial_result.extend( - self._HierarchyToLinesHelper( next_node._references, use_down_nodes ) ) + self._HierarchyToLinesHelper( + next_node._references, use_down_nodes ) ) return partial_result def HierarchyToLines( self ): @@ -126,11 +138,14 @@ def JumpToItem( self, handle : int, command ): def ShouldResolveItem( self, handle : int, direction : str ): node_index = abs( handle ) // 1000000 - if direction == 'down': - node = self._down_nodes[ node_index ] - else: - node = self._up_nodes[ node_index ] - return node._references is None + if ( ( handle >= 0 and direction == 'down' ) or + ( handle <= 0 and direction == 'up' ) ): + if direction == 'down': + node = self._down_nodes[ node_index ] + else: + node = self._up_nodes[ node_index ] + return node._references is None + return True def ResolveArguments( self, handle : int, direction : str ): From fcf89f04fedbb1e1834c4dcd34c48dc1fc8d8eef Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 11 Feb 2024 16:01:08 +0100 Subject: [PATCH 05/34] Add bindings for hierarchies --- autoload/youcompleteme.vim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 0e137506ee..2ccd2c4b09 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -1763,6 +1763,11 @@ endfunction silent! nnoremap (YCMToggleInlayHints) \ call ToggleInlayHints() +silent! nnoremap (YCMTypeHierarchy) + \ call youcompleteme#hierarchy#StartRequest( 'type' ) +silent! nnoremap (YCMCallHierarchy) + \ call youcompleteme#hierarchy#StartRequest( 'call' ) + " This is basic vim plugin boilerplate let &cpo = s:save_cpo unlet s:save_cpo From 65456b2db4ed066cfe95d942723b5c45ff24525f Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 11 Feb 2024 21:28:46 +0100 Subject: [PATCH 06/34] flake8 --- python/ycm/hierarchy_tree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 80b5630361..08953d425d 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -22,7 +22,7 @@ class HierarchyNode: def __init__( self, data, distance : int ): - self._references : Optional[List[int]] = None + self._references : Optional[ List[ int ] ] = None self._data = data self._distance_from_root = distance @@ -38,8 +38,8 @@ def ToLocation( self, subindex : int ): class HierarchyTree: def __init__( self ): - self._up_nodes : List[HierarchyNode] = [] - self._down_nodes : List[HierarchyNode] = [] + self._up_nodes : List[ HierarchyNode ] = [] + self._down_nodes : List[ HierarchyNode ] = [] self._kind : str = '' def SetRootNode( self, items, kind : str ): From 08353bc53784f12230102964aff9fb1933fad00c Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 May 2024 12:16:27 +0100 Subject: [PATCH 07/34] Fix error using mappings: mappings must end with --- autoload/youcompleteme.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 2ccd2c4b09..3ca046b20b 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -1764,9 +1764,9 @@ silent! nnoremap (YCMToggleInlayHints) \ call ToggleInlayHints() silent! nnoremap (YCMTypeHierarchy) - \ call youcompleteme#hierarchy#StartRequest( 'type' ) + \ call youcompleteme#hierarchy#StartRequest( 'type' ) silent! nnoremap (YCMCallHierarchy) - \ call youcompleteme#hierarchy#StartRequest( 'call' ) + \ call youcompleteme#hierarchy#StartRequest( 'call' ) " This is basic vim plugin boilerplate let &cpo = s:save_cpo From 0ce21e21e28c2f21ea96a52e0eec8edd252872ff Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sat, 11 May 2024 14:41:57 +0200 Subject: [PATCH 08/34] Avoid vim backtrace when no target description --- python/ycm/hierarchy_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 08953d425d..5aa36cb2b3 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -102,14 +102,14 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): partial_result.extend( [ ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + 3 * '\t' + l[ 'description' ], + str( l[ 'line_num' ] ) + 3 * '\t' + l.get( 'description', '' ), ( i * 1000000 + j ) ) for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) else: partial_result.extend( [ ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + 3 * '\t' + l[ 'description' ], + str( l[ 'line_num' ] ) + 3 * '\t' + l.get( 'description', '' ), ( i * 1000000 + j ) * -1 ) for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) if next_node._references: From f1937cf7a25078b91f30ef070a352ebc31588a5d Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 May 2024 16:06:44 +0100 Subject: [PATCH 09/34] Close the popup when non-handled key pressed This makes the popup disappear if you keep typing or enter inser mode or move the cursor etc. I found it jarring that previously it would just move the cursor behind the popup and such until you hit escape. Makes the popup more "modal" but without actually stopping you from continueing. Also: - Use simliar up/down keys as the symbol finder (c-p, c-k, c-n, c-j etc.) - SetupFoo -> SetUpFoo because the verb is "To set up" (rather than the noun "a setup". - purge barbaric 80+ character lines --- autoload/youcompleteme/hierarchy.vim | 43 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index ea61c57391..e00fc8f985 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -26,6 +26,11 @@ let s:lines_and_handles = v:none let s:select = -1 let s:kind = '' +let s:ingored_keys = [ + \ "\", + \ "\", + \ ] + function! youcompleteme#hierarchy#StartRequest( kind ) py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' @@ -43,30 +48,30 @@ function! youcompleteme#hierarchy#StartRequest( kind ) let s:lines_and_handles = lines_and_handles let s:kind = a:kind let s:select = 1 - call s:SetupMenu() + call s:SetUpMenu() endif endfunction function! s:MenuFilter( winid, key ) if a:key == "\" let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0 - call popup_close( s:popup_id, [ s:select - 1, 'resolve_up', will_change_root ] ) + call popup_close( + \ s:popup_id, + \ [ s:select - 1, 'resolve_up', will_change_root ] ) return 1 endif if a:key == "\" let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] < 0 - call popup_close( s:popup_id, [ s:select - 1, 'resolve_down', will_change_root ] ) - return 1 - endif - if a:key == "\" - call popup_close( s:popup_id, [ s:select - 1, 'cancel', v:none ] ) + call popup_close( + \ s:popup_id, + \ [ s:select - 1, 'resolve_down', will_change_root ] ) return 1 endif if a:key == "\" call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] ) return 1 endif - if a:key == "\" + if a:key == "\" || a:key == "\" || a:key == "\" || a:key == "k" let s:select -= 1 if s:select < 1 let s:select = 1 @@ -75,7 +80,7 @@ function! s:MenuFilter( winid, key ) \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) return 1 endif - if a:key == "\" + if a:key == "\" || a:key == "\" || a:key == "\" || a:key == "j" let s:select += 1 if s:select > len( s:lines_and_handles ) let s:select = len( s:lines_and_handles ) @@ -84,6 +89,14 @@ function! s:MenuFilter( winid, key ) \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) return 1 endif + if index( s:ingored_keys, a:key ) >= 0 + return 0 + endif + " Close the popup on any other key press + call popup_close( s:popup_id, [ s:select - 1, 'cancel', v:none ] ) + if a:key == "\" || a:key == "\" + return 1 + endif return 0 endfunction @@ -105,14 +118,15 @@ function! s:MenuCallback( winid, result ) endif endfunction -function! s:SetupMenu() +function! s:SetUpMenu() let menu_lines = [] for line_and_item in s:lines_and_handles call add( menu_lines, line_and_item[ 0 ] ) endfor let s:popup_id = popup_menu( menu_lines, #{ - \ filter: funcref( 's:MenuFilter' ), - \ callback: funcref( 's:MenuCallback' ) } ) + \ filter: funcref( 's:MenuFilter' ), + \ callback: funcref( 's:MenuCallback' ) + \ } ) call win_execute( s:popup_id, \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) endfunction @@ -127,10 +141,11 @@ function! s:ResolveItem( choice, direction, will_change_root ) \ 'vim.eval( "a:direction" ) )' ) let s:lines_and_handles = lines_and_handles_with_offset[ 0 ] if a:will_change_root - let s:select = 1 + indexof( s:lines_and_handles, { i, v -> v[0][0] =~ "[-+]" } ) + let s:select = 1 + indexof( s:lines_and_handles, + \ { i, v -> v[0][0] =~ "[-+]" } ) else let s:select += lines_and_handles_with_offset[ 1 ] endif endif - call s:SetupMenu() + call s:SetUpMenu() endfunction From c217e66c61506bcb98d68152f9bc574669f05205 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 May 2024 18:53:18 +0100 Subject: [PATCH 10/34] Basic popup aesthetics We try to be a bit consistent with the finder poppup, but implementation-wise this is simpler. The idea is that there are 3 columns, each having 1/3 of the popup width. We fix the width of the popup (like we do for the finder) and set the tabstop to 1/3 of the internal width (core_width). Then when displaying text, we truncate "columnns" according to that tabstop (to avoid mess). To do this, we pass structured data from the python layer to vimscript and construct the line text there. This will also help later when we add in the syntax highlight (text properties) like we have for the finder popup. --- autoload/youcompleteme/hierarchy.vim | 44 +++++++++++++++++++++++----- python/ycm/hierarchy_tree.py | 34 ++++++++++++++------- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index e00fc8f985..bc6eed241d 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -23,6 +23,9 @@ scriptencoding utf-8 let s:popup_id = -1 let s:lines_and_handles = v:none +" 1-based index of the selected item in the popup +" -1 means none set +" 0 means nothing, (Invalid) let s:select = -1 let s:kind = '' @@ -54,6 +57,8 @@ endfunction function! s:MenuFilter( winid, key ) if a:key == "\" + " ROot changes if we're showing super-tree of a sub-tree of the root + " (indeicated by the handle being positive) let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0 call popup_close( \ s:popup_id, @@ -61,6 +66,8 @@ function! s:MenuFilter( winid, key ) return 1 endif if a:key == "\" + " Root changes if we're showing sub-tree of a super-tree of the root + " (indeicated by the handle being negative) let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] < 0 call popup_close( \ s:popup_id, @@ -119,14 +126,32 @@ function! s:MenuCallback( winid, result ) endfunction function! s:SetUpMenu() - let menu_lines = [] - for line_and_item in s:lines_and_handles - call add( menu_lines, line_and_item[ 0 ] ) - endfor - let s:popup_id = popup_menu( menu_lines, #{ + let s:popup_id = popup_menu( [], #{ \ filter: funcref( 's:MenuFilter' ), - \ callback: funcref( 's:MenuCallback' ) + \ callback: funcref( 's:MenuCallback' ), + \ wrap: 0, + \ minwidth: &columns * 90/100, + \ maxwidth: &columns * 90/100, + \ maxheight: &lines * 75/100, + \ scrollbar: 1, + \ padding: [ 0, 0, 0, 0 ], \ } ) + let menu_lines = [] + let popup_width = popup_getpos( s:popup_id ).core_width + let tabstop = popup_width / 3 + for [ item, handle ] in s:lines_and_handles + let name = repeat( ' ', item.indent ) .. item.icon .. item.symbol + " TODO: Explain (understand) why it's -2 not -1 for the space (padding?) + let line = name[ : tabstop -2 ] + \ . "\t" + \ .. item.filepath[ : tabstop -2 ] + \ . "\t" + \ .. item.description[ : tabstop - 2 ] + call add( menu_lines, line ) + endfor + call popup_settext( s:popup_id, menu_lines ) + call win_execute( s:popup_id, + \ 'setlocal tabstop=' . tabstop ) call win_execute( s:popup_id, \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) endfunction @@ -141,8 +166,13 @@ function! s:ResolveItem( choice, direction, will_change_root ) \ 'vim.eval( "a:direction" ) )' ) let s:lines_and_handles = lines_and_handles_with_offset[ 0 ] if a:will_change_root + " When re-rooting the tree, put the cursor on the new "root" item, as this + " helps with orientation. This behaviour is consistent with an expansion + " where we _don't_ re-root the tree, so feels more natural than anything + " else. + " The new root is the element with indent of 0. let s:select = 1 + indexof( s:lines_and_handles, - \ { i, v -> v[0][0] =~ "[-+]" } ) + \ { i, v -> v[0].indent == 0 } ) else let s:select += lines_and_handles_with_offset[ 1 ] endif diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 5aa36cb2b3..6d392320d2 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -100,18 +100,32 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): kind = next_node._data[ 'kind' ] if use_down_nodes: partial_result.extend( [ - ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + - os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + 3 * '\t' + l.get( 'description', '' ), - ( i * 1000000 + j ) ) - for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) + ( + { + 'indent': indent, + 'icon': symbol, + 'symbol': kind + ': ' + name, + 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), + 'description': l.get( 'description', '' ), + }, + i * 1000000 + j + ) + for j, l in enumerate( next_node._data[ 'locations' ] ) + ] ) else: partial_result.extend( [ - ( ' ' * indent + symbol + kind + ': ' + name + 3 * '\t' + - os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + - str( l[ 'line_num' ] ) + 3 * '\t' + l.get( 'description', '' ), - ( i * 1000000 + j ) * -1 ) - for j, l in enumerate( next_node._data[ 'locations' ] ) ] ) + ( + { + 'indent': indent, + 'icon': symbol, + 'symbol': kind + ': ' + name, + 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), + 'description': l.get( 'description', '' ), + }, + ( i * 1000000 + j ) * -1 + ) + for j, l in enumerate( next_node._data[ 'locations' ] ) + ] ) if next_node._references: partial_result.extend( self._HierarchyToLinesHelper( From 72026b3cdfa3634e1339e3a4cece4a382f77fd8b Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Sat, 11 May 2024 19:03:28 +0100 Subject: [PATCH 11/34] Tidy handles: wrap in some little functions --- python/ycm/hierarchy_tree.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 6d392320d2..d9af054370 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -35,6 +35,16 @@ def ToLocation( self, subindex : int ): return file, line, column +MAX_HANDLES_PER_INDEX = 1000000 + +def handle_to_index( handle : int ): + return abs( handle ) // MAX_HANDLES_PER_INDEX + +def handle_to_location_index( handle : int ): + return abs( handle ) % MAX_HANDLES_PER_INDEX + +def make_handle( index : int, location_index : int ): + return index * MAX_HANDLES_PER_INDEX + location_index class HierarchyTree: def __init__( self ): @@ -54,7 +64,7 @@ def SetRootNode( self, items, kind : str ): def UpdateHierarchy( self, handle : int, items, direction : str ): - current_index = abs( handle ) // 1000000 + 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 @@ -87,10 +97,10 @@ def Reset( self ): def _HierarchyToLinesHelper( self, refs, use_down_nodes ): partial_result = [] nodes = self._down_nodes if use_down_nodes else self._up_nodes - for i in refs: - next_node = nodes[ i ] + for index in refs: + next_node = nodes[ index ] indent = 2 * next_node._distance_from_root - if i == 0: + if index == 0: can_expand = ( self._down_nodes[ 0 ]._references is None or self._up_nodes[ 0 ]._references is None ) else: @@ -108,9 +118,9 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), 'description': l.get( 'description', '' ), }, - i * 1000000 + j + make_handle( index, location_index ) ) - for j, l in enumerate( next_node._data[ 'locations' ] ) + for location_index, l in enumerate( next_node._data[ 'locations' ] ) ] ) else: partial_result.extend( [ @@ -122,9 +132,9 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), 'description': l.get( 'description', '' ), }, - ( i * 1000000 + j ) * -1 + make_handle( index, location_index ) * -1 ) - for j, l in enumerate( next_node._data[ 'locations' ] ) + for location_index, l in enumerate( next_node._data[ 'locations' ] ) ] ) if next_node._references: partial_result.extend( @@ -140,8 +150,8 @@ def HierarchyToLines( self ): def JumpToItem( self, handle : int, command ): - node_index = abs( handle ) // 1000000 - location_index = abs( handle ) % 1000000 + node_index = handle_to_index( handle ) + location_index = handle_to_location_index( handle ) if handle >= 0: node = self._down_nodes[ node_index ] else: @@ -151,7 +161,7 @@ def JumpToItem( self, handle : int, command ): def ShouldResolveItem( self, handle : int, direction : str ): - node_index = abs( handle ) // 1000000 + node_index = handle_to_index( handle ) if ( ( handle >= 0 and direction == 'down' ) or ( handle <= 0 and direction == 'up' ) ): if direction == 'down': @@ -163,7 +173,7 @@ def ShouldResolveItem( self, handle : int, direction : str ): def ResolveArguments( self, handle : int, direction : str ): - node_index = abs( handle ) // 1000000 + node_index = handle_to_index( handle ) if self._kind == 'call': direction = 'outgoing' if direction == 'up' else 'incoming' else: From 6ca4659090498d4e0bfc4d2ec902f327c3507e13 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 13 May 2024 21:24:52 +0100 Subject: [PATCH 12/34] Style the hierarchy popup like the finder popup This basically involves moving the properties mapping to its own place, and creating text properties to overlap the various parts of the popup columns. Style matches, and feels correct. Fiddly part is that we have to (for some reason) set cursorline every time we move the cursor, but ain't nobody gonna be able to explain why, and why that only necessary after launching the finder popup window once. Some wierd vim bug is my guess. --- autoload/youcompleteme/finder.vim | 48 +------------------ autoload/youcompleteme/hierarchy.vim | 70 +++++++++++++++++++++++---- autoload/youcompleteme/symbol.vim | 71 ++++++++++++++++++++++++++++ python/ycm/hierarchy_tree.py | 12 +++-- 4 files changed, 143 insertions(+), 58 deletions(-) create mode 100644 autoload/youcompleteme/symbol.vim diff --git a/autoload/youcompleteme/finder.vim b/autoload/youcompleteme/finder.vim index bab8c6e24d..d492d1f9dd 100644 --- a/autoload/youcompleteme/finder.vim +++ b/autoload/youcompleteme/finder.vim @@ -113,35 +113,6 @@ scriptencoding utf-8 " The other functions are utility for the most part and handle things like " TextChangedI event, starting/stopping drawing the spinner and such. -let s:highlight_group_for_symbol_kind = { - \ 'Array': 'Identifier', - \ 'Boolean': 'Boolean', - \ 'Class': 'Structure', - \ 'Constant': 'Constant', - \ 'Constructor': 'Function', - \ 'Enum': 'Structure', - \ 'EnumMember': 'Identifier', - \ 'Event': 'Identifier', - \ 'Field': 'Identifier', - \ 'Function': 'Function', - \ 'Interface': 'Structure', - \ 'Key': 'Identifier', - \ 'Method': 'Function', - \ 'Module': 'Include', - \ 'Namespace': 'Type', - \ 'Null': 'Keyword', - \ 'Number': 'Number', - \ 'Object': 'Structure', - \ 'Operator': 'Operator', - \ 'Package': 'Include', - \ 'Property': 'Identifier', - \ 'String': 'String', - \ 'Struct': 'Structure', - \ 'TypeParameter': 'Typedef', - \ 'Variable': 'Identifier', - \ } -let s:initialized_text_properties = v:false - let s:icon_spinner = [ '/', '-', '\', '|', '/', '-', '\', '|' ] let s:icon_done = 'X' let s:spinner_delay = 100 @@ -156,18 +127,7 @@ function! youcompleteme#finder#FindSymbol( scope ) abort return endif - if !s:initialized_text_properties - call prop_type_add( 'YCM-symbol-Normal', { 'highlight': 'Normal' } ) - for k in keys( s:highlight_group_for_symbol_kind ) - call prop_type_add( - \ 'YCM-symbol-' . k, - \ { 'highlight': s:highlight_group_for_symbol_kind[ k ] } ) - endfor - call prop_type_add( 'YCM-symbol-file', { 'highlight': 'Comment' } ) - call prop_type_add( 'YCM-symbol-filetype', { 'highlight': 'Special' } ) - call prop_type_add( 'YCM-symbol-line-num', { 'highlight': 'Number' } ) - let s:initialized_text_properties = v:true - endif + call youcompleteme#symbol#InitSymbolProperties() let s:find_symbol_status = { \ 'selected': -1, @@ -470,11 +430,7 @@ function! s:RedrawFinderPopup() abort let kind = result[ 'extra_data' ][ 'kind' ] let name = result[ 'extra_data' ][ 'name' ] let desc = kind .. ': ' .. name - if s:highlight_group_for_symbol_kind->has_key( kind ) - let prop = 'YCM-symbol-' . kind - else - let prop = 'YCM-symbol-Normal' - endif + let prop = youcompleteme#symbol#GetPropForSymbolKind( kind ) let props = [ \ { 'col': 1, \ 'length': len( kind ) + 2, diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index bc6eed241d..dd2e4e279d 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -35,6 +35,7 @@ let s:ingored_keys = [ \ ] function! youcompleteme#hierarchy#StartRequest( kind ) + call youcompleteme#symbol#InitSymbolProperties() py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' let lines_and_handles = py3eval( @@ -85,6 +86,8 @@ function! s:MenuFilter( winid, key ) endif call win_execute( s:popup_id, \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + call win_execute( s:popup_id, + \ 'set cursorline cursorlineopt&' ) return 1 endif if a:key == "\" || a:key == "\" || a:key == "\" || a:key == "j" @@ -94,6 +97,8 @@ function! s:MenuFilter( winid, key ) endif call win_execute( s:popup_id, \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + call win_execute( s:popup_id, + \ 'set cursorline cursorlineopt&' ) return 1 endif if index( s:ingored_keys, a:key ) >= 0 @@ -126,7 +131,7 @@ function! s:MenuCallback( winid, result ) endfunction function! s:SetUpMenu() - let s:popup_id = popup_menu( [], #{ + let opts = #{ \ filter: funcref( 's:MenuFilter' ), \ callback: funcref( 's:MenuCallback' ), \ wrap: 0, @@ -135,25 +140,74 @@ function! s:SetUpMenu() \ maxheight: &lines * 75/100, \ scrollbar: 1, \ padding: [ 0, 0, 0, 0 ], - \ } ) + \ highlight: 'Normal', + \ border: [], + \ } + if &ambiwidth ==# 'single' && &encoding ==? 'utf-8' + let opts[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '┛', '╰' ] + endif + + let s:popup_id = popup_create( [], opts ) let menu_lines = [] let popup_width = popup_getpos( s:popup_id ).core_width let tabstop = popup_width / 3 for [ item, handle ] in s:lines_and_handles - let name = repeat( ' ', item.indent ) .. item.icon .. item.symbol + let indent = repeat( ' ', item.indent ) + let name = indent + \ .. item.icon + \ .. item.kind + \ .. ': ' .. item.symbol + let trunc_name = name[ : tabstop - 2 ] + let props = [] + let name_pfx_len = len( indent ) + len( item.icon ) + len( item.kind ) + 2 + if len( trunc_name ) > name_pfx_len + let props += [ + \ { + \ 'col': name_pfx_len + 1, + \ 'length': len( trunc_name ) - name_pfx_len, + \ 'type': youcompleteme#symbol#GetPropForSymbolKind( item.kind ), + \ } + \ ] + endif + + let file_name = item.filepath .. ':' .. item.line_num + let trunc_path = file_name[ : tabstop - 2 ] + if len(trunc_path) > 0 + let props += [ + \ { + \ 'col': len(trunc_name) + 2, + \ 'length': min( [ len(trunc_path), len( item.filepath ) ] ), + \ 'type': 'YCM-symbol-file' + \ } + \ ] + if len(trunc_path) > len(item.filepath) + 1 + let props += [ + \ { + \ 'col': len(trunc_name) + 2 + len(item.filepath) + 1, + \ 'length': min( [ len(trunc_path), len( item.line_num ) ] ), + \ 'type': 'YCM-symbol-line-num' + \ } + \ ] + endif + endif + + let trunc_desc = item.description[ : tabstop - 2 ] + " TODO: Explain (understand) why it's -2 not -1 for the space (padding?) - let line = name[ : tabstop -2 ] + let line = trunc_name \ . "\t" - \ .. item.filepath[ : tabstop -2 ] + \ .. trunc_path \ . "\t" - \ .. item.description[ : tabstop - 2 ] - call add( menu_lines, line ) + \ .. trunc_desc + call add( menu_lines, { 'text': line, 'props': props } ) endfor - call popup_settext( s:popup_id, menu_lines ) call win_execute( s:popup_id, \ 'setlocal tabstop=' . tabstop ) + call popup_settext( s:popup_id, menu_lines ) call win_execute( s:popup_id, \ 'call cursor( [' . string( s:select ) . ', 1 ] )' ) + call win_execute( s:popup_id, + \ 'set cursorline cursorlineopt&' ) endfunction function! s:ResolveItem( choice, direction, will_change_root ) diff --git a/autoload/youcompleteme/symbol.vim b/autoload/youcompleteme/symbol.vim new file mode 100644 index 0000000000..f34120cdbd --- /dev/null +++ b/autoload/youcompleteme/symbol.vim @@ -0,0 +1,71 @@ +" Copyright (C) 2024 YouCompleteMe contributors +" +" This file is part of YouCompleteMe. +" +" YouCompleteMe is free software: you can redistribute it and/or modify +" it under the terms of the GNU General Public License as published by +" the Free Software Foundation, either version 3 of the License, or +" (at your option) any later version. +" +" YouCompleteMe is distributed in the hope that it will be useful, +" but WITHOUT ANY WARRANTY; without even the implied warranty of +" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +" GNU General Public License for more details. +" +" You should have received a copy of the GNU General Public License +" along with YouCompleteMe. If not, see . + + +let s:highlight_group_for_symbol_kind = { + \ 'Array': 'Identifier', + \ 'Boolean': 'Boolean', + \ 'Class': 'Structure', + \ 'Constant': 'Constant', + \ 'Constructor': 'Function', + \ 'Enum': 'Structure', + \ 'EnumMember': 'Identifier', + \ 'Event': 'Identifier', + \ 'Field': 'Identifier', + \ 'Function': 'Function', + \ 'Interface': 'Structure', + \ 'Key': 'Identifier', + \ 'Method': 'Function', + \ 'Module': 'Include', + \ 'Namespace': 'Type', + \ 'Null': 'Keyword', + \ 'Number': 'Number', + \ 'Object': 'Structure', + \ 'Operator': 'Operator', + \ 'Package': 'Include', + \ 'Property': 'Identifier', + \ 'String': 'String', + \ 'Struct': 'Structure', + \ 'TypeParameter': 'Typedef', + \ 'Variable': 'Identifier', + \ } +let s:initialized_text_properties = v:false + +function! youcompleteme#symbol#InitSymbolProperties() abort + if !s:initialized_text_properties + call prop_type_add( 'YCM-symbol-Normal', { 'highlight': 'Normal' } ) + for k in keys( s:highlight_group_for_symbol_kind ) + call prop_type_add( + \ 'YCM-symbol-' . k, + \ { 'highlight': s:highlight_group_for_symbol_kind[ k ] } ) + endfor + call prop_type_add( 'YCM-symbol-file', { 'highlight': 'Comment' } ) + call prop_type_add( 'YCM-symbol-filetype', { 'highlight': 'Special' } ) + call prop_type_add( 'YCM-symbol-line-num', { 'highlight': 'Number' } ) + let s:initialized_text_properties = v:true + endif +endfunction + +function! youcompleteme#symbol#GetPropForSymbolKind( kind ) abort + if s:highlight_group_for_symbol_kind->has_key( a:kind ) + return 'YCM-symbol-' . a:kind + endif + + return 'YCM-symbol-Normal' +endfunction + + diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index d9af054370..e8316a42ee 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -114,8 +114,10 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): { 'indent': indent, 'icon': symbol, - 'symbol': kind + ': ' + name, - 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), + 'symbol': name, + 'kind': kind, + 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ], + 'line_num': str( l[ 'line_num' ] ), 'description': l.get( 'description', '' ), }, make_handle( index, location_index ) @@ -128,8 +130,10 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): { 'indent': indent, 'icon': symbol, - 'symbol': kind + ': ' + name, - 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ] + ':' + str( l[ 'line_num' ] ), + 'symbol': name, + 'kind': kind, + 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ], + 'line_num': str( l[ 'line_num' ] ), 'description': l.get( 'description', '' ), }, make_handle( index, location_index ) * -1 From ea6cb61f551209fd6a9602f95926035c18f4cf68 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 13 May 2024 21:35:24 +0100 Subject: [PATCH 13/34] Trim leading/trailing whitespace from description This makes the columns seem less broken, particularly for leading whitespace. This is particularly problematic due to our use of tabstop: if the leading whitespace was a tab, it would go nuts. It would still go nuts for any other literal tab in the description. Perhaps we should fix that too. --- python/ycm/hierarchy_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index e8316a42ee..5f9f754971 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -118,7 +118,7 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): 'kind': kind, 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ], 'line_num': str( l[ 'line_num' ] ), - 'description': l.get( 'description', '' ), + 'description': l.get( 'description', '' ).strip(), }, make_handle( index, location_index ) ) @@ -134,7 +134,7 @@ def _HierarchyToLinesHelper( self, refs, use_down_nodes ): 'kind': kind, 'filepath': os.path.split( l[ 'filepath' ] )[ 1 ], 'line_num': str( l[ 'line_num' ] ), - 'description': l.get( 'description', '' ), + 'description': l.get( 'description', '' ).strip(), }, make_handle( index, location_index ) * -1 ) From fa73852d4b122b7a8431fbab67c105822ff762e4 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 13 May 2024 21:37:04 +0100 Subject: [PATCH 14/34] Explain offsets --- autoload/youcompleteme/hierarchy.vim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index dd2e4e279d..79c74f7536 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -157,6 +157,9 @@ function! s:SetUpMenu() \ .. item.icon \ .. item.kind \ .. ': ' .. item.symbol + " -2 because: + " 0-based index + " 1 for the tab character let trunc_name = name[ : tabstop - 2 ] let props = [] let name_pfx_len = len( indent ) + len( item.icon ) + len( item.kind ) + 2 @@ -193,7 +196,6 @@ function! s:SetUpMenu() let trunc_desc = item.description[ : tabstop - 2 ] - " TODO: Explain (understand) why it's -2 not -1 for the space (padding?) let line = trunc_name \ . "\t" \ .. trunc_path From 3376c05128d7ffba490e8c07e34a88ac047d554e Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 13 May 2024 21:56:19 +0100 Subject: [PATCH 15/34] Use correct bottom-right border character as we don't actually allow resizing --- autoload/youcompleteme/hierarchy.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 79c74f7536..8daea18ce8 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -144,7 +144,7 @@ function! s:SetUpMenu() \ border: [], \ } if &ambiwidth ==# 'single' && &encoding ==? 'utf-8' - let opts[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '┛', '╰' ] + let opts[ 'borderchars' ] = [ '─', '│', '─', '│', '╭', '╮', '╯', '╰' ] endif let s:popup_id = popup_create( [], opts ) From 2a687520e29871b54772707a8213c2a71c379cb3 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Mon, 13 May 2024 22:10:27 +0100 Subject: [PATCH 16/34] Log server exceptoins --- python/ycm/client/base_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index 7c47fe32d0..d2b22a957c 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -308,6 +308,7 @@ def _BuildUri( handler ): def MakeServerException( data ): + _logger.debug( 'Server exception: %s', data ) if data[ 'exception' ][ 'TYPE' ] == UnknownExtraConf.__name__: return UnknownExtraConf( data[ 'exception' ][ 'extra_conf_file' ] ) From 9d8d267ab6e6e3833729f23a8364a028ef556fd5 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 14 May 2024 22:06:13 +0200 Subject: [PATCH 17/34] flake8 --- python/ycm/hierarchy_tree.py | 4 ++++ python/ycm/tests/command_test.py | 15 ++++++++++----- test/hover.test.vim | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 5f9f754971..b3c3455622 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -37,15 +37,19 @@ def ToLocation( self, subindex : int ): MAX_HANDLES_PER_INDEX = 1000000 + def handle_to_index( handle : int ): return abs( handle ) // MAX_HANDLES_PER_INDEX + def handle_to_location_index( handle : int ): return abs( handle ) % MAX_HANDLES_PER_INDEX + def make_handle( index : int, location_index : int ): return index * MAX_HANDLES_PER_INDEX + location_index + class HierarchyTree: def __init__( self ): self._up_nodes : List[ HierarchyNode ] = [] diff --git a/python/ycm/tests/command_test.py b/python/ycm/tests/command_test.py index 89b4f350f9..d501371a7b 100644 --- a/python/ycm/tests/command_test.py +++ b/python/ycm/tests/command_test.py @@ -47,7 +47,8 @@ def test_SendCommandRequest_ExtraConfVimData_Works( self, ycm ): 'extra_conf_data': has_entries( { 'tempname()': '_TEMP_FILE_' } ), - } ) + } ), + False ) ) @@ -71,7 +72,8 @@ def test_SendCommandRequest_ExtraConfData_UndefinedValue( self, ycm ): 'tab_size': 2, 'insert_spaces': True, } ) - } ) + } ), + False ) ) @@ -102,7 +104,8 @@ def test_SendCommandRequest_BuildRange_NoVisualMarks( self, ycm, *args ): 'column_num': 12 } } - } + }, + False ) @@ -135,7 +138,8 @@ def test_SendCommandRequest_BuildRange_VisualMarks( self, ycm, *args ): 'column_num': 9 } } - } + }, + False ) @@ -153,7 +157,8 @@ def test_SendCommandRequest_IgnoreFileTypeOption( self, ycm, *args ): 'tab_size': 2, 'insert_spaces': True }, - } + }, + False ) with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request: diff --git a/test/hover.test.vim b/test/hover.test.vim index f19df03db3..e430980540 100644 --- a/test/hover.test.vim +++ b/test/hover.test.vim @@ -71,7 +71,7 @@ let s:cpp_lifetime = { \ '', \ 'Type: char', \ 'Offset: 16 bytes', - \ 'Size: 1 byte (+7 bytes padding)', + \ 'Size: 1 byte (+7 bytes padding), alignment 1 byte', \ 'nobody will live > 128 years', \ '', \ '// In PointInTime', From 6c3832d11f37d3162eddfe63aee9a1004829102e Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 17 May 2024 23:12:29 +0100 Subject: [PATCH 18/34] Avoid nasty crashes in othervim --- autoload/youcompleteme/hierarchy.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 8daea18ce8..6fa1866daf 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -22,7 +22,7 @@ set cpoptions&vim scriptencoding utf-8 let s:popup_id = -1 -let s:lines_and_handles = v:none +let s:lines_and_handles = v:null " 1-based index of the selected item in the popup " -1 means none set " 0 means nothing, (Invalid) @@ -35,6 +35,11 @@ let s:ingored_keys = [ \ ] function! youcompleteme#hierarchy#StartRequest( kind ) + if !py3eval( 'vimsupport.VimSupportsPopupWindows()' ) + echo 'Sorry, this feature is not supported in your editor' + return + endif + call youcompleteme#symbol#InitSymbolProperties() py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' From 3702f468d360018701a764febb2b806a308776ee Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 17 May 2024 23:13:48 +0100 Subject: [PATCH 19/34] Update README for type hierarchy; graduate some features --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index de59c43987..af2ce861b5 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Contents - [Diagnostic Display](#diagnostic-display) - [Diagnostic Highlighting Groups](#diagnostic-highlighting-groups) - [Symbol Search](#symbol-search) + - [Type/Call Hierarchy](#typecall-hierarchy) - [Commands](#commands) - [YcmCompleter subcommands](#ycmcompleter-subcommands) - [GoTo Commands](#goto-commands) @@ -677,6 +678,8 @@ Quick Feature Summary * Code formatting (`Format`) * Semantic highlighting * Inlay hints +* Type hierarchy +* Call hierarchy ### C♯ @@ -720,6 +723,7 @@ Quick Feature Summary * Type information for identifiers (`GetType`) * Code formatting (`Format`) * Management of `gopls` server instance +* Call hierarchy ### JavaScript and TypeScript @@ -759,6 +763,7 @@ Quick Feature Summary * Management of `rust-analyzer` server instance * Semantic highlighting * Inlay hints +* Call hierarchy ### Java @@ -782,6 +787,8 @@ Quick Feature Summary * Execute custom server command (`ExecuteCommand `) * Management of `jdt.ls` server instance * Semantic highlighting +* Type hierarchy +* Call hierarchy User Guide ---------- @@ -913,10 +920,6 @@ Ctrl-l is not a suggestion, just an example. ### Semantic highlighting -**NOTE**: This feature is highly experimental and offered in the hope that it is -useful. It shall not be considered stable; if you find issues with it, feel free -to report them, however. - Semantic highlighting is the process where the buffer text is coloured according to the underlying semantic type of the word, rather than classic syntax highlighting based on regular expressions. This can be powerful additional data @@ -1883,6 +1886,61 @@ so you can use window commands `...` for example. for that, or use a window command (e.g. `j`) or the mouse to leave the prompt buffer window. +### Type/Call Hierarchy + +***This feature requires Vim and is not supported in Neovim*** + +**NOTE**: This feature is highly experimental and offered in the hope that it is +useful. Please help us by reporting issues and offering feedback. + +YCM provides a way to view and navigate hierarchies. The following hierarchies +are supported: + +* Type hierachy `(YCMTypeHierarchy)`: Display subtypes and supertypes + of the symbol under cursor. Expand down to subtypes and up to supertypes. +* Call hierarchy `(YCMCallHierarchy)`: Display callees and callers of + the symbol under cursor. Expand down to callers and up to callees. + +Take a look at this [![asciicast](https://asciinema.org/a/659925.svg)](https://asciinema.org/a/659925) +for brief demo. + +Hierarchy UI can be initiated by mapping something to the indicated plug +mappings, for example: + +```viml +nmap yth (YCMTypeHierarchy) +nmap ych (YCMCallHierarchy) +``` + +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 +expanded to the right. Expand the tree "down" with ` and "up" with +``. + +The "root" of the tree can be re-focused to the selected item with +`` and further `` will show the parents of the selected item. This +can take a little getting used to, but it's particularly important with multiple +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 the hierarchy is displayed, the following keys are intercepted: + +* ``: Drill into the hierarchy at the selected item: expand and show + children of the selected item. +* ``: Show parents of the selected item. When applied to sub-types, this + will re-root the tree at that type, so that all parent types (are displayed). + Similar for callers. +* ``: Jump to the symbol currently selected. +* ``, ``, ``, `j`: Select the next item +* ``, ``, ``, `k`; Select the previous 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 +this way round is more intuitive because this is the typical way that call +stacks are displayed (with the current function at the top, and its callers +below). + Commands -------- @@ -2102,6 +2160,9 @@ Supported in filetypes: `c, cpp, objc, objcpp, cuda, go, java, rust` #### The `GoToCallers` and `GoToCallees` subcommands +Note: A much more powerful call and type hierarchy can be viewd interactively. +See [interactive type and call hierarchy](#interactive-type-and-call-hierarchy). + Populate the quickfix list with the callers, or callees respectively, of the function associated with the current cursor position. The semantics of this differ depending on the filetype and language server. @@ -3738,9 +3799,7 @@ let g:ycm_language_server = [] ### The `g:ycm_disable_signature_help` option This option allows you to disable all signature help for all completion engines. -There is no way to disable it per-completer. This option is _reserved_, meaning -that while signature help support remains experimental, its values and meaning -may change and it may be removed in a future version. +There is no way to disable it per-completer. Default: `0` From 489dcb158395d3084843af846f86e73527d3cc71 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 9 Jun 2024 23:37:48 +0200 Subject: [PATCH 20/34] Avoid skip_post_command_action hack when making Hierarchy request --- autoload/youcompleteme/hierarchy.vim | 10 ++++---- python/ycm/youcompleteme.py | 35 +++++++++++++++------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 6fa1866daf..b2574ea1bc 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -44,13 +44,13 @@ function! youcompleteme#hierarchy#StartRequest( kind ) py3 ycm_state.ResetCurrentHierarchy() if a:kind == 'call' let lines_and_handles = py3eval( - \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . - \ '[ "CallHierarchy" ], "", False, 0, 0 ), ' . + \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequestAsync( ' . + \ '[ "CallHierarchy" ] ), ' . \ 'vim.eval( "a:kind" ) )' ) else let lines_and_handles = py3eval( - \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' . - \ '[ "TypeHierarchy" ], "", False, 0, 0 ), ' . + \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequestAsync( ' . + \ '[ "TypeHierarchy" ] ), ' . \ 'vim.eval( "a:kind" ) )' ) endif if len( lines_and_handles ) @@ -63,7 +63,7 @@ endfunction function! s:MenuFilter( winid, key ) if a:key == "\" - " 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) let will_change_root = s:lines_and_handles[ s:select - 1 ][ 1 ] > 0 call popup_close( diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 150c3de8e4..2cbbf22e5c 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -112,7 +112,9 @@ def __init__( self, default_options = {} ): self._current_hierarchy = HierarchyTree() - def InitializeCurrentHierarchy( self, items, kind ): + def InitializeCurrentHierarchy( self, request_id, kind ): + items = self.GetCommandRequest( request_id ).Response() + self.FlushCommandRequest( request_id ) return self._current_hierarchy.SetRootNode( items, kind ) @@ -124,13 +126,13 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): def _ResolveHierarchyItem( self, handle : int, direction : str ): - return SendCommandRequest( + request_id = self.SendCommandRequestAsync( self._current_hierarchy.ResolveArguments( handle, direction ), - '', - self._user_options[ 'goto_buffer_command' ], - extra_data = None, - skip_post_command_action = True + silent = False ) + response = self.GetCommandRequest( request_id ).Response() + self.FlushCommandRequest( request_id ) + return response def ShouldResolveItem( self, handle : int, direction : str ): @@ -424,12 +426,13 @@ def _GetCommandRequestArguments( self, final_arguments = [] for argument in arguments: - if argument.startswith( 'ft=' ): - extra_data[ 'completer_target' ] = argument[ 3: ] - continue - elif argument.startswith( '--bufnr=' ): - extra_data[ 'bufnr' ] = int( argument[ len( '--bufnr=' ): ] ) - continue + if isinstance( argument, str ): + if argument.startswith( 'ft=' ): + extra_data[ 'completer_target' ] = argument[ 3: ] + continue + elif argument.startswith( '--bufnr=' ): + extra_data[ 'bufnr' ] = int( argument[ len( '--bufnr=' ): ] ) + continue final_arguments.append( argument ) @@ -456,8 +459,7 @@ def SendCommandRequest( self, final_arguments, modifiers, self._user_options[ 'goto_buffer_command' ], - extra_data, - 'Hierarchy' in arguments[ 0 ] ) + extra_data ) def GetCommandResponse( self, arguments ): @@ -469,7 +471,7 @@ def GetCommandResponse( self, arguments ): return GetCommandResponse( final_arguments, extra_data ) - def SendCommandRequestAsync( self, arguments ): + def SendCommandRequestAsync( self, arguments, silent = True ): final_arguments, extra_data = self._GetCommandRequestArguments( arguments, False, @@ -480,7 +482,8 @@ def SendCommandRequestAsync( self, arguments ): self._next_command_request_id += 1 self._command_requests[ request_id ] = SendCommandRequestAsync( final_arguments, - extra_data ) + extra_data, + silent ) return request_id From 8888bbea1ea11b546f89f56b229341580a2170ae Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Mon, 10 Jun 2024 00:23:51 +0200 Subject: [PATCH 21/34] 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. --- autoload/youcompleteme/hierarchy.vim | 4 +-- python/ycm/client/base_request.py | 17 +++++++++- python/ycm/client/command_request.py | 23 +++++++++++--- python/ycm/hierarchy_tree.py | 46 ++++++++++++++++------------ python/ycm/vimsupport.py | 20 ++++++------ python/ycm/youcompleteme.py | 28 +++++++++++++---- 6 files changed, 94 insertions(+), 44 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index b2574ea1bc..58416ccb68 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -64,7 +64,7 @@ endfunction function! s:MenuFilter( winid, key ) if a:key == "\" " 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 == "\" " 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, diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index d2b22a957c..1ef5de4dfa 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -167,7 +167,7 @@ def _MakeRequest( data, handler, method, timeout, payload ): 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() diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index b34c341886..6372fe36fc 100644 --- a/python/ycm/client/command_request.py +++ b/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 . -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 @@ def __init__( self, arguments, extra_data = None, silent = False ): 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 @@ def _HandleDetailedInfoResponse( self, modifiers ): 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 diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index b3c3455622..cb4fac555b 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -69,27 +69,16 @@ def SetRootNode( self, items, kind : str ): 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 @@ def ResolveArguments( self, handle : int, direction : str ): 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' ) ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 826a5b59fc..f84cfc9201 100644 --- a/python/ycm/vimsupport.py +++ b/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 }' ) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 2cbbf22e5c..3c3622c4f8 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -119,10 +119,22 @@ def InitializeCurrentHierarchy( self, request_id, kind ): 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 @@ def GetCommandResponse( self, arguments ): 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 @@ def SendCommandRequestAsync( self, arguments, silent = True ): self._command_requests[ request_id ] = SendCommandRequestAsync( final_arguments, extra_data, - silent ) + silent, + location = location ) return request_id From 546b05048b926f5f9deb4e07fdf0b1f6be19aed6 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Mon, 10 Jun 2024 20:36:01 +0200 Subject: [PATCH 22/34] Simplify getting a raw response from a completer command --- autoload/youcompleteme/hierarchy.vim | 9 +++++---- python/ycm/client/command_request.py | 8 ++++++++ python/ycm/youcompleteme.py | 16 ++++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 58416ccb68..15eec32009 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -42,15 +42,16 @@ function! youcompleteme#hierarchy#StartRequest( kind ) call youcompleteme#symbol#InitSymbolProperties() py3 ycm_state.ResetCurrentHierarchy() + py3 from ycm.client.command_request import GetRawCommandResponse if a:kind == 'call' let lines_and_handles = py3eval( - \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequestAsync( ' . - \ '[ "CallHierarchy" ] ), ' . + \ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' . + \ '[ "CallHierarchy" ], False ), ' . \ 'vim.eval( "a:kind" ) )' ) else let lines_and_handles = py3eval( - \ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequestAsync( ' . - \ '[ "TypeHierarchy" ] ), ' . + \ 'ycm_state.InitializeCurrentHierarchy( GetRawCommandResponse( ' . + \ '[ "TypeHierarchy" ], False ), ' . \ 'vim.eval( "a:kind" ) )' ) endif if len( lines_and_handles ) diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index 6372fe36fc..8216bf727c 100644 --- a/python/ycm/client/command_request.py +++ b/python/ycm/client/command_request.py @@ -248,3 +248,11 @@ def GetCommandResponse( arguments, extra_data = None ): silent = True ) # Block here to get the response return request.StringResponse() + + +def GetRawCommandResponse( arguments, silent, location = None ): + request = SendCommandRequestAsync( arguments, + extra_data = None, + silent = silent, + location = location ) + return request.Response() diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 3c3622c4f8..cdae62c48a 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -35,7 +35,8 @@ from ycm.client.completer_available_request import SendCompleterAvailableRequest from ycm.client.command_request import ( SendCommandRequest, SendCommandRequestAsync, - GetCommandResponse ) + GetCommandResponse, + GetRawCommandResponse ) from ycm.client.completion_request import CompletionRequest from ycm.client.resolve_completion_request import ResolveCompletionItem from ycm.client.signature_help_request import ( SignatureHelpRequest, @@ -112,9 +113,7 @@ def __init__( self, default_options = {} ): self._current_hierarchy = HierarchyTree() - def InitializeCurrentHierarchy( self, request_id, kind ): - items = self.GetCommandRequest( request_id ).Response() - self.FlushCommandRequest( request_id ) + def InitializeCurrentHierarchy( self, items, kind ): return self._current_hierarchy.SetRootNode( items, kind ) @@ -128,23 +127,20 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): location = self._current_hierarchy.HandleToLocation( handle ) kind = self._current_hierarchy._kind self._current_hierarchy.Reset() - request_id = self.SendCommandRequestAsync( + items = GetRawCommandResponse( [ f'{ kind.title() }Hierarchy' ], silent = False, location = location ) - handle = self.InitializeCurrentHierarchy( request_id, kind )[ 0 ][ 1 ] + handle = self.InitializeCurrentHierarchy( items, kind )[ 0 ][ 1 ] return self.UpdateCurrentHierarchy( handle, direction ) def _ResolveHierarchyItem( self, handle : int, direction : str ): - request_id = self.SendCommandRequestAsync( + return GetRawCommandResponse( self._current_hierarchy.ResolveArguments( handle, direction ), silent = False ) - response = self.GetCommandRequest( request_id ).Response() - self.FlushCommandRequest( request_id ) - return response def ShouldResolveItem( self, handle : int, direction : str ): From 31f6517a875582be69969404380e322712160d46 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 11 Jun 2024 07:55:39 +0200 Subject: [PATCH 23/34] Fix re-rooting from incoming calls to outgoing calls --- python/ycm/hierarchy_tree.py | 14 ++++++++++++-- python/ycm/youcompleteme.py | 4 +++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index cb4fac555b..2b22e6c74c 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -27,6 +27,16 @@ def __init__( self, data, distance : int ): self._distance_from_root = distance + def ToRootLocation( self, subindex : int ): + if location := self._data.get( 'root_location' ): + file = location[ 'filepath' ] + line = location[ 'line_num' ] + column = location[ 'column_num' ] + return file, line, column + else: + return self.ToLocation( subindex ) + + def ToLocation( self, subindex : int ): location = self._data[ 'locations' ][ subindex ] line = location[ 'line_num' ] @@ -186,7 +196,7 @@ def ResolveArguments( self, handle : int, direction : str ): ] - def HandleToLocation( self, handle : int ): + def HandleToRootLocation( self, handle : int ): node_index = handle_to_index( handle ) if handle >= 0: @@ -195,7 +205,7 @@ def HandleToLocation( self, handle : int ): node = self._up_nodes[ node_index ] location_index = handle_to_location_index( handle ) - return node.ToLocation( location_index ) + return node.ToRootLocation( location_index ) def UpdateChangesRoot( self, handle : int, direction : str ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index cdae62c48a..0933386c4d 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -124,7 +124,7 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): 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 ) + location = self._current_hierarchy.HandleToRootLocation( handle ) kind = self._current_hierarchy._kind self._current_hierarchy.Reset() items = GetRawCommandResponse( @@ -132,6 +132,8 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): silent = False, location = location ) + # [ 0 ] chooses the data for the 1st (and only) line. + # [ 1 ] chooses only the handle handle = self.InitializeCurrentHierarchy( items, kind )[ 0 ][ 1 ] return self.UpdateCurrentHierarchy( handle, direction ) From 67c02b8182ca968a5e9a2aa3abf3d07b928ed730 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 11 Jun 2024 08:57:16 +0200 Subject: [PATCH 24/34] Fix sending requests for a location in an unloaded buffer In order to send a ycmd request for an unloaded buffer one must first... load the buffer. That is the only way to reliably determine the buffer's filetype. However, we do not want to switch to the buffer, so everything needs to be done "in the background". `:badd` adds a buffer as unloaded, if it were not present before. `bufload()` loads a buffer that has previously not been loaded. --- python/ycm/client/base_request.py | 8 +++++--- python/ycm/youcompleteme.py | 13 ++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index 1ef5de4dfa..4df9adb1f7 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -252,9 +252,11 @@ 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 ) + vim.command( f'badd {file }' ) + vim.eval( f'bufload( "{ file }" )' ) + buffer_number = vimsupport.GetBufferNumberForFilename( file ) + buffer = vim.buffers[ buffer_number ] + file_data = vimsupport.GetUnsavedAndSpecifiedBufferData( buffer, file ) return { 'filepath': file, 'line_num': line, diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 0933386c4d..e80c8afbdf 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -436,13 +436,12 @@ def _GetCommandRequestArguments( self, final_arguments = [] for argument in arguments: - if isinstance( argument, str ): - if argument.startswith( 'ft=' ): - extra_data[ 'completer_target' ] = argument[ 3: ] - continue - elif argument.startswith( '--bufnr=' ): - extra_data[ 'bufnr' ] = int( argument[ len( '--bufnr=' ): ] ) - continue + if argument.startswith( 'ft=' ): + extra_data[ 'completer_target' ] = argument[ 3: ] + continue + elif argument.startswith( '--bufnr=' ): + extra_data[ 'bufnr' ] = int( argument[ len( '--bufnr=' ): ] ) + continue final_arguments.append( argument ) From e354a5402b7bfde6a11a6515e9af907054a3e75f Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 11 Jun 2024 20:08:17 +0200 Subject: [PATCH 25/34] Properly calculate selection offset when expanding hierarchy upwards Previously all new nodes were counted as 1. With call hierarchies, there may be more locations/lines per hierarchy node. In that case, the cursorline would be set to the wrong line. Instead, we need to add up the lengths of all the item locations. --- python/ycm/youcompleteme.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index e80c8afbdf..b54d3fdca1 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -121,7 +121,12 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): 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 + + if items is not None and direction == 'up': + offset = sum( map( lambda item: len( item[ 'locations' ] ), items ) ) + else: + offset = 0 + return self._current_hierarchy.HierarchyToLines(), offset else: location = self._current_hierarchy.HandleToRootLocation( handle ) From 5e03ee094302d615da7e2299bf6fc8300770ac23 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 11 Jun 2024 20:47:26 +0200 Subject: [PATCH 26/34] Fix python tests... again --- python/ycm/tests/command_test.py | 5 ----- python/ycm/tests/test_utils.py | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/python/ycm/tests/command_test.py b/python/ycm/tests/command_test.py index d501371a7b..52ce467030 100644 --- a/python/ycm/tests/command_test.py +++ b/python/ycm/tests/command_test.py @@ -48,7 +48,6 @@ def test_SendCommandRequest_ExtraConfVimData_Works( self, ycm ): 'tempname()': '_TEMP_FILE_' } ), } ), - False ) ) @@ -73,7 +72,6 @@ def test_SendCommandRequest_ExtraConfData_UndefinedValue( self, ycm ): 'insert_spaces': True, } ) } ), - False ) ) @@ -105,7 +103,6 @@ def test_SendCommandRequest_BuildRange_NoVisualMarks( self, ycm, *args ): } } }, - False ) @@ -139,7 +136,6 @@ def test_SendCommandRequest_BuildRange_VisualMarks( self, ycm, *args ): } } }, - False ) @@ -158,7 +154,6 @@ def test_SendCommandRequest_IgnoreFileTypeOption( self, ycm, *args ): 'insert_spaces': True }, }, - False ) with patch( 'ycm.youcompleteme.SendCommandRequest' ) as send_request: diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 7209481d6e..17025af9cc 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -35,8 +35,8 @@ BUFNR_REGEX = re.compile( - '^bufnr\\(\'(?P.+)\'(, ([01]))?\\)$' ) -BUFWINNR_REGEX = re.compile( '^bufwinnr\\((?P[0-9]+)\\)$' ) + '^bufnr\\( \'(?P.+)\'(, ([01]))? \\)$' ) +BUFWINNR_REGEX = re.compile( '^bufwinnr\\( (?P[0-9]+) \\)$' ) BWIPEOUT_REGEX = re.compile( '^(?:silent! )bwipeout!? (?P[0-9]+)$' ) GETBUFVAR_REGEX = re.compile( From bbd0f036fe17a380a162b048b2314aad7a7349c6 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 12 Jun 2024 20:29:05 +0200 Subject: [PATCH 27/34] Handle vim erros when invoking `:badd` and `bufload()` --- python/ycm/client/base_request.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index 4df9adb1f7..f04eca6085 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -251,9 +251,13 @@ def BuildRequestData( buffer_number = None ): def BuildRequestDataForLocation( location ): file, line, column = location - current_buffer = vim.current.buffer - vim.command( f'badd {file }' ) - vim.eval( f'bufload( "{ file }" )' ) + if file not in vim.buffers: + vim.command( f'badd { file }' ) + try: + vim.eval( f'bufload( "{ file }" )' ) + except vim.error as e: + if 'E325' not in str( e ): + raise buffer_number = vimsupport.GetBufferNumberForFilename( file ) buffer = vim.buffers[ buffer_number ] file_data = vimsupport.GetUnsavedAndSpecifiedBufferData( buffer, file ) From 866128c59e188fe4b2b8960b341c78c3c69ce5d7 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 12 Jun 2024 20:30:16 +0200 Subject: [PATCH 28/34] Flake8 fixes --- python/ycm/hierarchy_tree.py | 2 +- python/ycm/youcompleteme.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ycm/hierarchy_tree.py b/python/ycm/hierarchy_tree.py index 2b22e6c74c..2485ce47e0 100644 --- a/python/ycm/hierarchy_tree.py +++ b/python/ycm/hierarchy_tree.py @@ -210,4 +210,4 @@ def HandleToRootLocation( self, handle : int ): def UpdateChangesRoot( self, handle : int, direction : str ): return ( ( handle < 0 and direction == 'down' ) or - ( handle > 0 and direction == 'up' ) ) + ( handle > 0 and direction == 'up' ) ) diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index b54d3fdca1..9e60ae87bd 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -123,7 +123,7 @@ def UpdateCurrentHierarchy( self, handle : int, direction : str ): self._current_hierarchy.UpdateHierarchy( handle, items, direction ) if items is not None and direction == 'up': - offset = sum( map( lambda item: len( item[ 'locations' ] ), items ) ) + offset = sum( len( item[ 'locations' ] ) for item in items ) else: offset = 0 From 53f4a42fb268f640e54e06ce09c9a2cb8bdfadd9 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 12 Jun 2024 20:44:26 +0200 Subject: [PATCH 29/34] Add vim tests for hierarchies --- test/hierarchies.test.vim | 178 +++++++++++++++++++++++++++++++ test/testdata/cpp/hierarchies.cc | 16 +++ 2 files changed, 194 insertions(+) create mode 100644 test/hierarchies.test.vim create mode 100644 test/testdata/cpp/hierarchies.cc diff --git a/test/hierarchies.test.vim b/test/hierarchies.test.vim new file mode 100644 index 0000000000..b3a9527e8f --- /dev/null +++ b/test/hierarchies.test.vim @@ -0,0 +1,178 @@ +function! SetUp() + let g:ycm_auto_hover = 1 + let g:ycm_auto_trigger = 1 + let g:ycm_keep_logfiles = 1 + let g:ycm_log_level = 'DEBUG' + + call youcompleteme#test#setup#SetUp() +endfunction + +function! TearDown() + call youcompleteme#test#setup#CleanUp() +endfunction + +function! Test_Call_Hierarchy() + call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/hierarchies.cc', {} ) + call cursor( [ 1, 5 ] ) + + " Check that f's callers are present + function! Check1( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check2' ) ) + endfunction + + " Check that g's callers are present + function! Check2( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\\", funcref( 'Check8' ) ) + endfunction + + " Check that 1st h's callers are present + function! Check3( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check4' ) ) + endfunction + + " Check that 2nd h's callers are present + function! Check4( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\\\\", funcref( 'Check5' ) ) + endfunction + + " Try to access callees of f + function! Check5( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^-Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\\\\", funcref( 'Check6' ) ) + endfunction + + " Re-root at h + function! Check6( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 ) ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check7' ) ) + endfunction + + " Expansion after re-rooting works. + " NOTE: Clangd does not support outgoing calls, hence, we are stuck at just h. + function! Check7( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^-Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 ) ) } ) + call FeedAndCheckAgain( "\", funcref( 'Check8' ) ) + endfunction + + " Make sure it is closed + function! Check8( id ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) + endfunction + + call youcompleteme#hierarchy#StartRequest( 'call' ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) + " Check that `+Function f` is at the start of the only line in the popup + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call FeedAndCheckMain( "\", funcref( 'Check1' ) ) +endfunction + +function! Test_Type_Hierarchy() + "call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/hierarchies.cc', {} ) + call cursor( [ 13, 8 ] ) + + " Check that B1's subtypes are present + function! Check1( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check2' ) ) + endfunction + + " Try to access D1's subtypes + function! Check2( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check3' ) ) + endfunction + + " Check that 1st h's callers are present + function! Check3( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\", funcref( 'Check4' ) ) + endfunction + + " Check that 2nd h's callers are present + function! Check4( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\", funcref( 'Check5' ) ) + endfunction + + " Re-root at B0: supertypes->subtypes + function! Check5( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D0.*:15', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\\\", funcref( 'Check6' ) ) + endfunction + + " Re-root at D1: subtypes->supertypes + function! Check6( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\\\", funcref( 'Check7' ) ) + endfunction + + " Expansion after re-rooting works. + function! Check7( id ) + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call FeedAndCheckAgain( "\", funcref( 'Check7' ) ) + endfunction + + " Make sure it is closed + function! Check8( id ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) + endfunction + + call youcompleteme#hierarchy#StartRequest( 'type' ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) + " Check that `+Function f` is at the start of the only line in the popup + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call FeedAndCheckMain( "\", funcref( 'Check1' ) ) +endfunction diff --git a/test/testdata/cpp/hierarchies.cc b/test/testdata/cpp/hierarchies.cc new file mode 100644 index 0000000000..f2af7513de --- /dev/null +++ b/test/testdata/cpp/hierarchies.cc @@ -0,0 +1,16 @@ +int f(); + +int g() { + return f() + f(); +} + +int h() { + int x = g(); + return f() + x; +} + +struct B0 {}; +struct B1 : B0 {}; + +struct D0 : B0 {}; +struct D1 : B0, B1 {}; From f05f89e3dc925bac627e6dfd640224909cc66927 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 18 Jun 2024 03:54:17 +0200 Subject: [PATCH 30/34] Update ycmd submodule Changelog: 1. Upgrade all subservers 2. Hierarchy support --- README.md | 3 +++ third_party/ycmd | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index af2ce861b5..9edf24b960 100644 --- a/README.md +++ b/README.md @@ -723,6 +723,7 @@ Quick Feature Summary * Type information for identifiers (`GetType`) * Code formatting (`Format`) * Management of `gopls` server instance +* Inlay hints * Call hierarchy ### JavaScript and TypeScript @@ -745,6 +746,7 @@ Quick Feature Summary * Organize imports (`OrganizeImports`) * Management of `TSServer` server instance * Inlay hints +* Call hierarchy ### Rust @@ -787,6 +789,7 @@ Quick Feature Summary * Execute custom server command (`ExecuteCommand `) * Management of `jdt.ls` server instance * Semantic highlighting +* Inlay hints * Type hierarchy * Call hierarchy diff --git a/third_party/ycmd b/third_party/ycmd index 7475c55565..e81c15e9ee 160000 --- a/third_party/ycmd +++ b/third_party/ycmd @@ -1 +1 @@ -Subproject commit 7475c55565495d00a7edd0d9b5c336a887fb6249 +Subproject commit e81c15e9eeda987c1d04e9efefa24a92b13ad6d4 From 87939d62e02235eddacce6ef7c18a9b7e6b7c459 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 19 Jun 2024 20:53:13 +0100 Subject: [PATCH 31/34] Fix vim tests for hierarchies Previously the tests were using async result checking, but this is intended for insert mode only. In fact we were running off the end of the test and then the check callbacks were firing. Simplified by just making it the same but sequential, and replacding FeedAndCheck* with direct calls to feedkeys(..., 'xt'). --- test/hierarchies.test.vim | 224 +++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 126 deletions(-) diff --git a/test/hierarchies.test.vim b/test/hierarchies.test.vim index b3a9527e8f..3751c31dc6 100644 --- a/test/hierarchies.test.vim +++ b/test/hierarchies.test.vim @@ -15,164 +15,136 @@ function! Test_Call_Hierarchy() call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/hierarchies.cc', {} ) call cursor( [ 1, 5 ] ) + call youcompleteme#hierarchy#StartRequest( 'call' ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) + " Check that `+Function f` is at the start of the only line in the popup + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + + silent call feedkeys( "\", "xt" ) " Check that f's callers are present - function! Check1( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check2' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + silent call feedkeys( "\\", "xt" ) " Check that g's callers are present - function! Check2( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\\", funcref( 'Check8' ) ) - endfunction - + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + + silent call feedkeys( "\\\", "xt" ) " Check that 1st h's callers are present - function! Check3( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check4' ) ) - endfunction - + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + + silent call feedkeys( "\\", "xt" ) " Check that 2nd h's callers are present - function! Check4( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\\\\", funcref( 'Check5' ) ) - endfunction - + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + + silent call feedkeys( "\\\\\", "xt" ) " Try to access callees of f - function! Check5( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^-Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\\\\", funcref( 'Check6' ) ) - endfunction - + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) + call WaitForAssert( { -> assert_match( '^-Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + + silent call feedkeys( "\\\\\", "xt" ) " Re-root at h - function! Check6( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 ) ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check7' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[0] ) } ) + silent call feedkeys( "\\", "xt" ) " Expansion after re-rooting works. " NOTE: Clangd does not support outgoing calls, hence, we are stuck at just h. - function! Check7( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^-Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 ) ) } ) - call FeedAndCheckAgain( "\", funcref( 'Check8' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^-Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call feedkeys( "\", "xt" ) " Make sure it is closed - function! Check8( id ) - call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) - endfunction + call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) - call youcompleteme#hierarchy#StartRequest( 'call' ) - call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) - " Check that `+Function f` is at the start of the only line in the popup - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call FeedAndCheckMain( "\", funcref( 'Check1' ) ) + %bwipe! endfunction function! Test_Type_Hierarchy() - "call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/hierarchies.cc', {} ) + call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/hierarchies.cc', {} ) call cursor( [ 13, 8 ] ) + call youcompleteme#hierarchy#StartRequest( 'type' ) + call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) + " Check that `+Function f` is at the start of the only line in the popup + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + + silent call feedkeys( "\", "xt" ) " Check that B1's subtypes are present - function! Check1( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check2' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + silent call feedkeys( "\\", "xt" ) " Try to access D1's subtypes - function! Check2( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check3' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + silent call feedkeys( "\\", "xt" ) " Check that 1st h's callers are present - function! Check3( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\", funcref( 'Check4' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + silent call feedkeys( "\\", "xt" ) " Check that 2nd h's callers are present - function! Check4( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\", funcref( 'Check5' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + silent call feedkeys( "\", "xt" ) " Re-root at B0: supertypes->subtypes - function! Check5( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D0.*:15', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\\\", funcref( 'Check6' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D0.*:15', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + silent call feedkeys( "\\\\", "xt" ) " Re-root at D1: subtypes->supertypes - function! Check6( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\\\", funcref( 'Check7' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^+Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + silent call feedkeys( "\\\", "xt" ) " Expansion after re-rooting works. - function! Check7( id ) - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call FeedAndCheckAgain( "\", funcref( 'Check7' ) ) - endfunction + call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^ -Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call WaitForAssert( { -> assert_match( '^-Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call feedkeys( "\", "xt" ) " Make sure it is closed - function! Check8( id ) - call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) - endfunction + call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) - call youcompleteme#hierarchy#StartRequest( 'type' ) - call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) - " Check that `+Function f` is at the start of the only line in the popup - call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call FeedAndCheckMain( "\", funcref( 'Check1' ) ) + %bwipe! endfunction From 2eff94b685d5143fe9aa7642feb9a28da55f7721 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Wed, 19 Jun 2024 21:22:24 +0100 Subject: [PATCH 32/34] Remove use of indexof() indexof was added so recently that even vim-helptag-versions website doesn't have it. Replace with a loop. --- autoload/youcompleteme/hierarchy.vim | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/autoload/youcompleteme/hierarchy.vim b/autoload/youcompleteme/hierarchy.vim index 15eec32009..d5b3563bd9 100644 --- a/autoload/youcompleteme/hierarchy.vim +++ b/autoload/youcompleteme/hierarchy.vim @@ -233,8 +233,15 @@ function! s:ResolveItem( choice, direction, will_change_root ) " where we _don't_ re-root the tree, so feels more natural than anything " else. " The new root is the element with indent of 0. - let s:select = 1 + indexof( s:lines_and_handles, - \ { i, v -> v[0].indent == 0 } ) + " let s:select = 1 + indexof( s:lines_and_handles, + " \ { i, v -> v[0].indent == 0 } ) + let s:select = 1 + for item in s:lines_and_handles + if item[0].indent == 0 + break + endif + let s:select += 1 + endfor else let s:select += lines_and_handles_with_offset[ 1 ] endif From ac002d5f6c4a3abb26921b02e1c97ae7e6535fe0 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 19 Jun 2024 22:40:21 +0200 Subject: [PATCH 33/34] Clean up hierarchies.test.vim There were some unnecessary `WaitForAssert()` calls and unnecessary `silent` commands. --- test/hierarchies.test.vim | 150 ++++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 71 deletions(-) diff --git a/test/hierarchies.test.vim b/test/hierarchies.test.vim index 3751c31dc6..e9a51a12cf 100644 --- a/test/hierarchies.test.vim +++ b/test/hierarchies.test.vim @@ -17,67 +17,72 @@ function! Test_Call_Hierarchy() call youcompleteme#hierarchy#StartRequest( 'call' ) call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) - " Check that `+Function f` is at the start of the only line in the popup + " Check that `+Function f` is at the start of the only line in the popup. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call assert_match( '^+Function: f', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) - silent call feedkeys( "\", "xt" ) - " Check that f's callers are present + call feedkeys( "\", "xt" ) + " Check that f's callers are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ +Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) - silent call feedkeys( "\\", "xt" ) - " Check that g's callers are present + call feedkeys( "\\", "xt" ) + " Check that g's callers are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ +Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) + call assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) + " silent, because h has no incoming calls. silent call feedkeys( "\\\", "xt" ) - " Check that 1st h's callers are present + " Check that 1st h's callers are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) + call assert_match( '^ +Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) + " silent, because h has no incoming calls. silent call feedkeys( "\\", "xt" ) - " Check that 2nd h's callers are present + " Check that 2nd h's callers are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call assert_match( '^+Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) + call assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) + " silent, because clangd does not support outgoing calls. silent call feedkeys( "\\\\\", "xt" ) - " Try to access callees of f + " Try to access callees of f. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 5 ) } ) - call WaitForAssert( { -> assert_match( '^-Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) } ) + call assert_match( '^-Function: f.*:1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Function: g.*:4', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ -Function: h.*:8', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) + call assert_match( '^ -Function: h.*:9', getbufline( winbufnr( popup_list()[ 0 ] ), 5 )[ 0 ] ) + " silent, because clangd does not support outgoing calls. silent call feedkeys( "\\\\\", "xt" ) - " Re-root at h + " Re-root at h. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[0] ) } ) + call assert_match( '^+Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[0] ) + " silent, because clangd does not support outgoing calls. silent call feedkeys( "\\", "xt" ) " Expansion after re-rooting works. " NOTE: Clangd does not support outgoing calls, hence, we are stuck at just h. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^-Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call assert_match( '^-Function: h', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) call feedkeys( "\", "xt" ) - " Make sure it is closed + " Make sure it is closed. call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) %bwipe! @@ -89,61 +94,64 @@ function! Test_Type_Hierarchy() call youcompleteme#hierarchy#StartRequest( 'type' ) call WaitForAssert( { -> assert_equal( len( popup_list() ), 1 ) } ) - " Check that `+Function f` is at the start of the only line in the popup + " Check that `+Struct: B1` is at the start of the only line in the popup. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 1 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) + call assert_match( '^+Struct: B1', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) - silent call feedkeys( "\", "xt" ) - " Check that B1's subtypes are present + call feedkeys( "\", "xt" ) + " Check that B1's subtypes are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + " silent, because D1 has no subtypes. silent call feedkeys( "\\", "xt" ) - " Try to access D1's subtypes + " Try to access D1's subtypes. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 2 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) + call assert_match( '^+Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) - silent call feedkeys( "\\", "xt" ) - " Check that 1st h's callers are present + call feedkeys( "\\", "xt" ) + " Check that B1's supertypes are present. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + " silent, because there are no supertypes of B0. silent call feedkeys( "\\", "xt" ) - " Check that 2nd h's callers are present + " Try to access B0's supertypes. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call assert_match( '^ -Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^-Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) - silent call feedkeys( "\", "xt" ) - " Re-root at B0: supertypes->subtypes + call feedkeys( "\", "xt" ) + " Re-root at B0: supertypes->subtypes. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D0.*:15', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call assert_match( '^+Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ +Struct: D0.*:15', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^ +Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) - silent call feedkeys( "\\\\", "xt" ) - " Re-root at D1: subtypes->supertypes + call feedkeys( "\\\\", "xt" ) + " Re-root at D1: subtypes->supertypes. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 3 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^+Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) + call assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ +Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^+Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + " silent, because there are no subtypes of D1. silent call feedkeys( "\\\", "xt" ) " Expansion after re-rooting works. call WaitForAssert( { -> assert_equal( len( getbufline( winbufnr( popup_list()[ 0 ] ), 1, '$' ) ), 4 ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^ -Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) } ) - call WaitForAssert( { -> assert_match( '^-Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) } ) + call assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 1 )[ 0 ] ) + call assert_match( '^ +Struct: B0.*:12', getbufline( winbufnr( popup_list()[ 0 ] ), 2 )[ 0 ] ) + call assert_match( '^ -Struct: B1.*:13', getbufline( winbufnr( popup_list()[ 0 ] ), 3 )[ 0 ] ) + call assert_match( '^-Struct: D1.*:16', getbufline( winbufnr( popup_list()[ 0 ] ), 4 )[ 0 ] ) call feedkeys( "\", "xt" ) - " Make sure it is closed + " Make sure it is closed. call WaitForAssert( { -> assert_equal( len( popup_list() ), 0 ) } ) %bwipe! From 25ec3c9f8d52fe04905ecacb2d09475018cdda66 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 19 Jun 2024 23:20:33 +0200 Subject: [PATCH 34/34] Clean up BuildRequestDataForLocation - `badd` is unnecessary since we have `GetBufferNumberForFilename()` - Instead of passing `location`, we can pass `file`, `line` and `column` separately. --- python/ycm/client/base_request.py | 9 ++++----- python/ycm/client/command_request.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index f04eca6085..707339612a 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -249,16 +249,15 @@ def BuildRequestData( buffer_number = None ): } -def BuildRequestDataForLocation( location ): - file, line, column = location - if file not in vim.buffers: - vim.command( f'badd { file }' ) +def BuildRequestDataForLocation( file : str, line : int, column : int ): + buffer_number = vimsupport.GetBufferNumberForFilename( + file, + create_buffer_if_needed = True ) try: vim.eval( f'bufload( "{ file }" )' ) except vim.error as e: if 'E325' not in str( e ): raise - buffer_number = vimsupport.GetBufferNumberForFilename( file ) buffer = vim.buffers[ buffer_number ] file_data = vimsupport.GetUnsavedAndSpecifiedBufferData( buffer, file ) return { diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index 8216bf727c..a9045686d8 100644 --- a/python/ycm/client/command_request.py +++ b/python/ycm/client/command_request.py @@ -49,7 +49,7 @@ def __init__( self, def Start( self ): if self._location is not None: - self._request_data = BuildRequestDataForLocation( self._location ) + self._request_data = BuildRequestDataForLocation( *self._location ) elif self._bufnr is not None: self._request_data = BuildRequestData( self._bufnr ) else: