A Neovim plugin for visualizing call graphs, based on LSP call hierarchy features.
- Multi-directional analysis: Visualize incoming, outgoing, or mixed call graphs for functions and methods
- Export to Graphviz: Export call graphs to DOT format with subgraphs grouped by file location
- Flexible filtering: Custom location filters with invert option to include/exclude specific paths
- Customizable depth limits: Independent depth control for incoming and outgoing calls
- Recursive call detection: Visual highlighting of recursive function calls with special styling
- Multi-location support: Analyze call graphs from multiple cursor positions simultaneously
- Extensible hooks: Custom callbacks for analysis start and completion events
- Symbol type styling: Different colors for functions vs methods
- Neovim 0.12+ (Nightly recommended; not tested on 0.11 stable)
- An LSP server that supports call hierarchy (e.g., clangd)
- A Graphviz DOT visualizer of your choice (e.g.,
xdotrecomended,graphviz, online viewers)
Recommended installation with lazy.nvim:
{
"barreiroleo/callgraph.nvim",
opts = {},
keys = {
{ '<leader>ci', function() require("callgraph").run({ direction = "in" }) end, desc = 'Callgraph: incoming calls' },
{ '<leader>co', function() require("callgraph").run({ direction = "out" }) end, desc = 'Callgraph: outgoing calls' },
{ '<leader>cm', function() require("callgraph").run({ direction = "mix" }) end, desc = 'Callgraph: mixed calls' },
},
}opts = {
run = {
direction = "in", -- "in", "out", or "mix" for incoming, outgoing, or both
depth_limit_in = 10, -- Maximum depth for incoming calls
depth_limit_out = 6, -- Maximum depth for outgoing calls
filter_location = { "/usr/include/c", "/usr/include/c++" }, -- Paths to filter out
invert_filter = false, -- If true, only include paths in filter_location
root_location = vim.uri_from_fname(vim.fn.getcwd()), -- Root directory for analysis
-- Alternative: filter_location can be a function for custom filtering
-- filter_location = function(uri)
-- for _, filter in ipairs({ "/usr/include/c", "/usr/include/c++" }) do
-- if uri:find(filter or "", 1, true) then
-- vim.notify("Filtered out call to " .. uri, vim.log.levels.TRACE)
-- return true
-- end
-- end
-- return false
-- end,
},
export = {
file_path = "/tmp/callgraph.dot", -- Output file path
graph_name = "CallGraph", -- Name for the generated graph
direction = "LR", -- Graph layout: TB, LR, BT, RL
},
_dev = { -- Expect breaking changes from this API
profiling = false, -- Enable performance profiling
log_level = vim.log.levels.TRACE, -- Logging level for debugging
on_start = nil, -- Callback when analysis starts: function(opts)
on_finish = nil, -- Callback when analysis finishes: function(root_node)
}
}require("callgraph").setup(opts)- Initialize the plugin with configurationrequire("callgraph").run(opts, dev_opts)- Run call graph analysisrequire("callgraph").add_location()- Add current cursor position to analysis queue
direction:"in"|"out"|"mix"- Analysis directionfilter_location:string[]orfunction(uri: string): boolean- Location filteringinvert_filter:boolean- Whether to invert the filter logicdepth_limit_in/out:integer- Maximum analysis depth- Graph export supports multiple layouts:
"TB","LR","BT","RL"
-
Open a file supported by your LSP.
-
Run the analyzer through the Lua API:
-- Analyze incoming calls require("callgraph").run({ direction = "in" }) -- Analyze outgoing calls require("callgraph").run({ direction = "out" }) -- Analyze both directions (mixed) require("callgraph").run({ direction = "mix" })
-
You will receive a notification when the export is complete in your
file_path.
You can analyze call graphs from multiple cursor positions:
-- Add current cursor position to analysis queue
require("callgraph").add_location()
-- Move to another function and add it
require("callgraph").add_location()
-- Run analysis for all added locations
require("callgraph").run({ direction = "mix" })Use invert_filter to only include specific paths:
require("callgraph").run({
direction = "out",
filter_location = { "/path/to/my/project" },
invert_filter = true -- Only show calls within the specified paths
})Warning: The
_devAPI is experimental and may have breaking changes.
require("callgraph").run(
{ direction = "mix" },
{
profiling = true,
on_start = function(opts)
vim.notify("Starting analysis with: " .. vim.inspect(opts))
end,
on_finish = function(root_node)
vim.notify("Analysis complete! Root: " .. root_node.data.name)
end
}
)Note: Command API is planned. You can always write your own custom commands on top of the Lua API.
- Coral square boxes represent items under test.
- Green ellipses are static functions.
- Blue square boxes are class methods.
- Command API: Implement vim commands
- Auto-open exports: Open exported files automatically (via xdg or customizable)
- Multi-format export: Call Graphviz to export to PNG, SVG formats
- Enhanced styling: Rework and improve the DOT exporter
