aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter/dev.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/treesitter/dev.lua')
-rw-r--r--runtime/lua/vim/treesitter/dev.lua228
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,
})