Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[READY] Implement type/call hierarchy handling #4221

Merged
merged 34 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dbcca3d
Preliminary type/call hierarchy support
bstaletic Feb 10, 2024
93833dc
Clean up API
bstaletic Feb 11, 2024
705803a
Allow both directions at the same time - me is happy
bstaletic Feb 11, 2024
729806c
Support changing the current hierarchy root node
bstaletic Feb 11, 2024
fcf89f0
Add bindings for hierarchies
bstaletic Feb 11, 2024
65456b2
flake8
bstaletic Feb 11, 2024
08353bc
Fix error using mappings: <Cmd> mappings must end with <CR>
puremourning May 11, 2024
0ce21e2
Avoid vim backtrace when no target description
bstaletic May 11, 2024
f1937cf
Close the popup when non-handled key pressed
puremourning May 11, 2024
c217e66
Basic popup aesthetics
puremourning May 11, 2024
72026b3
Tidy handles: wrap in some little functions
puremourning May 11, 2024
6ca4659
Style the hierarchy popup like the finder popup
puremourning May 13, 2024
ea6cb61
Trim leading/trailing whitespace from description
puremourning May 13, 2024
fa73852
Explain offsets
puremourning May 13, 2024
3376c05
Use correct bottom-right border character as we don't actually allow …
puremourning May 13, 2024
2a68752
Log server exceptoins
puremourning May 13, 2024
9d8d267
flake8
bstaletic May 14, 2024
6c3832d
Avoid nasty crashes in othervim
puremourning May 17, 2024
3702f46
Update README for type hierarchy; graduate some features
puremourning May 17, 2024
489dcb1
Avoid skip_post_command_action hack when making Hierarchy request
bstaletic Jun 9, 2024
8888bbe
Fix re-rooting of call/type hierarchies
bstaletic Jun 9, 2024
546b050
Simplify getting a raw response from a completer command
bstaletic Jun 10, 2024
31f6517
Fix re-rooting from incoming calls to outgoing calls
bstaletic Jun 11, 2024
67c02b8
Fix sending requests for a location in an unloaded buffer
bstaletic Jun 11, 2024
e354a54
Properly calculate selection offset when expanding hierarchy upwards
bstaletic Jun 11, 2024
5e03ee0
Fix python tests... again
bstaletic Jun 11, 2024
bbd0f03
Handle vim erros when invoking `:badd` and `bufload()`
bstaletic Jun 12, 2024
866128c
Flake8 fixes
bstaletic Jun 12, 2024
53f4a42
Add vim tests for hierarchies
bstaletic Jun 12, 2024
f05f89e
Update ycmd submodule
bstaletic Jun 18, 2024
87939d6
Fix vim tests for hierarchies
puremourning Jun 19, 2024
2eff94b
Remove use of indexof()
puremourning Jun 19, 2024
ac002d5
Clean up hierarchies.test.vim
bstaletic Jun 19, 2024
25ec3c9
Clean up BuildRequestDataForLocation
bstaletic Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions autoload/youcompleteme.vim
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,11 @@ endfunction
silent! nnoremap <silent> <plug>(YCMToggleInlayHints)
\ <cmd>call <SID>ToggleInlayHints()<CR>

silent! nnoremap <silent> <plug>(YCMTypeHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'type' )<cr>
silent! nnoremap <silent> <plug>(YCMCallHierarchy)
\ <cmd>call youcompleteme#hierarchy#StartRequest( 'call' )<cr>

" This is basic vim plugin boilerplate
let &cpo = s:save_cpo
unlet s:save_cpo
48 changes: 2 additions & 46 deletions autoload/youcompleteme/finder.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
237 changes: 237 additions & 0 deletions autoload/youcompleteme/hierarchy.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
" 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 <http://www.gnu.org/licenses/>.

" 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
" 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 = ''

let s:ingored_keys = [
\ "\<CursorHold>",
\ "\<MouseMove>",
\ ]

function! youcompleteme#hierarchy#StartRequest( kind )
call youcompleteme#symbol#InitSymbolProperties()
py3 ycm_state.ResetCurrentHierarchy()
if a:kind == 'call'
let lines_and_handles = py3eval(
\ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' .
\ '[ "CallHierarchy" ], "", False, 0, 0 ), ' .
\ 'vim.eval( "a:kind" ) )' )
else
let lines_and_handles = py3eval(
\ 'ycm_state.InitializeCurrentHierarchy( ycm_state.SendCommandRequest( ' .
\ '[ "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:select = 1
call s:SetUpMenu()
endif
endfunction

function! s:MenuFilter( winid, key )
if a:key == "\<S-Tab>"
" ROot changes if we're showing super-tree of a sub-tree of the root
" (indeicated by the handle being positive)
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 == "\<Tab>"
" 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,
\ [ s:select - 1, 'resolve_down', will_change_root ] )
return 1
endif
if a:key == "\<CR>"
call popup_close( s:popup_id, [ s:select - 1, 'jump', v:none ] )
return 1
endif
if a:key == "\<Up>" || a:key == "\<C-p>" || a:key == "\<C-k>" || a:key == "k"
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 ] )' )
call win_execute( s:popup_id,
\ 'set cursorline cursorlineopt&' )
return 1
endif
if a:key == "\<Down>" || a:key == "\<C-n>" || a:key == "\<C-j>" || a:key == "j"
let s:select += 1
if s:select > len( s:lines_and_handles )
let s:select = len( s:lines_and_handles )
endif
call win_execute( s:popup_id,
\ 'call cursor( [' . string( s:select ) . ', 1 ] )' )
call win_execute( s:popup_id,
\ 'set cursorline cursorlineopt&' )
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 == "\<Esc>" || a:key == "\<C-c>"
return 1
endif
return 0
endfunction

function! s:MenuCallback( winid, result )
let operation = a:result[ 1 ]
let selection = a:result[ 0 ]
if operation == 'resolve_down'
call s:ResolveItem( selection, 'down', a:result[ 2 ] )
elseif operation == 'resolve_up'
call s:ResolveItem( selection, 'up', a:result[ 2 ] )
else
if operation == 'jump'
let handle = s:lines_and_handles[ selection ][ 1 ]
py3 ycm_state.JumpToHierarchyItem( vimsupport.GetIntValue( "handle" ) )
endif
py3 ycm_state.ResetCurrentHierarchy()
let s:kind = ''
let s:select = 1
endif
endfunction

function! s:SetUpMenu()
let opts = #{
\ filter: funcref( 's:MenuFilter' ),
\ 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 ],
\ 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 indent = repeat( ' ', item.indent )
let name = indent
\ .. 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
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 ]

let line = trunc_name
\ . "\t"
\ .. trunc_path
\ . "\t"
\ .. trunc_desc
call add( menu_lines, { 'text': line, 'props': props } )
endfor
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 )
let handle = s:lines_and_handles[ a:choice ][ 1 ]
if py3eval(
\ 'ycm_state.ShouldResolveItem( vimsupport.GetIntValue( "handle" ), vim.eval( "a:direction" ) )' )
let lines_and_handles_with_offset = py3eval(
\ 'ycm_state.UpdateCurrentHierarchy( ' .
\ 'vimsupport.GetIntValue( "handle" ), ' .
\ '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].indent == 0 } )
else
let s:select += lines_and_handles_with_offset[ 1 ]
endif
endif
call s:SetUpMenu()
endfunction
71 changes: 71 additions & 0 deletions autoload/youcompleteme/symbol.vim
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.


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


1 change: 1 addition & 0 deletions python/ycm/client/base_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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' ] )

Expand Down
Loading
Loading