mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 20:55:18 -07:00
fix(treesitter): fix parens stacking in inspector display (#26304)
When first opened, the tree-sitter inspector traverses all of the nodes in the buffer to calculate an array of nodes. This traversal is done only once, and _all_ nodes (both named and anonymous) are included. Toggling anonymous nodes in the inspector only changes how the tree is drawn in the buffer, but does not affect the underlying data structure at all. When the buffer is traversed and the list of nodes is calculated, we don't know whether or not anonymous nodes will be displayed in the inspector or not. Thus, we cannot determine during traversal where to put closing parentheses. Instead, this must be done when drawing. When we draw, the tree structure has been flatted into a single array, so we lose parent-child relationships that would otherwise make determining the number of closing parentheses straightforward. However, we can instead rely on the fact that a delta between the depth of a node and the depth of the successive node _must_ mean that more closing parentheses are required: (foo (bar) (baz) ↑ │ └ (bar) and (baz) have different depths, so (bar) must have an extra closing parenthesis This does not depend on whether or not anonymous nodes are displayed and so works in both cases.
This commit is contained in:
parent
52d738826c
commit
01b91deec7
@ -14,16 +14,12 @@ local M = {}
|
|||||||
local TSTreeView = {}
|
local TSTreeView = {}
|
||||||
|
|
||||||
---@class TSP.Node
|
---@class TSP.Node
|
||||||
---@field id integer Node id
|
---@field node TSNode Tree-sitter node
|
||||||
---@field text string Node text
|
---@field field string? Node field
|
||||||
---@field named boolean True if this is a named (non-anonymous) node
|
---@field depth integer Depth of this node in the tree
|
||||||
---@field depth integer Depth of the node within the tree
|
---@field text string? Text displayed in the inspector for this node. Not computed until the
|
||||||
---@field lnum integer Beginning line number of this node in the source buffer
|
--- inspector is drawn.
|
||||||
---@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 lang string Source language of this node
|
||||||
---@field root TSNode
|
|
||||||
|
|
||||||
---@class TSP.Injection
|
---@class TSP.Injection
|
||||||
---@field lang string Source language of this injection
|
---@field lang string Source language of this injection
|
||||||
@ -54,37 +50,14 @@ local function traverse(node, depth, lang, injections, tree)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for child, field in node:iter_children() do
|
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, {
|
table.insert(tree, {
|
||||||
id = child:id(),
|
node = child,
|
||||||
text = text,
|
field = field,
|
||||||
named = named,
|
|
||||||
depth = depth,
|
depth = depth,
|
||||||
lnum = lnum,
|
|
||||||
col = col,
|
|
||||||
end_lnum = end_lnum,
|
|
||||||
end_col = end_col,
|
|
||||||
lang = lang,
|
lang = lang,
|
||||||
})
|
})
|
||||||
|
|
||||||
traverse(child, depth + 1, lang, injections, tree)
|
traverse(child, depth + 1, lang, injections, tree)
|
||||||
|
|
||||||
if named then
|
|
||||||
tree[#tree].text = string.format('%s)', tree[#tree].text)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return tree
|
return tree
|
||||||
@ -132,7 +105,7 @@ function TSTreeView:new(bufnr, lang)
|
|||||||
|
|
||||||
local named = {} ---@type TSP.Node[]
|
local named = {} ---@type TSP.Node[]
|
||||||
for _, v in ipairs(nodes) do
|
for _, v in ipairs(nodes) do
|
||||||
if v.named then
|
if v.node:named() then
|
||||||
named[#named + 1] = v
|
named[#named + 1] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -213,7 +186,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins
|
|||||||
|
|
||||||
local cursor_node_id = cursor_node:id()
|
local cursor_node_id = cursor_node:id()
|
||||||
for i, v in treeview:iter() do
|
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 start = v.depth * treeview.opts.indent ---@type integer
|
||||||
local end_col = start + #v.text
|
local end_col = start + #v.text
|
||||||
api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, {
|
api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, {
|
||||||
@ -228,6 +201,8 @@ end
|
|||||||
|
|
||||||
--- Write the contents of this View into {bufnr}.
|
--- 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.
|
---@param bufnr integer Buffer number to write into.
|
||||||
---@package
|
---@package
|
||||||
function TSTreeView:draw(bufnr)
|
function TSTreeView:draw(bufnr)
|
||||||
@ -235,13 +210,35 @@ function TSTreeView:draw(bufnr)
|
|||||||
local lines = {} ---@type string[]
|
local lines = {} ---@type string[]
|
||||||
local lang_hl_marks = {} ---@type table[]
|
local lang_hl_marks = {} ---@type table[]
|
||||||
|
|
||||||
for _, item in self:iter() do
|
for i, item in self:iter() do
|
||||||
local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col)
|
local range_str = get_range_str(item.node:range())
|
||||||
local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
|
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(
|
local line = string.format(
|
||||||
'%s%s ; %s%s',
|
'%s%s ; %s%s',
|
||||||
string.rep(' ', item.depth * self.opts.indent),
|
string.rep(' ', item.depth * self.opts.indent),
|
||||||
item.text,
|
text,
|
||||||
range_str,
|
range_str,
|
||||||
lang_str
|
lang_str
|
||||||
)
|
)
|
||||||
@ -253,7 +250,7 @@ function TSTreeView:draw(bufnr)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
lines[#lines + 1] = line
|
lines[i] = line
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
@ -364,9 +361,9 @@ function M.inspect_tree(opts)
|
|||||||
desc = 'Jump to the node under the cursor in the source buffer',
|
desc = 'Jump to the node under the cursor in the source buffer',
|
||||||
callback = function()
|
callback = function()
|
||||||
local row = api.nvim_win_get_cursor(w)[1]
|
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_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,
|
end,
|
||||||
})
|
})
|
||||||
api.nvim_buf_set_keymap(b, 'n', 'a', '', {
|
api.nvim_buf_set_keymap(b, 'n', 'a', '', {
|
||||||
@ -374,7 +371,7 @@ function M.inspect_tree(opts)
|
|||||||
callback = function()
|
callback = function()
|
||||||
local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer
|
local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer
|
||||||
local curnode = treeview:get(row)
|
local curnode = treeview:get(row)
|
||||||
while curnode and not curnode.named do
|
while curnode and not curnode.node:named() do
|
||||||
row = row - 1
|
row = row - 1
|
||||||
curnode = treeview:get(row)
|
curnode = treeview:get(row)
|
||||||
end
|
end
|
||||||
@ -386,9 +383,9 @@ function M.inspect_tree(opts)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local id = curnode.id
|
local id = curnode.node:id()
|
||||||
for i, node in treeview:iter() do
|
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 })
|
api.nvim_win_set_cursor(w, { i, col })
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -424,20 +421,20 @@ function M.inspect_tree(opts)
|
|||||||
|
|
||||||
api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
|
api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
|
||||||
local row = api.nvim_win_get_cursor(w)[1]
|
local row = api.nvim_win_get_cursor(w)[1]
|
||||||
local pos = treeview:get(row)
|
local lnum, col, end_lnum, end_col = treeview:get(row).node:range()
|
||||||
api.nvim_buf_set_extmark(buf, treeview.ns, pos.lnum, pos.col, {
|
api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, {
|
||||||
end_row = pos.end_lnum,
|
end_row = end_lnum,
|
||||||
end_col = math.max(0, pos.end_col),
|
end_col = math.max(0, end_col),
|
||||||
hl_group = 'Visual',
|
hl_group = 'Visual',
|
||||||
})
|
})
|
||||||
|
|
||||||
local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
|
local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
|
||||||
|
|
||||||
-- Move the cursor if highlighted range is completely out of view
|
-- Move the cursor if highlighted range is completely out of view
|
||||||
if pos.lnum < topline and pos.end_lnum < topline then
|
if lnum < topline and end_lnum < topline then
|
||||||
api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 })
|
api.nvim_win_set_cursor(win, { end_lnum + 1, 0 })
|
||||||
elseif pos.lnum > botline and pos.end_lnum > botline then
|
elseif lnum > botline and end_lnum > botline then
|
||||||
api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 })
|
api.nvim_win_set_cursor(win, { lnum + 1, 0 })
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user