From 5b21a2d4d2338ed35276fabed6e50dec832e4c49 Mon Sep 17 00:00:00 2001 From: Yegappan Lakshmanan Date: Wed, 2 Feb 2022 19:00:16 -0800 Subject: [PATCH] Add support for the LspCalling and LspCalledBy commands --- README.md | 4 +- autoload/callhierarchy.vim | 89 ++++++++++++++++++++++++++++++++++++++ autoload/handlers.vim | 51 ++++++++++++++++++---- autoload/lsp.vim | 31 ++++++++++++- autoload/lspoptions.vim | 3 +- autoload/lspserver.vim | 49 ++++++++++++++++++--- doc/lsp.txt | 14 +++--- 7 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 autoload/callhierarchy.vim diff --git a/README.md b/README.md index 39edd9b..29bbb17 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,8 @@ Command|Description :LspOutline|Show the list of symbols defined in the current file in a separate window. :LspFormat|Format the current file using the LSP server. :{range}LspFormat|Format the specified range of lines. -:LspCalledBy|Display the list of symbols called by the current symbol. (NOT IMPLEMENTED YET). -:LspCalling|Display the list of symbols calling the current symbol (NOT IMPLEMENTED YET). +:LspCalledBy|Display the list of symbols called by the current symbol. +:LspCalling|Display the list of symbols calling the current symbol. :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 diff --git a/autoload/callhierarchy.vim b/autoload/callhierarchy.vim new file mode 100644 index 0000000..9d20dd7 --- /dev/null +++ b/autoload/callhierarchy.vim @@ -0,0 +1,89 @@ +vim9script + +# Functions for dealing with call hierarchy (incoming/outgoing calls) + +var util = {} +if has('patch-8.2.4019') + import './util.vim' as util_import + + util.WarnMsg = util_import.WarnMsg + util.LspUriToFile = util_import.LspUriToFile + util.GetLineByteFromPos = util_import.GetLineByteFromPos +else + import {WarnMsg, + LspUriToFile, + GetLineByteFromPos} from './util.vim' + + util.WarnMsg = WarnMsg + util.LspUriToFile = LspUriToFile + util.GetLineByteFromPos = GetLineByteFromPos +endif + +def s:createLoclistWithCalls(calls: list>, incoming: bool) + var qflist: list> = [] + + for item in calls + var fname: string + if incoming + fname = util.LspUriToFile(item.from.uri) + else + fname = util.LspUriToFile(item.to.uri) + endif + var bnr: number = fname->bufnr() + if bnr == -1 + bnr = fname->bufadd() + endif + if !bnr->bufloaded() + bnr->bufload() + endif + + var name: string + if incoming + name = item.from.name + else + name = item.to.name + endif + + if incoming + for r in item.fromRanges + var text: string = + bnr->getbufline(r.start.line + 1)[0]->trim("\t ", 1) + qflist->add({filename: fname, + lnum: r.start.line + 1, + col: util.GetLineByteFromPos(bnr, r.start) + 1, + text: name .. ': ' .. text}) + endfor + else + var pos: dict = item.to.range.start + var text: string = bnr->getbufline(pos.line + 1)[0]->trim("\t ", 1) + qflist->add({filename: fname, + lnum: item.to.range.start.line + 1, + col: util.GetLineByteFromPos(bnr, pos) + 1, + text: name .. ': ' .. text}) + endif + endfor + var save_winid = win_getid() + setloclist(0, [], ' ', {title: 'Incoming Calls', items: qflist}) + lopen + save_winid->win_gotoid() +enddef + +export def IncomingCalls(calls: list>) + if calls->empty() + util.WarnMsg('No incoming calls') + return + endif + + s:createLoclistWithCalls(calls, true) +enddef + +export def OutgoingCalls(calls: list>) + if calls->empty() + util.WarnMsg('No outgoing calls') + return + endif + + s:createLoclistWithCalls(calls, false) +enddef + +# vim: shiftwidth=2 softtabstop=2 diff --git a/autoload/handlers.vim b/autoload/handlers.vim index aa82659..43cf265 100644 --- a/autoload/handlers.vim +++ b/autoload/handlers.vim @@ -11,6 +11,7 @@ var outline = {} var textedit = {} var symbol = {} var codeaction = {} +var callhier = {} if has('patch-8.2.4019') import './lspoptions.vim' as opt_import @@ -20,6 +21,7 @@ if has('patch-8.2.4019') import './textedit.vim' as textedit_import import './symbol.vim' as symbol_import import './codeaction.vim' as codeaction_import + import './callhierarchy.vim' as callhierarchy_import opt.lspOptions = opt_import.lspOptions util.WarnMsg = util_import.WarnMsg @@ -34,6 +36,8 @@ if has('patch-8.2.4019') symbol.ShowReferences = symbol_import.ShowReferences symbol.GotoSymbol = symbol_import.GotoSymbol codeaction.ApplyCodeAction = codeaction_import.ApplyCodeAction + callhier.IncomingCalls = callhierarchy_import.IncomingCalls + callhier.OutgoingCalls = callhierarchy_import.OutgoingCalls else import lspOptions from './lspoptions.vim' import {WarnMsg, @@ -46,6 +50,7 @@ else import {ApplyTextEdits, ApplyWorkspaceEdit} from './textedit.vim' import {ShowReferences, GotoSymbol} from './symbol.vim' import ApplyCodeAction from './codeaction.vim' + import {IncomingCalls, OutgoingCalls} from './callhierarchy.vim' opt.lspOptions = lspOptions util.WarnMsg = WarnMsg @@ -60,6 +65,8 @@ else symbol.ShowReferences = ShowReferences symbol.GotoSymbol = GotoSymbol codeaction.ApplyCodeAction = ApplyCodeAction + callhier.IncomingCalls = IncomingCalls + callhier.OutgoingCalls = OutgoingCalls endif # process the 'initialize' method reply from the LSP server @@ -677,19 +684,43 @@ enddef # Result: CallHierarchyItem[] | null def s:processPrepareCallHierarchy(lspserver: dict, req: dict, reply: dict) if reply.result->empty() + if lspserver.callHierarchyType == 'incoming' + util.WarnMsg('No incoming calls') + else + util.WarnMsg('No outgoing calls') + endif return endif - var items: list = ['Select a Call Hierarchy Item:'] - for i in range(reply.result->len()) - items->add(printf("%d. %s", i + 1, reply.result[i].name)) - endfor - var choice = inputlist(items) - if choice < 1 || choice > items->len() - return + var choice: number = 1 + if reply.result->len() > 1 + var items: list = ['Select a Call Hierarchy Item:'] + for i in range(reply.result->len()) + items->add(printf("%d. %s", i + 1, reply.result[i].name)) + endfor + choice = inputlist(items) + if choice < 1 || choice > items->len() + return + endif endif - echomsg reply.result[choice - 1] + if lspserver.callHierarchyType == 'incoming' + LspGetIncomingCalls(reply.result[choice - 1]) + else + LspGetOutgoingCalls(reply.result[choice - 1]) + endif +enddef + +# process the 'callHierarchy/incomingCalls' reply from the LSP server +# Result: CallHierarchyIncomingCall[] | null +def s:processIncomingCalls(lspserver: dict, req: dict, reply: dict) + callhier.IncomingCalls(reply.result) +enddef + +# process the 'callHierarchy/outgoingCalls' reply from the LSP server +# Result: CallHierarchyOutgoingCall[] | null +def s:processOutgoingCalls(lspserver: dict, req: dict, reply: dict) + callhier.OutgoingCalls(reply.result) enddef # Process various reply messages from the LSP server @@ -715,7 +746,9 @@ export def ProcessReply(lspserver: dict, req: dict, reply: dict): 'textDocument/foldingRange': function('s:processFoldingRangeReply'), 'workspace/executeCommand': function('s:processWorkspaceExecuteReply'), 'workspace/symbol': function('s:processWorkspaceSymbolReply'), - 'textDocument/prepareCallHierarchy': function('s:processPrepareCallHierarchy') + 'textDocument/prepareCallHierarchy': function('s:processPrepareCallHierarchy'), + 'callHierarchy/incomingCalls': function('s:processIncomingCalls'), + 'callHierarchy/outgoingCalls': function('s:processOutgoingCalls') } if lsp_reply_handlers->has_key(req.method) diff --git a/autoload/lsp.vim b/autoload/lsp.vim index f51996e..822c465 100644 --- a/autoload/lsp.vim +++ b/autoload/lsp.vim @@ -734,14 +734,41 @@ export def IncomingCalls() return endif + lspserver.callHierarchyType = 'incoming' var fname: string = @% - lspserver.incomingCalls(fname) + lspserver.prepareCallHierarchy(fname) enddef +def g:LspGetIncomingCalls(item: dict) + var lspserver: dict = s:curbufGetServerChecked() + if lspserver->empty() + return + endif + + lspserver.incomingCalls(item) +enddef + +def g:LspGetOutgoingCalls(item: dict) + var lspserver: dict = s:curbufGetServerChecked() + if lspserver->empty() + return + endif + + lspserver.outgoingCalls(item) +enddef + + # Display all the symbols used by the current symbol. # Uses LSP "callHierarchy/outgoingCalls" request export def OutgoingCalls() - :echomsg 'Error: Not implemented yet' + var lspserver: dict = s:curbufGetServerChecked() + if lspserver->empty() + return + endif + + lspserver.callHierarchyType = 'outgoing' + var fname: string = @% + lspserver.prepareCallHierarchy(fname) enddef # Rename a symbol diff --git a/autoload/lspoptions.vim b/autoload/lspoptions.vim index 4b99d37..b8ac3f2 100644 --- a/autoload/lspoptions.vim +++ b/autoload/lspoptions.vim @@ -25,7 +25,8 @@ export var lspOptions: dict = { outlineWinSize: 20, # Open outline window on right side outlineOnRight: false, - # Suppress diagnostic hover from appearing when the mouse is over the line instead of the signature + # Suppress diagnostic hover from appearing when the mouse is over the line + # instead of the signature noDiagHoverOnLine: true, # Show a diagnostic message on a status line showDiagOnStatusLine: false, diff --git a/autoload/lspserver.vim b/autoload/lspserver.vim index 3b94373..c0ee844 100644 --- a/autoload/lspserver.vim +++ b/autoload/lspserver.vim @@ -678,10 +678,10 @@ def s:textDocFormat(lspserver: dict, fname: string, rangeFormat: bool, lspserver.sendMessage(req) enddef -# Request: "callHierarchy/incomingCalls" -# Param: CallHierarchyIncomingCallsParams -def s:incomingCalls(lspserver: dict, fname: string) - # Check whether LSP server supports incoming calls +# Request: "textDocument/prepareCallHierarchy" +# Param: CallHierarchyPrepareParams +def s:prepareCallHierarchy(lspserver: dict, fname: string) + # Check whether LSP server supports call hierarchy if !lspserver.caps->has_key('callHierarchyProvider') || !lspserver.caps.callHierarchyProvider util.ErrMsg("Error: LSP server does not support call hierarchy") @@ -696,6 +696,42 @@ def s:incomingCalls(lspserver: dict, fname: string) lspserver.sendMessage(req) enddef +# Request: "callHierarchy/incomingCalls" +# Param: CallHierarchyItem +def s:incomingCalls(lspserver: dict, hierItem: dict) + # Check whether LSP server supports call hierarchy + if !lspserver.caps->has_key('callHierarchyProvider') + || !lspserver.caps.callHierarchyProvider + util.ErrMsg("Error: LSP server does not support call hierarchy") + return + endif + + var req = lspserver.createRequest('callHierarchy/incomingCalls') + + # interface CallHierarchyIncomingCallsParams + # interface CallHierarchyItem + req.params->extend({item: hierItem}) + lspserver.sendMessage(req) +enddef + +# Request: "callHierarchy/outgoingCalls" +# Param: CallHierarchyItem +def s:outgoingCalls(lspserver: dict, hierItem: dict) + # Check whether LSP server supports call hierarchy + if !lspserver.caps->has_key('callHierarchyProvider') + || !lspserver.caps.callHierarchyProvider + util.ErrMsg("Error: LSP server does not support call hierarchy") + return + endif + + var req = lspserver.createRequest('callHierarchy/outgoingCalls') + + # interface CallHierarchyOutgoingCallsParams + # interface CallHierarchyItem + req.params->extend({item: hierItem}) + lspserver.sendMessage(req) +enddef + # Request: "textDocument/rename" # Param: RenameParams def s:renameSymbol(lspserver: dict, newName: string) @@ -885,7 +921,8 @@ export def NewLspServer(path: string, args: list): dict diagsMap: {}, workspaceSymbolPopup: 0, workspaceSymbolQuery: '', - peekSymbol: false + peekSymbol: false, + callHierarchyType: '' } # Add the LSP server functions lspserver->extend({ @@ -922,7 +959,9 @@ export def NewLspServer(path: string, args: list): dict docHighlight: function('s:docHighlight', [lspserver]), getDocSymbols: function('s:getDocSymbols', [lspserver]), textDocFormat: function('s:textDocFormat', [lspserver]), + prepareCallHierarchy: function('s:prepareCallHierarchy', [lspserver]), incomingCalls: function('s:incomingCalls', [lspserver]), + outgoingCalls: function('s:outgoingCalls', [lspserver]), renameSymbol: function('s:renameSymbol', [lspserver]), codeAction: function('s:codeAction', [lspserver]), workspaceQuery: function('s:workspaceQuerySymbols', [lspserver]), diff --git a/doc/lsp.txt b/doc/lsp.txt index e4e2843..8bbce82 100644 --- a/doc/lsp.txt +++ b/doc/lsp.txt @@ -2,7 +2,7 @@ Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com) For Vim version 8.2.2342 and above -Last change: Jan 21, 2022 +Last change: Feb 2, 2022 ============================================================================== *lsp-license* @@ -108,9 +108,9 @@ The following commands are provided: :LspFormat Format the current file using the LSP server. :{range}LspFormat Format the specified range of lines. :LspCalledBy Display the list of symbols called by the current - symbol. (NOT IMPLEMENTED YET). + symbol in a new location list. :LspCalling Display the list of symbols calling the current symbol - (NOT IMPLEMENTED YET). + in a new location list. :LspRename Rename the current symbol :LspCodeAction Apply the code action supplied by the LSP server to the diagnostic in the current line. @@ -384,12 +384,12 @@ diagnostic messages, you can add the following line to your .vimrc file: file using the LSP server. *:LspCalledBy* -:LspCalledBy Display the list of symbols called by the current - symbol. (NOT IMPLEMENTED YET). +:LspCalledBy Creates a new location list with the location of + the list of symbols called by the current symbol. *:LspCalling* -:LspCalling Display the list of symbols calling the current symbol - (NOT IMPLEMENTED YET). +:LspCalling Creates a new location list with the location of the + list of symbols calling the current symbol. *:LspRename* :LspRename Rename the current symbol. You will be prompted to -- 2.48.1