Plugin to display references, definitions, and implementations of document symbols with a view like JetBrains Idea.
textDocument/codeLens
feature;Neovim >= 0.9.0
With lazy.nvim
:
{
'Wansmer/symbol-usage.nvim',
event = 'BufReadPre', -- need run before LspAttach if you use nvim 0.9. On 0.10 use 'LspAttach'
config = function()
require('symbol-usage').setup()
end
}
Default options values:
local SymbolKind = vim.lsp.protocol.SymbolKind
---@type UserOpts
require('symbol-usage').setup({
---@type table<string, any> `nvim_set_hl`-like options for highlight virtual text
hl = { link = 'Comment' },
---@type lsp.SymbolKind[] Symbol kinds what need to be count (see `lsp.SymbolKind`)
kinds = { SymbolKind.Function, SymbolKind.Method },
---Additional filter for kinds. Recommended use in the filetypes override table.
---fiterKind: function(data: { symbol:table, parent:table, bufnr:integer }): boolean
---`symbol` and `parent` is an item from `textDocument/documentSymbol` request
---See: #filter-kinds
---@type table<lsp.SymbolKind, filterKind[]>
kinds_filter = {},
---@type 'above'|'end_of_line'|'textwidth' above by default
vt_position = 'above',
---Text to display when request is pending. If `false`, extmark will not be
---created until the request is finished. Recommended to use with `above`
---vt_position to avoid "jumping lines".
---@type string|table|false
request_pending_text = 'loading...',
---The function can return a string to which the highlighting group from `opts.hl` is applied.
---Alternatively, it can return a table of tuples of the form `{ { text, hl_group }, ... }`` - in this case the specified groups will be applied.
---See `#format-text-examples`
---@type function(symbol: Symbol): string|table Symbol{ definition = integer|nil, implementation = integer|nil, references = integer|nil }
-- text_format = function(symbol) end,
references = { enabled = true, include_declaration = false },
definition = { enabled = false },
implementation = { enabled = false },
---@type { lsp?: string[], filetypes?: string[] } Disables `symbol-usage.nvim' on certain LSPs or file types.
disable = { lsp = {}, filetypes = {} },
---@type UserOpts[] See default overridings in `lua/symbol-usage/langs.lua`
-- filetypes = {},
---@type 'start'|'end' At which position of `symbol.selectionRange` the request to the lsp server should start. Default is `end` (try changing it to `start` if the symbol counting is not correct).
symbol_request_pos = 'end', -- Recommended redifine only in `filetypes` override table
})
From LSP spec:
SymbolKind = {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26,
}
local function text_format(symbol)
local fragments = {}
if symbol.references then
local usage = symbol.references <= 1 and 'usage' or 'usages'
local num = symbol.references == 0 and 'no' or symbol.references
table.insert(fragments, ('%s %s'):format(num, usage))
end
if symbol.definition then
table.insert(fragments, symbol.definition .. ' defs')
end
if symbol.implementation then
table.insert(fragments, symbol.implementation .. ' impls')
end
return table.concat(fragments, ', ')
end
require('symbol-usage').setup({
text_format = text_format,
})
local function h(name) return vim.api.nvim_get_hl(0, { name = name }) end
-- hl-groups can have any name
vim.api.nvim_set_hl(0, 'SymbolUsageRounding', { fg = h('CursorLine').bg, italic = true })
vim.api.nvim_set_hl(0, 'SymbolUsageContent', { bg = h('CursorLine').bg, fg = h('Comment').fg, italic = true })
vim.api.nvim_set_hl(0, 'SymbolUsageRef', { fg = h('Function').fg, bg = h('CursorLine').bg, italic = true })
vim.api.nvim_set_hl(0, 'SymbolUsageDef', { fg = h('Type').fg, bg = h('CursorLine').bg, italic = true })
vim.api.nvim_set_hl(0, 'SymbolUsageImpl', { fg = h('@keyword').fg, bg = h('CursorLine').bg, italic = true })
local function text_format(symbol)
local res = {}
local round_start = { '', 'SymbolUsageRounding' }
local round_end = { '', 'SymbolUsageRounding' }
if symbol.references then
local usage = symbol.references <= 1 and 'usage' or 'usages'
local num = symbol.references == 0 and 'no' or symbol.references
table.insert(res, round_start)
table.insert(res, { ' ', 'SymbolUsageRef' })
table.insert(res, { ('%s %s'):format(num, usage), 'SymbolUsageContent' })
table.insert(res, round_end)
end
if symbol.definition then
if #res > 0 then
table.insert(res, { ' ', 'NonText' })
end
table.insert(res, round_start)
table.insert(res, { ' ', 'SymbolUsageDef' })
table.insert(res, { symbol.definition .. ' defs', 'SymbolUsageContent' })
table.insert(res, round_end)
end
if symbol.implementation then
if #res > 0 then
table.insert(res, { ' ', 'NonText' })
end
table.insert(res, round_start)
table.insert(res, { ' ', 'SymbolUsageImpl' })
table.insert(res, { symbol.implementation .. ' impls', 'SymbolUsageContent' })
table.insert(res, round_end)
end
return res
end
require('symbol-usage').setup({
text_format = text_format,
})
local function h(name) return vim.api.nvim_get_hl(0, { name = name }) end
vim.api.nvim_set_hl(0, 'SymbolUsageRef', { bg = h('Type').fg, fg = h('Normal').bg, bold = true })
vim.api.nvim_set_hl(0, 'SymbolUsageRefRound', { fg = h('Type').fg })
vim.api.nvim_set_hl(0, 'SymbolUsageDef', { bg = h('Function').fg, fg = h('Normal').bg, bold = true })
vim.api.nvim_set_hl(0, 'SymbolUsageDefRound', { fg = h('Function').fg })
vim.api.nvim_set_hl(0, 'SymbolUsageImpl', { bg = h('@parameter').fg, fg = h('Normal').bg, bold = true })
vim.api.nvim_set_hl(0, 'SymbolUsageImplRound', { fg = h('@parameter').fg })
local function text_format(symbol)
local res = {}
if symbol.references then
table.insert(res, { '', 'SymbolUsageRefRound' })
table.insert(res, { ' ' .. tostring(symbol.references), 'SymbolUsageRef' })
table.insert(res, { '', 'SymbolUsageRefRound' })
end
if symbol.definition then
if #res > 0 then
table.insert(res, { ' ', 'NonText' })
end
table.insert(res, { '', 'SymbolUsageDefRound' })
table.insert(res, { ' ' .. tostring(symbol.definition), 'SymbolUsageDef' })
table.insert(res, { '', 'SymbolUsageDefRound' })
end
if symbol.implementation then
if #res > 0 then
table.insert(res, { ' ', 'NonText' })
end
table.insert(res, { '', 'SymbolUsageImplRound' })
table.insert(res, { ' ' .. tostring(symbol.implementation), 'SymbolUsageImpl' })
table.insert(res, { '', 'SymbolUsageImplRound' })
end
return res
end
require('symbol-usage').setup({
text_format = text_format,
})
Each LSP server processes requests and returns results differently. Therefore, it is impossible to set general settings that are completely suitable for every programming language.
For example, in javascipt
arrow functions are not defined as SymbolKind.Function
, but as SymbolKind.Variable
or SymbolKind.Constant
.
I would like to know how many times an arrow function is used, but keeping track of all variables is not informative. For this purpose, you can define additional filters that will check that the variable contains exactly the function and not some other value.
You can see implementation examples here.
Setup symbol-usage
:
---Setup `symbol-usage`
---@param opts UserOpts
require('symbol-usage').setup(opts)
Toggle virtual text for current buffer:
require('symbol-usage').toggle()
Toggle virtual text for all buffers:
After re-enabling, the virtual text will appear after
BufEnter
---@return boolean True if active, false otherwise
require('symbol-usage').toggle_globally()
Refresh current buffer:
require('symbol-usage').refresh()
above
view;LineNr
instead of CursorLineNr
for symbol's line even it current line (actual, if you use number with statuscolumn
) (UPD: fixed at #25277);SymbolKind.Function
(e.g., tsserver, rust-analyzer)