aboutsummaryrefslogtreecommitdiff
path: root/test/functional/treesitter/parser_spec.lua
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/treesitter/parser_spec.lua')
-rw-r--r--test/functional/treesitter/parser_spec.lua347
1 files changed, 321 insertions, 26 deletions
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 2f8d204d36..eb4651a81d 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -1,5 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
+local ts_t = require('test.functional.treesitter.testutil')
local clear = n.clear
local dedent = t.dedent
@@ -8,6 +9,8 @@ local insert = n.insert
local exec_lua = n.exec_lua
local pcall_err = t.pcall_err
local feed = n.feed
+local run_query = ts_t.run_query
+local assert_alive = n.assert_alive
describe('treesitter parser API', function()
before_each(function()
@@ -88,6 +91,197 @@ describe('treesitter parser API', function()
eq(true, exec_lua('return parser:parse()[1] == tree2'))
end)
+ it('parses buffer asynchronously', function()
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ exec_lua(function()
+ _G.parser = vim.treesitter.get_parser(0, 'c')
+ _G.lang = vim.treesitter.language.inspect('c')
+ _G.parser:parse(nil, function(_, trees)
+ _G.tree = trees[1]
+ _G.root = _G.tree:root()
+ end)
+ vim.wait(100, function() end)
+ end)
+
+ 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(true, exec_lua('return lang.symbols[child:type()]'))
+
+ 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[string.format('"%s"', anon:type())]]=]))
+
+ 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(function()
+ _G.parser:parse(nil, function(_, trees)
+ _G.tree2 = trees[1]
+ _G.root2 = _G.tree2:root()
+ _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13)
+ end)
+ vim.wait(100, function() end)
+ end)
+ 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)
+
+ it('does not crash when editing large files', function()
+ insert([[printf("%s", "some text");]])
+ feed('yy49999p')
+
+ exec_lua(function()
+ _G.parser = vim.treesitter.get_parser(0, 'c')
+ _G.done = false
+ vim.treesitter.start(0, 'c')
+ _G.parser:parse(nil, function()
+ _G.done = true
+ end)
+ while not _G.done do
+ -- Busy wait until async parsing has completed
+ vim.wait(100, function() end)
+ end
+ end)
+
+ eq(true, exec_lua([[return done]]))
+ exec_lua(function()
+ vim.api.nvim_input('Lxj')
+ end)
+ exec_lua(function()
+ vim.api.nvim_input('xj')
+ end)
+ exec_lua(function()
+ vim.api.nvim_input('xj')
+ end)
+ assert_alive()
+ end)
+
+ it('resets parsing state on tree changes', function()
+ insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]])
+ feed('yy1000p')
+
+ exec_lua(function()
+ vim.cmd('set ft=lua')
+
+ vim.treesitter.start(0)
+ local parser = assert(vim.treesitter.get_parser(0))
+
+ parser:parse(true, function() end)
+ vim.api.nvim_buf_set_lines(0, 1, -1, false, {})
+ parser:parse(true)
+ end)
+ end)
+
+ it('resets when buffer was editing during an async parse', function()
+ insert([[printf("%s", "some text");]])
+ feed('yy49999p')
+ feed('gg4jO// Comment<Esc>')
+
+ exec_lua(function()
+ _G.parser = vim.treesitter.get_parser(0, 'c')
+ _G.done = false
+ vim.treesitter.start(0, 'c')
+ _G.parser:parse(nil, function()
+ _G.done = true
+ end)
+ end)
+
+ exec_lua(function()
+ vim.api.nvim_input('ggdj')
+ end)
+
+ eq(false, exec_lua([[return done]]))
+ exec_lua(function()
+ while not _G.done do
+ -- Busy wait until async parsing finishes
+ vim.wait(100, function() end)
+ end
+ end)
+ eq(true, exec_lua([[return done]]))
+ eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]]))
+ eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]]))
+ end)
+
+ it('handles multiple async parse calls', function()
+ insert([[printf("%s", "some text");]])
+ feed('yy49999p')
+
+ exec_lua(function()
+ -- Spy on vim.schedule
+ local schedule = vim.schedule
+ vim.schedule = function(fn)
+ _G.schedules = _G.schedules + 1
+ schedule(fn)
+ end
+ _G.schedules = 0
+ _G.parser = vim.treesitter.get_parser(0, 'c')
+ for i = 1, 5 do
+ _G['done' .. i] = false
+ _G.parser:parse(nil, function()
+ _G['done' .. i] = true
+ end)
+ end
+ schedule(function()
+ _G.schedules_snapshot = _G.schedules
+ end)
+ end)
+
+ eq(2, exec_lua([[return schedules_snapshot]]))
+ eq(
+ { false, false, false, false, false },
+ exec_lua([[return { done1, done2, done3, done4, done5 }]])
+ )
+ exec_lua(function()
+ while not _G.done1 do
+ -- Busy wait until async parsing finishes
+ vim.wait(100, function() end)
+ end
+ end)
+ eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]]))
+ end)
+
local test_text = [[
void ui_refresh(void)
{
@@ -310,6 +504,15 @@ end]]
eq({ 0, 0, 0, 13 }, ret)
end)
+ it('can run async parses with string parsers', function()
+ local ret = exec_lua(function()
+ local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c')
+ return { parser:parse(nil, function() end)[1]:root():range() }
+ end)
+
+ eq({ 0, 0, 0, 13 }, ret)
+ end)
+
it('allows to run queries with string parsers', function()
local txt = [[
int foo = 42;
@@ -430,7 +633,7 @@ int x = INT_MAX;
}, get_ranges())
n.feed('7ggI//<esc>')
- exec_lua([[parser:parse({6, 7})]])
+ exec_lua([[parser:parse({5, 6})]])
eq('table', exec_lua('return type(parser:children().c)'))
eq(2, exec_lua('return #parser:children().c:trees()'))
eq({
@@ -644,6 +847,109 @@ print()
end)
end)
+ describe('trim! directive', function()
+ it('can trim all whitespace', function()
+ -- luacheck: push ignore 611 613
+ insert([=[
+ print([[
+
+ f
+ helllo
+ there
+ asdf
+ asdfassd
+
+
+
+ ]])
+ print([[
+
+
+
+ ]])
+
+ print([[]])
+
+ print([[
+ ]])
+
+ print([[ hello 😃 ]])
+ ]=])
+ -- luacheck: pop
+
+ local query_text = [[
+ ; query
+ ((string_content) @str
+ (#trim! @str 1 1 1 1))
+ ]]
+
+ exec_lua(function()
+ vim.treesitter.start(0, 'lua')
+ end)
+
+ eq({
+ { 'str', { 2, 12, 6, 10 } },
+ { 'str', { 11, 10, 11, 10 } },
+ { 'str', { 17, 10, 17, 10 } },
+ { 'str', { 19, 10, 19, 10 } },
+ { 'str', { 22, 15, 22, 25 } },
+ }, run_query('lua', query_text))
+ end)
+
+ it('trims only empty lines by default (backwards compatible)', function()
+ insert(dedent [[
+ ## Heading
+
+ With some text
+
+ ## And another
+
+ With some more here]])
+
+ local query_text = [[
+ ; query
+ ((section) @fold
+ (#trim! @fold))
+ ]]
+
+ exec_lua(function()
+ vim.treesitter.start(0, 'markdown')
+ end)
+
+ eq({
+ { 'fold', { 0, 0, 2, 14 } },
+ { 'fold', { 4, 0, 6, 19 } },
+ }, run_query('markdown', query_text))
+ end)
+
+ it('can trim lines', function()
+ insert(dedent [[
+ - Fold list
+ - Fold list
+ - Fold list
+ - Fold list
+ - Fold list
+ - Fold list
+ ]])
+
+ local query_text = [[
+ ; query
+ ((list_item
+ (list)) @fold
+ (#trim! @fold 1 1 1 1))
+ ]]
+
+ exec_lua(function()
+ vim.treesitter.start(0, 'markdown')
+ end)
+
+ eq({
+ { 'fold', { 0, 0, 4, 13 } },
+ { 'fold', { 1, 2, 3, 15 } },
+ }, run_query('markdown', query_text))
+ end)
+ end)
+
it('tracks the root range properly (#22911)', function()
insert([[
int main() {
@@ -659,32 +965,19 @@ print()
vim.treesitter.start(0, 'c')
end)
- local function run_query()
- return exec_lua(function()
- local query = vim.treesitter.query.parse('c', query0)
- local parser = vim.treesitter.get_parser()
- local tree = parser:parse()[1]
- local res = {}
- for id, node in query:iter_captures(tree:root()) do
- table.insert(res, { query.captures[id], node:range() })
- end
- return res
- end)
- end
-
eq({
- { 'function', 0, 0, 2, 1 },
- { 'declaration', 1, 2, 1, 12 },
- }, run_query())
+ { 'function', { 0, 0, 2, 1 } },
+ { 'declaration', { 1, 2, 1, 12 } },
+ }, run_query('c', query0))
n.command 'normal ggO'
insert('int a;')
eq({
- { 'declaration', 0, 0, 0, 6 },
- { 'function', 1, 0, 3, 1 },
- { 'declaration', 2, 2, 2, 12 },
- }, run_query())
+ { 'declaration', { 0, 0, 0, 6 } },
+ { 'function', { 1, 0, 3, 1 } },
+ { 'declaration', { 2, 2, 2, 12 } },
+ }, run_query('c', query0))
end)
it('handles ranges when source is a multiline string (#20419)', function()
@@ -858,11 +1151,13 @@ print()
feed(':set ft=help<cr>')
exec_lua(function()
- vim.treesitter.get_parser(0, 'vimdoc', {
- injections = {
- vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))',
- },
- })
+ vim.treesitter
+ .get_parser(0, 'vimdoc', {
+ injections = {
+ vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))',
+ },
+ })
+ :parse()
end)
end)