Files
neovim/test/functional/treesitter/inspect_tree_spec.lua
phanium 16aab4cb48 fix(treesitter): InspectTree only show the largest injection #37906
Problem:
:InspectTree don't show luadoc injection lua file. Since luadoc share
the same "root" with comment in their common primary (lua) tree.
Current logic simply show the largest (comment injection) and ignore all
smaller one (luadoc injection).

Solution:
Handle different lang injections separately. Then sort them by
byte_length to ensure the draw tree consistent.
2026-02-24 16:22:30 -05:00

311 lines
10 KiB
Lua

local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local clear = n.clear
local insert = n.insert
local dedent = t.dedent
local eq = t.eq
local exec_lua = n.exec_lua
local feed = n.feed
describe('vim.treesitter.inspect_tree', function()
before_each(clear)
local expect_tree = function(x)
local expected = vim.split(vim.trim(dedent(x)), '\n')
local actual = n.buf_lines(0) ---@type string[]
eq(expected, actual)
end
it('working', function()
insert([[
print()
]])
exec_lua(function()
vim.treesitter.start(0, 'lua')
vim.treesitter.inspect_tree()
end)
expect_tree [[
(chunk ; [0, 0] - [2, 0]
(function_call ; [0, 0] - [0, 7]
name: (identifier) ; [0, 0] - [0, 5]
arguments: (arguments))) ; [0, 5] - [0, 7]
]]
end)
it('sets correct buffer name', function()
t.skip(t.is_zig_build(), 'vim.treesitter not found after chdir with build.zig')
n.api.nvim_set_current_dir(t.paths.test_source_path .. '/test/functional/fixtures')
n.command('edit lua/syntax_error.lua')
eq('lua/syntax_error.lua', n.fn.bufname('%'))
local full_path = n.api.nvim_buf_get_name(0)
n.exec_lua('vim.treesitter.start(0, "lua")')
n.exec_lua('vim.treesitter.inspect_tree()')
local expected = [[
(chunk ; [0, 0] - [1, 0]
(ERROR ; [0, 0] - [0, 21]
(identifier) ; [0, 5] - [0, 8]
(identifier) ; [0, 9] - [0, 15]
(identifier))) ; [0, 16] - [0, 21]
]]
expect_tree(expected)
eq('Syntax tree for lua/syntax_error.lua', n.fn.bufname('%'))
n.command('bwipe!')
eq('lua/syntax_error.lua', n.fn.bufname('%'))
n.api.nvim_set_current_dir('pack')
eq(full_path, n.fn.bufname('%'))
n.exec_lua('vim.treesitter.inspect_tree()')
expect_tree(expected)
eq('Syntax tree for ' .. full_path, n.fn.bufname('%'))
end)
it('can toggle to show anonymous nodes', function()
insert([[
print('hello')
]])
exec_lua(function()
vim.treesitter.start(0, 'lua')
vim.treesitter.inspect_tree()
end)
feed('a')
expect_tree [[
(chunk ; [0, 0] - [2, 0]
(function_call ; [0, 0] - [0, 14]
name: (identifier) ; [0, 0] - [0, 5]
arguments: (arguments ; [0, 5] - [0, 14]
"(" ; [0, 5] - [0, 6]
(string ; [0, 6] - [0, 13]
start: "'" ; [0, 6] - [0, 7]
content: (string_content) ; [0, 7] - [0, 12]
end: "'") ; [0, 12] - [0, 13]
")"))) ; [0, 13] - [0, 14]
]]
end)
it('works for injected trees', function()
insert([[
```lua
return
```
]])
exec_lua(function()
vim.treesitter.start(0, 'markdown')
vim.treesitter.get_parser():parse()
vim.treesitter.inspect_tree()
end)
expect_tree [[
(document ; [0, 0] - [4, 0]
(section ; [0, 0] - [4, 0]
(fenced_code_block ; [0, 0] - [3, 0]
(fenced_code_block_delimiter) ; [0, 0] - [0, 3]
(info_string ; [0, 3] - [0, 6]
(language)) ; [0, 3] - [0, 6]
(block_continuation) ; [1, 0] - [1, 0]
(code_fence_content ; [1, 0] - [2, 0]
(chunk ; [1, 0] - [2, 0]
(return_statement)) ; [1, 0] - [1, 6]
(block_continuation)) ; [2, 0] - [2, 0]
(fenced_code_block_delimiter)))) ; [2, 0] - [2, 3]
]]
end)
it('works with multiple injection on the same node', function()
insert([[--* #include<stdio.h>]])
exec_lua(function()
vim.treesitter.query.set(
'lua',
'injections',
[[
(comment
content: (_) @injection.content
(#set! injection.language "markdown"))
(comment
content: (_) @injection.content
(#set! injection.language "c")
(#offset! @injection.content 0 1 0 0))
]]
)
vim.treesitter.start(0, 'lua')
vim.treesitter.get_parser():parse(true)
vim.treesitter.inspect_tree()
end)
feed('I')
expect_tree [[
(chunk ; [0, 0] - [1, 0] lua
(comment ; [0, 0] - [0, 21] lua
content: (comment_content ; [0, 2] - [0, 21] lua
(document ; [0, 2] - [0, 21] markdown
(section ; [0, 2] - [0, 21] markdown
(list ; [0, 2] - [0, 21] markdown
(list_item ; [0, 2] - [0, 21] markdown
(list_marker_star) ; [0, 2] - [0, 4] markdown
(paragraph ; [0, 4] - [0, 21] markdown
(inline ; [0, 4] - [0, 21] markdown
(inline))))))) ; [0, 4] - [0, 21] markdown_inline
(translation_unit ; [0, 4] - [0, 21] c
(preproc_include ; [0, 4] - [0, 21] c
path: (system_lib_string)))))) ; [0, 12] - [0, 21] c
]]
end)
it('can toggle to show languages', function()
insert([[
```lua
return
```
]])
exec_lua(function()
vim.treesitter.start(0, 'markdown')
vim.treesitter.get_parser():parse()
vim.treesitter.inspect_tree()
end)
feed('I')
expect_tree [[
(document ; [0, 0] - [4, 0] markdown
(section ; [0, 0] - [4, 0] markdown
(fenced_code_block ; [0, 0] - [3, 0] markdown
(fenced_code_block_delimiter) ; [0, 0] - [0, 3] markdown
(info_string ; [0, 3] - [0, 6] markdown
(language)) ; [0, 3] - [0, 6] markdown
(block_continuation) ; [1, 0] - [1, 0] markdown
(code_fence_content ; [1, 0] - [2, 0] markdown
(chunk ; [1, 0] - [2, 0] lua
(return_statement)) ; [1, 0] - [1, 6] lua
(block_continuation)) ; [2, 0] - [2, 0] markdown
(fenced_code_block_delimiter)))) ; [2, 0] - [2, 3] markdown
]]
end)
it('updates source and tree buffer windows and closes them correctly', function()
local name = t.tmpname()
n.command('edit ' .. name)
insert([[
print()
]])
n.command('set filetype=lua | write')
-- setup two windows for the source buffer
exec_lua(function()
_G.source_win = vim.api.nvim_get_current_win()
_G.source_win2 = vim.api.nvim_open_win(0, false, {
win = 0,
split = 'left',
})
end)
-- setup three windows for the tree buffer
exec_lua(function()
vim.treesitter.inspect_tree()
_G.tree_win = vim.api.nvim_get_current_win()
_G.tree_win2 = vim.api.nvim_open_win(0, false, {
win = 0,
split = 'left',
})
_G.tree_win3 = vim.api.nvim_open_win(0, false, {
win = 0,
split = 'left',
})
end)
-- close original source window without closing tree views
exec_lua('vim.api.nvim_set_current_win(source_win)')
feed(':quit<CR>')
eq('', n.api.nvim_get_vvar('errmsg'))
eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win)'))
eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win2)'))
eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win3)'))
-- navigates correctly to the remaining source buffer window
exec_lua('vim.api.nvim_set_current_win(tree_win)')
feed('<CR>')
eq('', n.api.nvim_get_vvar('errmsg'))
eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2'))
-- close original tree window
exec_lua(function()
vim.api.nvim_set_current_win(_G.tree_win2)
vim.api.nvim_win_close(_G.tree_win, false)
end)
-- navigates correctly to the remaining source buffer window
feed('<CR>')
eq('', n.api.nvim_get_vvar('errmsg'))
eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2'))
-- close source buffer window and all remaining tree windows
n.expect_exit(n.command, 'quit')
end)
it('shows which nodes are missing', function()
insert([[
int main() {
if (a.) {
// ^ MISSING field_identifier here
if (1) d()
// ^ MISSING ";" here
}
}
]])
exec_lua(function()
vim.treesitter.start(0, 'c')
vim.treesitter.inspect_tree()
end)
feed('a')
expect_tree [[
(translation_unit ; [0, 0] - [8, 0]
(function_definition ; [0, 0] - [6, 1]
type: (primitive_type) ; [0, 0] - [0, 3]
declarator: (function_declarator ; [0, 4] - [0, 10]
declarator: (identifier) ; [0, 4] - [0, 8]
parameters: (parameter_list ; [0, 8] - [0, 10]
"(" ; [0, 8] - [0, 9]
")")) ; [0, 9] - [0, 10]
body: (compound_statement ; [0, 11] - [6, 1]
"{" ; [0, 11] - [0, 12]
(if_statement ; [1, 4] - [5, 5]
"if" ; [1, 4] - [1, 6]
condition: (parenthesized_expression ; [1, 7] - [1, 11]
"(" ; [1, 7] - [1, 8]
(field_expression ; [1, 8] - [1, 10]
argument: (identifier) ; [1, 8] - [1, 9]
operator: "." ; [1, 9] - [1, 10]
field: (MISSING field_identifier)) ; [1, 10] - [1, 10]
")") ; [1, 10] - [1, 11]
consequence: (compound_statement ; [1, 12] - [5, 5]
"{" ; [1, 12] - [1, 13]
(comment) ; [2, 4] - [2, 41]
(if_statement ; [3, 8] - [4, 36]
"if" ; [3, 8] - [3, 10]
condition: (parenthesized_expression ; [3, 11] - [3, 14]
"(" ; [3, 11] - [3, 12]
(number_literal) ; [3, 12] - [3, 13]
")") ; [3, 13] - [3, 14]
consequence: (expression_statement ; [3, 15] - [4, 36]
(call_expression ; [3, 15] - [3, 18]
function: (identifier) ; [3, 15] - [3, 16]
arguments: (argument_list ; [3, 16] - [3, 18]
"(" ; [3, 16] - [3, 17]
")")) ; [3, 17] - [3, 18]
(comment) ; [4, 8] - [4, 36]
(MISSING ";"))) ; [4, 36] - [4, 36]
"}")) ; [5, 4] - [5, 5]
"}"))) ; [6, 0] - [6, 1]
]]
end)
end)