From d40d58acbe9a6fc576d195fdce5ec7050e2a8cd9 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Sat, 19 Nov 2022 23:09:03 -0800 Subject: [PATCH] Add support for displaying type hiearchy in a popup window --- autoload/lsp/lsp.vim | 12 +++ autoload/lsp/lspserver.vim | 38 +++++++ autoload/lsp/symbol.vim | 50 +--------- autoload/lsp/typehierarchy.vim | 174 +++++++++++++++++++++++++++++++++ autoload/lsp/util.vim | 43 ++++++++ doc/lsp.txt | 100 +++++++++++-------- plugin/lsp.vim | 2 + 7 files changed, 328 insertions(+), 91 deletions(-) create mode 100644 autoload/lsp/typehierarchy.vim diff --git a/autoload/lsp/lsp.vim b/autoload/lsp/lsp.vim index 292b581..fe86fbe 100644 --- a/autoload/lsp/lsp.vim +++ b/autoload/lsp/lsp.vim @@ -43,6 +43,7 @@ def LspInitOnce() prop_type_add('LspTextRef', {'highlight': 'Search'}) prop_type_add('LspReadRef', {'highlight': 'DiffChange'}) prop_type_add('LspWriteRef', {'highlight': 'DiffDelete'}) + set ballooneval balloonevalterm lspInitializedOnce = true enddef @@ -648,6 +649,17 @@ export def OutgoingCalls() lspserver.outgoingCalls(@%) enddef +# Display the type hierarchy for the current symbol. Direction is 0 for +# sub types and 1 for super types. +export def TypeHierarchy(direction: number) + var lspserver: dict = buf.CurbufGetServerChecked() + if lspserver->empty() + return + endif + + lspserver.typeHierarchy(direction) +enddef + # Rename a symbol # Uses LSP "textDocument/rename" request export def Rename() diff --git a/autoload/lsp/lspserver.vim b/autoload/lsp/lspserver.vim index 817ec1c..67ceb59 100644 --- a/autoload/lsp/lspserver.vim +++ b/autoload/lsp/lspserver.vim @@ -20,6 +20,7 @@ import './hover.vim' import './signature.vim' import './codeaction.vim' import './callhierarchy.vim' as callhier +import './typehierarchy.vim' as typehier # LSP server standard output handler def Output_cb(lspserver: dict, chan: channel, msg: any): void @@ -224,6 +225,13 @@ def ProcessServerCaps(lspserver: dict, caps: dict) lspserver.isCallHierarchyProvider = false endif + # typeHierarchyProvider + if lspserver.caps->has_key('typeHierarchyProvider') + lspserver.isTypeHierarchyProvider = true + else + lspserver.isTypeHierarchyProvider = false + endif + # renameProvider if lspserver.caps->has_key('renameProvider') if lspserver.caps.renameProvider->type() == v:t_bool @@ -1212,6 +1220,33 @@ def OutgoingCalls(lspserver: dict, fname: string) callhier.OutgoingCalls(reply.result) enddef +# Request: "textDocument/typehierarchy" +# Support the clangd version of type hierarchy retrieval method. +# The method described in the LSP 3.17.0 standard is not supported as clangd +# doesn't support that method. +def TypeHiearchy(lspserver: dict, direction: number) + # Check whether LSP server supports type hierarchy + if !lspserver.isTypeHierarchyProvider + util.ErrMsg("Error: LSP server does not support type hierarchy") + return + endif + + # interface TypeHierarchy + # interface TextDocumentPositionParams + var param: dict + param = GetLspTextDocPosition() + # 0: children, 1: parent, 2: both + param.direction = direction + param.resolve = 5 + var reply = lspserver.rpc('textDocument/typeHierarchy', param) + if reply->empty() || reply.result->empty() + util.WarnMsg('No type hierarchy available') + return + endif + + typehier.ShowTypeHierarchy(lspserver, direction == 1, reply.result) +enddef + # Request: "textDocument/rename" # Param: RenameParams def RenameSymbol(lspserver: dict, newName: string) @@ -1502,6 +1537,8 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali omniCompletePending: false, completionTriggerChars: [], signaturePopup: -1, + typeHierPopup: -1, + typeHierFilePopup: -1, diagsMap: {}, workspaceSymbolPopup: -1, workspaceSymbolQuery: '', @@ -1552,6 +1589,7 @@ export def NewLspServer(path: string, args: list, isSync: bool, initiali textDocFormat: function(TextDocFormat, [lspserver]), incomingCalls: function(IncomingCalls, [lspserver]), outgoingCalls: function(OutgoingCalls, [lspserver]), + typeHierarchy: function(TypeHiearchy, [lspserver]), renameSymbol: function(RenameSymbol, [lspserver]), codeAction: function(CodeAction, [lspserver]), workspaceQuery: function(WorkspaceQuerySymbols, [lspserver]), diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim index 82a087f..17db794 100644 --- a/autoload/lsp/symbol.vim +++ b/autoload/lsp/symbol.vim @@ -229,11 +229,6 @@ enddef # Display or peek symbol references in a location list export def ShowReferences(lspserver: dict, refs: list>, peekSymbol: bool) - if refs->empty() - util.WarnMsg('Error: No references found') - return - endif - # create a location list with the location of the references var qflist: list> = [] for loc in refs @@ -314,49 +309,6 @@ def PeekSymbolLocation(lspserver: dict, fname: string, win_execute(pwid, cmds, 'silent!') enddef -# Jump to the symbol in file 'fname' at 'location'. The user specified -# window command modifiers (e.g. topleft) are in 'cmdmods'. -def JumpToSymbolLocation(lspserver: dict, fname: string, - location: dict, cmdmods: string) - # jump to the file and line containing the symbol - if cmdmods == '' - var bnr: number = fname->bufnr() - if bnr != bufnr() - var wid = fname->bufwinid() - if wid != -1 - wid->win_gotoid() - else - if bnr != -1 - # Reuse an existing buffer. If the current buffer has unsaved changes - # and 'hidden' is not set or if the current buffer is a special - # buffer, then open the buffer in a new window. - if (&modified && !&hidden) || &buftype != '' - exe $'sbuffer {bnr}' - else - exe $'buf {bnr}' - endif - else - if (&modified && !&hidden) || &buftype != '' - # if the current buffer has unsaved changes and 'hidden' is not set, - # or if the current buffer is a special buffer, then open the file - # in a new window - exe $'split {fname}' - else - exe $'edit {fname}' - endif - endif - endif - endif - else - exe $'{cmdmods} split {fname}' - endif - # Set the previous cursor location mark. Instead of using setpos(), m' is - # used so that the current location is added to the jump list. - normal m' - setcursorcharpos(location.range.start.line + 1, - location.range.start.character + 1) -enddef - # Jump to the definition, declaration or implementation of a symbol. # Also, used to peek at the definition, declaration or implementation of a # symbol. @@ -368,7 +320,7 @@ export def GotoSymbol(lspserver: dict, location: dict, else # Save the current cursor location in the tag stack. util.PushCursorToTagStack() - JumpToSymbolLocation(lspserver, fname, location, cmdmods) + util.JumpToLspLocation(fname, location, cmdmods) endif enddef diff --git a/autoload/lsp/typehierarchy.vim b/autoload/lsp/typehierarchy.vim new file mode 100644 index 0000000..ee5d72e --- /dev/null +++ b/autoload/lsp/typehierarchy.vim @@ -0,0 +1,174 @@ +vim9script + +# Functions for dealing with type hierarchy (super types/sub types) + +import './util.vim' +import './symbol.vim' + +# Parse the type hierarchy in 'typeHier' and displays a tree of type names +# in the current buffer. This function is called recursively to display the +# super/sub type hierarchy. +# +# Returns the line number where the next type name should be added. +def TypeTreeGenerate(super: bool, typeHier: dict, pfx_arg: string, + typeTree: list, typeUriMap: list>) + + var itemHasChildren = false + if super + if typeHier->has_key('parents') && !typeHier.parents->empty() + itemHasChildren = true + endif + else + if typeHier->has_key('children') && !typeHier.children->empty() + itemHasChildren = true + endif + endif + + var itemBranchPfx: string + if itemHasChildren + itemBranchPfx = '▾ ' + else + itemBranchPfx = pfx_arg->empty() ? '' : ' ' + endif + + var typestr: string + var kindstr = symbol.SymbolKindToName(typeHier.kind) + if kindstr != '' + typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name} ({kindstr[0]})' + else + typestr = $'{pfx_arg}{itemBranchPfx}{typeHier.name}' + endif + typeTree->add(typestr) + typeUriMap->add(typeHier) + + # last item to process + if !itemHasChildren + return + endif + + var items: list> + items = super ? typeHier.parents : typeHier.children + + for item in items + TypeTreeGenerate(super, item, pfx_arg .. '| ', typeTree, typeUriMap) + endfor +enddef + +# Display a popup with the file containing a type and highlight the line and +# the type name. +def UpdateTypeHierFileInPopup(lspserver: dict, typeUriMap: list>) + if lspserver.typeHierPopup->winbufnr() == -1 + return + endif + + lspserver.typeHierFilePopup->popup_close() + + var n = line('.', lspserver.typeHierPopup) - 1 + var fname: string = util.LspUriToFile(typeUriMap[n].uri) + + var bnr = fname->bufadd() + if bnr == 0 + return + endif + + var popupAttr = { + title: $"{fname->fnamemodify(':t')} ({fname->fnamemodify(':h')})", + wrap: false, + fixed: true, + minheight: 10, + maxheight: 10, + minwidth: winwidth(0) - 38, + maxwidth: winwidth(0) - 38, + cursorline: true, + border: [], + line: 'cursor+1', + col: 1 + } + lspserver.typeHierFilePopup = popup_create(bnr, popupAttr) + var cmds =<< trim eval END + [{typeUriMap[n].range.start.line + 1}, 1]->cursor() + normal! z. + END + win_execute(lspserver.typeHierFilePopup, cmds) + lspserver.typeHierFilePopup->clearmatches() + var start_col = util.GetLineByteFromPos(bnr, + typeUriMap[n].selectionRange.start) + 1 + var end_col = util.GetLineByteFromPos(bnr, typeUriMap[n].selectionRange.end) + var pos = [[typeUriMap[n].selectionRange.start.line + 1, + start_col, end_col - start_col + 1]] + matchaddpos('Search', pos, 10, -1, {window: lspserver.typeHierFilePopup}) +enddef + +def TypeHierPopupFilter(lspserver: dict, typeUriMap: list>, + popupID: number, key: string): bool + popupID->popup_filter_menu(key) + if lspserver.typeHierPopup->winbufnr() == -1 + # popup is closed + if lspserver.typeHierFilePopup->winbufnr() != -1 + lspserver.typeHierFilePopup->popup_close() + endif + lspserver.typeHierFilePopup = -1 + lspserver.typeHierPopup = -1 + else + UpdateTypeHierFileInPopup(lspserver, typeUriMap) + endif + + return true +enddef + +def TypeHierPopupCallback(lspserver: dict, typeUriMap: list>, + popupID: number, selIdx: number) + if lspserver.typeHierFilePopup->winbufnr() != -1 + lspserver.typeHierFilePopup->popup_close() + endif + lspserver.typeHierFilePopup = -1 + lspserver.typeHierPopup = -1 + + if selIdx <= 0 + # popup is canceled + return + endif + + var item = typeUriMap[selIdx - 1] + var fname = util.LspUriToFile(item.uri) + # Save the current cursor location in the tag stack. + util.PushCursorToTagStack() + util.JumpToLspLocation(fname, item, '') +enddef + +# Show the super or sub type hierarchy items 'types' as a tree in a popup window +export def ShowTypeHierarchy(lspserver: dict, super: bool, types: dict) + + if lspserver.typeHierPopup->winbufnr() != -1 + # If the type hierarchy popup window is already present, close it. + lspserver.typeHierPopup->popup_close() + endif + + var typeTree: list + var typeUriMap: list> + + # Generate a tree of the type hierarchy items + TypeTreeGenerate(super, types, '', typeTree, typeUriMap) + + # Display a popup window with the type hierarchy tree and a popup window for + # the file. + var popupAttr = { + title: $'{super ? "Super" : "Sub"}Type Hierarchy', + wrap: 0, + pos: 'topleft', + line: 'cursor+1', + col: winwidth(0) - 34, + minheight: 10, + maxheight: 10, + minwidth: 30, + maxwidth: 30, + mapping: false, + fixed: 1, + filter: function(TypeHierPopupFilter, [lspserver, typeUriMap]), + callback: function(TypeHierPopupCallback, [lspserver, typeUriMap]) + } + lspserver.typeHierPopup = popup_menu(typeTree, popupAttr) + UpdateTypeHierFileInPopup(lspserver, typeUriMap) +enddef + +# vim: tabstop=8 shiftwidth=2 softtabstop=2 diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index 7cd4c40..1af97b9 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -150,4 +150,47 @@ export def PushCursorToTagStack() }]}, 't') enddef +# Jump to the LSP 'location' in file 'fname'. The user specified +# window command modifiers (e.g. topleft) are in 'cmdmods'. +export def JumpToLspLocation(fname: string, location: dict, + cmdmods: string) + # jump to the file and line containing the symbol + if cmdmods == '' + var bnr: number = fname->bufnr() + if bnr != bufnr() + var wid = fname->bufwinid() + if wid != -1 + wid->win_gotoid() + else + if bnr != -1 + # Reuse an existing buffer. If the current buffer has unsaved changes + # and 'hidden' is not set or if the current buffer is a special + # buffer, then open the buffer in a new window. + if (&modified && !&hidden) || &buftype != '' + exe $'sbuffer {bnr}' + else + exe $'buf {bnr}' + endif + else + if (&modified && !&hidden) || &buftype != '' + # if the current buffer has unsaved changes and 'hidden' is not set, + # or if the current buffer is a special buffer, then open the file + # in a new window + exe $'split {fname}' + else + exe $'edit {fname}' + endif + endif + endif + endif + else + exe $'{cmdmods} split {fname}' + endif + # Set the previous cursor location mark. Instead of using setpos(), m' is + # used so that the current location is added to the jump list. + normal m' + setcursorcharpos(location.range.start.line + 1, + location.range.start.character + 1) +enddef + # vim: tabstop=8 shiftwidth=2 softtabstop=2 diff --git a/doc/lsp.txt b/doc/lsp.txt index 724464e..c567836 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -2,7 +2,7 @@ Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com) For Vim version 9.0 and above -Last change: Oct 30, 2022 +Last change: Nov 19, 2022 ============================================================================== *lsp-license* @@ -68,66 +68,68 @@ To use this plugin, add the following line to your .vimrc file: The following commands are provided: -:LspShowServers Display the list of registered LSP servers -:LspGotoDefinition Go to the definition of the symbol under cursor -:LspGotoDeclaration Go to the declaration of the symbol under cursor -:LspGotoTypeDef Go to the type definition of the symbol under cursor -:LspGotoImpl Go to the implementation of the symbol under cursor -:LspPeekDefinition Open the definition of the symbol under cursor in a - popup window. -:LspPeekDeclaration Open the declaration of the symbol under cursor in a - popup window. -:LspPeekTypeDef Open the type definition of the symbol under cursor in - a popup window. -:LspPeekImpl Open the implementation of the symbol under cursor in - a popup window. -:LspShowSignature Display the signature of the symbol under cursor. -:LspDiagShow Display the diagnostics messages from the LSP server - for the current buffer in a location list. +:LspCodeAction Apply the code action supplied by the LSP server to + the diagnostic in the current line. +:LspDiagCurrent Display the diagnostic message for the current line. :LspDiagFirst Jump to the first diagnostic message for the current buffer. +:LspDiagHighlightDisable + Disable highlighting lines with a diagnostic message + for the current Vim session. +:LspDiagHighlightEnable Enable highlighting lines with a diagnostic message + for the current Vim session. :LspDiagNext Jump to the next diagnostic message for the current buffer after the current line. :LspDiagPrev Jump to the previous diagnostic message for the current buffer before the current line. -:LspDiagCurrent Display the diagnostic message for the current line. -:LspDiagHighlightEnable Enable highlighting lines with a diagnostic message - for the current Vim session. -:LspDiagHighlightDisable - Disable highlighting lines with a diagnostic message - for the current Vim session. -:LspShowReferences Display the list of references to the keyword under - cursor in a new location list. -:LspPeekReferences Display the list of references to the keyword under - cursor in a location list associated with the preview - window. -:LspSwitchSourceHeader Switch between source and header files. -:LspHighlight Highlight all the matches for the keyword under cursor -:LspHighlightClear Clear all the matches highlighted by :LspHighlight -:LspOutline Show the list of symbols defined in the current file - in a separate window. +:LspDiagShow Display the diagnostics messages from the LSP server + for the current buffer in a location list. +:LspFold Fold the current file :LspFormat Format the current file using the LSP server. :{range}LspFormat Format the specified range of lines. +:LspGotoDeclaration Go to the declaration of the symbol under cursor +:LspGotoDefinition Go to the definition of the symbol under cursor +:LspGotoImpl Go to the implementation of the symbol under cursor +:LspGotoTypeDef Go to the type definition of the symbol under cursor +:LspHighlight Highlight all the matches for the keyword under cursor +:LspHighlightClear Clear all the matches highlighted by :LspHighlight :LspIncomingCalls Display the list of symbols calling the current symbol in a new location list. :LspOutgoingCalls Display the list of symbols called by the current symbol in a new location list. +:LspOutline Show the list of symbols defined in the current file + in a separate window. +:LspPeekDeclaration Open the declaration of the symbol under cursor in a + popup window. +:LspPeekDefinition Open the definition of the symbol under cursor in a + popup window. +:LspPeekImpl Open the implementation of the symbol under cursor in + a popup window. +:LspPeekReferences Display the list of references to the keyword under + cursor in a location list associated with the preview + window. +:LspPeekTypeDef Open the type definition of the symbol under cursor in + a popup window. :LspRename Rename the current symbol -:LspCodeAction Apply the code action supplied by the LSP server to - the diagnostic in the current line. -:LspSymbolSearch Perform a workspace wide search for a symbol :LspSelectionExpand Expand the current symbol range visual selection :LspSelectionShrink Shrink the current symbol range visual selection -:LspFold Fold the current file +:LspServerRestart Restart the LSP server for the current buffer. +:LspShowReferences Display the list of references to the keyword under + cursor in a new location list. +:LspShowServers Display the list of registered LSP servers +:LspShowServerCapabilities + Display the list of capabilities of a LSP server. +:LspShowSignature Display the signature of the symbol under cursor. +:LspSubTypeHierarchy Display the sub type hierarchy in a popup window. +:LspSuperTypeHierarchy Display the super type hierarchy in a popup window. +:LspSwitchSourceHeader Switch between source and header files. +:LspSymbolSearch Perform a workspace wide search for a symbol :LspWorkspaceAddFolder {folder} Add a folder to the workspace -:LspWorkspaceRemoveFolder {folder} - Remove a folder from the workspace :LspWorkspaceListFolders Show the list of folders in the workspace -:LspShowServerCapabilities - Display the list of capabilities of a LSP server. -:LspServerRestart Restart the LSP server for the current buffer. +:LspWorkspaceRemoveFolder {folder} + Remove a folder from the workspace ============================================================================== 4. Configuration *lsp-configuration* @@ -540,6 +542,20 @@ To get a particular option value you can use the following: > You can enter a new search pattern to do a workspace wide symbol search. + *:LspSuperTypeHierarchy* +:LspSuperTypeHierarchy Show the super type hierarchy for the symbol under the + cursor in a popup window. The file containing the + type is shown in another popup window. You can jump + to the location where a type is defined by browsing the + popup menu and selecting an entry. + + *:LspSubTypeHierarchy* +:LspSubTypeHierarchy Show the sub type hierarchy for the symbol under the + cursor in a popup window. The file containing the + type is shown in another popup window. You can jump + to the location where a type is defined by browsing the + popup menu and selecting an entry. + *:LspHover* :LspHover Show the documentation for the symbol under the cursor in a popup window. If you want to show the symbol diff --git a/plugin/lsp.vim b/plugin/lsp.vim index 342e459..31b5eb5 100644 --- a/plugin/lsp.vim +++ b/plugin/lsp.vim @@ -109,6 +109,8 @@ command! -nargs=0 -bar LspOutline lsp.Outline() command! -nargs=0 -bar -range=% LspFormat lsp.TextDocFormat(, , ) command! -nargs=0 -bar LspOutgoingCalls lsp.OutgoingCalls() command! -nargs=0 -bar LspIncomingCalls lsp.IncomingCalls() +command! -nargs=0 -bar LspSuperTypeHierarchy lsp.TypeHierarchy(1) +command! -nargs=0 -bar LspSubTypeHierarchy lsp.TypeHierarchy(0) command! -nargs=0 -bar LspRename lsp.Rename() command! -nargs=0 -bar -range LspCodeAction lsp.CodeAction(, ) command! -nargs=? -bar LspSymbolSearch lsp.SymbolSearch() -- 2.48.1