Skip to content

Commit

Permalink
feat: vim.inspect_pos, vim.show_pos, :Inspect
Browse files Browse the repository at this point in the history
  • Loading branch information
folke committed Dec 17, 2022
1 parent 04da043 commit ef91146
Show file tree
Hide file tree
Showing 13 changed files with 385 additions and 11 deletions.
2 changes: 1 addition & 1 deletion runtime/doc/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2683,7 +2683,7 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
Id of the created/updated extmark

nvim_create_namespace({name}) *nvim_create_namespace()*
Creates a new *namespace* or gets an existing one.
Creates a new namespace or gets an existing one. *namespace*

Namespaces are used for buffer highlights and virtual text, see
|nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|.
Expand Down
4 changes: 2 additions & 2 deletions runtime/doc/lsp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1335,15 +1335,15 @@ force_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()*
*vim.lsp.semantic_tokens.get_at_pos()*
get_at_pos({bufnr}, {row}, {col})
Return the semantic token(s) at the given position. If called without
argument, returns the token under the cursor.
arguments, returns the token under the cursor.

Parameters: ~
{bufnr} (number|nil) Buffer number (0 for current buffer, default)
{row} (number|nil) Position row (default cursor position)
{col} (number|nil) Position column (default cursor position)

Return: ~
table[]|nil tokens Table of tokens at position
(table|nil) List of tokens at position

start({bufnr}, {client_id}, {opts}) *vim.lsp.semantic_tokens.start()*
Start the semantic token highlighting engine for the given buffer with the
Expand Down
50 changes: 50 additions & 0 deletions runtime/doc/lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,56 @@ schedule_wrap({cb}) *vim.schedule_wrap()*
|vim.in_fast_event()|


==============================================================================
Lua module: inspector *lua-inspector*

inspect_pos({bufnr}, {row}, {col}, {filter}) *vim.inspect_pos()*
Get all the items at a given buffer position.

Can also be pretty-printed with `:Inspect!`. *:Inspect!*

Parameters: ~
{bufnr} (number|nil) defaults to the current buffer
{row} (number|nil) row to inspect, 0-based. Defaults to the row of
the current cursor
{col} (number|nil) col to inspect, 0-based. Defaults to the col of
the current cursor
{filter} (table|nil) a table with key-value pairs to filter the items
• syntax (boolean): include syntax based highlight groups
(defaults to true)
• treesitter (boolean): include treesitter based highlight
groups (defaults to true)
• extmarks (boolean|"all"): include extmarks. When `all`,
then extmarks without a `hl_group` will also be included
(defaults to true)
• semantic_tokens (boolean): include semantic tokens
(defaults to true)

Return: ~
(table) a table with the following key-value pairs. Items are in
"traversal order":
• treesitter: a list of treesitter captures
• syntax: a list of syntax groups
• semantic_tokens: a list of semantic tokens
• extmarks: a list of extmarks
• buffer: the buffer used to get the items
• row: the row used to get the items
• col: the col used to get the items

show_pos({bufnr}, {row}, {col}, {filter}) *vim.show_pos()*
Show all the items at a given buffer position.

Can also be shown with `:Inspect`. *:Inspect*

Parameters: ~
{bufnr} (number|nil) defaults to the current buffer
{row} (number|nil) row to inspect, 0-based. Defaults to the row of
the current cursor
{col} (number|nil) col to inspect, 0-based. Defaults to the col of
the current cursor
{filter} (table|nil) see |vim.inspect_pos()|




deep_equal({a}, {b}) *vim.deep_equal()*
Expand Down
4 changes: 4 additions & 0 deletions runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ NEW FEATURES *news-features*

The following new APIs or features were added.

|vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items
at a given buffer postion. Currently this includes treesitter captures,
semantic tokens, syntax groups and extmarks.

