mirror of
https://github.com/neovim/neovim.git
synced 2025-09-09 12:58:16 +00:00

Implement the LanguageTree structure to enable language injection. This is done be removing the old Parser metatable and replacing by the new structure, with the same API (almost). Some noticeable differences : - `parser:parse()` now returns a table of trees - There is no incremental parsing for child (injected) languages Co-authored-by: Thomas Vigouroux <tomvig38@gmail.com>
915 lines
36 KiB
Lua
915 lines
36 KiB
Lua
-- Test suite for testing interactions with API bindings
|
|
local helpers = require('test.functional.helpers')(after_each)
|
|
local Screen = require('test.functional.ui.screen')
|
|
|
|
local clear = helpers.clear
|
|
local eq = helpers.eq
|
|
local insert = helpers.insert
|
|
local exec_lua = helpers.exec_lua
|
|
local feed = helpers.feed
|
|
local pcall_err = helpers.pcall_err
|
|
local matches = helpers.matches
|
|
|
|
before_each(clear)
|
|
|
|
describe('treesitter API', function()
|
|
-- error tests not requiring a parser library
|
|
it('handles missing language', function()
|
|
eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
|
|
pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')"))
|
|
|
|
-- actual message depends on platform
|
|
matches("Error executing lua: Failed to load parser: uv_dlopen: .+",
|
|
pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
|
|
|
|
eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
|
|
pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
|
|
end)
|
|
end)
|
|
|
|
describe('treesitter API with C parser', function()
|
|
local function check_parser()
|
|
local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]]))
|
|
if not status then
|
|
if helpers.isCI() then
|
|
error("treesitter C parser not found, required on CI: " .. msg)
|
|
else
|
|
pending('no C parser, skipping')
|
|
end
|
|
end
|
|
return status
|
|
end
|
|
|
|
it('parses buffer', function()
|
|
if helpers.pending_win32(pending) or not check_parser() then return end
|
|
|
|
insert([[
|
|
int main() {
|
|
int x = 3;
|
|
}]])
|
|
|
|
exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
tree = parser:parse()[1]
|
|
root = tree:root()
|
|
lang = vim.treesitter.inspect_language('c')
|
|
]])
|
|
|
|
eq("<tree>", exec_lua("return tostring(tree)"))
|
|
eq("<node translation_unit>", exec_lua("return tostring(root)"))
|
|
eq({0,0,3,0}, exec_lua("return {root:range()}"))
|
|
|
|
eq(1, exec_lua("return root:child_count()"))
|
|
exec_lua("child = root:child(0)")
|
|
eq("<node function_definition>", exec_lua("return tostring(child)"))
|
|
eq({0,0,2,1}, exec_lua("return {child:range()}"))
|
|
|
|
eq("function_definition", exec_lua("return child:type()"))
|
|
eq(true, exec_lua("return child:named()"))
|
|
eq("number", type(exec_lua("return child:symbol()")))
|
|
eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]"))
|
|
|
|
exec_lua("anon = root:descendant_for_range(0,8,0,9)")
|
|
eq("(", exec_lua("return anon:type()"))
|
|
eq(false, exec_lua("return anon:named()"))
|
|
eq("number", type(exec_lua("return anon:symbol()")))
|
|
eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]"))
|
|
|
|
exec_lua("descendant = root:descendant_for_range(1,2,1,12)")
|
|
eq("<node declaration>", exec_lua("return tostring(descendant)"))
|
|
eq({1,2,1,12}, exec_lua("return {descendant:range()}"))
|
|
eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()"))
|
|
|
|
feed("2G7|ay")
|
|
exec_lua([[
|
|
tree2 = parser:parse()[1]
|
|
root2 = tree2:root()
|
|
descendant2 = root2:descendant_for_range(1,2,1,13)
|
|
]])
|
|
eq(false, exec_lua("return tree2 == tree1"))
|
|
eq(false, exec_lua("return root2 == root"))
|
|
eq("<node declaration>", exec_lua("return tostring(descendant2)"))
|
|
eq({1,2,1,13}, exec_lua("return {descendant2:range()}"))
|
|
|
|
eq(true, exec_lua("return child == child"))
|
|
-- separate lua object, but represents same node
|
|
eq(true, exec_lua("return child == root:child(0)"))
|
|
eq(false, exec_lua("return child == descendant2"))
|
|
eq(false, exec_lua("return child == nil"))
|
|
eq(false, exec_lua("return child == tree"))
|
|
|
|
eq("string", exec_lua("return type(child:id())"))
|
|
eq(true, exec_lua("return child:id() == child:id()"))
|
|
-- separate lua object, but represents same node
|
|
eq(true, exec_lua("return child:id() == root:child(0):id()"))
|
|
eq(false, exec_lua("return child:id() == descendant2:id()"))
|
|
eq(false, exec_lua("return child:id() == nil"))
|
|
eq(false, exec_lua("return child:id() == tree"))
|
|
|
|
-- unchanged buffer: return the same tree
|
|
eq(true, exec_lua("return parser:parse()[1] == tree2"))
|
|
end)
|
|
|
|
local test_text = [[
|
|
void ui_refresh(void)
|
|
{
|
|
int width = INT_MAX, height = INT_MAX;
|
|
bool ext_widgets[kUIExtCount];
|
|
for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
|
|
ext_widgets[i] = true;
|
|
}
|
|
|
|
bool inclusive = ui_override();
|
|
for (size_t i = 0; i < ui_count; i++) {
|
|
UI *ui = uis[i];
|
|
width = MIN(ui->width, width);
|
|
height = MIN(ui->height, height);
|
|
foo = BAR(ui->bazaar, bazaar);
|
|
for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
|
|
ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
|
|
}
|
|
}
|
|
}]]
|
|
|
|
it('allows to iterate over nodes children', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text);
|
|
|
|
local res = exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
|
|
func_node = parser:parse()[1]:root():child(0)
|
|
|
|
res = {}
|
|
for node, field in func_node:iter_children() do
|
|
table.insert(res, {node:type(), field})
|
|
end
|
|
return res
|
|
]])
|
|
|
|
eq({
|
|
{"primitive_type", "type"},
|
|
{"function_declarator", "declarator"},
|
|
{"compound_statement", "body"}
|
|
}, res)
|
|
end)
|
|
|
|
it('allows to get a child by field', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text);
|
|
|
|
local res = exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
|
|
func_node = parser:parse()[1]:root():child(0)
|
|
|
|
local res = {}
|
|
for _, node in ipairs(func_node:field("type")) do
|
|
table.insert(res, {node:type(), node:range()})
|
|
end
|
|
return res
|
|
]])
|
|
|
|
eq({{ "primitive_type", 0, 0, 0, 4 }}, res)
|
|
|
|
local res_fail = exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
|
|
return #func_node:field("foo") == 0
|
|
]])
|
|
|
|
assert(res_fail)
|
|
end)
|
|
|
|
local query = [[
|
|
((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN"))
|
|
"for" @keyword
|
|
(primitive_type) @type
|
|
(field_expression argument: (identifier) @fieldarg)
|
|
]]
|
|
|
|
it("supports runtime queries", function()
|
|
if not check_parser() then return end
|
|
|
|
local ret = exec_lua [[
|
|
return require"vim.treesitter.query".get_query("c", "highlights").captures[1]
|
|
]]
|
|
|
|
eq('variable', ret)
|
|
end)
|
|
|
|
it('support query and iter by capture', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text)
|
|
|
|
local res = exec_lua([[
|
|
cquery = vim.treesitter.parse_query("c", ...)
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
tree = parser:parse()[1]
|
|
res = {}
|
|
for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do
|
|
-- can't transmit node over RPC. just check the name and range
|
|
table.insert(res, {cquery.captures[cid], node:type(), node:range()})
|
|
end
|
|
return res
|
|
]], query)
|
|
|
|
eq({
|
|
{ "type", "primitive_type", 8, 2, 8, 6 },
|
|
{ "keyword", "for", 9, 2, 9, 5 },
|
|
{ "type", "primitive_type", 9, 7, 9, 13 },
|
|
{ "minfunc", "identifier", 11, 12, 11, 15 },
|
|
{ "fieldarg", "identifier", 11, 16, 11, 18 },
|
|
{ "min_id", "identifier", 11, 27, 11, 32 },
|
|
{ "minfunc", "identifier", 12, 13, 12, 16 },
|
|
{ "fieldarg", "identifier", 12, 17, 12, 19 },
|
|
{ "min_id", "identifier", 12, 29, 12, 35 },
|
|
{ "fieldarg", "identifier", 13, 14, 13, 16 }
|
|
}, res)
|
|
end)
|
|
|
|
it('support query and iter by match', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text)
|
|
|
|
local res = exec_lua([[
|
|
cquery = vim.treesitter.parse_query("c", ...)
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
tree = parser:parse()[1]
|
|
res = {}
|
|
for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do
|
|
-- can't transmit node over RPC. just check the name and range
|
|
local mrepr = {}
|
|
for cid,node in pairs(match) do
|
|
table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
|
|
end
|
|
table.insert(res, {pattern, mrepr})
|
|
end
|
|
return res
|
|
]], query)
|
|
|
|
eq({
|
|
{ 3, { { "type", "primitive_type", 8, 2, 8, 6 } } },
|
|
{ 2, { { "keyword", "for", 9, 2, 9, 5 } } },
|
|
{ 3, { { "type", "primitive_type", 9, 7, 9, 13 } } },
|
|
{ 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } },
|
|
{ 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } },
|
|
{ 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } },
|
|
{ 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } },
|
|
{ 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } }
|
|
}, res)
|
|
end)
|
|
|
|
it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function()
|
|
if not check_parser() then return end
|
|
|
|
insert('char* astring = "Hello World!";')
|
|
|
|
local res = exec_lua([[
|
|
cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))')
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
tree = parser:parse()[1]
|
|
res = {}
|
|
for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do
|
|
-- can't transmit node over RPC. just check the name and range
|
|
local mrepr = {}
|
|
for cid,node in pairs(match) do
|
|
table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
|
|
end
|
|
table.insert(res, {pattern, mrepr})
|
|
end
|
|
return res
|
|
]])
|
|
|
|
eq({
|
|
{ 1, { { "quote", '"', 0, 16, 0, 17 } } },
|
|
{ 2, { { "quote", '"', 0, 16, 0, 17 } } },
|
|
{ 1, { { "quote", '"', 0, 29, 0, 30 } } },
|
|
{ 2, { { "quote", '"', 0, 29, 0, 30 } } },
|
|
}, res)
|
|
end)
|
|
|
|
it('allows to add predicates', function()
|
|
insert([[
|
|
int main(void) {
|
|
return 0;
|
|
}
|
|
]])
|
|
|
|
local custom_query = "((identifier) @main (#is-main? @main))"
|
|
|
|
local res = exec_lua([[
|
|
local query = require"vim.treesitter.query"
|
|
|
|
local function is_main(match, pattern, bufnr, predicate)
|
|
local node = match[ predicate[2] ]
|
|
|
|
return query.get_node_text(node, bufnr)
|
|
end
|
|
|
|
local parser = vim.treesitter.get_parser(0, "c")
|
|
|
|
query.add_predicate("is-main?", is_main)
|
|
|
|
local query = query.parse_query("c", ...)
|
|
|
|
local nodes = {}
|
|
for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do
|
|
table.insert(nodes, {node:range()})
|
|
end
|
|
|
|
return nodes
|
|
]], custom_query)
|
|
|
|
eq({{0, 4, 0, 8}}, res)
|
|
|
|
local res_list = exec_lua[[
|
|
local query = require'vim.treesitter.query'
|
|
|
|
local list = query.list_predicates()
|
|
|
|
table.sort(list)
|
|
|
|
return list
|
|
]]
|
|
|
|
eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
|
|
end)
|
|
|
|
local hl_text = [[
|
|
/// Schedule Lua callback on main loop's event queue
|
|
static int nlua_schedule(lua_State *const lstate)
|
|
{
|
|
if (lua_type(lstate, 1) != LUA_TFUNCTION
|
|
|| lstate != lstate) {
|
|
lua_pushliteral(lstate, "vim.schedule: expected function");
|
|
return lua_error(lstate);
|
|
}
|
|
|
|
LuaRef cb = nlua_ref(lstate, 1);
|
|
|
|
multiqueue_put(main_loop.events, nlua_schedule_event,
|
|
1, (void *)(ptrdiff_t)cb);
|
|
return 0;
|
|
}]]
|
|
|
|
local hl_query = [[
|
|
(ERROR) @ErrorMsg
|
|
|
|
"if" @keyword
|
|
"else" @keyword
|
|
"for" @keyword
|
|
"return" @keyword
|
|
|
|
"const" @type
|
|
"static" @type
|
|
"struct" @type
|
|
"enum" @type
|
|
"extern" @type
|
|
|
|
(string_literal) @string
|
|
|
|
(number_literal) @number
|
|
(char_literal) @string
|
|
|
|
(type_identifier) @type
|
|
((type_identifier) @Special (#eq? @Special "LuaRef"))
|
|
|
|
(primitive_type) @type
|
|
(sized_type_specifier) @type
|
|
|
|
; Use lua regexes
|
|
((identifier) @Identifier (#contains? @Identifier "lua_"))
|
|
((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$"))
|
|
((identifier) @Normal (#vim-match? @Constant "^lstate$"))
|
|
|
|
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
|
|
|
|
(comment) @comment
|
|
]]
|
|
|
|
describe('when highlighting', function()
|
|
local screen
|
|
|
|
before_each(function()
|
|
screen = Screen.new(65, 18)
|
|
screen:attach()
|
|
screen:set_default_attr_ids({
|
|
[1] = {bold = true, foreground = Screen.colors.Blue1},
|
|
[2] = {foreground = Screen.colors.Blue1},
|
|
[3] = {bold = true, foreground = Screen.colors.SeaGreen4},
|
|
[4] = {bold = true, foreground = Screen.colors.Brown},
|
|
[5] = {foreground = Screen.colors.Magenta},
|
|
[6] = {foreground = Screen.colors.Red},
|
|
[7] = {bold = true, foreground = Screen.colors.SlateBlue},
|
|
[8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
|
|
[9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red},
|
|
[10] = {foreground = Screen.colors.Red, background = Screen.colors.Red},
|
|
[11] = {foreground = Screen.colors.Cyan4},
|
|
})
|
|
end)
|
|
|
|
it('supports highlighting', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(hl_text)
|
|
screen:expect{grid=[[
|
|
/// Schedule Lua callback on main loop's event queue |
|
|
static int nlua_schedule(lua_State *const lstate) |
|
|
{ |
|
|
if (lua_type(lstate, 1) != LUA_TFUNCTION |
|
|
|| lstate != lstate) { |
|
|
lua_pushliteral(lstate, "vim.schedule: expected function"); |
|
|
return lua_error(lstate); |
|
|
} |
|
|
|
|
|
LuaRef cb = nlua_ref(lstate, 1); |
|
|
|
|
|
multiqueue_put(main_loop.events, nlua_schedule_event, |
|
|
1, (void *)(ptrdiff_t)cb); |
|
|
return 0; |
|
|
^} |
|
|
{1:~ }|
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
|
|
exec_lua([[
|
|
local parser = vim.treesitter.get_parser(0, "c")
|
|
local highlighter = vim.treesitter.highlighter
|
|
local query = ...
|
|
test_hl = highlighter.new(parser, {queries = {c = query}})
|
|
]], hl_query)
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
|
|| {6:lstate} != {6:lstate}) { |
|
|
{11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
|
|
{4:return} {11:lua_error}(lstate); |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
^} |
|
|
{1:~ }|
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
|
|
feed("5Goc<esc>dd")
|
|
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
|
|| {6:lstate} != {6:lstate}) { |
|
|
{11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
|
|
{4:return} {11:lua_error}(lstate); |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
} |
|
|
{1:~ }|
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
|
|
feed('7Go*/<esc>')
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} |
|
|
|| {6:lstate} != {6:lstate}) { |
|
|
{11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); |
|
|
{4:return} {11:lua_error}(lstate); |
|
|
{8:*^/} |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
} |
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
|
|
feed('3Go/*<esc>')
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queue} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{2:/^*} |
|
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
|
{2: || lstate != lstate) {} |
|
|
{2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
|
|
{2: return lua_error(lstate);} |
|
|
{2:*/} |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
{8:}} |
|
|
|
|
|
]]}
|
|
|
|
feed("gg$")
|
|
feed("~")
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queu^E} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{2:/*} |
|
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
|
{2: || lstate != lstate) {} |
|
|
{2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
|
|
{2: return lua_error(lstate);} |
|
|
{2:*/} |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
{8:}} |
|
|
|
|
|
]]}
|
|
|
|
|
|
feed("re")
|
|
screen:expect{grid=[[
|
|
{2:/// Schedule Lua callback on main loop's event queu^e} |
|
|
{3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) |
|
|
{ |
|
|
{2:/*} |
|
|
{2: if (lua_type(lstate, 1) != LUA_TFUNCTION} |
|
|
{2: || lstate != lstate) {} |
|
|
{2: lua_pushliteral(lstate, "vim.schedule: expected function");} |
|
|
{2: return lua_error(lstate);} |
|
|
{2:*/} |
|
|
} |
|
|
|
|
|
{7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); |
|
|
|
|
|
multiqueue_put(main_loop.events, {11:nlua_schedule_event}, |
|
|
{5:1}, ({3:void} *)({3:ptrdiff_t})cb); |
|
|
{4:return} {5:0}; |
|
|
{8:}} |
|
|
|
|
|
]]}
|
|
end)
|
|
|
|
it("supports highlighting with custom parser", function()
|
|
if not check_parser() then return end
|
|
|
|
screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} })
|
|
|
|
insert(test_text)
|
|
|
|
screen:expect{ grid= [[
|
|
int width = INT_MAX, height = INT_MAX; |
|
|
bool ext_widgets[kUIExtCount]; |
|
|
for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
|
|
ext_widgets[i] = true; |
|
|
} |
|
|
|
|
|
bool inclusive = ui_override(); |
|
|
for (size_t i = 0; i < ui_count; i++) { |
|
|
UI *ui = uis[i]; |
|
|
width = MIN(ui->width, width); |
|
|
height = MIN(ui->height, height); |
|
|
foo = BAR(ui->bazaar, bazaar); |
|
|
for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
|
|
ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
|
|
} |
|
|
} |
|
|
^} |
|
|
|
|
|
]] }
|
|
|
|
exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
query = vim.treesitter.parse_query("c", "(declaration) @decl")
|
|
|
|
local nodes = {}
|
|
for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do
|
|
table.insert(nodes, node)
|
|
end
|
|
|
|
parser:set_included_regions({nodes})
|
|
|
|
local hl = vim.treesitter.highlighter.new(parser, {queries = {c = "(identifier) @type"}})
|
|
]])
|
|
|
|
screen:expect{ grid = [[
|
|
int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; |
|
|
bool {1:ext_widgets}[{1:kUIExtCount}]; |
|
|
for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { |
|
|
ext_widgets[i] = true; |
|
|
} |
|
|
|
|
|
bool {1:inclusive} = {1:ui_override}(); |
|
|
for (size_t {1:i} = 0; i < ui_count; i++) { |
|
|
UI *{1:ui} = {1:uis}[{1:i}]; |
|
|
width = MIN(ui->width, width); |
|
|
height = MIN(ui->height, height); |
|
|
foo = BAR(ui->bazaar, bazaar); |
|
|
for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { |
|
|
ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
|
|
} |
|
|
} |
|
|
^} |
|
|
|
|
|
]] }
|
|
end)
|
|
|
|
it("supports highlighting injected languages", function()
|
|
if not check_parser() then return end
|
|
|
|
insert([[
|
|
int x = INT_MAX;
|
|
#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
#define foo void main() { \
|
|
return 42; \
|
|
}
|
|
]])
|
|
|
|
screen:expect{grid=[[
|
|
int x = INT_MAX; |
|
|
#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))|
|
|
#define foo void main() { \ |
|
|
return 42; \ |
|
|
} |
|
|
^ |
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
|
|
exec_lua([[
|
|
local parser = vim.treesitter.get_parser(0, "c", {
|
|
queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}
|
|
})
|
|
local highlighter = vim.treesitter.highlighter
|
|
local query = ...
|
|
test_hl = highlighter.new(parser, {queries = {c = query}})
|
|
]], hl_query)
|
|
|
|
screen:expect{grid=[[
|
|
{3:int} x = {5:INT_MAX}; |
|
|
#define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))|
|
|
#define foo {3:void} main() { \ |
|
|
{4:return} {5:42}; \ |
|
|
} |
|
|
^ |
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
{1:~ }|
|
|
|
|
|
]]}
|
|
end)
|
|
end)
|
|
|
|
it('inspects language', function()
|
|
if not check_parser() then return end
|
|
|
|
local keys, fields, symbols = unpack(exec_lua([[
|
|
local lang = vim.treesitter.inspect_language('c')
|
|
local keys, symbols = {}, {}
|
|
for k,_ in pairs(lang) do
|
|
keys[k] = true
|
|
end
|
|
|
|
-- symbols array can have "holes" and is thus not a valid msgpack array
|
|
-- but we don't care about the numbers here (checked in the parser test)
|
|
for _, v in pairs(lang.symbols) do
|
|
table.insert(symbols, v)
|
|
end
|
|
return {keys, lang.fields, symbols}
|
|
]]))
|
|
|
|
eq({fields=true, symbols=true}, keys)
|
|
|
|
local fset = {}
|
|
for _,f in pairs(fields) do
|
|
eq("string", type(f))
|
|
fset[f] = true
|
|
end
|
|
eq(true, fset["directive"])
|
|
eq(true, fset["initializer"])
|
|
|
|
local has_named, has_anonymous
|
|
for _,s in pairs(symbols) do
|
|
eq("string", type(s[1]))
|
|
eq("boolean", type(s[2]))
|
|
if s[1] == "for_statement" and s[2] == true then
|
|
has_named = true
|
|
elseif s[1] == "|=" and s[2] == false then
|
|
has_anonymous = true
|
|
end
|
|
end
|
|
eq({true,true}, {has_named,has_anonymous})
|
|
end)
|
|
it('allows to set simple ranges', function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text)
|
|
|
|
local res = exec_lua [[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
return { parser:parse()[1]:root():range() }
|
|
]]
|
|
|
|
eq({0, 0, 19, 0}, res)
|
|
|
|
-- The following sets the included ranges for the current parser
|
|
-- As stated here, this only includes the function (thus the whole buffer, without the last line)
|
|
local res2 = exec_lua [[
|
|
local root = parser:parse()[1]:root()
|
|
parser:set_included_regions({{root:child(0)}})
|
|
parser:invalidate()
|
|
return { parser:parse()[1]:root():range() }
|
|
]]
|
|
|
|
eq({0, 0, 18, 1}, res2)
|
|
|
|
local range = exec_lua [[
|
|
local res = {}
|
|
for _, region in ipairs(parser:included_regions()) do
|
|
for _, node in ipairs(region) do
|
|
table.insert(res, {node:range()})
|
|
end
|
|
end
|
|
return res
|
|
]]
|
|
|
|
eq(range, { { 0, 0, 18, 1 } })
|
|
end)
|
|
it("allows to set complex ranges", function()
|
|
if not check_parser() then return end
|
|
|
|
insert(test_text)
|
|
|
|
local res = exec_lua [[
|
|
parser = vim.treesitter.get_parser(0, "c")
|
|
query = vim.treesitter.parse_query("c", "(declaration) @decl")
|
|
|
|
local nodes = {}
|
|
for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do
|
|
table.insert(nodes, node)
|
|
end
|
|
|
|
parser:set_included_regions({nodes})
|
|
|
|
local root = parser:parse()[1]:root()
|
|
|
|
local res = {}
|
|
for i=0,(root:named_child_count() - 1) do
|
|
table.insert(res, { root:named_child(i):range() })
|
|
end
|
|
return res
|
|
]]
|
|
|
|
eq({
|
|
{ 2, 2, 2, 40 },
|
|
{ 3, 2, 3, 32 },
|
|
{ 4, 7, 4, 25 },
|
|
{ 8, 2, 8, 33 },
|
|
{ 9, 7, 9, 20 },
|
|
{ 10, 4, 10, 20 },
|
|
{ 14, 9, 14, 27 } }, res)
|
|
end)
|
|
|
|
it("allows to create string parsers", function()
|
|
local ret = exec_lua [[
|
|
local parser = vim.treesitter.get_string_parser("int foo = 42;", "c")
|
|
return { parser:parse()[1]:root():range() }
|
|
]]
|
|
|
|
eq({ 0, 0, 0, 13 }, ret)
|
|
end)
|
|
|
|
it("allows to run queries with string parsers", function()
|
|
local txt = [[
|
|
int foo = 42;
|
|
int bar = 13;
|
|
]]
|
|
|
|
local ret = exec_lua([[
|
|
local str = ...
|
|
local parser = vim.treesitter.get_string_parser(str, "c")
|
|
|
|
local nodes = {}
|
|
local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))')
|
|
|
|
for _, node in query:iter_captures(parser:parse()[1]:root(), str, 0, 2) do
|
|
table.insert(nodes, { node:range() })
|
|
end
|
|
|
|
return nodes]], txt)
|
|
|
|
eq({ {0, 10, 0, 13} }, ret)
|
|
end)
|
|
|
|
describe("when creating a language tree", function()
|
|
local function get_ranges()
|
|
return exec_lua([[
|
|
local result = {}
|
|
parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end)
|
|
return result
|
|
]])
|
|
end
|
|
|
|
before_each(function()
|
|
insert([[
|
|
int x = INT_MAX;
|
|
#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
#define READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
#define VALUE 0
|
|
#define VALUE1 1
|
|
#define VALUE2 2
|
|
]])
|
|
end)
|
|
|
|
describe("when parsing regions independently", function()
|
|
it("should inject a language", function()
|
|
exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c", {
|
|
queries = {
|
|
c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}})
|
|
]])
|
|
|
|
eq("table", exec_lua("return type(parser:children().c)"))
|
|
eq(5, exec_lua("return #parser:children().c:trees()"))
|
|
eq({
|
|
{0, 2, 7, 0}, -- root tree
|
|
{3, 16, 3, 17}, -- VALUE 0
|
|
{4, 17, 4, 18}, -- VALUE1 1
|
|
{5, 17, 5, 18}, -- VALUE2 2
|
|
{1, 28, 1, 67}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
{2, 31, 2, 70} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
}, get_ranges())
|
|
end)
|
|
end)
|
|
|
|
describe("when parsing regions combined", function()
|
|
it("should inject a language", function()
|
|
exec_lua([[
|
|
parser = vim.treesitter.get_parser(0, "c", {
|
|
queries = {
|
|
c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}})
|
|
]])
|
|
|
|
eq("table", exec_lua("return type(parser:children().c)"))
|
|
eq(2, exec_lua("return #parser:children().c:trees()"))
|
|
eq({
|
|
{0, 2, 7, 0}, -- root tree
|
|
{3, 16, 5, 18}, -- VALUE 0
|
|
-- VALUE1 1
|
|
-- VALUE2 2
|
|
{1, 28, 2, 70} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
-- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
|
|
}, get_ranges())
|
|
end)
|
|
end)
|
|
end)
|
|
|
|
end)
|