diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/functional/api/keymap_spec.lua | 5 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 65 | ||||
-rw-r--r-- | test/functional/eval/environ_spec.lua | 61 | ||||
-rw-r--r-- | test/functional/fixtures/fake-lsp-server.lua | 17 | ||||
-rw-r--r-- | test/functional/helpers.lua | 13 | ||||
-rw-r--r-- | test/functional/legacy/assert_spec.lua | 12 | ||||
-rw-r--r-- | test/functional/legacy/backspace_opt_spec.lua | 67 | ||||
-rw-r--r-- | test/functional/legacy/memory_usage_spec.lua | 46 | ||||
-rw-r--r-- | test/functional/lua/treesitter_spec.lua | 1015 | ||||
-rw-r--r-- | test/functional/options/defaults_spec.lua | 26 | ||||
-rw-r--r-- | test/functional/plugin/lsp/diagnostic_spec.lua | 100 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 162 | ||||
-rw-r--r-- | test/functional/provider/define_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/treesitter/highlight_spec.lua | 475 | ||||
-rw-r--r-- | test/functional/treesitter/language_spec.lua | 71 | ||||
-rw-r--r-- | test/functional/treesitter/parser_spec.lua | 599 |
16 files changed, 1573 insertions, 1169 deletions
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index d8a9c3b411..4194945645 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -809,4 +809,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() command('normal lhs') eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) end) + + it("does not crash when setting keymap in a non-existing buffer #13541", function() + pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) + helpers.assert_alive() + end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index c42d5c34cc..1d8ffc2087 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -972,6 +972,12 @@ describe('API', function() nvim("input", "gu") eq({mode='no', blocking=false}, nvim("get_mode")) end) + + it("at '-- More --' prompt returns blocking=true #11899", function() + command('set more') + feed(':digraphs<cr>') + eq({mode='rm', blocking=true}, nvim("get_mode")) + end) end) describe('RPC (K_EVENT) #6166', function() @@ -1996,4 +2002,63 @@ describe('API', function() }, meths.get_option_info'showcmd') end) end) + + describe('nvim_echo', function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Brown, bold = true}, -- Statement + [4] = {foreground = Screen.colors.SlateBlue}, -- Special + }) + command('highlight Statement gui=bold guifg=Brown') + command('highlight Special guifg=SlateBlue') + end) + + it('can show highlighted line', function() + nvim_async("echo", {{"msg_a"}, {"msg_b", "Statement"}, {"msg_c", "Special"}}, true, {}) + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + msg_a{3:msg_b}{4:msg_c} | + ]]} + end) + + it('can show highlighted multiline', function() + nvim_async("echo", {{"msg_a\nmsg_a", "Statement"}, {"msg_b", "Special"}}, true, {}) + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }| + {0:~ }| + {2: }| + {3:msg_a} | + {3:msg_a}{4:msg_b} | + {1:Press ENTER or type command to continue}^ | + ]]} + end) + + it('can save message history', function() + nvim('command', 'set cmdheight=2') -- suppress Press ENTER + nvim("echo", {{"msg\nmsg"}, {"msg"}}, true, {}) + eq("msg\nmsgmsg", meths.exec('messages', true)) + end) + + it('can disable saving message history', function() + nvim('command', 'set cmdheight=2') -- suppress Press ENTER + nvim_async("echo", {{"msg\nmsg"}, {"msg"}}, false, {}) + eq("", meths.exec("messages", true)) + end) + end) end) diff --git a/test/functional/eval/environ_spec.lua b/test/functional/eval/environ_spec.lua index 54d2dc960b..9e19568249 100644 --- a/test/functional/eval/environ_spec.lua +++ b/test/functional/eval/environ_spec.lua @@ -3,6 +3,11 @@ local clear = helpers.clear local eq = helpers.eq local environ = helpers.funcs.environ local exists = helpers.funcs.exists +local system = helpers.funcs.system +local nvim_prog = helpers.nvim_prog +local command = helpers.command +local eval = helpers.eval +local setenv = helpers.funcs.setenv describe('environment variables', function() it('environ() handles empty env variable', function() @@ -17,3 +22,59 @@ describe('environment variables', function() eq(0, exists('$DOES_NOT_EXIST')) end) end) + +describe('empty $HOME', function() + local original_home = os.getenv('HOME') + + -- recover $HOME after each test + after_each(function() + if original_home ~= nil then + setenv('HOME', original_home) + end + os.remove('test_empty_home') + os.remove('./~') + end) + + local function tilde_in_cwd() + -- get files in cwd + command("let test_empty_home_cwd_files = split(globpath('.', '*'), '\n')") + -- get the index of the file named '~' + command('let test_empty_home_tilde_index = index(test_empty_home_cwd_files, "./~")') + return eval('test_empty_home_tilde_index') ~= -1 + end + + local function write_and_test_tilde() + system({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', + '-c', 'write test_empty_home', '+q'}) + eq(false, tilde_in_cwd()) + end + + it("'~' folder not created in cwd if $HOME and related env not defined", function() + command("unlet $HOME") + write_and_test_tilde() + + command("let $HOMEDRIVE='C:'") + command("let $USERPROFILE='C:\\'") + write_and_test_tilde() + + command("unlet $HOMEDRIVE") + write_and_test_tilde() + + command("unlet $USERPROFILE") + write_and_test_tilde() + + command("let $HOME='%USERPROFILE%'") + command("let $USERPROFILE='C:\\'") + write_and_test_tilde() + end) + + it("'~' folder not created in cwd if writing a file with invalid $HOME", function() + setenv('HOME', '/path/does/not/exist') + write_and_test_tilde() + end) + + it("'~' folder not created in cwd if writing a file with $HOME=''", function() + command("let $HOME=''") + write_and_test_tilde() + end) +end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index a30eb748d0..252db88b6b 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -109,6 +109,23 @@ function tests.basic_init() } end +function tests.check_workspace_configuration() + skeleton { + on_init = function(_params) + return { capabilities = {} } + end; + body = function() + notify('start') + notify('workspace/configuration', { items = { + { section = "testSetting1" }; + { section = "testSetting2" }; + } }) + expect_notification('workspace/configuration', { true; vim.NIL}) + notify('shutdown') + end; + } +end + function tests.basic_check_capabilities() skeleton { on_init = function(params) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 0829560b9c..4acb1a7d8d 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -725,6 +725,19 @@ function module.pending_win32(pending_fn) end end +function module.pending_c_parser(pending_fn) + local status, msg = unpack(module.exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]])) + if not status then + if module.isCI() then + error("treesitter C parser not found, required on CI: " .. msg) + else + pending_fn 'no C parser, skipping' + return true + end + end + return false +end + -- Calls pending() and returns `true` if the system is too slow to -- run fragile or expensive tests. Else returns `false`. function module.skip_fragile(pending_fn, cond) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index d48b8882af..515d6d91b8 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -242,9 +242,9 @@ describe('assert function:', function() -- assert_fails({cmd}, [, {error}]) describe('assert_fails', function() it('should change v:errors when error does not match v:errmsg', function() - eq(1, eval([[assert_fails('xxx', {})]])) - command([[call assert_match("Expected {} but got 'E731:", v:errors[0])]]) - expected_errors({"Expected {} but got 'E731: using Dictionary as a String'"}) + eq(1, eval([[assert_fails('xxx', 'E12345')]])) + command([[call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])]]) + expected_errors({"Expected 'E12345' but got 'E492: Not an editor command: xxx': xxx"}) end) it('should not change v:errors when cmd errors', function() @@ -258,9 +258,9 @@ describe('assert function:', function() end) it('can specify and get a message about what failed', function() - eq(1, eval([[assert_fails('xxx', {}, 'stupid')]])) - command([[call assert_match("stupid: Expected {} but got 'E731:", v:errors[0])]]) - expected_errors({"stupid: Expected {} but got 'E731: using Dictionary as a String'"}) + eq(1, eval([[assert_fails('xxx', 'E9876', 'stupid')]])) + command([[call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])]]) + expected_errors({"stupid: Expected 'E9876' but got 'E492: Not an editor command: xxx': stupid"}) end) it('can specify and get a message even when cmd succeeds', function() diff --git a/test/functional/legacy/backspace_opt_spec.lua b/test/functional/legacy/backspace_opt_spec.lua deleted file mode 100644 index 90bc6f74f0..0000000000 --- a/test/functional/legacy/backspace_opt_spec.lua +++ /dev/null @@ -1,67 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local call, clear = helpers.call, helpers.clear -local source, eq, nvim = helpers.source, helpers.eq, helpers.meths - -describe("test 'backspace' settings", function() - before_each(function() - clear() - - source([[ - func Exec(expr) - let str='' - try - exec a:expr - catch /.*/ - let str=v:exception - endtry - return str - endfunc - - func Test_backspace_option() - set backspace= - call assert_equal('', &backspace) - set backspace=indent - call assert_equal('indent', &backspace) - set backspace=eol - call assert_equal('eol', &backspace) - set backspace=start - call assert_equal('start', &backspace) - " Add the value - set backspace= - set backspace=indent - call assert_equal('indent', &backspace) - set backspace+=eol - call assert_equal('indent,eol', &backspace) - set backspace+=start - call assert_equal('indent,eol,start', &backspace) - " Delete the value - set backspace-=indent - call assert_equal('eol,start', &backspace) - set backspace-=start - call assert_equal('eol', &backspace) - set backspace-=eol - call assert_equal('', &backspace) - " Check the error - call assert_equal(0, match(Exec('set backspace=ABC'), '.*E474')) - call assert_equal(0, match(Exec('set backspace+=def'), '.*E474')) - " NOTE: Vim doesn't check following error... - "call assert_equal(0, match(Exec('set backspace-=ghi'), '.*E474')) - - " Check backwards compatibility with version 5.4 and earlier - set backspace=0 - call assert_equal('0', &backspace) - set backspace=1 - call assert_equal('1', &backspace) - set backspace=2 - call assert_equal('2', &backspace) - call assert_false(match(Exec('set backspace=3'), '.*E474')) - call assert_false(match(Exec('set backspace=10'), '.*E474')) - endfunc - ]]) - end) - - it('works', function() - call('Test_backspace_option') - eq({}, nvim.get_vvar('errors')) - end) -end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index fb0bacc2d2..5f7bbd887f 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -10,6 +10,29 @@ local source = helpers.source local poke_eventloop = helpers.poke_eventloop local uname = helpers.uname local load_adjust = helpers.load_adjust +local isCI = helpers.isCI + +local function isasan() + local version = eval('execute("version")') + return version:match('-fsanitize=[a-z,]*address') +end + +clear() +if isasan() then + pending('ASAN build is difficult to estimate memory usage', function() end) + return +elseif iswin() then + if isCI('github') then + pending('Windows runners in Github Actions do not have a stable environment to estimate memory usage', function() end) + return + elseif eval("executable('wmic')") == 0 then + pending('missing "wmic" command', function() end) + return + end +elseif eval("executable('ps')") == 0 then + pending('missing "ps" command', function() end) + return +end local monitor_memory_usage = { memory_usage = function(self) @@ -71,11 +94,6 @@ describe('memory usage', function() end end - local function isasan() - local version = eval('execute("version")') - return version:match('-fsanitize=[a-z,]*address') - end - before_each(clear) --[[ @@ -83,15 +101,6 @@ describe('memory usage', function() just after it finishes. ]]-- it('function capture vargs', function() - if isasan() then - pending('ASAN build is difficult to estimate memory usage') - end - if iswin() and eval("executable('wmic')") == 0 then - pending('missing "wmic" command') - elseif eval("executable('ps')") == 0 then - pending('missing "ps" command') - end - local pid = eval('getpid()') local before = monitor_memory_usage(pid) source([[ @@ -125,15 +134,6 @@ describe('memory usage', function() increase so much even when rerun Xtest.vim since system memory caches. ]]-- it('function capture lvars', function() - if isasan() then - pending('ASAN build is difficult to estimate memory usage') - end - if iswin() and eval("executable('wmic')") == 0 then - pending('missing "wmic" command') - elseif eval("executable('ps')") == 0 then - pending('missing "ps" command') - end - local pid = eval('getpid()') local before = monitor_memory_usage(pid) local fname = source([[ diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua deleted file mode 100644 index e522c339a9..0000000000 --- a/test/functional/lua/treesitter_spec.lua +++ /dev/null @@ -1,1015 +0,0 @@ --- Test suite for testing interactions with API bindings -local helpers = require('test.functional.helpers')(after_each) -local Screen = require('test.functional.ui.screen') - -local clear = helpers.clear -local eq = helpers.eq -local insert = helpers.insert -local exec_lua = helpers.exec_lua -local feed = helpers.feed -local pcall_err = helpers.pcall_err -local matches = helpers.matches - -before_each(clear) - -describe('treesitter API', function() - -- error tests not requiring a parser library - it('handles missing language', function() - eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", - pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) - - -- actual message depends on platform - matches("Error executing lua: Failed to load parser: uv_dlopen: .+", - pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) - - -- 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("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", - pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) - end) -end) - -describe('treesitter API with C parser', function() - local function check_parser() - local status, msg = unpack(exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]])) - if not status then - if helpers.isCI() then - error("treesitter C parser not found, required on CI: " .. msg) - else - pending('no C parser, skipping') - end - end - return status - end - - it('parses buffer', function() - if helpers.pending_win32(pending) or not check_parser() then return end - - insert([[ - int main() { - int x = 3; - }]]) - - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - root = tree:root() - lang = vim.treesitter.inspect_language('c') - ]]) - - eq("<tree>", exec_lua("return tostring(tree)")) - eq("<node translation_unit>", exec_lua("return tostring(root)")) - eq({0,0,3,0}, exec_lua("return {root:range()}")) - - eq(1, exec_lua("return root:child_count()")) - exec_lua("child = root:child(0)") - eq("<node function_definition>", exec_lua("return tostring(child)")) - eq({0,0,2,1}, exec_lua("return {child:range()}")) - - eq("function_definition", exec_lua("return child:type()")) - eq(true, exec_lua("return child:named()")) - eq("number", type(exec_lua("return child:symbol()"))) - eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]")) - - exec_lua("anon = root:descendant_for_range(0,8,0,9)") - eq("(", exec_lua("return anon:type()")) - eq(false, exec_lua("return anon:named()")) - eq("number", type(exec_lua("return anon:symbol()"))) - eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]")) - - exec_lua("descendant = root:descendant_for_range(1,2,1,12)") - eq("<node declaration>", exec_lua("return tostring(descendant)")) - eq({1,2,1,12}, exec_lua("return {descendant:range()}")) - eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()")) - - feed("2G7|ay") - exec_lua([[ - tree2 = parser:parse()[1] - root2 = tree2:root() - descendant2 = root2:descendant_for_range(1,2,1,13) - ]]) - eq(false, exec_lua("return tree2 == tree1")) - eq(false, exec_lua("return root2 == root")) - eq("<node declaration>", exec_lua("return tostring(descendant2)")) - eq({1,2,1,13}, exec_lua("return {descendant2:range()}")) - - eq(true, exec_lua("return child == child")) - -- separate lua object, but represents same node - eq(true, exec_lua("return child == root:child(0)")) - eq(false, exec_lua("return child == descendant2")) - eq(false, exec_lua("return child == nil")) - eq(false, exec_lua("return child == tree")) - - eq("string", exec_lua("return type(child:id())")) - eq(true, exec_lua("return child:id() == child:id()")) - -- separate lua object, but represents same node - eq(true, exec_lua("return child:id() == root:child(0):id()")) - eq(false, exec_lua("return child:id() == descendant2:id()")) - eq(false, exec_lua("return child:id() == nil")) - eq(false, exec_lua("return child:id() == tree")) - - -- unchanged buffer: return the same tree - eq(true, exec_lua("return parser:parse()[1] == tree2")) - end) - - local test_text = [[ -void ui_refresh(void) -{ - int width = INT_MAX, height = INT_MAX; - bool ext_widgets[kUIExtCount]; - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - ext_widgets[i] = true; - } - - bool inclusive = ui_override(); - for (size_t i = 0; i < ui_count; i++) { - UI *ui = uis[i]; - width = MIN(ui->width, width); - height = MIN(ui->height, height); - foo = BAR(ui->bazaar, bazaar); - for (UIExtension j = 0; (int)j < kUIExtCount; j++) { - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); - } - } -}]] - - it('allows to iterate over nodes children', function() - if not check_parser() then return end - - insert(test_text); - - local res = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - - func_node = parser:parse()[1]:root():child(0) - - res = {} - for node, field in func_node:iter_children() do - table.insert(res, {node:type(), field}) - end - return res - ]]) - - eq({ - {"primitive_type", "type"}, - {"function_declarator", "declarator"}, - {"compound_statement", "body"} - }, res) - end) - - it('allows to get a child by field', function() - if not check_parser() then return end - - insert(test_text); - - local res = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - - func_node = parser:parse()[1]:root():child(0) - - local res = {} - for _, node in ipairs(func_node:field("type")) do - table.insert(res, {node:type(), node:range()}) - end - return res - ]]) - - eq({{ "primitive_type", 0, 0, 0, 4 }}, res) - - local res_fail = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - - return #func_node:field("foo") == 0 - ]]) - - assert(res_fail) - end) - - local query = [[ - ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN")) - "for" @keyword - (primitive_type) @type - (field_expression argument: (identifier) @fieldarg) - ]] - - it("supports runtime queries", function() - if not check_parser() then return end - - local ret = exec_lua [[ - return require"vim.treesitter.query".get_query("c", "highlights").captures[1] - ]] - - eq('variable', ret) - end) - - it('support query and iter by capture', function() - if not check_parser() then return end - - insert(test_text) - - local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do - -- can't transmit node over RPC. just check the name and range - table.insert(res, {cquery.captures[cid], node:type(), node:range()}) - end - return res - ]], query) - - eq({ - { "type", "primitive_type", 8, 2, 8, 6 }, - { "keyword", "for", 9, 2, 9, 5 }, - { "type", "primitive_type", 9, 7, 9, 13 }, - { "minfunc", "identifier", 11, 12, 11, 15 }, - { "fieldarg", "identifier", 11, 16, 11, 18 }, - { "min_id", "identifier", 11, 27, 11, 32 }, - { "minfunc", "identifier", 12, 13, 12, 16 }, - { "fieldarg", "identifier", 12, 17, 12, 19 }, - { "min_id", "identifier", 12, 29, 12, 35 }, - { "fieldarg", "identifier", 13, 14, 13, 16 } - }, res) - end) - - it('support query and iter by match', function() - if not check_parser() then return end - - insert(test_text) - - local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do - -- can't transmit node over RPC. just check the name and range - local mrepr = {} - for cid,node in pairs(match) do - table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) - end - table.insert(res, {pattern, mrepr}) - end - return res - ]], query) - - eq({ - { 3, { { "type", "primitive_type", 8, 2, 8, 6 } } }, - { 2, { { "keyword", "for", 9, 2, 9, 5 } } }, - { 3, { { "type", "primitive_type", 9, 7, 9, 13 } } }, - { 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } }, - { 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } }, - { 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } }, - { 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } }, - { 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } } - }, res) - end) - - it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() - if not check_parser() then return end - - insert('char* astring = "Hello World!";') - - local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, 1) do - -- can't transmit node over RPC. just check the name and range - local mrepr = {} - for cid,node in pairs(match) do - table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) - end - table.insert(res, {pattern, mrepr}) - end - return res - ]]) - - eq({ - { 1, { { "quote", '"', 0, 16, 0, 17 } } }, - { 2, { { "quote", '"', 0, 16, 0, 17 } } }, - { 1, { { "quote", '"', 0, 29, 0, 30 } } }, - { 2, { { "quote", '"', 0, 29, 0, 30 } } }, - }, res) - end) - - it('allows to add predicates', function() - insert([[ - int main(void) { - return 0; - } - ]]) - - local custom_query = "((identifier) @main (#is-main? @main))" - - local res = exec_lua([[ - local query = require"vim.treesitter.query" - - local function is_main(match, pattern, bufnr, predicate) - local node = match[ predicate[2] ] - - return query.get_node_text(node, bufnr) - end - - local parser = vim.treesitter.get_parser(0, "c") - - query.add_predicate("is-main?", is_main) - - local query = query.parse_query("c", ...) - - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do - table.insert(nodes, {node:range()}) - end - - return nodes - ]], custom_query) - - eq({{0, 4, 0, 8}}, res) - - local res_list = exec_lua[[ - local query = require'vim.treesitter.query' - - local list = query.list_predicates() - - table.sort(list) - - return list - ]] - - eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) - end) - - local hl_text = [[ -/// Schedule Lua callback on main loop's event queue -static int nlua_schedule(lua_State *const lstate) -{ - if (lua_type(lstate, 1) != LUA_TFUNCTION - || lstate != lstate) { - lua_pushliteral(lstate, "vim.schedule: expected function"); - return lua_error(lstate); - } - - LuaRef cb = nlua_ref(lstate, 1); - - multiqueue_put(main_loop.events, nlua_schedule_event, - 1, (void *)(ptrdiff_t)cb); - return 0; -}]] - -local hl_query = [[ -(ERROR) @ErrorMsg - -"if" @keyword -"else" @keyword -"for" @keyword -"return" @keyword - -"const" @type -"static" @type -"struct" @type -"enum" @type -"extern" @type - -(string_literal) @string - -(number_literal) @number -(char_literal) @string - -(type_identifier) @type -((type_identifier) @Special (#eq? @Special "LuaRef")) - -(primitive_type) @type -(sized_type_specifier) @type - -; Use lua regexes -((identifier) @Identifier (#contains? @Identifier "lua_")) -((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$")) -((identifier) @Normal (#vim-match? @Constant "^lstate$")) - -((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right)) - -(comment) @comment -]] - - describe('when highlighting', function() - local screen - - before_each(function() - screen = Screen.new(65, 18) - screen:attach() - screen:set_default_attr_ids({ - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {foreground = Screen.colors.Blue1}, - [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [4] = {bold = true, foreground = Screen.colors.Brown}, - [5] = {foreground = Screen.colors.Magenta}, - [6] = {foreground = Screen.colors.Red}, - [7] = {bold = true, foreground = Screen.colors.SlateBlue}, - [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red}, - [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red}, - [11] = {foreground = Screen.colors.Cyan4}, - }) - end) - - it('supports highlighting', function() - if not check_parser() then return end - - insert(hl_text) - screen:expect{grid=[[ - /// Schedule Lua callback on main loop's event queue | - static int nlua_schedule(lua_State *const lstate) | - { | - if (lua_type(lstate, 1) != LUA_TFUNCTION | - || lstate != lstate) { | - lua_pushliteral(lstate, "vim.schedule: expected function"); | - return lua_error(lstate); | - } | - | - LuaRef cb = nlua_ref(lstate, 1); | - | - multiqueue_put(main_loop.events, nlua_schedule_event, | - 1, (void *)(ptrdiff_t)cb); | - return 0; | - ^} | - {1:~ }| - {1:~ }| - | - ]]} - - exec_lua([[ - local parser = vim.treesitter.get_parser(0, "c") - local highlighter = vim.treesitter.highlighter - local query = ... - test_hl = highlighter.new(parser, {queries = {c = query}}) - ]], hl_query) - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - ^} | - {1:~ }| - {1:~ }| - | - ]]} - - feed("5Goc<esc>dd") - - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }| - {1:~ }| - | - ]]} - - feed('7Go*/<esc>') - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - {8:*^/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }| - | - ]]} - - feed('3Go/*<esc>') - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/^*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]]} - - feed("gg$") - feed("~") - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queu^E} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]]} - - - feed("re") - screen:expect{grid=[[ - {2:/// Schedule Lua callback on main loop's event queu^e} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]]} - end) - - it("supports highlighting with custom parser", function() - if not check_parser() then return end - - screen:set_default_attr_ids({ {bold = true, foreground = Screen.colors.SeaGreen4} }) - - insert(test_text) - - screen:expect{ grid= [[ - int width = INT_MAX, height = INT_MAX; | - bool ext_widgets[kUIExtCount]; | - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - bool inclusive = ui_override(); | - for (size_t i = 0; i < ui_count; i++) { | - UI *ui = uis[i]; | - width = MIN(ui->width, width); | - height = MIN(ui->height, height); | - foo = BAR(ui->bazaar, bazaar); | - for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]] } - - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.parse_query("c", "(declaration) @decl") - - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do - table.insert(nodes, node) - end - - parser:set_included_regions({nodes}) - - local hl = vim.treesitter.highlighter.new(parser, {queries = {c = "(identifier) @type"}}) - ]]) - - screen:expect{ grid = [[ - int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | - bool {1:ext_widgets}[{1:kUIExtCount}]; | - for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - bool {1:inclusive} = {1:ui_override}(); | - for (size_t {1:i} = 0; i < ui_count; i++) { | - UI *{1:ui} = {1:uis}[{1:i}]; | - width = MIN(ui->width, width); | - height = MIN(ui->height, height); | - foo = BAR(ui->bazaar, bazaar); | - for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]] } - end) - - it("supports highlighting injected languages", function() - if not check_parser() then return end - - insert([[ - int x = INT_MAX; - #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - #define foo void main() { \ - return 42; \ - } - ]]) - - screen:expect{grid=[[ - int x = INT_MAX; | - #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))| - #define foo void main() { \ | - return 42; \ | - } | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} - - exec_lua([[ - local parser = vim.treesitter.get_parser(0, "c", { - queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"} - }) - local highlighter = vim.treesitter.highlighter - local query = ... - test_hl = highlighter.new(parser, {queries = {c = query}}) - ]], hl_query) - - screen:expect{grid=[[ - {3:int} x = {5:INT_MAX}; | - #define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))| - #define foo {3:void} main() { \ | - {4:return} {5:42}; \ | - } | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]} - end) - end) - - it('inspects language', function() - if not check_parser() then return end - - local keys, fields, symbols = unpack(exec_lua([[ - local lang = vim.treesitter.inspect_language('c') - local keys, symbols = {}, {} - for k,_ in pairs(lang) do - keys[k] = true - end - - -- symbols array can have "holes" and is thus not a valid msgpack array - -- but we don't care about the numbers here (checked in the parser test) - for _, v in pairs(lang.symbols) do - table.insert(symbols, v) - end - return {keys, lang.fields, symbols} - ]])) - - eq({fields=true, symbols=true}, keys) - - local fset = {} - for _,f in pairs(fields) do - eq("string", type(f)) - fset[f] = true - end - eq(true, fset["directive"]) - eq(true, fset["initializer"]) - - local has_named, has_anonymous - for _,s in pairs(symbols) do - eq("string", type(s[1])) - eq("boolean", type(s[2])) - if s[1] == "for_statement" and s[2] == true then - has_named = true - elseif s[1] == "|=" and s[2] == false then - has_anonymous = true - end - end - eq({true,true}, {has_named,has_anonymous}) - end) - it('allows to set simple ranges', function() - if not check_parser() then return end - - insert(test_text) - - local res = exec_lua [[ - parser = vim.treesitter.get_parser(0, "c") - return { parser:parse()[1]:root():range() } - ]] - - eq({0, 0, 19, 0}, res) - - -- The following sets the included ranges for the current parser - -- As stated here, this only includes the function (thus the whole buffer, without the last line) - local res2 = exec_lua [[ - local root = parser:parse()[1]:root() - parser:set_included_regions({{root:child(0)}}) - parser:invalidate() - return { parser:parse()[1]:root():range() } - ]] - - eq({0, 0, 18, 1}, res2) - - local range = exec_lua [[ - local res = {} - for _, region in ipairs(parser:included_regions()) do - for _, node in ipairs(region) do - table.insert(res, {node:range()}) - end - end - return res - ]] - - eq(range, { { 0, 0, 18, 1 } }) - - 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() - if not check_parser() then return end - - insert(test_text) - - local res = exec_lua [[ - parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.parse_query("c", "(declaration) @decl") - - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do - table.insert(nodes, node) - end - - parser:set_included_regions({nodes}) - - local root = parser:parse()[1]:root() - - local res = {} - for i=0,(root:named_child_count() - 1) do - table.insert(res, { root:named_child(i):range() }) - end - return res - ]] - - eq({ - { 2, 2, 2, 40 }, - { 3, 2, 3, 32 }, - { 4, 7, 4, 25 }, - { 8, 2, 8, 33 }, - { 9, 7, 9, 20 }, - { 10, 4, 10, 20 }, - { 14, 9, 14, 27 } }, res) - end) - - it("allows to create string parsers", function() - local ret = exec_lua [[ - local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") - return { parser:parse()[1]:root():range() } - ]] - - eq({ 0, 0, 0, 13 }, ret) - end) - - it("allows to run queries with string parsers", function() - local txt = [[ - int foo = 42; - int bar = 13; - ]] - - local ret = exec_lua([[ - local str = ... - local parser = vim.treesitter.get_string_parser(str, "c") - - local nodes = {} - local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') - - for _, node in query:iter_captures(parser:parse()[1]:root(), str, 0, 2) do - table.insert(nodes, { node:range() }) - end - - return nodes]], txt) - - eq({ {0, 10, 0, 13} }, ret) - end) - - describe("when creating a language tree", function() - local function get_ranges() - return exec_lua([[ - local result = {} - parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end) - return result - ]]) - end - - before_each(function() - insert([[ -int x = INT_MAX; -#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) -#define READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) -#define VALUE 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", { - queries = { - c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) - ]]) - - eq("table", exec_lua("return type(parser:children().c)")) - eq(5, exec_lua("return #parser:children().c:trees()")) - eq({ - {0, 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, 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)) - }, get_ranges()) - end) - end) - - describe("when parsing regions combined", function() - it("should inject a language", function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { - queries = { - c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) - ]]) - - eq("table", exec_lua("return type(parser:children().c)")) - eq(2, exec_lua("return #parser:children().c:trees()")) - eq({ - {0, 0, 7, 0}, -- root tree - {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)) - }, 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", { - queries = { - c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}}) - ]]) - - 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)) - }, get_ranges()) - 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", { - queries = { c = "(preproc_def (preproc_arg) @c)"}}) - - 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.parse_query("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, 0, 1) do - result = metadata.key - end - - return result - ]]) - - eq(result, "value") - end) - end) - - describe("when setting for a capture match", function() - it("should set/get the data correctly", function() - insert([[ - int x = 3; - ]]) - - local result = exec_lua([[ - local result - - query = vim.treesitter.parse_query("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, 0, 1) do - result = metadata[pattern].key - end - - return result - ]]) - - eq(result, "value") - end) - end) - end) -end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 92d077ed14..eb5e284385 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -204,9 +204,8 @@ describe('startup defaults', function() end) describe('$NVIM_LOG_FILE', function() - local datasubdir = iswin() and 'nvim-data' or 'nvim' local xdgdir = 'Xtest-startup-xdg-logpath' - local xdgdatadir = xdgdir..'/'..datasubdir + local xdgcachedir = xdgdir..'/nvim' after_each(function() os.remove('Xtest-logpath') rmdir(xdgdir) @@ -218,28 +217,21 @@ describe('startup defaults', function() }}) eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) end) - it('defaults to stdpath("data")/log if empty', function() - eq(true, mkdir(xdgdir) and mkdir(xdgdatadir)) + it('defaults to stdpath("cache")/log if empty', function() + eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) clear({env={ - XDG_DATA_HOME=xdgdir, + XDG_CACHE_HOME=xdgdir, NVIM_LOG_FILE='', -- Empty is invalid. }}) - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) - it('defaults to stdpath("data")/log if invalid', function() - eq(true, mkdir(xdgdir) and mkdir(xdgdatadir)) + it('defaults to stdpath("cache")/log if invalid', function() + eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) clear({env={ - XDG_DATA_HOME=xdgdir, + XDG_CACHE_HOME=xdgdir, NVIM_LOG_FILE='.', -- Any directory is invalid. }}) - eq(xdgdir..'/'..datasubdir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) - end) - it('defaults to .nvimlog if stdpath("data") is invalid', function() - clear({env={ - XDG_DATA_HOME='Xtest-missing-xdg-dir', - NVIM_LOG_FILE='.', -- Any directory is invalid. - }}) - eq('.nvimlog', eval('$NVIM_LOG_FILE')) + eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) end) end) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 3a676359ab..4705a76465 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -12,41 +12,41 @@ describe('vim.lsp.diagnostic', function() clear() exec_lua [[ - require('vim.lsp') - - make_range = function(x1, y1, x2, y2) - return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } - end - - make_error = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 1, - } - end - - make_warning = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 2, - } - end - - make_information = function(msg, x1, y1, x2, y2) - return { - range = make_range(x1, y1, x2, y2), - message = msg, - severity = 3, - } - end - - count_of_extmarks_for_client = function(bufnr, client_id) - return #vim.api.nvim_buf_get_extmarks( - bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {} - ) - end + require('vim.lsp') + + make_range = function(x1, y1, x2, y2) + return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } + end + + make_error = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 1, + } + end + + make_warning = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 2, + } + end + + make_information = function(msg, x1, y1, x2, y2) + return { + range = make_range(x1, y1, x2, y2), + message = msg, + severity = 3, + } + end + + count_of_extmarks_for_client = function(bufnr, client_id) + return #vim.api.nvim_buf_get_extmarks( + bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {} + ) + end ]] fake_uri = "file://fake/uri" @@ -640,6 +640,36 @@ describe('vim.lsp.diagnostic', function() eq(expected_spacing, #spacing) end) + + it('allows filtering via severity limit', function() + local get_extmark_count_with_severity = function(severity_limit) + return exec_lua([[ + PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + underline = false, + virtual_text = { + severity_limit = ... + }, + }) + + PublishDiagnostics(nil, nil, { + uri = fake_uri, + diagnostics = { + make_warning('Delayed Diagnostic', 4, 4, 4, 4), + } + }, 1 + ) + + return count_of_extmarks_for_client(diagnostic_bufnr, 1) + ]], severity_limit) + end + + -- No messages with Error or higher + eq(0, get_extmark_count_with_severity("Error")) + + -- But now we don't filter it + eq(1, get_extmark_count_with_severity("Warning")) + eq(1, get_extmark_count_with_severity("Hint")) + end) end) describe('lsp.util.show_line_diagnostics', function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f01d90bbeb..41fdf845df 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -262,6 +262,63 @@ describe('LSP', function() } end) + it('client should return settings via workspace/configuration handler', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "workspace/configuration", { items = { + { section = "testSetting1" }; + { section = "testSetting2" }; + }}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "check_workspace_configuration"; + on_init = function(_client) + client = _client + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_callback = function(err, method, params, client_id) + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'start' then + exec_lua([=[ + local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID) + client.config.settings = { + testSetting1 = true; + testSetting2 = false; + }]=]) + end + if method == 'workspace/configuration' then + local result = exec_lua([=[ + local method, params = ... + return require'vim.lsp.handlers'['workspace/configuration'](err, method, params, TEST_RPC_CLIENT_ID)]=], method, params) + client.notify('workspace/configuration', result) + end + if method == 'shutdown' then + client.stop() + end + end; + } + end) + it('workspace/configuration returns NIL per section if client was started without config.settings', function() + fake_lsp_server_setup('workspace/configuration no settings') + eq({ + NIL, + NIL, + }, exec_lua [[ + local params = { + items = { + {section = 'foo'}, + {section = 'bar'}, + } + } + return vim.lsp.handlers['workspace/configuration'](nil, nil, params, TEST_RPC_CLIENT_ID) + ]]) + end) + it('should verify capabilities sent', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; @@ -1003,7 +1060,7 @@ describe('LSP', function() return { edits = { make_edit(0, 0, 0, 3, "First ↥ 🤦 🦄") - }, + }, textDocument = { uri = "file://fake/uri"; version = editVersion @@ -1044,7 +1101,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(...) + vim.lsp.util.apply_text_document_edit(args[1]) ]], edit, versionedBuf) end @@ -1067,6 +1124,7 @@ describe('LSP', function() }, buf_lines(target_bufnr)) end) end) + describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() local expected = { @@ -1082,6 +1140,106 @@ describe('LSP', function() ]]) end) end) + + describe('apply_workspace_edit', function() + local replace_line_edit = function(row, new_line, editVersion) + return { + edits = { + -- NOTE: This is a hack if you have a line longer than 1000 it won't replace it + make_edit(row, 0, row, 1000, new_line) + }, + textDocument = { + uri = "file://fake/uri"; + version = editVersion + } + } + end + + -- Some servers send all the edits separately, but with the same version. + -- We should not stop applying the edits + local make_workspace_edit = function(changes) + return { + documentChanges = changes + } + end + + local target_bufnr, changedtick = nil, nil + + before_each(function() + local ret = exec_lua [[ + local bufnr = vim.uri_to_bufnr("file://fake/uri") + local lines = { + "Original Line #1", + "Original Line #2" + } + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + local update_changed_tick = function() + vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + end + + update_changed_tick() + vim.api.nvim_buf_attach(bufnr, false, { + on_changedtick = function() + update_changed_tick() + end + }) + + return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} + ]] + + target_bufnr = ret[1] + changedtick = ret[2] + end) + + it('apply_workspace_edit applies a single edit', function() + local new_lines = { + "First Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq({ + "First Line", + "Original Line #2", + }, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + + it('apply_workspace_edit applies multiple edits', function() + local new_lines = { + "First Line", + "Second Line", + } + + local edits = {} + for row, line in ipairs(new_lines) do + table.insert(edits, replace_line_edit(row - 1, line, changedtick)) + end + + eq(new_lines, exec_lua([[ + local args = {...} + local workspace_edits = args[1] + local target_bufnr = args[2] + + vim.lsp.util.apply_workspace_edit(workspace_edits) + + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + ]], make_workspace_edit(edits), target_bufnr)) + end) + end) + describe('completion_list_to_complete_items', function() -- Completion option precedence: -- textEdit.newText > insertText > label diff --git a/test/functional/provider/define_spec.lua b/test/functional/provider/define_spec.lua index 1d50ce0a56..12efbec326 100644 --- a/test/functional/provider/define_spec.lua +++ b/test/functional/provider/define_spec.lua @@ -136,7 +136,7 @@ local function command_specs_for(fn, sync, first_arg_factory, init) end) it('with nargs/count', function() - call(fn, args..', {"nargs": "1", "range": "5"}') + call(fn, args..', {"nargs": "1", "count": "5"}') local function on_setup() command('5RpcCommand arg') end @@ -152,7 +152,7 @@ local function command_specs_for(fn, sync, first_arg_factory, init) end) it('with nargs/count/bang', function() - call(fn, args..', {"nargs": "1", "range": "5", "bang": ""}') + call(fn, args..', {"nargs": "1", "count": "5", "bang": ""}') local function on_setup() command('5RpcCommand! arg') end @@ -169,7 +169,7 @@ local function command_specs_for(fn, sync, first_arg_factory, init) end) it('with nargs/count/bang/register', function() - call(fn, args..', {"nargs": "1", "range": "5", "bang": "",'.. + call(fn, args..', {"nargs": "1", "count": "5", "bang": "",'.. ' "register": ""}') local function on_setup() command('5RpcCommand! b arg') @@ -188,7 +188,7 @@ local function command_specs_for(fn, sync, first_arg_factory, init) end) it('with nargs/count/bang/register/eval', function() - call(fn, args..', {"nargs": "1", "range": "5", "bang": "",'.. + call(fn, args..', {"nargs": "1", "count": "5", "bang": "",'.. ' "register": "", "eval": "@<reg>"}') local function on_setup() command('let @b = "regb"') diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua new file mode 100644 index 0000000000..cb73bfbbe1 --- /dev/null +++ b/test/functional/treesitter/highlight_spec.lua @@ -0,0 +1,475 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local insert = helpers.insert +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local pending_c_parser = helpers.pending_c_parser + +before_each(clear) + +local hl_query = [[ + (ERROR) @ErrorMsg + + "if" @keyword + "else" @keyword + "for" @keyword + "return" @keyword + + "const" @type + "static" @type + "struct" @type + "enum" @type + "extern" @type + + (string_literal) @string + + (number_literal) @number + (char_literal) @string + + (type_identifier) @type + ((type_identifier) @Special (#eq? @Special "LuaRef")) + + (primitive_type) @type + (sized_type_specifier) @type + + ; Use lua regexes + ((identifier) @Identifier (#contains? @Identifier "lua_")) + ((identifier) @Constant (#lua-match? @Constant "^[A-Z_]+$")) + ((identifier) @Normal (#vim-match? @Constant "^lstate$")) + + ((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right)) + + (comment) @comment +]] + +local hl_text = [[ +/// Schedule Lua callback on main loop's event queue +static int nlua_schedule(lua_State *const lstate) +{ + if (lua_type(lstate, 1) != LUA_TFUNCTION + || lstate != lstate) { + lua_pushliteral(lstate, "vim.schedule: expected function"); + return lua_error(lstate); + } + + LuaRef cb = nlua_ref(lstate, 1); + + multiqueue_put(main_loop.events, nlua_schedule_event, + 1, (void *)(ptrdiff_t)cb); + return 0; +}]] + +local 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); + } + } +}]] + +describe('treesitter highlighting', function() + local screen + + before_each(function() + screen = Screen.new(65, 18) + screen:attach() + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.Blue1}; + [2] = {foreground = Screen.colors.Blue1}; + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}; + [4] = {bold = true, foreground = Screen.colors.Brown}; + [5] = {foreground = Screen.colors.Magenta}; + [6] = {foreground = Screen.colors.Red}; + [7] = {bold = true, foreground = Screen.colors.SlateBlue}; + [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [9] = {foreground = Screen.colors.Magenta, background = Screen.colors.Red}; + [10] = {foreground = Screen.colors.Red, background = Screen.colors.Red}; + [11] = {foreground = Screen.colors.Cyan4}; + } + + exec_lua([[ hl_query = ... ]], hl_query) + end) + + it('is updated with edits', function() + if pending_c_parser(pending) then return end + + insert(hl_text) + screen:expect{grid=[[ + /// Schedule Lua callback on main loop's event queue | + static int nlua_schedule(lua_State *const lstate) | + { | + if (lua_type(lstate, 1) != LUA_TFUNCTION | + || lstate != lstate) { | + lua_pushliteral(lstate, "vim.schedule: expected function"); | + return lua_error(lstate); | + } | + | + LuaRef cb = nlua_ref(lstate, 1); | + | + multiqueue_put(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + + exec_lua [[ + local parser = vim.treesitter.get_parser(0, "c") + local highlighter = vim.treesitter.highlighter + test_hl = highlighter.new(parser, {queries = {c = hl_query}}) + ]] + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + + feed("5Goc<esc>dd") + + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + } | + {1:~ }| + {1:~ }| + | + ]]} + + feed('7Go*/<esc>') + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | + || {6:lstate} != {6:lstate}) { | + {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | + {4:return} {11:lua_error}(lstate); | + {8:*^/} | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + } | + {1:~ }| + | + ]]} + + feed('3Go/*<esc>') + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queue} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {2:/^*} | + {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {2: || lstate != lstate) {} | + {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {2: return lua_error(lstate);} | + {2:*/} | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + {8:}} | + | + ]]} + + feed("gg$") + feed("~") + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queu^E} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {2:/*} | + {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {2: || lstate != lstate) {} | + {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {2: return lua_error(lstate);} | + {2:*/} | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + {8:}} | + | + ]]} + + + feed("re") + screen:expect{grid=[[ + {2:/// Schedule Lua callback on main loop's event queu^e} | + {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + { | + {2:/*} | + {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {2: || lstate != lstate) {} | + {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {2: return lua_error(lstate);} | + {2:*/} | + } | + | + {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5:0}; | + {8:}} | + | + ]]} + end) + + it('is updated with :sort', function() + if pending_c_parser(pending) then return end + + insert(test_text) + exec_lua [[ + local parser = vim.treesitter.get_parser(0, "c") + test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}}) + ]] + screen:expect{grid=[[ + {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | + {3:bool} ext_widgets[kUIExtCount]; | + {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {3:bool} inclusive = ui_override(); | + {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | + {3:UI} *ui = uis[i]; | + width = {5:MIN}(ui->width, width); | + height = {5:MIN}(ui->height, height); | + foo = {5:BAR}(ui->bazaar, bazaar); | + {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]]} + + feed ":sort<cr>" + screen:expect{grid=[[ + ^ | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {3:UI} *ui = uis[i]; | + ext_widgets[i] = true; | + foo = {5:BAR}(ui->bazaar, bazaar); | + {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | + height = {5:MIN}(ui->height, height); | + width = {5:MIN}(ui->width, width); | + } | + {3:bool} ext_widgets[kUIExtCount]; | + {3:bool} inclusive = ui_override(); | + {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | + {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | + {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | + } | + } | + {3:void} ui_refresh({3:void}) | + :sort | + ]]} + + feed "u" + + screen:expect{grid=[[ + {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | + {3:bool} ext_widgets[kUIExtCount]; | + {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {3:bool} inclusive = ui_override(); | + {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | + {3:UI} *ui = uis[i]; | + width = {5:MIN}(ui->width, width); | + height = {5:MIN}(ui->height, height); | + foo = {5:BAR}(ui->bazaar, bazaar); | + {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + 19 changes; before #2 {MATCH:.*}| + ]]} + end) + + it("supports with custom parser", function() + if pending_c_parser(pending) then return end + + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.SeaGreen4}; + } + + insert(test_text) + + screen:expect{ grid= [[ + int width = INT_MAX, height = INT_MAX; | + bool ext_widgets[kUIExtCount]; | + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool inclusive = ui_override(); | + for (size_t i = 0; i < ui_count; i++) { | + UI *ui = uis[i]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]] } + + exec_lua [[ + parser = vim.treesitter.get_parser(0, "c") + query = vim.treesitter.parse_query("c", "(declaration) @decl") + + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do + table.insert(nodes, node) + end + + parser:set_included_regions({nodes}) + + local hl = vim.treesitter.highlighter.new(parser, {queries = {c = "(identifier) @type"}}) + ]] + + screen:expect{ grid = [[ + int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | + bool {1:ext_widgets}[{1:kUIExtCount}]; | + for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool {1:inclusive} = {1:ui_override}(); | + for (size_t {1:i} = 0; i < ui_count; i++) { | + UI *{1:ui} = {1:uis}[{1:i}]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]] } + end) + + it("supports injected languages", function() + if pending_c_parser(pending) then return end + + insert([[ + int x = INT_MAX; + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) + #define foo void main() { \ + return 42; \ + } + ]]) + + screen:expect{grid=[[ + int x = INT_MAX; | + #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))| + #define foo void main() { \ | + return 42; \ | + } | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + exec_lua [[ + local parser = vim.treesitter.get_parser(0, "c", { + queries = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"} + }) + local highlighter = vim.treesitter.highlighter + 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:~ }| + | + ]]} + end) +end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua new file mode 100644 index 0000000000..a5801271cb --- /dev/null +++ b/test/functional/treesitter/language_spec.lua @@ -0,0 +1,71 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local pcall_err = helpers.pcall_err +local matches = helpers.matches +local pending_c_parser = helpers.pending_c_parser + +before_each(clear) + +describe('treesitter API', function() + -- error tests not requiring a parser library + it('handles missing language', function() + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) + + -- actual message depends on platform + matches("Error executing lua: Failed to load parser: uv_dlopen: .+", + pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) + + -- 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("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) + end) + + it('inspects language', function() + if pending_c_parser(pending) then return end + + local keys, fields, symbols = unpack(exec_lua([[ + local lang = vim.treesitter.inspect_language('c') + local keys, symbols = {}, {} + for k,_ in pairs(lang) do + keys[k] = true + end + + -- symbols array can have "holes" and is thus not a valid msgpack array + -- but we don't care about the numbers here (checked in the parser test) + for _, v in pairs(lang.symbols) do + table.insert(symbols, v) + end + return {keys, lang.fields, symbols} + ]])) + + eq({fields=true, symbols=true}, keys) + + local fset = {} + for _,f in pairs(fields) do + eq("string", type(f)) + fset[f] = true + end + eq(true, fset["directive"]) + eq(true, fset["initializer"]) + + local has_named, has_anonymous + for _,s in pairs(symbols) do + eq("string", type(s[1])) + eq("boolean", type(s[2])) + if s[1] == "for_statement" and s[2] == true then + has_named = true + elseif s[1] == "|=" and s[2] == false then + has_anonymous = true + end + end + eq({true,true}, {has_named,has_anonymous}) + end) +end) + diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua new file mode 100644 index 0000000000..f99362fbdf --- /dev/null +++ b/test/functional/treesitter/parser_spec.lua @@ -0,0 +1,599 @@ +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 feed = helpers.feed +local pending_c_parser = helpers.pending_c_parser + +before_each(clear) + +describe('treesitter parser API', function() + + it('parses buffer', function() + if helpers.pending_win32(pending) or pending_c_parser(pending) then return end + + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + root = tree:root() + lang = vim.treesitter.inspect_language('c') + ]]) + + eq("<tree>", exec_lua("return tostring(tree)")) + eq("<node translation_unit>", exec_lua("return tostring(root)")) + eq({0,0,3,0}, exec_lua("return {root:range()}")) + + eq(1, exec_lua("return root:child_count()")) + exec_lua("child = root:child(0)") + eq("<node function_definition>", exec_lua("return tostring(child)")) + eq({0,0,2,1}, exec_lua("return {child:range()}")) + + eq("function_definition", exec_lua("return child:type()")) + eq(true, exec_lua("return child:named()")) + eq("number", type(exec_lua("return child:symbol()"))) + eq({'function_definition', true}, exec_lua("return lang.symbols[child:symbol()]")) + + exec_lua("anon = root:descendant_for_range(0,8,0,9)") + eq("(", exec_lua("return anon:type()")) + eq(false, exec_lua("return anon:named()")) + eq("number", type(exec_lua("return anon:symbol()"))) + eq({'(', false}, exec_lua("return lang.symbols[anon:symbol()]")) + + exec_lua("descendant = root:descendant_for_range(1,2,1,12)") + eq("<node declaration>", exec_lua("return tostring(descendant)")) + eq({1,2,1,12}, exec_lua("return {descendant:range()}")) + eq("(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))", exec_lua("return descendant:sexpr()")) + + feed("2G7|ay") + exec_lua([[ + tree2 = parser:parse()[1] + root2 = tree2:root() + descendant2 = root2:descendant_for_range(1,2,1,13) + ]]) + eq(false, exec_lua("return tree2 == tree1")) + eq(false, exec_lua("return root2 == root")) + eq("<node declaration>", exec_lua("return tostring(descendant2)")) + eq({1,2,1,13}, exec_lua("return {descendant2:range()}")) + + eq(true, exec_lua("return child == child")) + -- separate lua object, but represents same node + eq(true, exec_lua("return child == root:child(0)")) + eq(false, exec_lua("return child == descendant2")) + eq(false, exec_lua("return child == nil")) + eq(false, exec_lua("return child == tree")) + + eq("string", exec_lua("return type(child:id())")) + eq(true, exec_lua("return child:id() == child:id()")) + -- separate lua object, but represents same node + eq(true, exec_lua("return child:id() == root:child(0):id()")) + eq(false, exec_lua("return child:id() == descendant2:id()")) + eq(false, exec_lua("return child:id() == nil")) + eq(false, exec_lua("return child:id() == tree")) + + -- unchanged buffer: return the same tree + eq(true, exec_lua("return parser:parse()[1] == tree2")) + end) + + local test_text = [[ +void ui_refresh(void) +{ + int width = INT_MAX, height = INT_MAX; + bool ext_widgets[kUIExtCount]; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ext_widgets[i] = true; + } + + bool inclusive = ui_override(); + for (size_t i = 0; i < ui_count; i++) { + UI *ui = uis[i]; + width = MIN(ui->width, width); + height = MIN(ui->height, height); + foo = BAR(ui->bazaar, bazaar); + for (UIExtension j = 0; (int)j < kUIExtCount; j++) { + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); + } + } +}]] + + it('allows to iterate over nodes children', function() + if pending_c_parser(pending) then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse()[1]:root():child(0) + + res = {} + for node, field in func_node:iter_children() do + table.insert(res, {node:type(), field}) + end + return res + ]]) + + eq({ + {"primitive_type", "type"}, + {"function_declarator", "declarator"}, + {"compound_statement", "body"} + }, res) + end) + + it('allows to get a child by field', function() + if pending_c_parser(pending) then return end + + insert(test_text); + + local res = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + func_node = parser:parse()[1]:root():child(0) + + local res = {} + for _, node in ipairs(func_node:field("type")) do + table.insert(res, {node:type(), node:range()}) + end + return res + ]]) + + eq({{ "primitive_type", 0, 0, 0, 4 }}, res) + + local res_fail = exec_lua([[ + parser = vim.treesitter.get_parser(0, "c") + + return #func_node:field("foo") == 0 + ]]) + + assert(res_fail) + end) + + local query = [[ + ((call_expression function: (identifier) @minfunc (argument_list (identifier) @min_id)) (eq? @minfunc "MIN")) + "for" @keyword + (primitive_type) @type + (field_expression argument: (identifier) @fieldarg) + ]] + + it("supports runtime queries", function() + if pending_c_parser(pending) then return end + + local ret = exec_lua [[ + return require"vim.treesitter.query".get_query("c", "highlights").captures[1] + ]] + + eq('variable', ret) + end) + + it('support query and iter by capture', function() + if pending_c_parser(pending) then return end + + insert(test_text) + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", ...) + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + res = {} + for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + table.insert(res, {cquery.captures[cid], node:type(), node:range()}) + end + return res + ]], query) + + eq({ + { "type", "primitive_type", 8, 2, 8, 6 }, + { "keyword", "for", 9, 2, 9, 5 }, + { "type", "primitive_type", 9, 7, 9, 13 }, + { "minfunc", "identifier", 11, 12, 11, 15 }, + { "fieldarg", "identifier", 11, 16, 11, 18 }, + { "min_id", "identifier", 11, 27, 11, 32 }, + { "minfunc", "identifier", 12, 13, 12, 16 }, + { "fieldarg", "identifier", 12, 17, 12, 19 }, + { "min_id", "identifier", 12, 29, 12, 35 }, + { "fieldarg", "identifier", 13, 14, 13, 16 } + }, res) + end) + + it('support query and iter by match', function() + if pending_c_parser(pending) then return end + + insert(test_text) + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", ...) + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid,node in pairs(match) do + table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) + end + table.insert(res, {pattern, mrepr}) + end + return res + ]], query) + + eq({ + { 3, { { "type", "primitive_type", 8, 2, 8, 6 } } }, + { 2, { { "keyword", "for", 9, 2, 9, 5 } } }, + { 3, { { "type", "primitive_type", 9, 7, 9, 13 } } }, + { 4, { { "fieldarg", "identifier", 11, 16, 11, 18 } } }, + { 1, { { "minfunc", "identifier", 11, 12, 11, 15 }, { "min_id", "identifier", 11, 27, 11, 32 } } }, + { 4, { { "fieldarg", "identifier", 12, 17, 12, 19 } } }, + { 1, { { "minfunc", "identifier", 12, 13, 12, 16 }, { "min_id", "identifier", 12, 29, 12, 35 } } }, + { 4, { { "fieldarg", "identifier", 13, 14, 13, 16 } } } + }, res) + end) + + it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function() + if pending_c_parser(pending) then return end + + insert('char* astring = "Hello World!";') + + local res = exec_lua([[ + cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') + parser = vim.treesitter.get_parser(0, "c") + tree = parser:parse()[1] + res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid,node in pairs(match) do + table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()}) + end + table.insert(res, {pattern, mrepr}) + end + return res + ]]) + + eq({ + { 1, { { "quote", '"', 0, 16, 0, 17 } } }, + { 2, { { "quote", '"', 0, 16, 0, 17 } } }, + { 1, { { "quote", '"', 0, 29, 0, 30 } } }, + { 2, { { "quote", '"', 0, 29, 0, 30 } } }, + }, res) + end) + + it('allows to add predicates', function() + insert([[ + int main(void) { + return 0; + } + ]]) + + local custom_query = "((identifier) @main (#is-main? @main))" + + local res = exec_lua([[ + local query = require"vim.treesitter.query" + + local function is_main(match, pattern, bufnr, predicate) + local node = match[ predicate[2] ] + + return query.get_node_text(node, bufnr) + end + + local parser = vim.treesitter.get_parser(0, "c") + + query.add_predicate("is-main?", is_main) + + local query = query.parse_query("c", ...) + + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do + table.insert(nodes, {node:range()}) + end + + return nodes + ]], custom_query) + + eq({{0, 4, 0, 8}}, res) + + local res_list = exec_lua[[ + local query = require'vim.treesitter.query' + + local list = query.list_predicates() + + table.sort(list) + + return list + ]] + + eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list) + end) + + + it('allows to set simple ranges', function() + if pending_c_parser(pending) then return end + + insert(test_text) + + local res = exec_lua [[ + parser = vim.treesitter.get_parser(0, "c") + return { parser:parse()[1]:root():range() } + ]] + + eq({0, 0, 19, 0}, res) + + -- The following sets the included ranges for the current parser + -- As stated here, this only includes the function (thus the whole buffer, without the last line) + local res2 = exec_lua [[ + local root = parser:parse()[1]:root() + parser:set_included_regions({{root:child(0)}}) + parser:invalidate() + return { parser:parse()[1]:root():range() } + ]] + + eq({0, 0, 18, 1}, res2) + + local range = exec_lua [[ + local res = {} + for _, region in ipairs(parser:included_regions()) do + for _, node in ipairs(region) do + table.insert(res, {node:range()}) + end + end + return res + ]] + + eq(range, { { 0, 0, 18, 1 } }) + + 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() + if pending_c_parser() then return end + + insert(test_text) + + local res = exec_lua [[ + parser = vim.treesitter.get_parser(0, "c") + query = vim.treesitter.parse_query("c", "(declaration) @decl") + + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do + table.insert(nodes, node) + end + + parser:set_included_regions({nodes}) + + local root = parser:parse()[1]:root() + + local res = {} + for i=0,(root:named_child_count() - 1) do + table.insert(res, { root:named_child(i):range() }) + end + return res + ]] + + eq({ + { 2, 2, 2, 40 }, + { 3, 2, 3, 32 }, + { 4, 7, 4, 25 }, + { 8, 2, 8, 33 }, + { 9, 7, 9, 20 }, + { 10, 4, 10, 20 }, + { 14, 9, 14, 27 } }, res) + end) + + it("allows to create string parsers", function() + local ret = exec_lua [[ + local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") + return { parser:parse()[1]:root():range() } + ]] + + eq({ 0, 0, 0, 13 }, ret) + end) + + it("allows to run queries with string parsers", function() + local txt = [[ + int foo = 42; + int bar = 13; + ]] + + local ret = exec_lua([[ + local str = ... + local parser = vim.treesitter.get_string_parser(str, "c") + + local nodes = {} + local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))') + + for _, node in query:iter_captures(parser:parse()[1]:root(), str) 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.parse_query("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_u *)read_string((x), (size_t)(y)) +#define READ_STRING_OK(x, y) (char_u *)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", { + queries = { + c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) + ]]) + + eq("table", exec_lua("return type(parser:children().c)")) + eq(5, exec_lua("return #parser:children().c:trees()")) + eq({ + {0, 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, 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)) + }, get_ranges()) + end) + end) + + describe("when parsing regions combined", function() + it("should inject a language", function() + exec_lua([[ + parser = vim.treesitter.get_parser(0, "c", { + queries = { + c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) + ]]) + + eq("table", exec_lua("return type(parser:children().c)")) + eq(2, exec_lua("return #parser:children().c:trees()")) + eq({ + {0, 0, 7, 0}, -- root tree + {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)) + }, 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", { + queries = { + c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}}) + ]]) + + 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)) + }, get_ranges()) + 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", { + queries = { c = "(preproc_def (preproc_arg) @c)"}}) + + 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.parse_query("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) + end) + + describe("when setting for a capture match", function() + it("should set/get the data correctly", function() + insert([[ + int x = 3; + ]]) + + local result = exec_lua([[ + local result + + query = vim.treesitter.parse_query("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 + result = metadata[pattern].key + end + + return result + ]]) + + eq(result, "value") + end) + end) + end) +end) |