• Added support for semantic token highlighting to the LSP client. This
functionality is enabled by default when a client that supports this feature
is attached to a buffer. Opt-out can be performed by deleting the
Expand Down
3 changes: 3 additions & 0 deletions runtime/lua/vim/_init_packages.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ setmetatable(vim, {
if vim._submodules[key] then
t[key] = require('vim.' .. key)
return t[key]
elseif key == 'inspect_pos' or key == 'show_pos' then
require('vim._inspector')
return t[key]
elseif vim.startswith(key, 'uri_') then
local val = require('vim.uri')[key]
if val ~= nil then
Expand Down
238 changes: 238 additions & 0 deletions runtime/lua/vim/_inspector.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---@class InspectorFilter
---@field syntax boolean include syntax based highlight groups (defaults to true)
---@field treesitter boolean include treesitter based highlight groups (defaults to true)
---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
---@field semantic_tokens boolean include semantic tokens (defaults to true)
local defaults = {
syntax = true,
treesitter = true,
extmarks = true,
semantic_tokens = true,
}

---Get all the items at a given buffer position.
---
---Can also be pretty-printed with `:Inspect!`. *:Inspect!*
---
---@param bufnr? number defaults to the current buffer
---@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
---@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
---@param filter? InspectorFilter (table|nil) a table with key-value pairs to filter the items
--- - syntax (boolean): include syntax based highlight groups (defaults to true)
--- - treesitter (boolean): include treesitter based highlight groups (defaults to true)
--- - extmarks (boolean|"all"): include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
--- - semantic_tokens (boolean): include semantic tokens (defaults to true)
---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:number,col:number,row:number} (table) a table with the following key-value pairs. Items are in "traversal order":
--- - treesitter: a list of treesitter captures
--- - syntax: a list of syntax groups
--- - semantic_tokens: a list of semantic tokens
--- - extmarks: a list of extmarks
--- - buffer: the buffer used to get the items
--- - row: the row used to get the items
--- - col: the col used to get the items
function vim.inspect_pos(bufnr, row, col, filter)
filter = vim.tbl_deep_extend('force', defaults, filter or {})

bufnr = bufnr or 0
if row == nil or col == nil then
-- get the row/col from the first window displaying the buffer
local win = bufnr == 0 and vim.api.nvim_get_current_win() or vim.fn.bufwinid(bufnr)
if win == -1 then
error('row/col is required for buffers not visible in a window')
end
local cursor = vim.api.nvim_win_get_cursor(win)
row, col = cursor[1] - 1, cursor[2]
end
bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr

local results = {
treesitter = {},
syntax = {},
extmarks = {},
semantic_tokens = {},
buffer = bufnr,
row = row,
col = col,
}

-- resolve hl links
---@private
local function resolve_hl(data)
if data.hl_group then
local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group)
local name = vim.fn.synIDattr(vim.fn.synIDtrans(hlid), 'name')
data.hl_group_link = name
end
return data
end

-- treesitter
if filter.treesitter then
for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do
capture.hl_group = '@' .. capture.capture
table.insert(results.treesitter, resolve_hl(capture))
end
end

-- syntax
if filter.syntax then
for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
table.insert(results.syntax, resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') }))
end
end

-- semantic tokens
if filter.semantic_tokens then
for _, token in ipairs(vim.lsp.semantic_tokens.get_at_pos(bufnr, row, col) or {}) do
token.hl_groups = {
type = resolve_hl({ hl_group = '@' .. token.type }),
modifiers = vim.tbl_map(function(modifier)
return resolve_hl({ hl_group = '@' .. modifier })
end, token.modifiers or {}),
}
table.insert(results.semantic_tokens, token)
end
end

-- extmarks
if filter.extmarks then
for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do
if ns:find('vim_lsp_semantic_tokens') ~= 1 then
local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true })
for _, extmark in ipairs(extmarks) do
extmark = {
ns_id = nsid,
ns = ns,
id = extmark[1],
row = extmark[2],
col = extmark[3],
opts = resolve_hl(extmark[4]),
}
local end_row = extmark.opts.end_row or extmark.row -- inclusive
local end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive
if
(filter.extmarks == 'all' or extmark.opts.hl_group) -- filter hl_group
and (row >= extmark.row and row <= end_row) -- within the rows of the extmark
and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col
and (row < end_row or col < end_col) -- either not in the last row or in range of the col
then
table.insert(results.extmarks, extmark)
end
end
end
end
end
return results
end

---Show all the items at a given buffer position.
---
---Can also be shown with `:Inspect`. *:Inspect*
---
---@param bufnr? number defaults to the current buffer
---@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
---@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
---@param filter? InspectorFilter (table|nil) see |vim.inspect_pos()|
function vim.show_pos(bufnr, row, col, filter)
local items = vim.inspect_pos(bufnr, row, col, filter)

