diff options
Diffstat (limited to 'runtime/lua/vim/treesitter/dev.lua')
-rw-r--r-- | runtime/lua/vim/treesitter/dev.lua | 228 |
1 files changed, 120 insertions, 108 deletions
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 69ddc9b558..dc2a14d238 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -1,31 +1,29 @@ local api = vim.api ----@class TSDevModule local M = {} ----@class TSTreeView +---@class (private) vim.treesitter.dev.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 ---- - indent (number): Number of spaces to indent nested lines. Default is 2. ----@field nodes TSP.Node[] ----@field named TSP.Node[] +---@field opts vim.treesitter.dev.TSTreeViewOpts +---@field nodes vim.treesitter.dev.Node[] +---@field named vim.treesitter.dev.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 +---@private +---@class (private) vim.treesitter.dev.TSTreeViewOpts +---@field anon boolean If true, display anonymous nodes. +---@field lang boolean If true, display the language alongside each node. +---@field indent number Number of spaces to indent nested lines. + +---@class (private) vim.treesitter.dev.Node +---@field node TSNode Treesitter node +---@field field string? Node field +---@field depth integer Depth of this node in the tree +---@field text string? Text displayed in the inspector for this node. Not computed until the +--- inspector is drawn. ---@field lang string Source language of this node ----@field root TSNode ----@class TSP.Injection +---@class (private) vim.treesitter.dev.Injection ---@field lang string Source language of this injection ---@field root TSNode Root node of the injection @@ -43,48 +41,26 @@ local TSTreeView = {} --- ---@param node TSNode Starting node to begin traversal |tsnode| ---@param depth integer Current recursion depth +---@param field string|nil The field of the current node ---@param lang string Language of the tree currently being traversed ----@param injections table<string, TSP.Injection> Mapping of node ids to root nodes +---@param injections table<string, vim.treesitter.dev.Injection> 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) +---@param tree vim.treesitter.dev.Node[] Output table containing a list of tables each representing a node in the tree +local function traverse(node, depth, field, lang, injections, tree) + table.insert(tree, { + node = node, + depth = depth, + lang = lang, + field = field, + }) + local injection = injections[node:id()] if injection then - traverse(injection.root, depth, injection.lang, injections, tree) + traverse(injection.root, depth + 1, nil, 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'):gsub('"', '\\"')) - 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) - - if named then - tree[#tree].text = string.format('%s)', tree[#tree].text) - end + for child, child_field in node:iter_children() do + traverse(child, depth + 1, child_field, lang, injections, tree) end return tree @@ -95,44 +71,45 @@ end ---@param bufnr integer Source buffer number ---@param lang string|nil Language of source buffer --- ----@return TSTreeView|nil +---@return vim.treesitter.dev.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' + local err = parser --[[ @as string ]] + return nil, 'No parser available for the given buffer:\n' .. err 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(true)[1]:root() - local injections = {} ---@type table<string, TSP.Injection> + local injections = {} ---@type table<string, vim.treesitter.dev.Injection> 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) + for _, tree in pairs(child:trees()) do 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(), + lang = child:lang(), root = r, } end - end) + end end end) - local nodes = traverse(root, 0, parser:lang(), injections, {}) + local nodes = traverse(root, 0, nil, parser:lang(), injections, {}) - local named = {} ---@type TSP.Node[] + local named = {} ---@type vim.treesitter.dev.Node[] for _, v in ipairs(nodes) do - if v.named then + if v.node:named() then named[#named + 1] = v end end @@ -141,6 +118,7 @@ function TSTreeView:new(bufnr, lang) ns = api.nvim_create_namespace('treesitter/dev-inspect'), nodes = nodes, named = named, + ---@type vim.treesitter.dev.TSTreeViewOpts opts = { anon = false, lang = false, @@ -155,16 +133,12 @@ end local decor_ns = api.nvim_create_namespace('ts.dev') ----@param lnum integer ----@param col integer ----@param end_lnum integer ----@param end_col integer +---@param range Range4 ---@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) +local function range_to_string(range) + ---@type integer, integer, integer, integer + local row, col, end_row, end_col = unpack(range) + return string.format('[%d, %d] - [%d, %d]', row, col, end_row, end_col) end ---@param w integer @@ -183,7 +157,10 @@ end 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.wo[w].foldmethod = 'expr' + vim.wo[w].foldexpr = 'v:lua.vim.treesitter.foldexpr()' -- explicitly set foldexpr + vim.wo[w].foldenable = false -- Don't fold on first open InspectTree + vim.wo[w].foldlevel = 99 vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' @@ -192,7 +169,7 @@ end --- Updates the cursor position in the inspector to match the node under the cursor. --- ---- @param treeview TSTreeView +--- @param treeview vim.treesitter.dev.TSTreeView --- @param lang string --- @param source_buf integer --- @param inspect_buf integer @@ -213,7 +190,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 + if v.node:id() == cursor_node_id then 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, { @@ -228,6 +205,8 @@ end --- Write the contents of this View into {bufnr}. --- +--- Calling this function computes the text that is displayed for each node. +--- ---@param bufnr integer Buffer number to write into. ---@package function TSTreeView:draw(bufnr) @@ -235,13 +214,35 @@ function TSTreeView:draw(bufnr) 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) + for i, item in self:iter() do + local range_str = range_to_string({ item.node:range() }) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + + local text ---@type string + if item.node:named() then + if item.field then + text = string.format('%s: (%s', item.field, item.node:type()) + else + text = string.format('(%s', item.node:type()) + end + else + text = string.format('"%s"', item.node:type():gsub('\n', '\\n'):gsub('"', '\\"')) + end + + local next = self:get(i + 1) + if not next or next.depth <= item.depth then + local parens = item.depth - (next and next.depth or 0) + (item.node:named() and 1 or 0) + if parens > 0 then + text = string.format('%s%s', text, string.rep(')', parens)) + end + end + + item.text = text + local line = string.format( '%s%s ; %s%s', string.rep(' ', item.depth * self.opts.indent), - item.text, + text, range_str, lang_str ) @@ -253,7 +254,7 @@ function TSTreeView:draw(bufnr) } end - lines[#lines + 1] = line + lines[i] = line end api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) @@ -275,7 +276,7 @@ end --- The node number is dependent on whether or not anonymous nodes are displayed. --- ---@param i integer Node number to get ----@return TSP.Node +---@return vim.treesitter.dev.Node ---@package function TSTreeView:get(i) local t = self.opts.anon and self.nodes or self.named @@ -284,7 +285,7 @@ end --- Iterate over all of the nodes in this View. --- ----@return (fun(): integer, TSP.Node) Iterator over all nodes in this View +---@return (fun(): integer, vim.treesitter.dev.Node) Iterator over all nodes in this View ---@return table ---@return integer ---@package @@ -292,22 +293,31 @@ 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. +--- @class vim.treesitter.dev.inspect_tree.Opts +--- @inlinedoc +--- +--- The language of the source buffer. If omitted, the filetype of the source +--- buffer is used. +--- @field lang string? +--- +--- Buffer to draw the tree into. If omitted, a new buffer is created. +--- @field bufnr integer? +--- +--- Window id to display the tree buffer in. If omitted, a new window is +--- created with {command}. +--- @field winid integer? +--- +--- Vimscript command to create the window. Default value is "60vnew". +--- Only used when {winid} is nil. +--- @field command string? +--- +--- 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. +--- @field title (string|fun(bufnr:integer):string|nil) --- @private --- ---- @param opts InspectTreeOpts? +--- @param opts vim.treesitter.dev.inspect_tree.Opts? function M.inspect_tree(opts) vim.validate({ opts = { opts, 't', true }, @@ -364,9 +374,9 @@ function M.inspect_tree(opts) 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 = treeview:get(row) + local lnum, col = treeview:get(row).node:start() api.nvim_set_current_win(win) - api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + api.nvim_win_set_cursor(win, { lnum + 1, col }) end, }) api.nvim_buf_set_keymap(b, 'n', 'a', '', { @@ -374,7 +384,7 @@ function M.inspect_tree(opts) callback = function() local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer local curnode = treeview:get(row) - while curnode and not curnode.named do + while curnode and not curnode.node:named() do row = row - 1 curnode = treeview:get(row) end @@ -386,9 +396,9 @@ function M.inspect_tree(opts) return end - local id = curnode.id + local id = curnode.node:id() for i, node in treeview:iter() do - if node.id == id then + if node.node:id() == id then api.nvim_win_set_cursor(w, { i, col }) break end @@ -424,20 +434,20 @@ function M.inspect_tree(opts) api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] - 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), + local lnum, col, end_lnum, end_col = treeview:get(row).node:range() + api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, { + end_row = end_lnum, + end_col = math.max(0, 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 }) + if lnum < topline and end_lnum < topline then + api.nvim_win_set_cursor(win, { end_lnum + 1, 0 }) + elseif lnum > botline and end_lnum > botline then + api.nvim_win_set_cursor(win, { lnum + 1, 0 }) end end, }) @@ -462,7 +472,9 @@ function M.inspect_tree(opts) return true end + local treeview_opts = treeview.opts treeview = assert(TSTreeView:new(buf, opts.lang)) + treeview.opts = treeview_opts treeview:draw(b) end, }) |