diff options
Diffstat (limited to 'test')
38 files changed, 1947 insertions, 1046 deletions
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 8ed642b43e..fb8ed6a9d7 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -392,6 +392,129 @@ describe('api/buf', function() end) end) + describe('nvim_buf_get_lines, nvim_buf_set_text', function() + local get_lines, set_text = curbufmeths.get_lines, curbufmeths.set_text + + it('works', function() + insert([[ + hello foo! + text + ]]) + + eq({'hello foo!'}, get_lines(0, 1, true)) + + + -- can replace a single word + set_text(0, 6, 0, 9, {'world'}) + eq({'hello world!', 'text'}, get_lines(0, 2, true)) + + -- can insert text + set_text(0, 0, 0, 0, {'well '}) + eq({'well hello world!', 'text'}, get_lines(0, 2, true)) + + -- can delete text + set_text(0, 0, 0, 5, {''}) + eq({'hello world!', 'text'}, get_lines(0, 2, true)) + + -- can replace with multiple lines + set_text(0, 6, 0, 11, {'foo', 'wo', 'more'}) + eq({'hello foo', 'wo', 'more!', 'text'}, get_lines(0, 4, true)) + + -- will join multiple lines if needed + set_text(0, 6, 3, 4, {'bar'}) + eq({'hello bar'}, get_lines(0, 1, true)) + end) + + it('works with undo', function() + insert([[ + hello world! + foo bar + ]]) + + -- setting text + set_text(0, 0, 0, 0, {'well '}) + feed('u') + eq({'hello world!'}, get_lines(0, 1, true)) + + -- deleting text + set_text(0, 0, 0, 6, {''}) + feed('u') + eq({'hello world!'}, get_lines(0, 1, true)) + + -- inserting newlines + set_text(0, 0, 0, 0, {'hello', 'mr '}) + feed('u') + eq({'hello world!'}, get_lines(0, 1, true)) + + -- deleting newlines + set_text(0, 0, 1, 4, {'hello'}) + feed('u') + eq({'hello world!'}, get_lines(0, 1, true)) + end) + + it('updates the cursor position', function() + insert([[ + hello world! + ]]) + + -- position the cursor on `!` + curwin('set_cursor', {1, 11}) + -- replace 'world' with 'foo' + set_text(0, 6, 0, 11, {'foo'}) + eq('hello foo!', curbuf_depr('get_line', 0)) + -- cursor should be moved left by two columns (replacement is shorter by 2 chars) + eq({1, 9}, curwin('get_cursor')) + end) + + it('can handle NULs', function() + set_text(0, 0, 0, 0, {'ab\0cd'}) + eq('ab\0cd', curbuf_depr('get_line', 0)) + end) + + it('adjusts extmarks', function() + local ns = request('nvim_create_namespace', "my-fancy-plugin") + insert([[ + foo bar + baz + ]]) + local id1 = curbufmeths.set_extmark(ns, 0, 1, {}) + local id2 = curbufmeths.set_extmark(ns, 0, 7, {}) + local id3 = curbufmeths.set_extmark(ns, 1, 1, {}) + set_text(0, 4, 0, 7, {"q"}) + + eq({'foo q', 'baz'}, get_lines(0, 2, true)) + -- mark before replacement point is unaffected + eq({0, 1}, curbufmeths.get_extmark_by_id(ns, id1, {})) + -- mark gets shifted back because the replacement was shorter + eq({0, 5}, curbufmeths.get_extmark_by_id(ns, id2, {})) + -- mark on the next line is unaffected + eq({1, 1}, curbufmeths.get_extmark_by_id(ns, id3, {})) + + -- replacing the text spanning two lines will adjust the mark on the next line + set_text(0, 3, 1, 3, {"qux"}) + eq({'fooqux', ''}, get_lines(0, 2, true)) + eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id3, {})) + -- but mark before replacement point is still unaffected + eq({0, 1}, curbufmeths.get_extmark_by_id(ns, id1, {})) + -- and the mark in the middle was shifted to the end of the insertion + eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id2, {})) + + -- marks should be put back into the same place after undoing + set_text(0, 0, 0, 2, {''}) + feed('u') + eq({0, 1}, curbufmeths.get_extmark_by_id(ns, id1, {})) + eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id2, {})) + eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id3, {})) + + -- marks should be shifted over by the correct number of bytes for multibyte + -- chars + set_text(0, 0, 0, 0, {'Ø'}) + eq({0, 3}, curbufmeths.get_extmark_by_id(ns, id1, {})) + eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {})) + eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {})) + end) + end) + describe('nvim_buf_get_offset', function() local get_offset = curbufmeths.get_offset it('works', function() 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/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 29cd38ef0d..9ee2570798 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -3,6 +3,8 @@ local eq, clear, eval, command, nvim, next_msg = helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.nvim, helpers.next_msg local meths = helpers.meths +local exec_lua = helpers.exec_lua +local retry = helpers.retry describe('notify', function() local channel @@ -72,4 +74,18 @@ describe('notify', function() nvim('unsubscribe', 'event1') eq(2, eval('1+1')) -- Still alive? end) + + it('cancels stale events on channel close', function() + if helpers.pending_win32(pending) then return end + local catchan = eval("jobstart(['cat'], {'rpc': v:true})") + eq({id=catchan, stream='job', mode='rpc', client = {}}, exec_lua ([[ + vim.rpcnotify(..., "nvim_call_function", 'chanclose', {..., 'rpc'}) + vim.rpcnotify(..., "nvim_subscribe", "daily_rant") + return vim.api.nvim_get_chan_info(...) + ]], catchan)) + eq(2, eval('1+1')) -- Still alive? + eq({false, 'Invalid channel: '..catchan}, + exec_lua ([[ return {pcall(vim.rpcrequest, ..., 'nvim_eval', '1+1')}]], catchan)) + retry(nil, 3000, function() eq({}, meths.get_chan_info(catchan)) end) -- cat be dead :( + end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index eb5fd7eca7..30128e9c40 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() @@ -1921,4 +1927,79 @@ describe('API', function() eq({}, meths.get_runtime_file("foobarlang/", true)) end) end) + + describe('nvim_get_all_options_info', function() + it('should have key value pairs of option names', function() + local options_info = meths.get_all_options_info() + neq(nil, options_info.listchars) + neq(nil, options_info.tabstop) + + eq(meths.get_option_info'winhighlight', options_info.winhighlight) + end) + end) + + describe('nvim_get_option_info', function() + it('should error for unknown options', function() + eq("no such option: 'bogus'", pcall_err(meths.get_option_info, 'bogus')) + end) + + it('should return the same options for short and long name', function() + eq(meths.get_option_info'winhl', meths.get_option_info'winhighlight') + end) + + it('should have information about window options', function() + eq({ + commalist = false; + default = ""; + flaglist = false; + global_local = false; + last_set_chan = 0; + last_set_linenr = 0; + last_set_sid = 0; + name = "winhighlight"; + scope = "win"; + shortname = "winhl"; + type = "string"; + was_set = false; + }, meths.get_option_info'winhl') + end) + + it('should have information about buffer options', function() + eq({ + commalist = false, + default = "", + flaglist = false, + global_local = false, + last_set_chan = 0, + last_set_linenr = 0, + last_set_sid = 0, + name = "filetype", + scope = "buf", + shortname = "ft", + type = "string", + was_set = false + }, meths.get_option_info'filetype') + end) + + it('should have information about global options', function() + -- precondition: the option was changed from its default + -- in test setup. + eq(false, meths.get_option'showcmd') + + eq({ + commalist = false, + default = true, + flaglist = false, + global_local = false, + last_set_chan = 0, + last_set_linenr = 0, + last_set_sid = -2, + name = "showcmd", + scope = "global", + shortname = "sc", + type = "boolean", + was_set = true + }, meths.get_option_info'showcmd') + end) + end) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index ff0fdbea45..d5f03db03a 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -428,9 +428,9 @@ end) describe('clean', function() clear() - ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) ~= nil) + ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) ~= nil) clear('--clean') - ok(string.match(meths.get_option('runtimepath'), funcs.stdpath('config')) == nil) + ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) == nil) end) describe('user config init', function() diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua index ed110efeb4..7d09a652ba 100644 --- a/test/functional/eval/api_functions_spec.lua +++ b/test/functional/eval/api_functions_spec.lua @@ -47,6 +47,10 @@ describe('eval-API', function() eq('Vim(call):E5555: API call: Invalid buffer id: 17', err) end) + it('cannot change texts if textlocked', function() + command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])") + eq('Vim(call):E5555: API call: E523: Not allowed here', pcall_err(command, "normal! yy")) + end) it("use buffer numbers and windows ids as handles", function() local screen = Screen.new(40, 8) 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/eval/executable_spec.lua b/test/functional/eval/executable_spec.lua index a1cf056907..28aefb72e5 100644 --- a/test/functional/eval/executable_spec.lua +++ b/test/functional/eval/executable_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local eq, clear, call, iswin, write_file, command = helpers.eq, helpers.clear, helpers.call, helpers.iswin, helpers.write_file, helpers.command +local exc_exec = helpers.exc_exec local eval = helpers.eval describe('executable()', function() @@ -10,6 +11,20 @@ describe('executable()', function() it('returns 1 for commands in $PATH', function() local exe = iswin() and 'ping' or 'ls' eq(1, call('executable', exe)) + command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') + eq(1, call('executable', 'null')) + eq(1, call('executable', 'true')) + eq(1, call('executable', 'false')) + end) + + it('fails for invalid values', function() + for _, input in ipairs({'""', 'v:null', 'v:true', 'v:false', '{}', '[]'}) do + eq('Vim(call):E928: String required', exc_exec('call executable('..input..')')) + end + command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') + for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do + eq('Vim(call):E928: String required', exc_exec('call executable('..input..')')) + end end) it('returns 0 for non-existent files', function() diff --git a/test/functional/eval/exepath_spec.lua b/test/functional/eval/exepath_spec.lua index 10a11aeacc..08d2c59af8 100644 --- a/test/functional/eval/exepath_spec.lua +++ b/test/functional/eval/exepath_spec.lua @@ -1,14 +1,40 @@ local helpers = require('test.functional.helpers')(after_each) local eq, clear, call, iswin = helpers.eq, helpers.clear, helpers.call, helpers.iswin +local command = helpers.command +local exc_exec = helpers.exc_exec +local matches = helpers.matches -describe('exepath() (Windows)', function() - if not iswin() then return end -- N/A for Unix. +describe('exepath()', function() + before_each(clear) - it('append extension if omitted', function() - local filename = 'cmd' - local pathext = '.exe' - clear({env={PATHEXT=pathext}}) - eq(call('exepath', filename..pathext), call('exepath', filename)) + it('returns 1 for commands in $PATH', function() + local exe = iswin() and 'ping' or 'ls' + local ext_pat = iswin() and '%.EXE$' or '$' + matches(exe .. ext_pat, call('exepath', exe)) + command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') + ext_pat = iswin() and '%.CMD$' or '$' + matches('null' .. ext_pat, call('exepath', 'null')) + matches('true' .. ext_pat, call('exepath', 'true')) + matches('false' .. ext_pat, call('exepath', 'false')) end) + + it('fails for invalid values', function() + for _, input in ipairs({'""', 'v:null', 'v:true', 'v:false', '{}', '[]'}) do + eq('Vim(call):E928: String required', exc_exec('call exepath('..input..')')) + end + command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') + for _, input in ipairs({'v:null', 'v:true', 'v:false'}) do + eq('Vim(call):E928: String required', exc_exec('call exepath('..input..')')) + end + end) + + if iswin() then + it('append extension if omitted', function() + local filename = 'cmd' + local pathext = '.exe' + clear({env={PATHEXT=pathext}}) + eq(call('exepath', filename..pathext), call('exepath', filename)) + end) + end end) diff --git a/test/functional/eval/function_spec.lua b/test/functional/eval/function_spec.lua index 776e760aaf..ce8850fcc2 100644 --- a/test/functional/eval/function_spec.lua +++ b/test/functional/eval/function_spec.lua @@ -2,7 +2,10 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq +local matches = helpers.matches local exc_exec = helpers.exc_exec +local iswin = helpers.iswin +local eval = helpers.eval describe('Up to MAX_FUNC_ARGS arguments are handled by', function() local max_func_args = 20 -- from eval.h @@ -27,3 +30,8 @@ describe('Up to MAX_FUNC_ARGS arguments are handled by', function() eq('Vim(call):E740: Too many arguments for function rpcnotify', ret) end) end) + +it('windowsversion()', function() + clear() + matches(iswin() and '^%d+%.%d+$' or '^$', eval('windowsversion()')) +end) diff --git a/test/functional/fixtures/bin/false b/test/functional/fixtures/bin/false new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/false diff --git a/test/functional/fixtures/bin/false.cmd b/test/functional/fixtures/bin/false.cmd new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/false.cmd diff --git a/test/functional/fixtures/bin/null b/test/functional/fixtures/bin/null new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/null diff --git a/test/functional/fixtures/bin/null.cmd b/test/functional/fixtures/bin/null.cmd new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/null.cmd diff --git a/test/functional/fixtures/bin/true b/test/functional/fixtures/bin/true new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/true diff --git a/test/functional/fixtures/bin/true.cmd b/test/functional/fixtures/bin/true.cmd new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/functional/fixtures/bin/true.cmd 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/fixtures/lua/syntax_error.lua b/test/functional/fixtures/lua/syntax_error.lua new file mode 100644 index 0000000000..8c0cf026ee --- /dev/null +++ b/test/functional/fixtures/lua/syntax_error.lua @@ -0,0 +1 @@ +- <= the syntax error 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/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 7e4de7c39a..67dc5f5a16 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -25,14 +25,14 @@ local function attach_buffer(evname) local evname = ... local events = {} - function test_register(bufnr, id, changedtick, utf_sizes) + function test_register(bufnr, id, changedtick, utf_sizes, preview) local function callback(...) table.insert(events, {id, ...}) if test_unreg == id then return true end end - local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes} + local opts = {[evname]=callback, on_detach=callback, utf_sizes=utf_sizes, preview=preview} if changedtick then opts.on_changedtick = callback end @@ -288,9 +288,10 @@ describe('lua: nvim_buf_attach on_bytes', function() -- TODO: while we are brewing the real strong coffe, -- verify should check buf_get_offset after every check_events if verify then - meths.buf_get_offset(0, meths.buf_line_count(0)) + local len = meths.buf_get_offset(0, meths.buf_line_count(0)) + eq(len == -1 and 1 or len, string.len(shadowbytes)) end - exec_lua("return test_register(...)", 0, "test1",false, nil) + exec_lua("return test_register(...)", 0, "test1", false, false, true) meths.buf_get_changedtick(0) local verify_name = "test1" @@ -493,6 +494,87 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + + it('inccomand=nosplit and substitute', function() + if verify then pending("Verification can't be done when previewing") end + + local check_events = setup_eventcheck(verify, {"abcde"}) + meths.set_option('inccommand', 'nosplit') + + feed ':%s/bcd/' + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 0, 0 }; + } + + feed 'a' + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 3, 3, 0, 1, 1 }; + } + end) + + it('nvim_buf_set_text insert', function() + local check_events = setup_eventcheck(verify, {"bastext"}) + meths.buf_set_text(0, 0, 3, 0, 3, {"fiol","kontra"}) + check_events { + { "test1", "bytes", 1, 3, 0, 3, 3, 0, 0, 0, 1, 6, 11 }; + } + + meths.buf_set_text(0, 1, 6, 1, 6, {"punkt","syntgitarr","övnings"}) + check_events { + { "test1", "bytes", 1, 4, 1, 6, 14, 0, 0, 0, 2, 8, 25 }; + } + + eq({ "basfiol", "kontrapunkt", "syntgitarr", "övningstext" }, + meths.buf_get_lines(0, 0, -1, true)) + end) + + it('nvim_buf_set_text replace', function() + local check_events = setup_eventcheck(verify, origlines) + + meths.buf_set_text(0, 2, 3, 2, 8, {"very text"}) + check_events { + { "test1", "bytes", 1, 3, 2, 3, 35, 0, 5, 5, 0, 9, 9 }; + } + + meths.buf_set_text(0, 3, 5, 3, 7, {" splitty","line "}) + check_events { + { "test1", "bytes", 1, 4, 3, 5, 57, 0, 2, 2, 1, 5, 14 }; + } + + meths.buf_set_text(0, 0, 8, 1, 2, {"JOINY"}) + check_events { + { "test1", "bytes", 1, 5, 0, 8, 8, 1, 2, 10, 0, 5, 5 }; + } + + meths.buf_set_text(0, 4, 0, 6, 0, {"was 5,6",""}) + check_events { + { "test1", "bytes", 1, 6, 4, 0, 75, 2, 0, 32, 1, 0, 8 }; + } + + eq({ "originalJOINYiginal line 2", "orivery text line 3", "origi splitty", + "line l line 4", "was 5,6", " indented line" }, + meths.buf_get_lines(0, 0, -1, true)) + + end) + + it('nvim_buf_set_text delete', function() + local check_events = setup_eventcheck(verify, origlines) + + -- really {""} but accepts {} as a shorthand + meths.buf_set_text(0, 0, 0, 1, 0, {}) + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 16, 0, 0, 0 }; + } + + -- TODO(bfredl): this works but is not as convenient as set_lines + meths.buf_set_text(0, 4, 15, 5, 17, {""}) + check_events { + { "test1", "bytes", 1, 4, 4, 15, 79, 1, 17, 18, 0, 0, 0 }; + } + eq({ "original line 2", "original line 3", "original line 4", + "original line 5", "original line 6" }, + meths.buf_get_lines(0, 0, -1, true)) + end) end describe('(with verify) handles', function() diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua deleted file mode 100644 index 65dc1b3e03..0000000000 --- a/test/functional/lua/treesitter_spec.lua +++ /dev/null @@ -1,922 +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')")) - - 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 0 - #define VALUE1 1 - #define VALUE2 2 - ]]) - end) - - describe("when parsing regions independently", function() - it("should inject a language", function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { - queries = { - c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) - ]]) - - eq("table", exec_lua("return type(parser:children().c)")) - eq(5, exec_lua("return #parser:children().c:trees()")) - eq({ - {0, 2, 7, 0}, -- root tree - {3, 16, 3, 17}, -- VALUE 0 - {4, 17, 4, 18}, -- VALUE1 1 - {5, 17, 5, 18}, -- VALUE2 2 - {1, 28, 1, 67}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - {2, 31, 2, 70} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) - }, get_ranges()) - end) - end) - - describe("when parsing regions combined", function() - it("should inject a language", function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { - queries = { - c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) - ]]) - - eq("table", exec_lua("return type(parser:children().c)")) - eq(2, exec_lua("return #parser:children().c:trees()")) - eq({ - {0, 2, 7, 0}, -- root tree - {3, 16, 5, 18}, -- VALUE 0 - -- VALUE1 1 - -- VALUE2 2 - {1, 28, 2, 70} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) - -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) - }, get_ranges()) - end) - end) - end) - -end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index e9e1f7ec12..e253db5297 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1453,3 +1453,18 @@ describe('lua stdlib', function() end) end) end) + +describe('lua: require("mod") from packages', function() + before_each(function() + command('set rtp+=test/functional/fixtures') + end) + + it('propagates syntax error', function() + local syntax_error_msg = exec_lua [[ + local _, err = pcall(require, "syntax_error") + return err + ]] + + matches("unexpected symbol", syntax_error_msg) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f01d90bbeb..ec06cb0639 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -262,6 +262,48 @@ 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('should verify capabilities sent', function() local expected_callbacks = { {NIL, "shutdown", {}, 1}; diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 1431054494..2c681eb9d8 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -605,10 +605,10 @@ describe('clipboard (with fake clipboard.vim)', function() {0:~ }| {4: }| :registers | - {1:--- Registers ---} | - "* some{2:^J}star data{2:^J} | - "+ such{2:^J}plus{2:^J}stuff | - ": let g:test_clip['+'] = ['such', 'plus', 'stuff'] | + {1:Type Name Content} | + l "* some{2:^J}star data{2:^J} | + c "+ such{2:^J}plus{2:^J}stuff | + c ": let g:test_clip['+'] = ['such', 'plus', 'stuff'] | {3:Press ENTER or type command to continue}^ | ]], { [0] = {bold = true, foreground = Screen.colors.Blue}, 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/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 3ef41fde1d..8a5dd7ef18 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -1477,7 +1477,7 @@ describe("TUI", function() retry(nil, 3000, function() -- Wait for log file to be flushed. local log = read_file('Xtest_tui_verbose_log') or '' - eq('--- Terminal info --- {{{\n', string.match(log, '--- Terminal.-\n')) + eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) ok(#log > 50) end) end) 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..520574c08a --- /dev/null +++ b/test/functional/treesitter/parser_spec.lua @@ -0,0 +1,576 @@ +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, 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) + + + 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, 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/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 7b05e90459..6ce8b33a63 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -886,6 +886,41 @@ describe("folded lines", function() | ]]) end + command("set foldcolumn=auto") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 2 lines: line 1························}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]], unchanged=true} + else + screen:expect{grid=[[ + {7:+}{5:^+-- 2 lines: line 1························}| + {7: }line 3 | + {7: }line 4 | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged=true} + end -- fdc should not change with a new fold as the maximum is 1 feed("zf3j") @@ -924,6 +959,41 @@ describe("folded lines", function() ]]) end + command("set foldcolumn=auto:1") + if multigrid then screen:expect{grid=[[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7:+}{5:^+-- 4 lines: line 1························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]], unchanged=true} + else + screen:expect{grid=[[ + {7:+}{5:^+-- 4 lines: line 1························}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]], unchanged=true} + end + -- relax the maximum fdc thus fdc should expand to -- accomodate the current number of folds command("set foldcolumn=auto:4") diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index a741136111..7bca741ae3 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -45,13 +45,33 @@ describe('ui/mouse/input', function() it('single left click moves cursor', function() feed('<LeftMouse><2,1>') - screen:expect([[ + screen:expect{grid=[[ testing | mo^use | support and selection | {0:~ }| | + ]], mouse_enabled=true} + feed('<LeftMouse><0,0>') + screen:expect([[ + ^testing | + mouse | + support and selection | + {0:~ }| + | ]]) + end) + + it("in external ui works with unset 'mouse'", function() + meths.set_option('mouse', '') + feed('<LeftMouse><2,1>') + screen:expect{grid=[[ + testing | + mo^use | + support and selection | + {0:~ }| + | + ]], mouse_enabled=false} feed('<LeftMouse><0,0>') screen:expect([[ ^testing | diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 6601c2d68e..719e2ee82a 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1562,6 +1562,77 @@ describe('ext_multigrid', function() {1:~ }| ]]} + command('tabnext') + command('$tabnew') + screen:expect{grid=[[ + ## grid 1 + {16: }{17:2}{16: [No Name] }{17:2}{16: [No Name] }{7: [No Name] }{12: }{16:X}| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + [7:-----------------------------------------------------]| + {11:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 (hidden) + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 (hidden) + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 5 (hidden) + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 6 (hidden) + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 7 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + + command('tabclose') command('tabclose') screen:expect{grid=[[ ## grid 1 diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 8fa9fcc42f..fcf6926433 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -170,7 +170,7 @@ function Screen.new(width, height) ruler = {}, hl_groups = {}, _default_attr_ids = nil, - _mouse_enabled = true, + mouse_enabled = true, _attrs = {}, _hl_info = {[0]={}}, _attr_table = {[0]={{},{}}}, @@ -318,7 +318,7 @@ function Screen:expect(expected, attr_ids, ...) assert(next({...}) == nil, "invalid args to expect()") if type(expected) == "table" then assert(not (attr_ids ~= nil)) - local is_key = {grid=true, attr_ids=true, condition=true, + local is_key = {grid=true, attr_ids=true, condition=true, mouse_enabled=true, any=true, mode=true, unchanged=true, intermediate=true, reset=true, timeout=true, request_cb=true, hl_groups=true} for _, v in ipairs(ext_keys) do @@ -422,12 +422,15 @@ screen:redraw_debug() to show all intermediate screen states. ]]) if expected.mode ~= nil then extstate.mode = self.mode end + if expected.mouse_enabled ~= nil then + extstate.mouse_enabled = self.mouse_enabled + end if expected.win_viewport == nil then extstate.win_viewport = nil end -- Convert assertion errors into invalid screen state descriptions. - for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do + for _, k in ipairs(concat_tables(ext_keys, {'mode', 'mouse_enabled'})) do -- Empty states are considered the default and need not be mentioned. if (not (expected[k] == nil and isempty(extstate[k]))) then local status, res = pcall(eq, expected[k], extstate[k], k) @@ -799,11 +802,11 @@ function Screen:_handle_busy_stop() end function Screen:_handle_mouse_on() - self._mouse_enabled = true + self.mouse_enabled = true end function Screen:_handle_mouse_off() - self._mouse_enabled = false + self.mouse_enabled = false end function Screen:_handle_mode_change(mode, idx) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 222275eb4d..656f613c6a 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -20,6 +20,7 @@ describe('search highlighting', function() [2] = {background = colors.Yellow}, -- Search [3] = {reverse = true}, [4] = {foreground = colors.Red}, -- Message + [6] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey}, -- Folded }) end) @@ -38,6 +39,21 @@ describe('search highlighting', function() ]]) end) + it('is disabled in folded text', function() + insert("some text\nmore text") + feed_command('1,2fold') + feed("gg/text") + screen:expect([[ + {6:+-- 2 lines: some text·················}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /text^ | + ]]) + end) + it('works', function() insert([[ some text @@ -455,6 +471,19 @@ describe('search highlighting', function() {4:search hit BOTTOM, continuing at TOP} | ]]) + -- check hilights work also in folds + feed("zf4j") + command("%foldopen") + screen:expect([[ + very {5:spec^ial}{2: te}{6:xt} | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:search hit BOTTOM, continuing at TOP} | + ]]) + feed_command("call clearmatches()") screen:expect([[ very spec{2:^ial te}xt | diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 0ed62b21b2..d1b8de5e4e 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -76,6 +76,28 @@ describe('Signs', function() ]]) end) + it('allows signs with no text', function() + feed('ia<cr>b<cr><esc>') + command('sign define piet1 text= texthl=Search') + command('sign place 1 line=1 name=piet1 buffer=1') + screen:expect([[ + a | + b | + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('can be called right after :split', function() feed('ia<cr>b<cr>c<cr><esc>gg') -- This used to cause a crash due to :sign using a special redraw @@ -244,6 +266,50 @@ describe('Signs', function() ]]} end) + it('ignores signs with no icon and text when calculting the signcolumn width', function() + feed('ia<cr>b<cr>c<cr><esc>') + command('set number') + command('set signcolumn=auto:2') + command('sign define pietSearch text=>> texthl=Search') + command('sign define pietError text= texthl=Error') + command('sign place 2 line=1 name=pietError buffer=1') + -- no signcolumn with only empty sign + screen:expect([[ + {6: 1 }a | + {6: 2 }b | + {6: 3 }c | + {6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- single column with 1 sign with text and one sign without + command('sign place 1 line=1 name=pietSearch buffer=1') + screen:expect([[ + {1:>>}{6: 1 }a | + {2: }{6: 2 }b | + {2: }{6: 3 }c | + {2: }{6: ^4 } | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('can have 32bit sign IDs', function() command('sign define piet text=>> texthl=Search') command('sign place 100000 line=1 name=piet buffer=1') |