local lines = { {} }

---@private
local function append(str, hl)
table.insert(lines[#lines], { str, hl })
end

---@private
local function nl()
table.insert(lines, {})
end

---@private
local function item(data, comment)
append(' - ')
append(data.hl_group, data.hl_group)
append(' ')
if data.hl_group ~= data.hl_group_link then
append('links to ', 'MoreMsg')
append(data.hl_group_link, data.hl_group_link)
append(' ')
end
if comment then
append(comment, 'Comment')
end
nl()
end

-- treesitter
if #items.treesitter > 0 then
append('Treesitter', 'Title')
nl()
for _, capture in ipairs(items.treesitter) do
item(capture, capture.lang)
end
nl()
end

if #items.semantic_tokens > 0 then
append('Semantic Tokens', 'Title')
nl()
for _, token in ipairs(items.semantic_tokens) do
local client = vim.lsp.get_client_by_id(token.client_id)
client = client and (' (' .. client.name .. ')') or ''
item(token.hl_groups.type, 'type' .. client)
for _, modifier in ipairs(token.hl_groups.modifiers) do
item(modifier, 'modifier' .. client)
end
end
nl()
end

-- syntax
if #items.syntax > 0 then
append('Syntax', 'Title')
nl()
for _, syn in ipairs(items.syntax) do
item(syn)
end
nl()
end
-- extmarks
if #items.extmarks > 0 then
append('Extmarks', 'Title')
nl()
for _, extmark in ipairs(items.extmarks) do
if extmark.opts.hl_group then
item(extmark.opts, extmark.ns)
else
append(' - ')
append(extmark.ns, 'Comment')
nl()
end
end
nl()
end

if #lines[#lines] == 0 then
table.remove(lines)
end

local chunks = {}
for _, line in ipairs(lines) do
vim.list_extend(chunks, line)
table.insert(chunks, { '\n' })
end
if #chunks == 0 then
chunks = {
{
'No items found at position '
.. items.row
.. ','
.. items.col
.. ' in buffer '
.. items.buffer,
},
}
end
vim.api.nvim_echo(chunks, false, {})
end
7 changes: 4 additions & 3 deletions runtime/lua/vim/lsp/semantic_tokens.lua
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,13 @@ function M.stop(bufnr, client_id)
end

--- Return the semantic token(s) at the given position.
--- If called without argument, returns the token under the cursor.
--- If called without arguments, returns the token under the cursor.
---
---@param bufnr number|nil Buffer number (0 for current buffer, default)
---@param row number|nil Position row (default cursor position)
---@param col number|nil Position column (default cursor position)
---
---@return table[]|nil tokens Table of tokens at position
---@return table|nil (table|nil) List of tokens at position
function M.get_at_pos(bufnr, row, col)
if bufnr == nil or bufnr == 0 then
bufnr = api.nvim_get_current_buf()
Expand All @@ -609,7 +609,7 @@ function M.get_at_pos(bufnr, row, col)
end

local tokens = {}
for _, client in pairs(highlighter.client_state) do
for client_id, client in pairs(highlighter.client_state) do
local highlights = client.current_result.highlights
if highlights then
local idx = binary_search(highlights, row)
Expand All @@ -621,6 +621,7 @@ function M.get_at_pos(bufnr, row, col)
end

if token.start_col <= col and token.end_col > col then
token.client_id = client_id
tokens[#tokens + 1] = token
end
end
Expand Down
2 changes: 1 addition & 1 deletion runtime/lua/vim/treesitter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function M.get_captures_at_pos(bufnr, row, col)
if M.is_in_node_range(node, row, col) then
local c = q._query.captures[capture] -- name of the capture in the query
if c ~= nil then
table.insert(matches, { capture = c, metadata = metadata })
table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() })
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions runtime/plugin/nvim.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
vim.api.nvim_create_user_command('Inspect', function(cmd)
if cmd.bang then
vim.pretty_print(vim.inspect_pos())
else
vim.show_pos()
end
end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true })
Loading

0 comments on commit ef91146

Please sign in to comment.