local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local dedent = helpers.dedent local eq = helpers.eq local insert = helpers.insert local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err local feed = helpers.feed local is_os = helpers.is_os describe('treesitter parser API', function() before_each(function() clear() exec_lua[[ vim.g.__ts_debug = 1 ]] end) it('parses buffer', function() 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.language.inspect('c') ]]) eq("", exec_lua("return tostring(tree)")) eq("", 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("", 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("", 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("", 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() 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('does not get parser for empty filetype', function() insert(test_text); eq('.../treesitter.lua:0: There is no parser available for buffer 1 and one' .. ' could not be created because lang could not be determined. Either' .. ' pass lang or set the buffer filetype', pcall_err(exec_lua, 'vim.treesitter.get_parser(0)')) -- Must provide language for buffers with an empty filetype exec_lua("vim.treesitter.get_parser(0, 'c')") end) it('allows to get a child by field', function() 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() local ret = exec_lua [[ return vim.treesitter.query.get("c", "highlights").captures[1] ]] eq('variable', ret) end) it("supports caching queries", function() local long_query = query:rep(100) local function q(n) return exec_lua ([[ local query, n = ... local before = vim.uv.hrtime() for i=1,n,1 do cquery = vim.treesitter.query.parse("c", ...) end local after = vim.uv.hrtime() return after - before ]], long_query, n) end local firstrun = q(1) local manyruns = q(100) -- First run should be at least 200x slower than an 100 subsequent runs. local factor = is_os('win') and 100 or 200 assert(factor * manyruns < firstrun, ('firstrun: %f ms, manyruns: %f ms'):format(firstrun / 1e6, manyruns / 1e6)) end) it('support query and iter by capture', function() insert(test_text) local res = exec_lua([[ cquery = vim.treesitter.query.parse("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() insert(test_text) local res = exec_lua([[ cquery = vim.treesitter.query.parse("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('supports getting text of multiline node', function() insert(test_text) local res = exec_lua([[ local parser = vim.treesitter.get_parser(0, "c") local tree = parser:parse()[1] return vim.treesitter.get_node_text(tree:root(), 0) ]]) eq(test_text, res) local res2 = exec_lua([[ local parser = vim.treesitter.get_parser(0, "c") local root = parser:parse()[1]:root() return vim.treesitter.get_node_text(root:child(0):child(0), 0) ]]) eq('void', res2) end) it('support getting text where start of node is one past EOF', function() local text = [[ def run a = <<~E end]] insert(text) eq('', exec_lua[[ local fake_node = {} function fake_node:start() return 3, 0, 23 end function fake_node:end_() return 3, 0, 23 end function fake_node:range(bytes) if bytes then return 3, 0, 23, 3, 0, 23 end return 3, 0, 3, 0 end return vim.treesitter.get_node_text(fake_node, 0) ]]) end) it('support getting empty text if node range is zero width', function() local text = [[ ```lua {} ```]] insert(text) local result = exec_lua([[ local fake_node = {} function fake_node:start() return 1, 0, 7 end function fake_node:end_() return 1, 0, 7 end function fake_node:range() return 1, 0, 1, 0 end return vim.treesitter.get_node_text(fake_node, 0) == '' ]]) eq(true, result) end) it('can match special regex characters like \\ * + ( with `vim-match?`', function() insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') local res = exec_lua([[ cquery = vim.treesitter.query.parse("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'.. '([_] @times (#vim-match? @times "^\\\\*$"))'.. '([_] @paren (#vim-match? @paren "^\\\\($"))'.. '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'.. '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))') parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} for pattern, match in cquery:iter_matches(tree:root(), 0) 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({ { 2, { { "times", '*', 0, 4, 0, 5 } } }, { 5, { { "string", 'string_literal', 0, 16, 0, 20 } } }, { 4, { { "escape", 'escape_sequence', 0, 17, 0, 19 } } }, { 3, { { "paren", '(', 0, 22, 0, 23 } } }, { 1, { { "plus", '+', 0, 25, 0, 26 } } }, { 2, { { "times", '*', 0, 30, 0, 31 } } }, }, res) end) it('supports builtin query predicate any-of?', function() insert([[ #include int main(void) { int i; for(i=1; i<=100; i++) { if(((i%3)||(i%5))== 0) printf("number= %d FizzBuzz\n", i); else if((i%3)==0) printf("number= %d Fizz\n", i); else if((i%5)==0) printf("number= %d Buzz\n", i); else printf("number= %d\n",i); } return 0; } ]]) exec_lua([[ function get_query_result(query_text) cquery = vim.treesitter.query.parse("c", query_text) parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} for cid, node in cquery:iter_captures(tree:root(), 0) do -- can't transmit node over RPC. just check the name, range, and text local text = vim.treesitter.get_node_text(node, 0) local range = {node:range()} table.insert(res, {cquery.captures[cid], node:type(), range, text}) end return res end ]]) local res0 = exec_lua([[return get_query_result(...)]], [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]]) eq({ { "c-keyword", "primitive_type", { 2, 2, 2, 5 }, "int" }, { "c-keyword", "primitive_type", { 3, 4, 3, 7 }, "int" }, }, res0) local res1 = exec_lua([[return get_query_result(...)]], [[ ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings "\"number= %d FizzBuzz\\n\"" "\"number= %d Fizz\\n\"" "\"number= %d Buzz\\n\"" )) ]]) eq({ { "fizzbuzz-strings", "string_literal", { 6, 15, 6, 38 }, "\"number= %d FizzBuzz\\n\""}, { "fizzbuzz-strings", "string_literal", { 8, 15, 8, 34 }, "\"number= %d Fizz\\n\""}, { "fizzbuzz-strings", "string_literal", { 10, 15, 10, 34 }, "\"number= %d Buzz\\n\""}, }, res1) end) it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() insert('char* astring = "Hello World!";') local res = exec_lua([[ cquery = vim.treesitter.query.parse("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) 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 = 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("c", ...) local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do table.insert(nodes, {node:range()}) end return nodes ]], custom_query) eq({{0, 4, 0, 8}}, res) local res_list = exec_lua[[ local query = vim.treesitter.query local list = query.list_predicates() table.sort(list) return list ]] eq({ 'any-of?', 'contains?', 'eq?', 'has-ancestor?', 'has-parent?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) end) it('allows to set simple ranges', function() 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(true)[1]:root():range() } ]] eq({0, 0, 18, 1}, res2) eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]]) local range_tbl = exec_lua [[ parser:set_included_regions { { { 0, 0, 17, 1 } } } parser:parse() return parser:included_regions() ]] eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } }) end) it("allows to set complex ranges", function() insert(test_text) local res = exec_lua [[ parser = vim.treesitter.get_parser(0, "c") query = vim.treesitter.query.parse("c", "(declaration) @decl") local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do table.insert(nodes, node) end parser:set_included_regions({nodes}) local root = parser:parse(true)[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.query.parse("c", '((identifier) @id (eq? @id "foo"))') for _, node in query:iter_captures(parser:parse()[1]:root(), str) do table.insert(nodes, { node:range() }) end return nodes]], txt) eq({ {0, 10, 0, 13} }, ret) end) it("should use node range when omitted", 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.query.parse("c", '((identifier) @foo)') local first_child = parser:parse()[1]:root():child(1) for _, node in query:iter_captures(first_child, str) do table.insert(nodes, { node:range() }) end return nodes]], txt) eq({ {1, 10, 1, 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 *)read_string((x), (size_t)(y)) #define READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) #define VALUE 123 #define VALUE1 123 #define VALUE2 123 ]]) end) describe("when parsing regions independently", function() it("should inject a language", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}}) parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) eq(5, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 7, 0}, -- root tree {3, 14, 3, 17}, -- VALUE 123 {4, 15, 4, 18}, -- VALUE1 123 {5, 15, 5, 18}, -- VALUE2 123 {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) helpers.feed('ggo') eq(5, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 8, 0}, -- root tree {4, 14, 4, 17}, -- VALUE 123 {5, 15, 5, 18}, -- VALUE1 123 {6, 15, 6, 18}, -- VALUE2 123 {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) {3, 29, 3, 66} -- READ_STRING_OK(x, y) (char *)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", { injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))'}}) parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) eq(2, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 7, 0}, -- root tree {3, 14, 5, 18}, -- VALUE 123 -- VALUE1 123 -- VALUE2 123 {1, 26, 2, 66} -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) helpers.feed('ggo') eq("table", exec_lua("return type(parser:children().c)")) eq(2, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 8, 0}, -- root tree {4, 14, 6, 18}, -- VALUE 123 -- VALUE1 123 -- VALUE2 123 {2, 26, 3, 66} -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) end) describe("when using injection.self", function() it("should inject the source language", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))'}}) parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) eq(5, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 7, 0}, -- root tree {3, 14, 3, 17}, -- VALUE 123 {4, 15, 4, 18}, -- VALUE1 123 {5, 15, 5, 18}, -- VALUE2 123 {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) helpers.feed('ggo') eq(5, exec_lua("return #parser:children().c:trees()")) eq({ {0, 0, 8, 0}, -- root tree {4, 14, 4, 17}, -- VALUE 123 {5, 15, 5, 18}, -- VALUE1 123 {6, 15, 6, 18}, -- VALUE2 123 {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) {3, 29, 3, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) end) describe("when using the offset directive", function() it("should shift the range by the directive amount", function() exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { c = '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}}) parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) eq({ {0, 0, 7, 0}, -- root tree {3, 16, 3, 16}, -- VALUE 123 {4, 17, 4, 17}, -- VALUE1 123 {5, 17, 5, 17}, -- VALUE2 123 {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y)) }, get_ranges()) end) it("should list all directives", function() local res_list = exec_lua[[ local query = vim.treesitter.query local list = query.list_directives() table.sort(list) return list ]] eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list) end) end) end) describe("when getting the language for a range", function() before_each(function() insert([[ int x = INT_MAX; #define VALUE 123456789 ]]) end) it("should return the correct language tree", function() local result = exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))'}}) parser:parse(true) local sub_tree = parser:language_for_range({1, 18, 1, 19}) return sub_tree == parser:children().c ]]) eq(result, true) end) end) describe("when getting/setting match data", function() describe("when setting for the whole match", function() it("should set/get the data correctly", function() insert([[ int x = 3; ]]) local result = exec_lua([[ local result query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! "key" "value"))') parser = vim.treesitter.get_parser(0, "c") for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do result = metadata.key end return result ]]) eq(result, "value") end) describe("when setting a key on a capture", function() it("it should create the nested table", function() insert([[ int x = 3; ]]) local result = exec_lua([[ local query = vim.treesitter.query local value query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value"))') parser = vim.treesitter.get_parser(0, "c") for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do for _, nested_tbl in pairs(metadata) do return nested_tbl.key end end ]]) eq(result, "value") end) it("it should not overwrite the nested table", function() insert([[ int x = 3; ]]) local result = exec_lua([[ local query = vim.treesitter.query local result query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))') parser = vim.treesitter.get_parser(0, "c") for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do for _, nested_tbl in pairs(metadata) do return nested_tbl end end ]]) local expected = { ["key"] = "value", ["key2"] = "value2", } eq(expected, result) end) end) end) end) it('tracks the root range properly (#22911)', function() insert([[ int main() { int x = 3; }]]) local query0 = [[ (declaration) @declaration (function_definition) @function ]] exec_lua([[ vim.treesitter.start(0, 'c') ]]) local function run_query() return exec_lua([[ local query = vim.treesitter.query.parse("c", ...) parser = vim.treesitter.get_parser() tree = parser:parse()[1] res = {} for id, node in query:iter_captures(tree:root()) do table.insert(res, {query.captures[id], node:range()}) end return res ]], query0) end eq({ { 'function', 0, 0, 2, 1 }, { 'declaration', 1, 2, 1, 12 } }, run_query()) helpers.command'normal ggO' insert('int a;') eq({ { 'declaration', 0, 0, 0, 6 }, { 'function', 1, 0, 3, 1 }, { 'declaration', 2, 2, 2, 12 } }, run_query()) end) it('handles ranges when source is a multiline string (#20419)', function() local source = [==[ vim.cmd[[ set number set cmdheight=2 set lastsatus=2 ]] set query = [[;; query ((function_call name: [ (identifier) @_cdef_identifier (_ _ (identifier) @_cdef_identifier) ] arguments: (arguments (string content: _ @injection.content))) (#set! injection.language "c") (#eq? @_cdef_identifier "cdef")) ]] ]==] local r = exec_lua([[ local parser = vim.treesitter.get_string_parser(..., 'lua') parser:parse(true) local ranges = {} parser:for_each_tree(function(tstree, tree) ranges[tree:lang()] = { tstree:root():range(true) } end) return ranges ]], source) eq({ lua = { 0, 6, 6, 16, 4, 438 }, query = { 6, 20, 113, 15, 6, 431 }, vim = { 1, 0, 16, 4, 6, 89 } }, r) -- The above ranges are provided directly from treesitter, however query directives may mutate -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure -- add_bytes() produces the same result. local rb = exec_lua([[ local r, source = ... local add_bytes = require('vim.treesitter._range').add_bytes for lang, range in pairs(r) do r[lang] = {range[1], range[2], range[4], range[5]} r[lang] = add_bytes(source, r[lang]) end return r ]], r, source) eq(rb, r) end) it("does not produce empty injection ranges (#23409)", function() insert [[ Examples: >lua local a = {} < ]] -- This is not a valid injection since (code) has children and include-children is not set exec_lua [[ parser1 = require('vim.treesitter.languagetree').new(0, "vimdoc", { injections = { vimdoc = "((codeblock (language) @injection.language (code) @injection.content))" } }) parser1:parse(true) ]] eq(0, exec_lua("return #vim.tbl_keys(parser1:children())")) exec_lua [[ parser2 = require('vim.treesitter.languagetree').new(0, "vimdoc", { injections = { vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" } }) parser2:parse(true) ]] eq(1, exec_lua("return #vim.tbl_keys(parser2:children())")) eq( { { { 1, 0, 21, 2, 0, 42 } } }, exec_lua("return parser2:children().lua:included_regions()")) end) it("parsers injections incrementally", function() insert(dedent[[ >lua local a = {} < >lua local b = {} < >lua local c = {} < >lua local d = {} < >lua local e = {} < >lua local f = {} < >lua local g = {} < ]]) exec_lua [[ parser = require('vim.treesitter.languagetree').new(0, "vimdoc", { injections = { vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" } }) ]] --- Do not parse injections by default eq(0, exec_lua [[ parser:parse() return #vim.tbl_keys(parser:children()) ]]) --- Only parse injections between lines 0, 2 eq(1, exec_lua [[ parser:parse({0, 2}) return #parser:children().lua:trees() ]]) eq(2, exec_lua [[ parser:parse({2, 6}) return #parser:children().lua:trees() ]]) eq(7, exec_lua [[ parser:parse(true) return #parser:children().lua:trees() ]]) end) it('fails to load queries', function() local function test(exp, cquery) eq(exp, pcall_err(exec_lua, "vim.treesitter.query.parse('c', ...)", cquery)) end -- Invalid node type test( '.../query.lua:0: Query error at 1:2. Invalid node type "dentifier":\n'.. '(dentifier) @variable\n'.. ' ^', '(dentifier) @variable') -- Impossible pattern test( '.../query.lua:0: Query error at 1:13. Impossible pattern:\n'.. '(identifier (identifier) @variable)\n'.. ' ^', '(identifier (identifier) @variable)') -- Invalid syntax test( '.../query.lua:0: Query error at 1:13. Invalid syntax:\n'.. '(identifier @variable\n'.. ' ^', '(identifier @variable') -- Invalid field name test( '.../query.lua:0: Query error at 1:15. Invalid field name "invalid_field":\n'.. '((identifier) invalid_field: (identifier))\n'.. ' ^', '((identifier) invalid_field: (identifier))') -- Invalid capture name test( '.../query.lua:0: Query error at 3:2. Invalid capture name "ok.capture":\n'.. '@ok.capture\n'.. ' ^', '((identifier) @id \n(#eq? @id\n@ok.capture\n))') end) describe('is_valid()', function() before_each(function() insert(dedent[[ Treesitter integration *treesitter* Nvim integrates the `tree-sitter` library for incremental parsing of buffers: https://tree-sitter.github.io/tree-sitter/ ]]) feed(':set ft=help') exec_lua [[ vim.treesitter.get_parser(0, "vimdoc", { injections = { vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" } }) ]] end) it('is valid excluding, invalid including children initially', function() eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is fully valid after a full parse', function() exec_lua('vim.treesitter.get_parser():parse(true)') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is fully valid after a parsing a range on parsed tree', function() exec_lua('vim.treesitter.get_parser():parse({5, 7})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) describe('when adding content with injections', function() before_each(function() feed('G') insert(dedent[[ >lua local a = {} < ]]) end) it('is fully invalid after changes', function() eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is valid excluding, invalid including children after a rangeless parse', function() exec_lua('vim.treesitter.get_parser():parse()') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is fully valid after a range parse that leads to parsing not parsed injections', function() exec_lua('vim.treesitter.get_parser():parse({5, 7})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is valid excluding, invalid including children after a range parse that does not lead to parsing not parsed injections', function() exec_lua('vim.treesitter.get_parser():parse({2, 4})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) end) describe('when removing content with injections', function() before_each(function() feed('G') insert(dedent[[ >lua local a = {} < >lua local a = {} < ]]) exec_lua('vim.treesitter.get_parser():parse(true)') feed('Gd3k') end) it('is fully invalid after changes', function() eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is valid excluding, invalid including children after a rangeless parse', function() exec_lua('vim.treesitter.get_parser():parse()') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is fully valid after a range parse that leads to parsing modified child tree', function() exec_lua('vim.treesitter.get_parser():parse({5, 7})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) it('is valid excluding, invalid including children after a range parse that does not lead to parsing modified child tree', function() exec_lua('vim.treesitter.get_parser():parse({2, 4})') eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)')) eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()')) end) end) end) end)