From c48b1421af28d0317c807bca00c7e2fff97d9ad0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 6 Jun 2023 08:23:20 -0700 Subject: refactor!: rename "playground" => "dev" #23919 Problem: "playground" is new jargon that overlaps with existing concepts: "dev" (`:help dev`) and "view" (also "scratch" `:help scratch-buffer`) . Solution: We should consistently use "dev" as the namespace for where "developer tools" live. For purposes of a "throwaway sandbox object", we can use the name "view". - Rename `TSPlayground` => `TSView` - Rename `playground.lua` => `dev.lua` --- runtime/lua/vim/treesitter/dev.lua | 441 +++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 runtime/lua/vim/treesitter/dev.lua (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua new file mode 100644 index 0000000000..99cd147658 --- /dev/null +++ b/runtime/lua/vim/treesitter/dev.lua @@ -0,0 +1,441 @@ +local api = vim.api + +---@class TSDevModule +local M = {} + +---@class TSTreeView +---@field ns integer API namespace +---@field opts table Options table with the following keys: +--- - anon (boolean): If true, display anonymous nodes +--- - lang (boolean): If true, display the language alongside each node +---@field nodes TSP.Node[] +---@field named TSP.Node[] +local TSTreeView = {} + +---@class TSP.Node +---@field id integer Node id +---@field text string Node text +---@field named boolean True if this is a named (non-anonymous) node +---@field depth integer Depth of the node within the tree +---@field lnum integer Beginning line number of this node in the source buffer +---@field col integer Beginning column number of this node in the source buffer +---@field end_lnum integer Final line number of this node in the source buffer +---@field end_col integer Final column number of this node in the source buffer +---@field lang string Source language of this node +---@field root TSNode + +--- Traverse all child nodes starting at {node}. +--- +--- This is a recursive function. The {depth} parameter indicates the current recursion level. +--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed +--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order +--- they were visited. +--- +--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each +--- injected language has a series of trees nested within the primary language's tree, and the root +--- node of each of these trees is contained within a node in the primary tree. The {injections} +--- table maps nodes in the primary tree to root nodes of injected trees. +--- +---@param node TSNode Starting node to begin traversal |tsnode| +---@param depth integer Current recursion depth +---@param lang string Language of the tree currently being traversed +---@param injections table Mapping of node ids to root nodes of injected language trees (see +--- explanation above) +---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree +---@private +local function traverse(node, depth, lang, injections, tree) + local injection = injections[node:id()] + if injection then + traverse(injection.root, depth, injection.lang, injections, tree) + end + + for child, field in node:iter_children() do + local type = child:type() + local lnum, col, end_lnum, end_col = child:range() + local named = child:named() + local text ---@type string + if named then + if field then + text = string.format('%s: (%s)', field, type) + else + text = string.format('(%s)', type) + end + else + text = string.format('"%s"', type:gsub('\n', '\\n')) + end + + table.insert(tree, { + id = child:id(), + text = text, + named = named, + depth = depth, + lnum = lnum, + col = col, + end_lnum = end_lnum, + end_col = end_col, + lang = lang, + }) + + traverse(child, depth + 1, lang, injections, tree) + end + + return tree +end + +--- Create a new treesitter view. +--- +---@param bufnr integer Source buffer number +---@param lang string|nil Language of source buffer +--- +---@return TSTreeView|nil +---@return string|nil Error message, if any +--- +---@package +function TSTreeView:new(bufnr, lang) + local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) + if not ok then + return nil, 'No parser available for the given buffer' + end + + -- For each child tree (injected language), find the root of the tree and locate the node within + -- the primary tree that contains that root. Add a mapping from the node in the primary tree to + -- the root in the child tree to the {injections} table. + local root = parser:parse()[1]:root() + local injections = {} ---@type table + parser:for_each_child(function(child, lang_) + child:for_each_tree(function(tree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = lang_, + root = r, + } + end + end) + end) + + local nodes = traverse(root, 0, parser:lang(), injections, {}) + + local named = {} ---@type TSP.Node[] + for _, v in ipairs(nodes) do + if v.named then + named[#named + 1] = v + end + end + + local t = { + ns = api.nvim_create_namespace(''), + nodes = nodes, + named = named, + opts = { + anon = false, + lang = false, + }, + } + + setmetatable(t, self) + self.__index = self + return t +end + +local decor_ns = api.nvim_create_namespace('ts.dev') + +---@private +---@param lnum integer +---@param col integer +---@param end_lnum integer +---@param end_col integer +---@return string +local function get_range_str(lnum, col, end_lnum, end_col) + if lnum == end_lnum then + return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col) + end + return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) +end + +--- Write the contents of this View into {bufnr}. +--- +---@param bufnr integer Buffer number to write into. +---@package +function TSTreeView:draw(bufnr) + vim.bo[bufnr].modifiable = true + local lines = {} ---@type string[] + local lang_hl_marks = {} ---@type table[] + + for _, item in self:iter() do + local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) + local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + local line = + string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) + + if self.opts.lang then + lang_hl_marks[#lang_hl_marks + 1] = { + col = #line - #lang_str, + end_col = #line, + } + end + + lines[#lines + 1] = line + end + + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) + + for i, m in ipairs(lang_hl_marks) do + api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, { + hl_group = 'Title', + end_col = m.end_col, + }) + end + + vim.bo[bufnr].modifiable = false +end + +--- Get node {i} from this View. +--- +--- The node number is dependent on whether or not anonymous nodes are displayed. +--- +---@param i integer Node number to get +---@return TSP.Node +---@package +function TSTreeView:get(i) + local t = self.opts.anon and self.nodes or self.named + return t[i] +end + +--- Iterate over all of the nodes in this View. +--- +---@return (fun(): integer, TSP.Node) Iterator over all nodes in this View +---@return table +---@return integer +---@package +function TSTreeView:iter() + return ipairs(self.opts.anon and self.nodes or self.named) +end + +--- @class InspectTreeOpts +--- @field lang string? The language of the source buffer. If omitted, the +--- filetype of the source buffer is used. +--- @field bufnr integer? Buffer to draw the tree into. If omitted, a new +--- buffer is created. +--- @field winid integer? Window id to display the tree buffer in. If omitted, +--- a new window is created with {command}. +--- @field command string? Vimscript command to create the window. Default +--- value is "60vnew". Only used when {winid} is nil. +--- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a +--- function, it accepts the buffer number of the source +--- buffer as its only argument and should return a string. + +--- @private +--- +--- @param opts InspectTreeOpts +function M.inspect_tree(opts) + vim.validate({ + opts = { opts, 't', true }, + }) + + opts = opts or {} + + local buf = api.nvim_get_current_buf() + local win = api.nvim_get_current_win() + local pg = assert(TSTreeView:new(buf, opts.lang)) + + -- Close any existing dev window + if vim.b[buf].dev then + local w = vim.b[buf].dev + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end + + local w = opts.winid + if not w then + vim.cmd(opts.command or '60vnew') + w = api.nvim_get_current_win() + end + + local b = opts.bufnr + if b then + api.nvim_win_set_buf(w, b) + else + b = api.nvim_win_get_buf(w) + end + + vim.b[buf].dev = w + + vim.wo[w].scrolloff = 5 + vim.wo[w].wrap = false + vim.wo[w].foldmethod = 'manual' -- disable folding + vim.bo[b].buflisted = false + vim.bo[b].buftype = 'nofile' + vim.bo[b].bufhidden = 'wipe' + vim.b[b].disable_query_linter = true + vim.bo[b].filetype = 'query' + + local title --- @type string? + local opts_title = opts.title + if not opts_title then + local bufname = api.nvim_buf_get_name(buf) + title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) + elseif type(opts_title) == 'function' then + title = opts_title(buf) + end + + assert(type(title) == 'string', 'Window title must be a string') + api.nvim_buf_set_name(b, title) + + pg:draw(b) + + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_set_keymap(b, 'n', '', '', { + desc = 'Jump to the node under the cursor in the source buffer', + callback = function() + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_set_current_win(win) + api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'a', '', { + desc = 'Toggle anonymous nodes', + callback = function() + local row, col = unpack(api.nvim_win_get_cursor(w)) + local curnode = pg:get(row) + while curnode and not curnode.named do + row = row - 1 + curnode = pg:get(row) + end + + pg.opts.anon = not pg.opts.anon + pg:draw(b) + + if not curnode then + return + end + + local id = curnode.id + for i, node in pg:iter() do + if node.id == id then + api.nvim_win_set_cursor(w, { i, col }) + break + end + end + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'I', '', { + desc = 'Toggle language display', + callback = function() + pg.opts.lang = not pg.opts.lang + pg:draw(b) + end, + }) + + local group = api.nvim_create_augroup('treesitter/dev', {}) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { + end_row = pos.end_lnum, + end_col = math.max(0, pos.end_col), + hl_group = 'Visual', + }) + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if pos.lnum < topline and pos.end_lnum < topline then + api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) + elseif pos.lnum > botline and pos.end_lnum > botline then + api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + end + end, + }) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + + local cursor_node = vim.treesitter.get_node({ + bufnr = buf, + lang = opts.lang, + ignore_injections = false, + }) + if not cursor_node then + return + end + + local cursor_node_id = cursor_node:id() + for i, v in pg:iter() do + if v.id == cursor_node_id then + local start = v.depth + local end_col = start + #v.text + api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { + end_col = end_col, + hl_group = 'Visual', + }) + api.nvim_win_set_cursor(w, { i, 0 }) + break + end + end + end, + }) + + api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + pg = assert(TSTreeView:new(buf, opts.lang)) + pg:draw(b) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufHidden', { + group = group, + buffer = buf, + once = true, + callback = function() + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end, + }) +end + +return M -- cgit From be74807eef13ff8c90d55cf8b22b01d6d33b1641 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 18 Jul 2023 15:42:30 +0100 Subject: docs(lua): more improvements (#24387) * docs(lua): teach lua2dox how to table * docs(lua): teach gen_vimdoc.py about local functions No more need to mark local functions with @private * docs(lua): mention @nodoc and @meta in dev-lua-doc * fixup! Co-authored-by: Justin M. Keyes --------- Co-authored-by: Justin M. Keyes --- runtime/lua/vim/treesitter/dev.lua | 2 -- 1 file changed, 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 99cd147658..1bb5a08205 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -42,7 +42,6 @@ local TSTreeView = {} ---@param injections table Mapping of node ids to root nodes of injected language trees (see --- explanation above) ---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree ----@private local function traverse(node, depth, lang, injections, tree) local injection = injections[node:id()] if injection then @@ -141,7 +140,6 @@ end local decor_ns = api.nvim_create_namespace('ts.dev') ----@private ---@param lnum integer ---@param col integer ---@param end_lnum integer -- cgit From 369f58797dbd3c0e18035d26e6f5d6634be7a2a9 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Mon, 7 Aug 2023 16:16:12 +0300 Subject: fix(treesitter): escape quotes in :InspectTree view #24582 Problem: Anonymous nodes containing double quotes break the highlighting. Solution: Escape double quotes in anonymous nodes. --- runtime/lua/vim/treesitter/dev.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 1bb5a08205..e94e8f08dc 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -152,6 +152,12 @@ local function get_range_str(lnum, col, end_lnum, end_col) return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) end +---@param text string +---@return string +local function escape_quotes(text) + return string.format('"%s"', text:sub(2, #text - 1):gsub('"', '\\"')) +end + --- Write the contents of this View into {bufnr}. --- ---@param bufnr integer Buffer number to write into. @@ -164,8 +170,9 @@ function TSTreeView:draw(bufnr) for _, item in self:iter() do local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + local text = item.named and item.text or escape_quotes(item.text) local line = - string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) + string.format('%s%s ; %s%s', string.rep(' ', item.depth), text, range_str, lang_str) if self.opts.lang then lang_hl_marks[#lang_hl_marks + 1] = { -- cgit From 2ca076e45fb3f1c08f6a1a374834df0701b8d778 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 10 Aug 2023 14:21:56 +0100 Subject: feat(treesitter)!: incremental injection parsing Problem: Treesitter highlighting is slow for large files with lots of injections. Solution: Only parse injections we are going to render during a redraw cycle. --- - `LanguageTree:parse()` will no longer parse injections by default and now requires an explicit range argument to be passed. - `TSHighlighter` now parses injections incrementally during on_win callbacks for the line range being rendered. - Plugins which require certain injections to be parsed must run `parser:parse({ start_row, end_row })` before using the tree. --- runtime/lua/vim/treesitter/dev.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index e94e8f08dc..f7625eb94b 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -99,7 +99,7 @@ function TSTreeView:new(bufnr, lang) -- For each child tree (injected language), find the root of the tree and locate the node within -- the primary tree that contains that root. Add a mapping from the node in the primary tree to -- the root in the child tree to the {injections} table. - local root = parser:parse()[1]:root() + local root = parser:parse(true)[1]:root() local injections = {} ---@type table parser:for_each_child(function(child, lang_) child:for_each_tree(function(tree) -- cgit From 5d8ab32f3871b0232972cac1116ac7cba98389e5 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 25 Aug 2023 11:17:36 -0700 Subject: feat(treesitter): add a query editor (#24703) --- runtime/lua/vim/treesitter/dev.lua | 198 +++++++++++++++++++++++++++++++++---- 1 file changed, 179 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index f7625eb94b..b7f2c0e473 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -124,7 +124,7 @@ function TSTreeView:new(bufnr, lang) end local t = { - ns = api.nvim_create_namespace(''), + ns = api.nvim_create_namespace('treesitter/dev-inspect'), nodes = nodes, named = named, opts = { @@ -158,6 +158,29 @@ local function escape_quotes(text) return string.format('"%s"', text:sub(2, #text - 1):gsub('"', '\\"')) end +---@param w integer +---@return boolean closed Whether the window was closed. +local function close_win(w) + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + return true + end + + return false +end + +---@param w integer +---@param b integer +local function set_dev_properties(w, b) + vim.wo[w].scrolloff = 5 + vim.wo[w].wrap = false + vim.wo[w].foldmethod = 'manual' -- disable folding + vim.bo[b].buflisted = false + vim.bo[b].buftype = 'nofile' + vim.bo[b].bufhidden = 'wipe' + vim.bo[b].filetype = 'query' +end + --- Write the contents of this View into {bufnr}. --- ---@param bufnr integer Buffer number to write into. @@ -247,12 +270,9 @@ function M.inspect_tree(opts) local win = api.nvim_get_current_win() local pg = assert(TSTreeView:new(buf, opts.lang)) - -- Close any existing dev window - if vim.b[buf].dev then - local w = vim.b[buf].dev - if api.nvim_win_is_valid(w) then - api.nvim_win_close(w, true) - end + -- Close any existing inspector window + if vim.b[buf].dev_inspect then + close_win(vim.b[buf].dev_inspect) end local w = opts.winid @@ -268,16 +288,10 @@ function M.inspect_tree(opts) b = api.nvim_win_get_buf(w) end - vim.b[buf].dev = w - - vim.wo[w].scrolloff = 5 - vim.wo[w].wrap = false - vim.wo[w].foldmethod = 'manual' -- disable folding - vim.bo[b].buflisted = false - vim.bo[b].buftype = 'nofile' - vim.bo[b].bufhidden = 'wipe' + vim.b[buf].dev_inspect = w + vim.b[b].dev_base = win -- base window handle vim.b[b].disable_query_linter = true - vim.bo[b].filetype = 'query' + set_dev_properties(w, b) local title --- @type string? local opts_title = opts.title @@ -306,7 +320,7 @@ function M.inspect_tree(opts) api.nvim_buf_set_keymap(b, 'n', 'a', '', { desc = 'Toggle anonymous nodes', callback = function() - local row, col = unpack(api.nvim_win_get_cursor(w)) + local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer local curnode = pg:get(row) while curnode and not curnode.named do row = row - 1 @@ -336,6 +350,15 @@ function M.inspect_tree(opts) pg:draw(b) end, }) + api.nvim_buf_set_keymap(b, 'n', 'o', '', { + desc = 'Toggle query previewer', + callback = function() + local preview_w = vim.b[buf].dev_preview + if not preview_w or not close_win(preview_w) then + M.preview_query() + end + end, + }) local group = api.nvim_create_augroup('treesitter/dev', {}) @@ -436,11 +459,148 @@ function M.inspect_tree(opts) buffer = buf, once = true, callback = function() - if api.nvim_win_is_valid(w) then - api.nvim_win_close(w, true) + close_win(w) + end, + }) +end + +local preview_ns = api.nvim_create_namespace('treesitter/dev-preview') + +---@param query_win integer +---@param base_win integer +local function update_preview_highlights(query_win, base_win) + local base_buf = api.nvim_win_get_buf(base_win) + local query_buf = api.nvim_win_get_buf(query_win) + local parser = vim.treesitter.get_parser(base_buf) + local lang = parser:lang() + api.nvim_buf_clear_namespace(base_buf, preview_ns, 0, -1) + local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') + + local ok_query, query = pcall(vim.treesitter.query.parse, lang, query_content) + if not ok_query then + return + end + + local cursor_word = vim.fn.expand('') --[[@as string]] + -- Only highlight captures if the cursor is on a capture name + if cursor_word:find('^@') == nil then + return + end + -- Remove the '@' from the cursor word + cursor_word = cursor_word:sub(2) + local topline, botline = vim.fn.line('w0', base_win), vim.fn.line('w$', base_win) + for id, node in query:iter_captures(parser:trees()[1]:root(), base_buf, topline - 1, botline) do + local capture_name = query.captures[id] + if capture_name == cursor_word then + local lnum, col, end_lnum, end_col = node:range() + api.nvim_buf_set_extmark(base_buf, preview_ns, lnum, col, { + end_row = end_lnum, + end_col = end_col, + hl_group = 'Visual', + virt_text = { + { capture_name, 'Title' }, + }, + }) + end + end +end + +--- @private +function M.preview_query() + local buf = api.nvim_get_current_buf() + local win = api.nvim_get_current_win() + + -- Close any existing previewer window + if vim.b[buf].dev_preview then + close_win(vim.b[buf].dev_preview) + end + + local cmd = '60vnew' + -- If the inspector is open, place the previewer above it. + local base_win = vim.b[buf].dev_base ---@type integer? + local base_buf = base_win and api.nvim_win_get_buf(base_win) + local inspect_win = base_buf and vim.b[base_buf].dev_inspect + if base_win and base_buf and api.nvim_win_is_valid(inspect_win) then + vim.api.nvim_set_current_win(inspect_win) + buf = base_buf + win = base_win + cmd = 'new' + end + vim.cmd(cmd) + + local ok, parser = pcall(vim.treesitter.get_parser, buf) + if not ok then + return nil, 'No parser available for the given buffer' + end + local lang = parser:lang() + + local query_win = api.nvim_get_current_win() + local query_buf = api.nvim_win_get_buf(query_win) + + vim.b[buf].dev_preview = query_win + vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' + set_dev_properties(query_win, query_buf) + + -- Note that omnifunc guesses the language based on the containing folder, + -- so we add the parser's language to the buffer's name so that omnifunc + -- can infer the language later. + api.nvim_buf_set_name(query_buf, string.format('%s/query_previewer.scm', lang)) + + local group = api.nvim_create_augroup('treesitter/dev-preview', {}) + api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { + group = group, + buffer = query_buf, + desc = 'Update query previewer diagnostics when the query changes', + callback = function() + vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) + end, + }) + api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, { + group = group, + buffer = query_buf, + desc = 'Update query previewer highlights when the cursor moves', + callback = function() + update_preview_highlights(query_win, win) + end, + }) + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = query_buf, + desc = 'Clear the query previewer highlights when leaving the previewer', + callback = function() + api.nvim_buf_clear_namespace(buf, preview_ns, 0, -1) + end, + }) + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = buf, + desc = 'Clear the query previewer highlights when leaving the source buffer', + callback = function() + if not api.nvim_buf_is_loaded(query_buf) then + return true end + + api.nvim_buf_clear_namespace(query_buf, preview_ns, 0, -1) + end, + }) + api.nvim_create_autocmd('BufHidden', { + group = group, + buffer = buf, + desc = 'Close the previewer window when the source buffer is hidden', + once = true, + callback = function() + close_win(query_win) end, }) + + api.nvim_buf_set_lines(query_buf, 0, -1, false, { + ';; Write your query here. Use @captures to highlight matches in the source buffer.', + ';; Completion for grammar nodes is available (see :h compl-omni)', + '', + '', + }) + vim.cmd('normal! G') + vim.cmd.startinsert() end return M -- cgit From 2bf3e82676d5caf674cf5ed1eb9677376d9cfa35 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 26 Aug 2023 13:24:29 -0700 Subject: fix(treesitter): validate window before updating preview highlights --- runtime/lua/vim/treesitter/dev.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index b7f2c0e473..72b6e3db4a 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -560,7 +560,9 @@ function M.preview_query() buffer = query_buf, desc = 'Update query previewer highlights when the cursor moves', callback = function() - update_preview_highlights(query_win, win) + if api.nvim_win_is_valid(win) then + update_preview_highlights(query_win, win) + end end, }) api.nvim_create_autocmd('BufLeave', { -- cgit From 7a76fb8547548304450b59624f9c75a554396504 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 12 Sep 2023 11:38:31 +0100 Subject: fix(treesitter): remove more double recursion Do not call `for_each_child` in functions that are already recursive. --- runtime/lua/vim/treesitter/dev.lua | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 72b6e3db4a..67aa8670ba 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -101,17 +101,15 @@ function TSTreeView:new(bufnr, lang) -- the root in the child tree to the {injections} table. local root = parser:parse(true)[1]:root() local injections = {} ---@type table - parser:for_each_child(function(child, lang_) - child:for_each_tree(function(tree) - local r = tree:root() - local node = root:named_descendant_for_range(r:range()) - if node then - injections[node:id()] = { - lang = lang_, - root = r, - } - end - end) + parser:for_each_tree(function(tree, ltree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = ltree:lang(), + root = r, + } + end end) local nodes = traverse(root, 0, parser:lang(), injections, {}) -- cgit From 1f551e068f728ff38bd7fdcfa3a6daf362bab9da Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 12 Sep 2023 15:26:57 +0100 Subject: fix(treesitter): fixup for InspectTree Fixes #25120 --- runtime/lua/vim/treesitter/dev.lua | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 67aa8670ba..e7af259d28 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -101,16 +101,18 @@ function TSTreeView:new(bufnr, lang) -- the root in the child tree to the {injections} table. local root = parser:parse(true)[1]:root() local injections = {} ---@type table - parser:for_each_tree(function(tree, ltree) - local r = tree:root() - local node = root:named_descendant_for_range(r:range()) - if node then - injections[node:id()] = { - lang = ltree:lang(), - root = r, - } - end - end) + for _, child in pairs(parser:children()) do + child:for_each_tree(function(tree, ltree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = ltree:lang(), + root = r, + } + end + end) + end local nodes = traverse(root, 0, parser:lang(), injections, {}) -- cgit From 28233bcb49067aaa70fa6e5fec14e2cc4bcaa315 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 15 Sep 2023 03:10:55 -0700 Subject: refactor(treesitter): rename "preview" => "edit" #25161 "Edit" more closely describes the generic application than "Preview", though the buffer contents don't (yet) map to an actual file on disk. https://github.com/neovim/neovim/pull/24703#discussion_r1321719133 --- runtime/lua/vim/treesitter/dev.lua | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index e7af259d28..bc54853103 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -351,11 +351,11 @@ function M.inspect_tree(opts) end, }) api.nvim_buf_set_keymap(b, 'n', 'o', '', { - desc = 'Toggle query previewer', + desc = 'Toggle query editor', callback = function() - local preview_w = vim.b[buf].dev_preview - if not preview_w or not close_win(preview_w) then - M.preview_query() + local edit_w = vim.b[buf].dev_edit + if not edit_w or not close_win(edit_w) then + M.edit_query() end end, }) @@ -464,16 +464,16 @@ function M.inspect_tree(opts) }) end -local preview_ns = api.nvim_create_namespace('treesitter/dev-preview') +local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') ---@param query_win integer ---@param base_win integer -local function update_preview_highlights(query_win, base_win) +local function update_editor_highlights(query_win, base_win) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) local parser = vim.treesitter.get_parser(base_buf) local lang = parser:lang() - api.nvim_buf_clear_namespace(base_buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') local ok_query, query = pcall(vim.treesitter.query.parse, lang, query_content) @@ -493,7 +493,7 @@ local function update_preview_highlights(query_win, base_win) local capture_name = query.captures[id] if capture_name == cursor_word then local lnum, col, end_lnum, end_col = node:range() - api.nvim_buf_set_extmark(base_buf, preview_ns, lnum, col, { + api.nvim_buf_set_extmark(base_buf, edit_ns, lnum, col, { end_row = end_lnum, end_col = end_col, hl_group = 'Visual', @@ -506,17 +506,17 @@ local function update_preview_highlights(query_win, base_win) end --- @private -function M.preview_query() +function M.edit_query() local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() - -- Close any existing previewer window - if vim.b[buf].dev_preview then - close_win(vim.b[buf].dev_preview) + -- Close any existing editor window + if vim.b[buf].dev_edit then + close_win(vim.b[buf].dev_edit) end local cmd = '60vnew' - -- If the inspector is open, place the previewer above it. + -- If the inspector is open, place the editor above it. local base_win = vim.b[buf].dev_base ---@type integer? local base_buf = base_win and api.nvim_win_get_buf(base_win) local inspect_win = base_buf and vim.b[base_buf].dev_inspect @@ -537,20 +537,20 @@ function M.preview_query() local query_win = api.nvim_get_current_win() local query_buf = api.nvim_win_get_buf(query_win) - vim.b[buf].dev_preview = query_win + vim.b[buf].dev_edit = query_win vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' set_dev_properties(query_win, query_buf) -- Note that omnifunc guesses the language based on the containing folder, -- so we add the parser's language to the buffer's name so that omnifunc -- can infer the language later. - api.nvim_buf_set_name(query_buf, string.format('%s/query_previewer.scm', lang)) + api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang)) - local group = api.nvim_create_augroup('treesitter/dev-preview', {}) + local group = api.nvim_create_augroup('treesitter/dev-edit', {}) api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { group = group, buffer = query_buf, - desc = 'Update query previewer diagnostics when the query changes', + desc = 'Update query editor diagnostics when the query changes', callback = function() vim.treesitter.query.lint(query_buf, { langs = lang, clear = false }) end, @@ -558,37 +558,37 @@ function M.preview_query() api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, { group = group, buffer = query_buf, - desc = 'Update query previewer highlights when the cursor moves', + desc = 'Update query editor highlights when the cursor moves', callback = function() if api.nvim_win_is_valid(win) then - update_preview_highlights(query_win, win) + update_editor_highlights(query_win, win) end end, }) api.nvim_create_autocmd('BufLeave', { group = group, buffer = query_buf, - desc = 'Clear the query previewer highlights when leaving the previewer', + desc = 'Clear highlights when leaving the query editor', callback = function() - api.nvim_buf_clear_namespace(buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1) end, }) api.nvim_create_autocmd('BufLeave', { group = group, buffer = buf, - desc = 'Clear the query previewer highlights when leaving the source buffer', + desc = 'Clear the query editor highlights when leaving the source buffer', callback = function() if not api.nvim_buf_is_loaded(query_buf) then return true end - api.nvim_buf_clear_namespace(query_buf, preview_ns, 0, -1) + api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1) end, }) api.nvim_create_autocmd('BufHidden', { group = group, buffer = buf, - desc = 'Close the previewer window when the source buffer is hidden', + desc = 'Close the editor window when the source buffer is hidden', once = true, callback = function() close_win(query_win) -- cgit From 28f54a78782318cb9c356a372b9e52a3a6b1f8dd Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 16 Sep 2023 10:05:59 -0700 Subject: feat(treesitter): add lang parameter to the query editor (#25181) --- runtime/lua/vim/treesitter/dev.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index bc54853103..7f24ba8590 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -468,11 +468,11 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') ---@param query_win integer ---@param base_win integer -local function update_editor_highlights(query_win, base_win) +---@param lang string +local function update_editor_highlights(query_win, base_win, lang) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) - local parser = vim.treesitter.get_parser(base_buf) - local lang = parser:lang() + local parser = vim.treesitter.get_parser(base_buf, lang) api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') @@ -506,7 +506,8 @@ local function update_editor_highlights(query_win, base_win) end --- @private -function M.edit_query() +--- @param lang? string language to open the query editor for. +function M.edit_query(lang) local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() @@ -528,11 +529,11 @@ function M.edit_query() end vim.cmd(cmd) - local ok, parser = pcall(vim.treesitter.get_parser, buf) + local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) if not ok then return nil, 'No parser available for the given buffer' end - local lang = parser:lang() + lang = parser:lang() local query_win = api.nvim_get_current_win() local query_buf = api.nvim_win_get_buf(query_win) @@ -561,7 +562,7 @@ function M.edit_query() desc = 'Update query editor highlights when the cursor moves', callback = function() if api.nvim_win_is_valid(win) then - update_editor_highlights(query_win, win) + update_editor_highlights(query_win, win, lang) end end, }) -- cgit From 1b55f51d0d8468ca357514a868ac8e188b0c8722 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 20 Sep 2023 04:15:23 -0700 Subject: docs: misc #24561 fix #24699 fix #25253 --- runtime/lua/vim/treesitter/dev.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 7f24ba8590..61d84017d4 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -597,8 +597,9 @@ function M.edit_query(lang) }) api.nvim_buf_set_lines(query_buf, 0, -1, false, { - ';; Write your query here. Use @captures to highlight matches in the source buffer.', - ';; Completion for grammar nodes is available (see :h compl-omni)', + ';; Write queries here (see $VIMRUNTIME/queries/ for examples).', + ';; Move cursor to a capture ("@foo") to highlight matches in the source buffer.', + ';; Completion for grammar nodes is available (:help compl-omni)', '', '', }) -- cgit From bc0bf9d030bbcb01db69c44cf88b95ca41dd3065 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Wed, 20 Sep 2023 19:03:40 -0700 Subject: docs: fix type warnings --- runtime/lua/vim/treesitter/dev.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 61d84017d4..db30d638af 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -258,7 +258,7 @@ end --- @private --- ---- @param opts InspectTreeOpts +--- @param opts InspectTreeOpts? function M.inspect_tree(opts) vim.validate({ opts = { opts, 't', true }, -- cgit From 3fd7449d5abe9a75fed8fb6b68c5958bd1a9ee12 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Tue, 17 Oct 2023 13:34:39 -0700 Subject: fix(treesitter): check that buf is loaded in autocommands (#25679) --- runtime/lua/vim/treesitter/dev.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index db30d638af..d9f0a21106 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -366,6 +366,10 @@ function M.inspect_tree(opts) group = group, buffer = b, callback = function() + if not api.nvim_buf_is_loaded(buf) then + return true + end + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] local pos = pg:get(row) @@ -438,6 +442,9 @@ function M.inspect_tree(opts) group = group, buffer = b, callback = function() + if not api.nvim_buf_is_loaded(buf) then + return true + end api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) end, }) @@ -449,7 +456,6 @@ function M.inspect_tree(opts) if not api.nvim_buf_is_loaded(b) then return true end - api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) end, }) -- cgit From 315c711700a87fe3fa546906ab39557ebba19baf Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Wed, 18 Oct 2023 21:18:24 -0700 Subject: fix(treesitter): set cursor position when opening inspector --- runtime/lua/vim/treesitter/dev.lua | 64 ++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index d9f0a21106..39c03cd1a5 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -181,6 +181,42 @@ local function set_dev_properties(w, b) vim.bo[b].filetype = 'query' end +--- Updates the cursor position in the inspector to match the node under the cursor. +--- +--- @param pg TSTreeView +--- @param lang string +--- @param source_buf integer +--- @param inspect_buf integer +--- @param inspect_win integer +--- @param pos? { [1]: integer, [2]: integer } +local function set_inspector_cursor(pg, lang, source_buf, inspect_buf, inspect_win, pos) + api.nvim_buf_clear_namespace(inspect_buf, pg.ns, 0, -1) + + local cursor_node = vim.treesitter.get_node({ + bufnr = source_buf, + lang = lang, + pos = pos, + ignore_injections = false, + }) + if not cursor_node then + return + end + + local cursor_node_id = cursor_node:id() + for i, v in pg:iter() do + if v.id == cursor_node_id then + local start = v.depth + local end_col = start + #v.text + api.nvim_buf_set_extmark(inspect_buf, pg.ns, i - 1, start, { + end_col = end_col, + hl_group = 'Visual', + }) + api.nvim_win_set_cursor(inspect_win, { i, 0 }) + break + end + end +end + --- Write the contents of this View into {bufnr}. --- ---@param bufnr integer Buffer number to write into. @@ -307,6 +343,9 @@ function M.inspect_tree(opts) pg:draw(b) + local cursor = api.nvim_win_get_cursor(win) + set_inspector_cursor(pg, opts.lang, buf, b, w, { cursor[1] - 1, cursor[2] }) + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) api.nvim_buf_set_keymap(b, 'n', '', '', { desc = 'Jump to the node under the cursor in the source buffer', @@ -398,30 +437,7 @@ function M.inspect_tree(opts) return true end - api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - - local cursor_node = vim.treesitter.get_node({ - bufnr = buf, - lang = opts.lang, - ignore_injections = false, - }) - if not cursor_node then - return - end - - local cursor_node_id = cursor_node:id() - for i, v in pg:iter() do - if v.id == cursor_node_id then - local start = v.depth - local end_col = start + #v.text - api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { - end_col = end_col, - hl_group = 'Visual', - }) - api.nvim_win_set_cursor(w, { i, 0 }) - break - end - end + set_inspector_cursor(pg, opts.lang, buf, b, w) end, }) -- cgit From 7bc5ee7f9327e8210c78bd21935130840aaf63f2 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 29 Nov 2023 08:10:02 -0600 Subject: fix(treesitter): use proper query syntax for inspector (#26274) --- runtime/lua/vim/treesitter/dev.lua | 69 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 36 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 39c03cd1a5..1c581e3f29 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -55,12 +55,12 @@ local function traverse(node, depth, lang, injections, tree) local text ---@type string if named then if field then - text = string.format('%s: (%s)', field, type) + text = string.format('%s: (%s', field, type) else - text = string.format('(%s)', type) + text = string.format('(%s', type) end else - text = string.format('"%s"', type:gsub('\n', '\\n')) + text = string.format('"%s"', type:gsub('\n', '\\n'):gsub('"', '\\"')) end table.insert(tree, { @@ -76,6 +76,10 @@ local function traverse(node, depth, lang, injections, tree) }) traverse(child, depth + 1, lang, injections, tree) + + if named then + tree[#tree].text = string.format('%s)', tree[#tree].text) + end end return tree @@ -152,12 +156,6 @@ local function get_range_str(lnum, col, end_lnum, end_col) return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) end ----@param text string ----@return string -local function escape_quotes(text) - return string.format('"%s"', text:sub(2, #text - 1):gsub('"', '\\"')) -end - ---@param w integer ---@return boolean closed Whether the window was closed. local function close_win(w) @@ -183,14 +181,14 @@ end --- Updates the cursor position in the inspector to match the node under the cursor. --- ---- @param pg TSTreeView +--- @param treeview TSTreeView --- @param lang string --- @param source_buf integer --- @param inspect_buf integer --- @param inspect_win integer --- @param pos? { [1]: integer, [2]: integer } -local function set_inspector_cursor(pg, lang, source_buf, inspect_buf, inspect_win, pos) - api.nvim_buf_clear_namespace(inspect_buf, pg.ns, 0, -1) +local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos) + api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1) local cursor_node = vim.treesitter.get_node({ bufnr = source_buf, @@ -203,11 +201,11 @@ local function set_inspector_cursor(pg, lang, source_buf, inspect_buf, inspect_w end local cursor_node_id = cursor_node:id() - for i, v in pg:iter() do + for i, v in treeview:iter() do if v.id == cursor_node_id then local start = v.depth local end_col = start + #v.text - api.nvim_buf_set_extmark(inspect_buf, pg.ns, i - 1, start, { + api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, { end_col = end_col, hl_group = 'Visual', }) @@ -229,9 +227,8 @@ function TSTreeView:draw(bufnr) for _, item in self:iter() do local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' - local text = item.named and item.text or escape_quotes(item.text) local line = - string.format('%s%s ; %s%s', string.rep(' ', item.depth), text, range_str, lang_str) + string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) if self.opts.lang then lang_hl_marks[#lang_hl_marks + 1] = { @@ -304,7 +301,7 @@ function M.inspect_tree(opts) local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() - local pg = assert(TSTreeView:new(buf, opts.lang)) + local treeview = assert(TSTreeView:new(buf, opts.lang)) -- Close any existing inspector window if vim.b[buf].dev_inspect then @@ -341,17 +338,17 @@ function M.inspect_tree(opts) assert(type(title) == 'string', 'Window title must be a string') api.nvim_buf_set_name(b, title) - pg:draw(b) + treeview:draw(b) local cursor = api.nvim_win_get_cursor(win) - set_inspector_cursor(pg, opts.lang, buf, b, w, { cursor[1] - 1, cursor[2] }) + set_inspector_cursor(treeview, opts.lang, buf, b, w, { cursor[1] - 1, cursor[2] }) - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) api.nvim_buf_set_keymap(b, 'n', '', '', { desc = 'Jump to the node under the cursor in the source buffer', callback = function() local row = api.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) + local pos = treeview:get(row) api.nvim_set_current_win(win) api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) end, @@ -360,21 +357,21 @@ function M.inspect_tree(opts) desc = 'Toggle anonymous nodes', callback = function() local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer - local curnode = pg:get(row) + local curnode = treeview:get(row) while curnode and not curnode.named do row = row - 1 - curnode = pg:get(row) + curnode = treeview:get(row) end - pg.opts.anon = not pg.opts.anon - pg:draw(b) + treeview.opts.anon = not treeview.opts.anon + treeview:draw(b) if not curnode then return end local id = curnode.id - for i, node in pg:iter() do + for i, node in treeview:iter() do if node.id == id then api.nvim_win_set_cursor(w, { i, col }) break @@ -385,8 +382,8 @@ function M.inspect_tree(opts) api.nvim_buf_set_keymap(b, 'n', 'I', '', { desc = 'Toggle language display', callback = function() - pg.opts.lang = not pg.opts.lang - pg:draw(b) + treeview.opts.lang = not treeview.opts.lang + treeview:draw(b) end, }) api.nvim_buf_set_keymap(b, 'n', 'o', '', { @@ -409,10 +406,10 @@ function M.inspect_tree(opts) return true end - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) - api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { + local pos = treeview:get(row) + api.nvim_buf_set_extmark(buf, treeview.ns, pos.lnum, pos.col, { end_row = pos.end_lnum, end_col = math.max(0, pos.end_col), hl_group = 'Visual', @@ -437,7 +434,7 @@ function M.inspect_tree(opts) return true end - set_inspector_cursor(pg, opts.lang, buf, b, w) + set_inspector_cursor(treeview, opts.lang, buf, b, w) end, }) @@ -449,8 +446,8 @@ function M.inspect_tree(opts) return true end - pg = assert(TSTreeView:new(buf, opts.lang)) - pg:draw(b) + treeview = assert(TSTreeView:new(buf, opts.lang)) + treeview:draw(b) end, }) @@ -461,7 +458,7 @@ function M.inspect_tree(opts) if not api.nvim_buf_is_loaded(buf) then return true end - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) end, }) @@ -472,7 +469,7 @@ function M.inspect_tree(opts) if not api.nvim_buf_is_loaded(b) then return true end - api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + api.nvim_buf_clear_namespace(b, treeview.ns, 0, -1) end, }) -- cgit From b6e339eb90e1a04f407f381739e46ad3c84f69c5 Mon Sep 17 00:00:00 2001 From: Pham Huy Hoang Date: Wed, 29 Nov 2023 23:16:52 +0900 Subject: fix(treesitter): make InspectTree correctly handle nested injections (#26085) Problem: Only injections under the top level tree are found. Solution: Iterate through all trees to find injections. When two injections are contained within the same node in the parent tree, prefer the injection with the larger byte length. --- runtime/lua/vim/treesitter/dev.lua | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 1c581e3f29..aa4331946a 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -105,18 +105,23 @@ function TSTreeView:new(bufnr, lang) -- the root in the child tree to the {injections} table. local root = parser:parse(true)[1]:root() local injections = {} ---@type table - for _, child in pairs(parser:children()) do - child:for_each_tree(function(tree, ltree) - local r = tree:root() - local node = root:named_descendant_for_range(r:range()) - if node then - injections[node:id()] = { - lang = ltree:lang(), - root = r, - } - end - end) - end + + parser:for_each_tree(function(parent_tree, parent_ltree) + local parent = parent_tree:root() + for _, child in pairs(parent_ltree:children()) do + child:for_each_tree(function(tree, ltree) + local r = tree:root() + local node = assert(parent:named_descendant_for_range(r:range())) + local id = node:id() + if not injections[id] or r:byte_length() > injections[id].root:byte_length() then + injections[id] = { + lang = ltree:lang(), + root = r, + } + end + end) + end + end) local nodes = traverse(root, 0, parser:lang(), injections, {}) -- cgit From 18c1fd8e9d759da6806747910320dce6bea2ab42 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 29 Nov 2023 08:59:36 -0600 Subject: docs: document TSNode:byte_length() (#26287) Also update the type annotation of TSNode:id(), which returns a string, not an integer. --- runtime/lua/vim/treesitter/dev.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index aa4331946a..d6825eb024 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -24,6 +24,10 @@ local TSTreeView = {} ---@field lang string Source language of this node ---@field root TSNode +---@class TSP.Injection +---@field lang string Source language of this injection +---@field root TSNode Root node of the injection + --- Traverse all child nodes starting at {node}. --- --- This is a recursive function. The {depth} parameter indicates the current recursion level. @@ -39,8 +43,8 @@ local TSTreeView = {} ---@param node TSNode Starting node to begin traversal |tsnode| ---@param depth integer Current recursion depth ---@param lang string Language of the tree currently being traversed ----@param injections table Mapping of node ids to root nodes of injected language trees (see ---- explanation above) +---@param injections table Mapping of node ids to root nodes +--- of injected language trees (see explanation above) ---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree local function traverse(node, depth, lang, injections, tree) local injection = injections[node:id()] @@ -104,7 +108,7 @@ function TSTreeView:new(bufnr, lang) -- the primary tree that contains that root. Add a mapping from the node in the primary tree to -- the root in the child tree to the {injections} table. local root = parser:parse(true)[1]:root() - local injections = {} ---@type table + local injections = {} ---@type table parser:for_each_tree(function(parent_tree, parent_ltree) local parent = parent_tree:root() -- cgit From 4a8bf24ac690004aedf5540fa440e788459e5e34 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:17:53 -0600 Subject: fix(treesitter): adjust indentation in inspector highlights (#26302) --- runtime/lua/vim/treesitter/dev.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/treesitter/dev.lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index d6825eb024..69ddc9b558 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -8,6 +8,7 @@ local M = {} ---@field opts table Options table with the following keys: --- - anon (boolean): If true, display anonymous nodes --- - lang (boolean): If true, display the language alongside each node +--- - indent (number): Number of spaces to indent nested lines. Default is 2. ---@field nodes TSP.Node[] ---@field named TSP.Node[] local TSTreeView = {} @@ -143,6 +144,7 @@ function TSTreeView:new(bufnr, lang) opts = { anon = false, lang = false, + indent = 2, }, } @@ -212,7 +214,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins local cursor_node_id = cursor_node:id() for i, v in treeview:iter() do if v.id == cursor_node_id then - local start = v.depth + local start = v.depth * treeview.opts.indent ---@type integer local end_col = start + #v.text api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, { end_col = end_col, @@ -236,8 +238,13 @@ function TSTreeView:draw(bufnr) for _, item in self:iter() do local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' - local line = - string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) + local line = string.format( + '%s%s ; %s%s', + string.rep(' ', item.depth * self.opts.indent), + item.text, + range_str, + lang_str + ) if self.opts.lang then lang_hl_marks[#lang_hl_marks + 1] = { -- cgit