tessl install tessl/npm-coc-nvim@0.0.0LSP based intellisense engine for neovim & vim8.
Complexity: Intermediate | Category: Navigation
Common Tasks: View callers | View callees | Analyze call graph | Navigate call chains
View and navigate incoming and outgoing calls for functions and methods.
:call CocAction('showIncomingCalls')Display functions that call the current function.
:call CocAction('showOutgoingCalls')Display functions called by the current function.
let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
echo 'Callers: ' . len(incoming) . ', Callees: ' . len(outgoing)CocAction('showIncomingCalls')Show tree view of incoming calls (callers) for the function at cursor.
Example:
nnoremap <silent> <leader>ci :call CocAction('showIncomingCalls')<CR>
" Auto-show incoming calls for entry points
function! ShowCallersIfPublic() abort
let func = CocAction('getCurrentFunctionSymbol')
if func =~# '^public'
call CocAction('showIncomingCalls')
endif
endfunctionCocAction('showOutgoingCalls')Show tree view of outgoing calls (callees) for the function at cursor.
Example:
nnoremap <silent> <leader>co :call CocAction('showOutgoingCalls')<CR>
" Map both
nnoremap <silent> <leader>ci :call CocAction('showIncomingCalls')<CR>
nnoremap <silent> <leader>co :call CocAction('showOutgoingCalls')<CR>CocAction('incomingCalls')
" Returns: listGet incoming calls (callers) data without showing the tree view.
Returns: List of call hierarchy items with properties:
name: Function/method namekind: Symbol kinddetail: Additional detailslocation: Location object with uri and rangefromRanges: Array of ranges where calls occurExample:
let incoming = CocAction('incomingCalls')
echo 'Found ' . len(incoming) . ' callers'
for call in incoming
echo call.name . ' calls this function'
if has_key(call, 'location')
let file = fnamemodify(call.location.uri, ':t')
echo ' in ' . file
endif
endforCocAction('outgoingCalls')
" Returns: listGet outgoing calls (callees) data without showing the tree view.
Returns: List of call hierarchy items representing functions called by the current function.
Example:
let outgoing = CocAction('outgoingCalls')
echo 'This function calls ' . len(outgoing) . ' functions'
for call in outgoing
echo ' - ' . call.name
" Show call locations
if has_key(call, 'fromRanges')
for range in call.fromRanges
let line = range.start.line + 1
echo ' Line ' . line
endfor
endif
endforCall hierarchy settings in coc-settings.json:
{
"callHierarchy.enableTooltip": true,
"callHierarchy.openCommand": "edit"
}" Show incoming calls (who calls this function)
nnoremap <silent> <leader>ci :call CocAction('showIncomingCalls')<CR>
" Show outgoing calls (what this function calls)
nnoremap <silent> <leader>co :call CocAction('showOutgoingCalls')<CR>
" Show both in split
nnoremap <silent> <leader>ch :call ShowCallHierarchy()<CR>
function! ShowCallHierarchy() abort
call CocAction('showIncomingCalls')
vsplit
call CocAction('showOutgoingCalls')
endfunctionfunction! ShowCallInfo() abort
let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
let func = CocAction('getCurrentFunctionSymbol')
echo "=== Call Hierarchy for: " . func . " ==="
echo "Incoming calls (callers): " . len(incoming)
echo "Outgoing calls (callees): " . len(outgoing)
if len(incoming) > 0
echo "\nCallers:"
for call in incoming
echo " - " . call.name
if has_key(call, 'detail')
echo " " . call.detail
endif
endfor
endif
if len(outgoing) > 0
echo "\nCallees:"
for call in outgoing
echo " - " . call.name
endfor
endif
endfunction
command! CallInfo call ShowCallInfo()
nnoremap <silent> <leader>ch :CallInfo<CR>function! ListAllCallers() abort
let incoming = CocAction('incomingCalls')
if len(incoming) == 0
echo "No callers found"
return
endif
" Build quickfix list
let qflist = []
for call in incoming
if has_key(call, 'location')
let loc = call.location
call add(qflist, {
\ 'filename': substitute(loc.uri, 'file://', '', ''),
\ 'lnum': loc.range.start.line + 1,
\ 'col': loc.range.start.character + 1,
\ 'text': call.name . ' (caller)'
\ })
endif
endfor
call setqflist(qflist)
copen
echo len(incoming) . " callers found"
endfunction
command! ListCallers call ListAllCallers()function! CallGraphStats() abort
let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
let func = CocAction('getCurrentFunctionSymbol')
echo "=== Call Graph Statistics for: " . func . " ==="
echo "Direct callers: " . len(incoming)
echo "Direct callees: " . len(outgoing)
" Calculate fan-in and fan-out
echo "Fan-in (complexity of being called): " . len(incoming)
echo "Fan-out (complexity of calling others): " . len(outgoing)
" Determine function type
if len(incoming) == 0 && len(outgoing) > 0
echo "Type: Entry point / Root function"
elseif len(incoming) > 0 && len(outgoing) == 0
echo "Type: Leaf function"
elseif len(incoming) > 0 && len(outgoing) > 0
echo "Type: Intermediate function"
else
echo "Type: Isolated function (may be unused)"
endif
endfunction
command! CallStats call CallGraphStats()" Navigate to caller
function! GoToCaller(index) abort
let incoming = CocAction('incomingCalls')
if a:index >= len(incoming)
echo "Caller index out of range"
return
endif
let call = incoming[a:index]
if has_key(call, 'location')
let loc = call.location
let file = substitute(loc.uri, 'file://', '', '')
let line = loc.range.start.line + 1
let col = loc.range.start.character + 1
execute 'edit ' . fnameescape(file)
call cursor(line, col)
normal! zz
echo "Navigated to caller: " . call.name
endif
endfunction
command! -nargs=1 GoToCaller call GoToCaller(<args>)
" Quick navigation
nnoremap <silent> <leader>c1 :GoToCaller 0<CR>
nnoremap <silent> <leader>c2 :GoToCaller 1<CR>
nnoremap <silent> <leader>c3 :GoToCaller 2<CR>function! ShowCallPath() abort
" Build a simple call path
let current_func = CocAction('getCurrentFunctionSymbol')
if empty(current_func)
echo "Not in a function"
return
endif
let incoming = CocAction('incomingCalls')
let path = [current_func]
" Add callers (limited to prevent deep recursion)
for call in incoming[:2]
call add(path, call.name)
endfor
if len(incoming) > 3
call add(path, '...')
endif
echo "Call path: " . join(reverse(path), ' → ')
endfunction
nnoremap <silent> <leader>cp :call ShowCallPath()<CR>function! InteractiveCallTree(direction) abort
let calls = CocAction(a:direction ==# 'in' ? 'incomingCalls' : 'outgoingCalls')
let title = a:direction ==# 'in' ? 'Incoming' : 'Outgoing'
if len(calls) == 0
echo "No " . tolower(title) . " calls"
return
endif
let items = []
for i in range(len(calls))
let text = (i + 1) . '. ' . calls[i].name
if has_key(calls[i], 'detail')
let text .= ' - ' . calls[i].detail
endif
call add(items, text)
endfor
let choice = inputlist(['Select ' . tolower(title) . ' call to navigate:'] + items)
if choice > 0 && choice <= len(calls)
let call = calls[choice - 1]
if has_key(call, 'location')
let loc = call.location
let file = substitute(loc.uri, 'file://', '', '')
execute 'edit ' . fnameescape(file)
call cursor(loc.range.start.line + 1, loc.range.start.character + 1)
normal! zz
echo "Jumped to: " . call.name
endif
endif
endfunction
nnoremap <silent> <leader>ci :call InteractiveCallTree('in')<CR>
nnoremap <silent> <leader>co :call InteractiveCallTree('out')<CR>function! ExportCallHierarchy(filename) abort
let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
let current = CocAction('getCurrentFunctionSymbol')
let lines = []
call add(lines, "Call Hierarchy for: " . current)
call add(lines, "Generated: " . strftime('%Y-%m-%d %H:%M:%S'))
call add(lines, "")
call add(lines, "Incoming Calls (Callers): " . len(incoming))
for call in incoming
let line = " - " . call.name
if has_key(call, 'location')
let file = fnamemodify(call.location.uri, ':t')
let line .= " (" . file . ")"
endif
call add(lines, line)
endfor
call add(lines, "")
call add(lines, "Outgoing Calls (Callees): " . len(outgoing))
for call in outgoing
call add(lines, " - " . call.name)
endfor
call writefile(lines, a:filename)
echo "Call hierarchy exported to " . a:filename
endfunction
command! -nargs=1 -complete=file ExportCalls call ExportCallHierarchy(<q-args>)function! FindUnusedFunctions() abort
" Find functions with no incoming calls
let symbols = CocAction('documentSymbols')
let unused = []
function! CheckSymbol(symbol) abort closure
if a:symbol.kind ==# 'Function' || a:symbol.kind ==# 'Method'
" Would need to position cursor at symbol to check calls
" This is a simplified example
let incoming = CocAction('incomingCalls')
if len(incoming) == 0
call add(unused, a:symbol.name)
endif
endif
endfunction
" Note: This is expensive, checking each function individually
echo "Scanning for unused functions (this may take time)..."
if len(unused) == 0
echo "No obviously unused functions found"
else
echo "Potentially unused functions:"
for func in unused
echo " - " . func
endfor
echo "\nNote: Check if functions are entry points or exported"
endif
endfunction
command! FindUnused call FindUnusedFunctions()function! CallHierarchyToQuickFix(direction) abort
let calls = CocAction(a:direction ==# 'in' ? 'incomingCalls' : 'outgoingCalls')
let qflist = []
for call in calls
if has_key(call, 'location')
let loc = call.location
let text = call.name
if has_key(call, 'detail')
let text .= ' - ' . call.detail
endif
call add(qflist, {
\ 'filename': substitute(loc.uri, 'file://', '', ''),
\ 'lnum': loc.range.start.line + 1,
\ 'col': loc.range.start.character + 1,
\ 'text': text
\ })
endif
endfor
if len(qflist) > 0
call setqflist(qflist)
copen
echo len(qflist) . " calls loaded"
else
echo "No calls found"
endif
endfunction
command! IncomingCallsQF call CallHierarchyToQuickFix('in')
command! OutgoingCallsQF call CallHierarchyToQuickFix('out')
nnoremap <silent> <leader>cqi :IncomingCallsQF<CR>
nnoremap <silent> <leader>cqo :OutgoingCallsQF<CR>function! ShowCallCounts() abort
let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
let func = CocAction('getCurrentFunctionSymbol')
if empty(func)
echo "Not in a function"
return
endif
echo func . " is called by " . len(incoming) . " function(s)"
echo func . " calls " . len(outgoing) . " function(s)"
" Calculate complexity score
let complexity = len(outgoing)
if complexity == 0
echo "Complexity: Low (leaf function)"
elseif complexity <= 5
echo "Complexity: Moderate"
else
echo "Complexity: High (consider refactoring)"
endif
endfunction
nnoremap <silent> <leader>cc :call ShowCallCounts()<CR>Check if language server supports call hierarchy:
if CocHasProvider('callHierarchy')
call CocAction('showIncomingCalls')
else
echo "Language server doesn't support call hierarchy"
endifHandle cases where cursor isn't in a function:
let func = CocAction('getCurrentFunctionSymbol')
if empty(func)
echo "Place cursor in a function to view call hierarchy"
else
call CocAction('showIncomingCalls')
endifProblem: Call hierarchy commands show nothing.
Solutions:
echo CocHasProvider('callHierarchy')echo CocAction('getCurrentFunctionSymbol'):CocOpenLog:CocRestart:CocUpdateProblem: Some calls are missing.
Solutions:
:CocList folders:CocRestartProblem: Call hierarchy is slow to load.
Solutions:
"callHierarchy.enableTooltip": false