diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/doc/api.txt | 2 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 13 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 50 | ||||
-rw-r--r-- | runtime/doc/news.txt | 4 | ||||
-rw-r--r-- | runtime/lua/vim/_init_packages.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/_inspector.lua | 238 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/semantic_tokens.lua | 45 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 2 | ||||
-rw-r--r-- | runtime/plugin/nvim.lua | 7 |
9 files changed, 362 insertions, 2 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index d555cff443..8a33fc58a3 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -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()|. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 75d5c067b1..b101740b03 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1332,6 +1332,19 @@ force_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()* Parameters: ~ • {bufnr} (nil|number) default: current buffer + *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 + 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) 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 given client. The client must already be attached to the buffer. diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 5a1c186192..9c98ed7771 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -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()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index bd0d1cfc5b..e5336edb5a 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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 diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 19c8608732..0c4ee8636d 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -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 diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua new file mode 100644 index 0000000000..f46a525910 --- /dev/null +++ b/runtime/lua/vim/_inspector.lua @@ -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 diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index b7ffedab2b..e14d3e51cd 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -585,6 +585,51 @@ function M.stop(bufnr, client_id) end end +--- Return the semantic token(s) at the given position. +--- 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 (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() + end + + local highlighter = STHighlighter.active[bufnr] + if not highlighter then + return + end + + if row == nil or col == nil then + local cursor = api.nvim_win_get_cursor(0) + row, col = cursor[1] - 1, cursor[2] + end + + local tokens = {} + 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) + for i = idx, #highlights do + local token = highlights[i] + + if token.line > row then + break + end + + if token.start_col <= col and token.end_col > col then + token.client_id = client_id + tokens[#tokens + 1] = token + end + end + end + end + return tokens +end + --- Force a refresh of all semantic tokens --- --- Only has an effect if the buffer is currently active for semantic token diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 25f0e7dc5e..582922ecb6 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -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 diff --git a/runtime/plugin/nvim.lua b/runtime/plugin/nvim.lua new file mode 100644 index 0000000000..815886f896 --- /dev/null +++ b/runtime/plugin/nvim.lua @@ -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 }) |