# Functions for dealing with call hierarchy (incoming/outgoing calls)
import './util.vim'
+import './buffer.vim' as buf
def CreateLoclistWithCalls(calls: list<dict<any>>, incoming: bool)
var qflist: list<dict<any>> = []
save_winid->win_gotoid()
enddef
-export def IncomingCalls(calls: list<dict<any>>)
- if calls->empty()
+# Jump to the location of the symbol under the cursor in the call hierarchy
+# tree window.
+def CallHierarchyItemJump()
+ var item: dict<any> = w:LspCallHierItemMap[line('.')].item
+ util.JumpToLspLocation(item, '')
+enddef
+
+# Refresh the call hierarchy tree for the symbol at index 'idx'.
+def CallHierarchyTreeItemRefresh(idx: number)
+ var treeItem: dict<any> = w:LspCallHierItemMap[idx]
+
+ if treeItem.open
+ # Already retrieved the children for this item
+ return
+ endif
+
+ if !treeItem->has_key('children')
+ # First time retrieving the children for the item at index 'idx'
+ var lspserver = buf.BufLspServerGet(w:LspBufnr)
+ if lspserver->empty() || !lspserver.running
+ return
+ endif
+
+ var reply: any
+ if w:LspCallHierIncoming
+ reply = lspserver.getIncomingCalls(treeItem.item)
+ else
+ reply = lspserver.getOutgoingCalls(treeItem.item)
+ endif
+
+ treeItem.children = []
+ if !reply->empty()
+ for item in reply
+ treeItem.children->add({item: w:LspCallHierIncoming ? item.from :
+ item.to, open: false})
+ endfor
+ endif
+ endif
+
+ # Clear and redisplay the tree in the window
+ treeItem.open = true
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Open the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemOpen()
+ CallHierarchyTreeItemRefresh(line('.'))
+enddef
+
+# Refresh the entire call hierarchy tree
+def CallHierarchyTreeRefreshCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the incoming call hierarchy tree
+def CallHierarchyTreeIncomingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = true
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Display the outgoing call hierarchy tree
+def CallHierarchyTreeOutgoingCmd()
+ w:LspCallHierItemMap[2].open = false
+ w:LspCallHierItemMap[2]->remove('children')
+ w:LspCallHierIncoming = false
+ CallHierarchyTreeItemRefresh(2)
+enddef
+
+# Close the call hierarchy tree item under the cursor
+def CallHierarchyTreeItemClose()
+ var treeItem: dict<any> = w:LspCallHierItemMap[line('.')]
+ treeItem.open = false
+ var save_cursor = getcurpos()
+ CallHierarchyTreeRefresh()
+ setpos('.', save_cursor)
+enddef
+
+# Recursively add the call hierarchy items to w:LspCallHierItemMap
+def CallHierarchyTreeItemShow(incoming: bool, treeItem: dict<any>, pfx: string)
+ var item = treeItem.item
+ var treePfx: string
+ if treeItem.open && treeItem->has_key('children')
+ treePfx = '▼'
+ else
+ treePfx = '▶'
+ endif
+ var fname = util.LspUriToFile(item.uri)
+ var s = $'{pfx}{treePfx} {item.name} ({fname->fnamemodify(":t")} [{fname->fnamemodify(":h")}])'
+ append('$', s)
+ w:LspCallHierItemMap->add(treeItem)
+ if treeItem.open && treeItem->has_key('children')
+ for child in treeItem.children
+ CallHierarchyTreeItemShow(incoming, child, $'{pfx} ')
+ endfor
+ endif
+enddef
+
+def CallHierarchyTreeRefresh()
+ :setlocal modifiable
+ :silent! :%d _
+
+ setline(1, $'# {w:LspCallHierIncoming ? "Incoming calls to" : "Outgoing calls from"} "{w:LspCallHierarchyTree.item.name}"')
+ w:LspCallHierItemMap = [{}, {}]
+ CallHierarchyTreeItemShow(w:LspCallHierIncoming, w:LspCallHierarchyTree, '')
+ :setlocal nomodifiable
+enddef
+
+def CallHierarchyTreeShow(incoming: bool, prepareItem: dict<any>,
+ items: list<dict<any>>)
+ var save_bufnr = bufnr()
+ var wid = bufwinid('LSP-CallHierarchy')
+ if wid != -1
+ wid->win_gotoid()
+ else
+ :new LSP-CallHierarchy
+ :setlocal buftype=nofile
+ :setlocal bufhidden=wipe
+ :setlocal noswapfile
+ :setlocal nonumber nornu
+ :setlocal fdc=0 signcolumn=no
+
+ nnoremap <buffer> <CR> <ScriptCmd>CallHierarchyItemJump()<CR>
+ nnoremap <buffer> - <ScriptCmd>CallHierarchyTreeItemOpen()<CR>
+ nnoremap <buffer> + <ScriptCmd>CallHierarchyTreeItemClose()<CR>
+ command -buffer LspCallHierarchyRefresh CallHierarchyTreeRefreshCmd()
+ command -buffer LspCallHierarchyIncoming CallHierarchyTreeIncomingCmd()
+ command -buffer LspCallHierarchyOutgoing CallHierarchyTreeOutgoingCmd()
+
+ syntax match Comment '^#.*$'
+ syntax match Directory '(.*)$'
+ endif
+
+ w:LspBufnr = save_bufnr
+ w:LspCallHierIncoming = incoming
+ w:LspCallHierarchyTree = {}
+ w:LspCallHierarchyTree.item = prepareItem
+ w:LspCallHierarchyTree.open = true
+ w:LspCallHierarchyTree.children = []
+ for item in items
+ w:LspCallHierarchyTree.children->add({item: incoming ? item.from : item.to, open: false})
+ endfor
+
+ CallHierarchyTreeRefresh()
+
+ setlocal nomodified
+ setlocal nomodifiable
+enddef
+
+export def IncomingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No incoming calls')
+ return
+ endif
+
+ var reply = lspserver.getIncomingCalls(prepareReply)
+ if reply->empty()
util.WarnMsg('No incoming calls')
return
endif
- CreateLoclistWithCalls(calls, true)
+ CallHierarchyTreeShow(true, prepareReply, reply)
enddef
-export def OutgoingCalls(calls: list<dict<any>>)
- if calls->empty()
+export def OutgoingCalls(lspserver: dict<any>)
+ var prepareReply = lspserver.prepareCallHierarchy()
+ if prepareReply->empty()
+ util.WarnMsg('No outgoing calls')
+ return
+ endif
+
+ var reply = lspserver.getOutgoingCalls(prepareReply)
+ if reply->empty()
util.WarnMsg('No outgoing calls')
return
endif
- CreateLoclistWithCalls(calls, false)
+ CallHierarchyTreeShow(false, prepareReply, reply)
enddef
# vim: tabstop=8 shiftwidth=2 softtabstop=2
return
endif
- var reply = PrepareCallHierarchy(lspserver)
- if reply->empty()
- util.WarnMsg('No incoming calls')
- return
- endif
+ callhier.IncomingCalls(lspserver)
+enddef
+def GetIncomingCalls(lspserver: dict<any>, item: dict<any>): any
# Request: "callHierarchy/incomingCalls"
# Param: CallHierarchyIncomingCallsParams
var param = {}
- param.item = reply
- reply = lspserver.rpc('callHierarchy/incomingCalls', param)
- if reply->empty() || reply.result->empty()
- util.WarnMsg('No incoming calls')
- return
+ param.item = item
+ var reply = lspserver.rpc('callHierarchy/incomingCalls', param)
+ if reply->empty()
+ return null
endif
-
- callhier.IncomingCalls(reply.result)
+ return reply.result
enddef
# Request: "callHierarchy/outgoingCalls"
return
endif
- var reply = PrepareCallHierarchy(lspserver)
- if reply->empty()
- util.WarnMsg('No outgoing calls')
- return
- endif
+ callhier.OutgoingCalls(lspserver)
+enddef
+def GetOutgoingCalls(lspserver: dict<any>, item: dict<any>): any
# Request: "callHierarchy/outgoingCalls"
# Param: CallHierarchyOutgoingCallsParams
var param = {}
- param.item = reply
- reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
- if reply->empty() || reply.result->empty()
- util.WarnMsg('No outgoing calls')
- return
+ param.item = item
+ var reply = lspserver.rpc('callHierarchy/outgoingCalls', param)
+ if reply->empty()
+ return null
endif
-
- callhier.OutgoingCalls(reply.result)
+ return reply.result
enddef
# Request: "textDocument/typehierarchy"
docHighlight: function(DocHighlight, [lspserver]),
getDocSymbols: function(GetDocSymbols, [lspserver]),
textDocFormat: function(TextDocFormat, [lspserver]),
+ prepareCallHierarchy: function(PrepareCallHierarchy, [lspserver]),
incomingCalls: function(IncomingCalls, [lspserver]),
+ getIncomingCalls: function(GetIncomingCalls, [lspserver]),
outgoingCalls: function(OutgoingCalls, [lspserver]),
+ getOutgoingCalls: function(GetOutgoingCalls, [lspserver]),
typeHierarchy: function(TypeHiearchy, [lspserver]),
renameSymbol: function(RenameSymbol, [lspserver]),
codeAction: function(CodeAction, [lspserver]),
# 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}'
+ exe $'belowright sbuffer {bnr}'
else
exe $'buf {bnr}'
endif
# 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}'
+ exe $'belowright split {fname}'
else
exe $'edit {fname}'
endif
Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com)
For Vim version 9.0 and above
-Last change: Nov 19, 2022
+Last change: Nov 23, 2022
==============================================================================
*lsp-license*
: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.
+ in a window.
:LspOutgoingCalls Display the list of symbols called by the current
- symbol in a new location list.
+ symbol in a window.
: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
file using the LSP server.
*:LspIncomingCalls*
-:LspIncomingCalls Creates a new location list with the location of the
- list of symbols calling the current symbol.
+:LspIncomingCalls Display a hierarchy of symbols calling the symbol
+ under the cursor in a window. See
+ |lsp-call-hierarchy| for more information.
*:LspOutoingCalls*
-:LspOutoingCalls Creates a new location list with the location of
- the list of symbols called by the current symbol.
+:LspOutoingCalls Display a hierarchy of symbols called by the symbol
+ under the cursor in a window. See
+ |lsp-call-hierarchy| for more information.
*:LspRename*
:LspRename Rename the current symbol. You will be prompted to
the symbol is defined in multiple places in the code.
==============================================================================
-9. Autocommands *lsp-autocmds*
+9. Call Hierarchy *lsp-call-hierarchy*
+
+The |:LspIncomingCalls| and the |:LspOutoingCalls| commands can be used to
+display the call hierarchy of a symbol. For example, the functions calling a
+function or the functions called by a function. These two commands open a
+window containing the call hierarchy tree. You can use the Vim motion
+commands to browse the call hierarchy.
+
+In the call hierarchy tree window, the following keys are supported:
+
+<Enter> Jump to the location of the symbol under the
+ cursor.
+- Expand and show the symbols calling or called
+ by the symbol under the cursor.
++ Close the call hierarchy for the symbol under
+ the cursor.
+
+You can display either the incoming call hierarchy or the outgoing call
+hierarchy in this window. You cannot display both at the same time.
+
+In the call hierarchy tree window, the following commands are supported:
+
+:LspCallHierarchyRefresh Query the language server again for the top
+ level symbol and refresh the call hierarchy
+ tree.
+:LspCallHierarchyIncoming Display the incoming call hierarchy for the
+ top level symbol. If the window is currently
+ displaying the outgoing calls, then it is
+ refreshed to display the incoming calls.
+:LspCallHierarchyOutgoing Display the outgoing call hierarchy for the
+ top level symbol. If the window is currently
+ displaying the incoming calls, then it is
+ refreshed to display the outgoing calls.
+
+==============================================================================
+10. Autocommands *lsp-autocmds*
*LspAttached*
LspAttached A |User| autocommand fired when the LSP client
has processed the diagnostics.
==============================================================================
-10. Debugging *lsp-debug*
+11. Debugging *lsp-debug*
To debug this plugin, you can log the language server protocol messages sent
and received by the plugin from the language server. The following command
:LspServerTrace { off | messages | verbose }
<
==============================================================================
-11. Custom Command Handlers *lsp-custom-commands*
+12. Custom Command Handlers *lsp-custom-commands*
When applying a code action, the language server may issue a non-standard
command. For example, the Java language server uses non-standard commands
:sleep 1
cursor(1, 6)
:LspIncomingCalls
- assert_equal(2, winnr('$'))
- var l = getloclist(0)
- assert_equal([7, 3], [l[0].lnum, l[0].col])
- assert_equal('aFunc: xFunc();', l[0].text)
- assert_equal([12, 3], [l[1].lnum, l[1].col])
- assert_equal('bFunc: xFunc();', l[1].text)
- setloclist(0, [], 'f')
+ assert_equal([1, 2], [winnr(), winnr('$')])
+ var l = getline(1, '$')
+ assert_equal('# Incoming calls to "xFunc"', l[0])
+ assert_match('▼ xFunc (Xtest.c \[.*\])', l[1])
+ assert_match(' ▶ aFunc (Xtest.c \[.*\])', l[2])
+ assert_match(' ▶ bFunc (Xtest.c \[.*\])', l[3])
:%bw!
enddef