diff options
Diffstat (limited to 'test/functional/treesitter')
-rw-r--r-- | test/functional/treesitter/fold_spec.lua | 474 | ||||
-rw-r--r-- | test/functional/treesitter/highlight_spec.lua | 291 | ||||
-rw-r--r-- | test/functional/treesitter/language_spec.lua | 21 | ||||
-rw-r--r-- | test/functional/treesitter/node_spec.lua | 28 | ||||
-rw-r--r-- | test/functional/treesitter/parser_spec.lua | 573 | ||||
-rw-r--r-- | test/functional/treesitter/utils_spec.lua | 17 |
6 files changed, 1229 insertions, 175 deletions
diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua new file mode 100644 index 0000000000..a8abbc002b --- /dev/null +++ b/test/functional/treesitter/fold_spec.lua @@ -0,0 +1,474 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local eq = helpers.eq +local insert = helpers.insert +local exec_lua = helpers.exec_lua +local command = helpers.command +local feed = helpers.feed +local Screen = require('test.functional.ui.screen') + +before_each(clear) + +describe('treesitter foldexpr', function() + clear() + + 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); + } + } +}]] + + local function get_fold_levels() + return exec_lua([[ + local res = {} + for i = 1, vim.api.nvim_buf_line_count(0) do + res[i] = vim.treesitter.foldexpr(i) + end + return res + ]]) + end + + it("can compute fold levels", function() + insert(test_text) + + exec_lua([[vim.treesitter.get_parser(0, "c")]]) + + eq({ + [1] = '>1', + [2] = '1', + [3] = '1', + [4] = '1', + [5] = '>2', + [6] = '2', + [7] = '2', + [8] = '1', + [9] = '1', + [10] = '>2', + [11] = '2', + [12] = '2', + [13] = '2', + [14] = '2', + [15] = '>3', + [16] = '3', + [17] = '3', + [18] = '2', + [19] = '1' }, get_fold_levels()) + + end) + + it("recomputes fold levels after lines are added/removed", function() + insert(test_text) + + exec_lua([[vim.treesitter.get_parser(0, "c")]]) + + command('1,2d') + + eq({ + [1] = '0', + [2] = '0', + [3] = '>1', + [4] = '1', + [5] = '1', + [6] = '0', + [7] = '0', + [8] = '>1', + [9] = '1', + [10] = '1', + [11] = '1', + [12] = '1', + [13] = '>2', + [14] = '2', + [15] = '2', + [16] = '1', + [17] = '0' }, get_fold_levels()) + + command('1put!') + + eq({ + [1] = '>1', + [2] = '1', + [3] = '1', + [4] = '1', + [5] = '>2', + [6] = '2', + [7] = '2', + [8] = '1', + [9] = '1', + [10] = '>2', + [11] = '2', + [12] = '2', + [13] = '2', + [14] = '2', + [15] = '>3', + [16] = '3', + [17] = '3', + [18] = '2', + [19] = '1' }, get_fold_levels()) + end) + + it("updates folds in all windows", function() + local screen = Screen.new(60, 48) + screen:attach() + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + [3] = {bold = true, reverse = true}; + [4] = {reverse = true}; + }) + + exec_lua([[vim.treesitter.get_parser(0, "c")]]) + command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=9]]) + command('split') + + insert(test_text) + + screen:expect{grid=[[ + {1:-}void ui_refresh(void) | + {1:│}{ | + {1:│} int width = INT_MAX, height = INT_MAX; | + {1:│} bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:2} ext_widgets[i] = true; | + {1:2} } | + {1:│} | + {1:│} bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:2} UI *ui = uis[i]; | + {1:2} width = MIN(ui->width, width); | + {1:2} height = MIN(ui->height, height); | + {1:2} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:3} } | + {1:2} } | + {1:│}^} | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + {1:-}void ui_refresh(void) | + {1:│}{ | + {1:│} int width = INT_MAX, height = INT_MAX; | + {1:│} bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:2} ext_widgets[i] = true; | + {1:2} } | + {1:│} | + {1:│} bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:2} UI *ui = uis[i]; | + {1:2} width = MIN(ui->width, width); | + {1:2} height = MIN(ui->height, height); | + {1:2} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:3} } | + {1:2} } | + {1:│}} | + {2:~ }| + {2:~ }| + {2:~ }| + {4:[No Name] [+] }| + | + ]]} + + command('1,2d') + + screen:expect{grid=[[ + {1: } ^int width = INT_MAX, height = INT_MAX; | + {1: } bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:│} ext_widgets[i] = true; | + {1:│} } | + {1: } | + {1: } bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:│} UI *ui = uis[i]; | + {1:│} width = MIN(ui->width, width); | + {1:│} height = MIN(ui->height, height); | + {1:│} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:2} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:2} } | + {1:│} } | + {1: }} | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + {1: } int width = INT_MAX, height = INT_MAX; | + {1: } bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:│} ext_widgets[i] = true; | + {1:│} } | + {1: } | + {1: } bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:│} UI *ui = uis[i]; | + {1:│} width = MIN(ui->width, width); | + {1:│} height = MIN(ui->height, height); | + {1:│} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:2} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:2} } | + {1:│} } | + {1: }} | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {4:[No Name] [+] }| + | + ]]} + + + feed([[O<C-u><C-r>"<BS><Esc>]]) + + screen:expect{grid=[[ + {1:-}void ui_refresh(void) | + {1:│}^{ | + {1:│} int width = INT_MAX, height = INT_MAX; | + {1:│} bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:2} ext_widgets[i] = true; | + {1:2} } | + {1:│} | + {1:│} bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:2} UI *ui = uis[i]; | + {1:2} width = MIN(ui->width, width); | + {1:2} height = MIN(ui->height, height); | + {1:2} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:3} } | + {1:2} } | + {1:│}} | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:[No Name] [+] }| + {1:-}void ui_refresh(void) | + {1:│}{ | + {1:│} int width = INT_MAX, height = INT_MAX; | + {1:│} bool ext_widgets[kUIExtCount]; | + {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + {1:2} ext_widgets[i] = true; | + {1:2} } | + {1:│} | + {1:│} bool inclusive = ui_override(); | + {1:-} for (size_t i = 0; i < ui_count; i++) { | + {1:2} UI *ui = uis[i]; | + {1:2} width = MIN(ui->width, width); | + {1:2} height = MIN(ui->height, height); | + {1:2} foo = BAR(ui->bazaar, bazaar); | + {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1:3} } | + {1:2} } | + {1:│}} | + {2:~ }| + {2:~ }| + {2:~ }| + {4:[No Name] [+] }| + | + ]]} + + end) + + it("doesn't open folds in diff mode", function() + local screen = Screen.new(60, 36) + screen:attach() + + exec_lua([[vim.treesitter.get_parser(0, "c")]]) + command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=9]]) + insert(test_text) + command('16d') + + command('new') + insert(test_text) + + command('windo diffthis') + feed('do') + + screen:expect{grid=[[ + {1:+ }{2:+-- 9 lines: void ui_refresh(void)·······················}| + {1: } for (size_t i = 0; i < ui_count; i++) { | + {1: } UI *ui = uis[i]; | + {1: } width = MIN(ui->width, width); | + {1: } height = MIN(ui->height, height); | + {1: } foo = BAR(ui->bazaar, bazaar); | + {1: } for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1: } ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1: } } | + {1: } } | + {1: }} | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {4:[No Name] [+] }| + {1:+ }{2:+-- 9 lines: void ui_refresh(void)·······················}| + {1: } for (size_t i = 0; i < ui_count; i++) { | + {1: } UI *ui = uis[i]; | + {1: } width = MIN(ui->width, width); | + {1: } height = MIN(ui->height, height); | + {1: } foo = BAR(ui->bazaar, bazaar); | + {1: } for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {1: } ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {1: } ^} | + {1: } } | + {1: }} | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {5:[No Name] [+] }| + | + ]], attr_ids={ + [1] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4}; + [2] = {background = Screen.colors.LightGrey, foreground = Screen.colors.Blue4}; + [3] = {foreground = Screen.colors.Blue, bold = true}; + [4] = {reverse = true}; + [5] = {reverse = true, bold = true}; + }} + end) + +end) + +describe('treesitter foldtext', function() + local test_text = [[ +void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const 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); + } + } +}]] + local screen + + before_each(function() + screen = Screen.new(60, 5) + screen:set_default_attr_ids({ + [0] = {foreground = Screen.colors.Blue, bold = true}, + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray}; + [2] = {bold = true, background = Screen.colors.LightGray, foreground = Screen.colors.SeaGreen}; + [3] = {foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGray}; + [4] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.LightGray}; + [5] = {bold = true, background = Screen.colors.LightGray, foreground = Screen.colors.Brown}; + [6] = {background = Screen.colors.Red1}; + [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Red}; + [8] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red}; + [9] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.Red}; + [10] = {bold = true}; + }) + screen:attach() + end) + + it('displays highlighted content', function() + command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]]) + insert(test_text) + exec_lua([[vim.treesitter.get_parser(0, "c")]]) + + feed('ggVGzf') + screen:expect{grid=[[ + {2:^void}{1: }{3:qsort}{4:(}{2:void}{1: }{5:*}{3:base}{4:,}{1: }{2:size_t}{1: }{3:nel}{4:,}{1: }{2:size_t}{1: }{3:width}{4:,}{1: }{2:int}{1: }{4:(}{5:*}{3:compa}| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end) + + it('handles deep nested captures', function() + command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]]) + insert([[ +function FoldInfo.new() + return setmetatable({ + start_counts = {}, + stop_counts = {}, + levels0 = {}, + levels = {}, + }, FoldInfo) +end]]) + exec_lua([[vim.treesitter.get_parser(0, "lua")]]) + + feed('ggjVGkzfgg') + screen:expect{grid=[[ + ^function FoldInfo.new() | + {1: }{5:return}{1: }{4:setmetatable({}{1:·····································}| + end | + {0:~ }| + | + ]]} + + command('hi! Visual guibg=Red') + feed('GVgg') + screen:expect{grid=[[ + ^f{6:unction FoldInfo.new()} | + {7: }{8:return}{7: }{9:setmetatable({}{7:·····································}| + {6:end} | + {0:~ }| + {10:-- VISUAL LINE --} | + ]]} + + feed('10l<C-V>') + screen:expect{grid=[[ + {6:function F}^oldInfo.new() | + {7: }{8:return}{7: }{9:se}{4:tmetatable({}{1:·····································}| + {6:end} | + {0:~ }| + {10:-- VISUAL BLOCK --} | + ]]} + end) + + it('falls back to default', function() + command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext()]]) + insert(test_text) + + feed('ggVGzf') + screen:expect{grid=[[ + {1:^+-- 19 lines: void qsort(void *base, size_t nel, size_t widt}| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + end) +end) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 2a2311c0fa..e037c9e215 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -11,7 +11,7 @@ local eq = helpers.eq before_each(clear) -local hl_query = [[ +local hl_query_c = [[ (ERROR) @error "if" @keyword @@ -47,7 +47,7 @@ local hl_query = [[ (comment) @comment ]] -local hl_text = [[ +local hl_text_c = [[ /// Schedule Lua callback on main loop's event queue static int nlua_schedule(lua_State *const lstate) { @@ -64,7 +64,7 @@ static int nlua_schedule(lua_State *const lstate) return 0; }]] -local test_text = [[ +local test_text_c = [[ void ui_refresh(void) { int width = INT_MAX, height = INT_MAX; @@ -85,7 +85,57 @@ void ui_refresh(void) } }]] -describe('treesitter highlighting', function() +local injection_text_c = [[ +int x = INT_MAX; +#define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) +#define foo void main() { \ + return 42; \ + } +]] + +local injection_grid_c = [[ + int x = INT_MAX; | + #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) | + #define foo void main() { \ | + return 42; \ | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | +]] + +local injection_grid_expected_c = [[ + {3:int} x = {5:INT_MAX}; | + #define {5:READ_STRING}(x, y) ({3:char} *)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:~ }| + | +]] + +describe('treesitter highlighting (C)', function() local screen before_each(function() @@ -105,13 +155,13 @@ describe('treesitter highlighting', function() [11] = {foreground = Screen.colors.Cyan4}; } - exec_lua([[ hl_query = ... ]], hl_query) + exec_lua([[ hl_query = ... ]], hl_query_c) command [[ hi link @error ErrorMsg ]] command [[ hi link @warning WarningMsg ]] end) it('is updated with edits', function() - insert(hl_text) + insert(hl_text_c) screen:expect{grid=[[ /// Schedule Lua callback on main loop's event queue | static int nlua_schedule(lua_State *const lstate) | @@ -274,7 +324,7 @@ describe('treesitter highlighting', function() end) it('is updated with :sort', function() - insert(test_text) + insert(test_text_c) exec_lua [[ local parser = vim.treesitter.get_parser(0, "c") test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}}) @@ -351,7 +401,7 @@ describe('treesitter highlighting', function() [1] = {bold = true, foreground = Screen.colors.SeaGreen4}; } - insert(test_text) + insert(test_text_c) screen:expect{ grid= [[ int width = INT_MAX, height = INT_MAX; | @@ -376,7 +426,7 @@ describe('treesitter highlighting', function() exec_lua [[ parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.parse_query("c", "(declaration) @decl") + query = vim.treesitter.query.parse("c", "(declaration) @decl") local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do @@ -411,85 +461,58 @@ describe('treesitter highlighting', function() end) it("supports injected languages", function() - insert([[ - int x = INT_MAX; - #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - #define foo void main() { \ - return 42; \ - } - ]]) + insert(injection_text_c) - 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:~ }| - | - ]]} + screen:expect{grid=injection_grid_c} exec_lua [[ local parser = vim.treesitter.get_parser(0, "c", { - injections = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @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"))'} }) local highlighter = vim.treesitter.highlighter test_hl = highlighter.new(parser, {queries = {c = 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:~ }| - | - ]]} + screen:expect{grid=injection_grid_expected_c} + end) + + it("supports injecting by ft name in metadata['injection.language']", function() + insert(injection_text_c) + + screen:expect{grid=injection_grid_c} + + exec_lua [[ + vim.treesitter.language.register("c", "foo") + local parser = vim.treesitter.get_parser(0, "c", { + injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "fOO")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "fOO"))'} + }) + local highlighter = vim.treesitter.highlighter + test_hl = highlighter.new(parser, {queries = {c = hl_query}}) + ]] + + screen:expect{grid=injection_grid_expected_c} end) it("supports overriding queries, like ", function() insert([[ int x = INT_MAX; - #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) #define foo void main() { \ return 42; \ } ]]) exec_lua [[ - local injection_query = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)" - require('vim.treesitter.query').set_query("c", "highlights", hl_query) - require('vim.treesitter.query').set_query("c", "injections", injection_query) + local injection_query = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' + vim.treesitter.query.set("c", "highlights", hl_query) + vim.treesitter.query.set("c", "injections", injection_query) vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, "c")) ]] 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 {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) | #define foo {3:void} main() { \ | {4:return} {5:42}; \ | } | @@ -510,7 +533,7 @@ describe('treesitter highlighting', function() end) it("supports highlighting with custom highlight groups", function() - insert(hl_text) + insert(hl_text_c) exec_lua [[ local parser = vim.treesitter.get_parser(0, "c") @@ -567,7 +590,7 @@ describe('treesitter highlighting', function() it("supports highlighting with priority", function() insert([[ int x = INT_MAX; - #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) #define foo void main() { \ return 42; \ } @@ -575,12 +598,12 @@ describe('treesitter highlighting', function() exec_lua [[ local parser = vim.treesitter.get_parser(0, "c") - test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query..'\n((translation_unit) @Error (set! "priority" 101))\n'}}) + test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query..'\n((translation_unit) @constant (#set! "priority" 101))\n'}}) ]] - -- expect everything to have Error highlight + -- expect everything to have Constant highlight screen:expect{grid=[[ {12:int}{8: x = INT_MAX;} | - {8:#define READ_STRING(x, y) (}{12:char_u}{8: *)read_string((x), (}{12:size_t}{8:)(y))}| + {8:#define READ_STRING(x, y) (}{12:char}{8: *)read_string((x), (}{12:size_t}{8:)(y))} | {8:#define foo }{12:void}{8: main() { \} | {8: }{12:return}{8: 42; \} | {8: }} | @@ -599,13 +622,13 @@ describe('treesitter highlighting', function() | ]], attr_ids={ [1] = {bold = true, foreground = Screen.colors.Blue1}; - [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [8] = {foreground = Screen.colors.Magenta1}; -- bold will not be overwritten at the moment - [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100}; + [12] = {bold = true, foreground = Screen.colors.Magenta1}; }} eq({ - {capture='Error', metadata = { priority='101' }, lang='c' }; + {capture='constant', metadata = { priority='101' }, lang='c' }; {capture='type', metadata = { }, lang='c' }; }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]]) end) @@ -692,7 +715,7 @@ describe('treesitter highlighting', function() end) it("supports conceal attribute", function() - insert(hl_text) + insert(hl_text_c) -- conceal can be empty or a single cchar. exec_lua [=[ @@ -753,3 +776,129 @@ describe('treesitter highlighting', function() eq(nil, get_hl"@total.nonsense.but.a.lot.of.dots") end) end) + +describe('treesitter highlighting (help)', function() + local screen + + before_each(function() + screen = Screen.new(40, 6) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Blue1}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + [3] = {bold = true, foreground = Screen.colors.Brown}; + [4] = {foreground = Screen.colors.Cyan4}; + [5] = {foreground = Screen.colors.Magenta1}; + } + end) + + it("correctly redraws added/removed injections", function() + insert[[ + >ruby + -- comment + local this_is = 'actually_lua' + < + ]] + + exec_lua [[ + vim.bo.filetype = 'help' + vim.treesitter.start() + ]] + + screen:expect{grid=[[ + {1:>ruby} | + {1: -- comment} | + {1: local this_is = 'actually_lua'} | + < | + ^ | + | + ]]} + + helpers.curbufmeths.set_text(0, 1, 0, 5, {'lua'}) + + screen:expect{grid=[[ + {1:>lua} | + {1: -- comment} | + {1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} | + < | + ^ | + | + ]]} + + helpers.curbufmeths.set_text(0, 1, 0, 4, {'ruby'}) + + screen:expect{grid=[[ + {1:>ruby} | + {1: -- comment} | + {1: local this_is = 'actually_lua'} | + < | + ^ | + | + ]]} + end) + +end) + +describe('treesitter highlighting (nested injections)', function() + local screen + + before_each(function() + screen = Screen.new(80, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.SlateBlue}; + [2] = {bold = true, foreground = Screen.colors.Brown}; + [3] = {foreground = Screen.colors.Cyan4}; + [4] = {foreground = Screen.colors.Fuchsia}; + } + end) + + it("correctly redraws nested injections (GitHub #25252)", function() + insert[=[ +function foo() print("Lua!") end + +local lorem = { + ipsum = {}, + bar = {}, +} +vim.cmd([[ + augroup RustLSP + autocmd CursorHold silent! lua vim.lsp.buf.document_highlight() + augroup END +]]) + ]=] + + exec_lua [[ + vim.opt.scrolloff = 0 + vim.bo.filetype = 'lua' + vim.treesitter.start() + ]] + + -- invalidate the language tree + feed("ggi--[[<ESC>04x") + + screen:expect{grid=[[ + {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + + -- spam newline insert/delete to invalidate Lua > Vim > Lua region + feed("3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0") + + screen:expect{grid=[[ + {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | + | + {2:local} {3:lorem} {2:=} {1:{} | + ^ {3:ipsum} {2:=} {1:{},} | + {3:bar} {2:=} {1:{},} | + {1:}} | + | + ]]} + end) + +end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index f95b05a1cc..9b871a72fb 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -18,22 +18,27 @@ describe('treesitter language API', function() -- actual message depends on platform matches("Failed to load parser for language 'borklang': uv_dlopen: .+", - pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) + pcall_err(exec_lua, "parser = vim.treesitter.language.add('borklang', { path = 'borkbork.so' })")) - -- Should not throw an error when silent - eq(false, exec_lua("return vim.treesitter.require_language('borklang', nil, true)")) - eq(false, exec_lua("return vim.treesitter.require_language('borklang', 'borkbork.so', true)")) + eq(false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang')")) + + eq(false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang', { path = 'borkbork.so' })")) eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", - pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) + pcall_err(exec_lua, "parser = vim.treesitter.language.inspect('borklang')")) matches("Failed to load parser: uv_dlsym: .+", - pcall_err(exec_lua, 'vim.treesitter.require_language("c", nil, false, "borklang")')) + pcall_err(exec_lua, 'vim.treesitter.language.add("c", { symbol_name = "borklang" })')) + end) + + it('shows error for invalid language name', function() + eq(".../language.lua:0: '/foo/' is not a valid language name", + pcall_err(exec_lua, 'vim.treesitter.language.add("/foo/")')) end) it('inspects language', function() local keys, fields, symbols = unpack(exec_lua([[ - local lang = vim.treesitter.inspect_language('c') + local lang = vim.treesitter.language.inspect('c') local keys, symbols = {}, {} for k,_ in pairs(lang) do keys[k] = true @@ -77,7 +82,7 @@ describe('treesitter language API', function() command("set filetype=borklang") -- Should throw an error when filetype changes to borklang eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", - pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) + pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0, 'borklang')")) end) it('retrieve the tree given a range', function () diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index a82dce47b7..eef75d0e91 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -4,6 +4,7 @@ local clear = helpers.clear local eq = helpers.eq local exec_lua = helpers.exec_lua local insert = helpers.insert +local assert_alive = helpers.assert_alive before_each(clear) @@ -14,6 +15,31 @@ end describe('treesitter node API', function() clear() + it('double free tree', function() + insert('F') + exec_lua([[ + vim.treesitter.start(0, 'lua') + vim.treesitter.get_node():tree() + vim.treesitter.get_node():tree() + collectgarbage() + ]]) + assert_alive() + end) + + it('double free tree 2', function() + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + local x = parser:parse()[1]:root():tree() + vim.api.nvim_buf_set_text(0, 0,0, 0,0, {'y'}) + parser:parse() + vim.api.nvim_buf_set_text(0, 0,0, 0,1, {'z'}) + parser:parse() + collectgarbage() + x:root() + ]]) + assert_alive() + end) + it('can move between siblings', function() insert([[ int main(int x, int y, int z) { @@ -26,7 +52,7 @@ describe('treesitter node API', function() parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] root = tree:root() - lang = vim.treesitter.inspect_language('c') + lang = vim.treesitter.language.inspect('c') function node_text(node) return query.get_node_text(node, 0) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index f006ad4539..6f386115ae 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -1,17 +1,21 @@ 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 -local skip = helpers.skip - -before_each(clear) describe('treesitter parser API', function() - clear() + before_each(function() + clear() + exec_lua[[ + vim.g.__ts_debug = 1 + ]] + end) it('parses buffer', function() insert([[ @@ -23,7 +27,7 @@ describe('treesitter parser API', function() parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] root = tree:root() - lang = vim.treesitter.inspect_language('c') + lang = vim.treesitter.language.inspect('c') ]]) eq("<tree>", exec_lua("return tostring(tree)")) @@ -124,6 +128,18 @@ void ui_refresh(void) }, 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); @@ -159,7 +175,7 @@ void ui_refresh(void) it("supports runtime queries", function() local ret = exec_lua [[ - return require"vim.treesitter.query".get_query("c", "highlights").captures[1] + return vim.treesitter.query.get("c", "highlights").captures[1] ]] eq('variable', ret) @@ -170,11 +186,11 @@ void ui_refresh(void) local function q(n) return exec_lua ([[ local query, n = ... - local before = vim.loop.hrtime() + local before = vim.uv.hrtime() for i=1,n,1 do - cquery = vim.treesitter.parse_query("c", ...) + cquery = vim.treesitter.query.parse("c", ...) end - local after = vim.loop.hrtime() + local after = vim.uv.hrtime() return after - before ]], long_query, n) end @@ -182,15 +198,16 @@ void ui_refresh(void) local firstrun = q(1) local manyruns = q(100) - -- First run should be at least 4x slower. - assert(400 * manyruns < firstrun, ('firstrun: %d ms, manyruns: %d ms'):format(firstrun / 1000, manyruns / 1000)) + -- 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.parse_query("c", ...) + cquery = vim.treesitter.query.parse("c", ...) parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} @@ -219,7 +236,7 @@ void ui_refresh(void) insert(test_text) local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", ...) + cquery = vim.treesitter.query.parse("c", ...) parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} @@ -263,13 +280,13 @@ void ui_refresh(void) eq('void', res2) end) - it('support getting text where start of node is past EOF', function() + it('support getting text where start of node is one past EOF', function() local text = [[ def run a = <<~E end]] insert(text) - local result = exec_lua([[ + eq('', exec_lua[[ local fake_node = {} function fake_node:start() return 3, 0, 23 @@ -277,9 +294,14 @@ end]] function fake_node:end_() return 3, 0, 23 end - return vim.treesitter.get_node_text(fake_node, 0) == nil + 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) ]]) - eq(true, result) end) it('support getting empty text if node range is zero width', function() @@ -296,6 +318,9 @@ 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) @@ -305,7 +330,7 @@ end]] insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'.. + cquery = vim.treesitter.query.parse("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'.. '([_] @times (#vim-match? @times "^\\\\*$"))'.. '([_] @paren (#vim-match? @paren "^\\\\($"))'.. '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'.. @@ -355,7 +380,7 @@ end]] ]]) exec_lua([[ function get_query_result(query_text) - cquery = vim.treesitter.parse_query("c", query_text) + cquery = vim.treesitter.query.parse("c", query_text) parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} @@ -395,7 +420,7 @@ end]] insert('char* astring = "Hello World!";') local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))') + 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 = {} @@ -428,7 +453,7 @@ end]] local custom_query = "((identifier) @main (#is-main? @main))" local res = exec_lua([[ - local query = require"vim.treesitter.query" + local query = vim.treesitter.query local function is_main(match, pattern, bufnr, predicate) local node = match[ predicate[2] ] @@ -440,7 +465,7 @@ end]] query.add_predicate("is-main?", is_main) - local query = query.parse_query("c", ...) + local query = query.parse("c", ...) local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do @@ -453,7 +478,7 @@ end]] eq({{0, 4, 0, 8}}, res) local res_list = exec_lua[[ - local query = require'vim.treesitter.query' + local query = vim.treesitter.query local list = query.list_predicates() @@ -462,10 +487,9 @@ end]] return list ]] - eq({ 'any-of?', 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_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) @@ -482,22 +506,12 @@ end]] local root = parser:parse()[1]:root() parser:set_included_regions({{root:child(0)}}) parser:invalidate() - return { parser:parse()[1]:root():range() } + return { parser:parse(true)[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 } }) + 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 } } } @@ -507,12 +521,13 @@ end]] 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.parse_query("c", "(declaration) @decl") + query = vim.treesitter.query.parse("c", "(declaration) @decl") local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do @@ -521,7 +536,7 @@ end]] parser:set_included_regions({nodes}) - local root = parser:parse()[1]:root() + local root = parser:parse(true)[1]:root() local res = {} for i=0,(root:named_child_count() - 1) do @@ -560,7 +575,7 @@ end]] local parser = vim.treesitter.get_string_parser(str, "c") local nodes = {} - local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') + 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() }) @@ -582,7 +597,7 @@ end]] local parser = vim.treesitter.get_string_parser(str, "c") local nodes = {} - local query = vim.treesitter.parse_query("c", '((identifier) @foo)') + 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 @@ -606,8 +621,8 @@ 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 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 @@ -619,7 +634,8 @@ int x = INT_MAX; exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { - c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) + 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)")) @@ -629,8 +645,19 @@ int x = INT_MAX; {3, 14, 3, 17}, -- VALUE 123 {4, 15, 4, 18}, -- VALUE1 123 {5, 15, 5, 18}, -- VALUE2 123 - {1, 26, 1, 65}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - {2, 29, 2, 68} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + {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<esc>') + 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) @@ -640,7 +667,8 @@ int x = INT_MAX; exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { - c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) + 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)")) @@ -650,52 +678,54 @@ int x = INT_MAX; {3, 14, 5, 18}, -- VALUE 123 -- VALUE1 123 -- VALUE2 123 - {1, 26, 2, 68} -- 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)) + {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()) - end) - end) - - describe("when providing parsing information through a directive", function() - it("should inject a language", function() - exec_lua([=[ - vim.treesitter.add_directive("inject-clang!", function(match, _, _, pred, metadata) - metadata.language = "c" - metadata.combined = true - metadata.content = pred[2] - end) - - parser = vim.treesitter.get_parser(0, "c", { - injections = { - c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" .. - "(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}}) - ]=]) + helpers.feed('ggo<esc>') 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 + {0, 0, 8, 0}, -- root tree + {4, 14, 6, 18}, -- VALUE 123 -- VALUE1 123 -- VALUE2 123 - {1, 26, 2, 68} -- 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)) + {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) - it("should not inject bad languages", function() - skip(is_os('win')) - exec_lua([=[ - vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata) - metadata.language = "{" - metadata.combined = true - metadata.content = pred[2] - 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_function_def value: ((preproc_arg) @_a (#inject-bad! @_a)))"}}) - ]=]) + 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<esc>') + 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) @@ -704,22 +734,23 @@ int x = INT_MAX; exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { - c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}}) + 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, 15, 3, 16}, -- VALUE 123 - {4, 16, 4, 17}, -- VALUE1 123 - {5, 16, 5, 17}, -- VALUE2 123 - {1, 26, 1, 65}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - {2, 29, 2, 68} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) + {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 = require'vim.treesitter.query' + local query = vim.treesitter.query local list = query.list_directives() @@ -728,7 +759,7 @@ int x = INT_MAX; return list ]] - eq({ 'offset!', 'set!' }, res_list) + eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list) end) end) end) @@ -744,7 +775,8 @@ int x = INT_MAX; 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) @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}) @@ -765,7 +797,7 @@ int x = INT_MAX; local result = exec_lua([[ local result - query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! "key" "value"))') + 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 @@ -785,10 +817,10 @@ int x = INT_MAX; ]]) local result = exec_lua([[ - local query = require("vim.treesitter.query") + local query = vim.treesitter.query local value - query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "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 @@ -807,10 +839,10 @@ int x = INT_MAX; ]]) local result = exec_lua([[ - local query = require("vim.treesitter.query") + local query = vim.treesitter.query local result - query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))') + 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 @@ -829,4 +861,355 @@ int x = INT_MAX; 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<cr>') + + 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) diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua index 7f5a864c3d..9c07959098 100644 --- a/test/functional/treesitter/utils_spec.lua +++ b/test/functional/treesitter/utils_spec.lua @@ -28,4 +28,21 @@ describe('treesitter utils', function() eq(true, exec_lua('return vim.treesitter.is_ancestor(ancestor, child)')) eq(false, exec_lua('return vim.treesitter.is_ancestor(child, ancestor)')) end) + + it('can detect if a position is contained in a node', function() + exec_lua([[ + node = { + range = function() + return 0, 4, 0, 8 + end, + } + ]]) + + eq(false, exec_lua('return vim.treesitter.is_in_node_range(node, 0, 3)')) + for i = 4, 7 do + eq(true, exec_lua('return vim.treesitter.is_in_node_range(node, 0, ...)', i)) + end + -- End column exclusive + eq(false, exec_lua('return vim.treesitter.is_in_node_range(node, 0, 8)')) + end) end) |