or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/coc.nvim@0.0.x

docs

index.md
tile.json

tessl/npm-coc-nvim

tessl install tessl/npm-coc-nvim@0.0.0

LSP based intellisense engine for neovim & vim8.

call-hierarchy.mddocs/navigation/

Call Hierarchy [Intermediate]

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.

Common Tasks

Show Incoming Calls (Callers)

:call CocAction('showIncomingCalls')

Display functions that call the current function.

Show Outgoing Calls (Callees)

:call CocAction('showOutgoingCalls')

Display functions called by the current function.

Get Call Data

let incoming = CocAction('incomingCalls')
let outgoing = CocAction('outgoingCalls')
echo 'Callers: ' . len(incoming) . ', Callees: ' . len(outgoing)

CocAction API

showIncomingCalls

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
endfunction

showOutgoingCalls

CocAction('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>

incomingCalls

CocAction('incomingCalls')
" Returns: list

Get incoming calls (callers) data without showing the tree view.

Returns: List of call hierarchy items with properties:

  • name: Function/method name
  • kind: Symbol kind
  • detail: Additional details
  • location: Location object with uri and range
  • fromRanges: Array of ranges where calls occur

Example:

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
endfor

outgoingCalls

CocAction('outgoingCalls')
" Returns: list

Get 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
endfor

Configuration

Call hierarchy settings in coc-settings.json:

{
  "callHierarchy.enableTooltip": true,
  "callHierarchy.openCommand": "edit"
}

Examples

Basic Call Hierarchy

" 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')
endfunction

Display Call Information

function! 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>

List All Callers in QuickFix

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()

Call Graph Statistics

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 Call Chain

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

Show Call Path

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>

Interactive Call Tree

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>

Export Call Hierarchy

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>)

Find Unused Functions

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()

Call Hierarchy in QuickFix

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>

Call Count Display

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>

Error Handling

No Call Hierarchy Support

Check if language server supports call hierarchy:

if CocHasProvider('callHierarchy')
  call CocAction('showIncomingCalls')
else
  echo "Language server doesn't support call hierarchy"
endif

Not in Function

Handle 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')
endif

Troubleshooting

Call Hierarchy Not Working

Problem: Call hierarchy commands show nothing.

Solutions:

  1. Check LSP support: echo CocHasProvider('callHierarchy')
  2. Verify cursor is in function: echo CocAction('getCurrentFunctionSymbol')
  3. Check logs: :CocOpenLog
  4. Restart language server: :CocRestart
  5. Update extension: :CocUpdate

Incomplete Call Information

Problem: Some calls are missing.

Solutions:

  1. Language server may not have indexed all files
  2. Save all files and wait for indexing
  3. Check workspace folders: :CocList folders
  4. Restart language server: :CocRestart

Slow Performance

Problem: Call hierarchy is slow to load.

Solutions:

  1. Large codebases may take time to analyze
  2. Disable tooltip: "callHierarchy.enableTooltip": false
  3. Check language server performance in logs
  4. Consider limiting workspace scope

See Also

  • Code Navigation - Jump to definitions and references
  • Symbols & Outline - View document structure
  • Workspace - Workspace-wide symbol operations
  • List Interface - Interactive navigation