diff options
Diffstat (limited to 'test')
27 files changed, 1250 insertions, 126 deletions
diff --git a/test/README.md b/test/README.md index 37aa54c157..c6e173ead2 100644 --- a/test/README.md +++ b/test/README.md @@ -116,7 +116,7 @@ Filtering Tests ### Filter by name -Another filter method is by setting a pattern of test name to `TEST_FILTER`. +Another filter method is by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. ``` lua it('foo api',function() @@ -131,6 +131,10 @@ To run only test with filter name: TEST_FILTER='foo.*api' make functionaltest +To run all tests except ones matching a filter: + + TEST_FILTER_OUT='foo.*api' make functionaltest + ### Filter by file To run a *specific* unit test: diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 45a01be620..a8f538b951 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -420,7 +420,7 @@ describe('API/extmarks', function() end) it('marks move with open line', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -489,7 +489,7 @@ describe('API/extmarks', function() end) it('marks move with line splits (using enter)', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -500,7 +500,7 @@ describe('API/extmarks', function() end) it('marks at last line move on insert new line', function() - -- open_line in misc1.c + -- open_line in change.c set_extmark(ns, marks[1], 0, 4) feed('0i<cr><esc>') check_undo_redo(ns, marks[1], 0, 4, 1, 4) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index d53208a915..22201e21a2 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -974,6 +974,40 @@ describe('API', function() eq('hello', nvim('get_option_value', 'makeprg', {})) eq('', nvim('get_option_value', 'makeprg', {scope = 'local'})) end) + + it('clears the local value of an option with nil', function() + -- Set global value + nvim('set_option_value', 'shiftwidth', 42, {}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + + -- Set local value + nvim('set_option_value', 'shiftwidth', 8, {scope = 'local'}) + eq(8, nvim('get_option_value', 'shiftwidth', {})) + eq(8, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'global'})) + + -- Clear value without scope + nvim('set_option_value', 'shiftwidth', NIL, {}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + + -- Clear value with explicit scope + nvim('set_option_value', 'shiftwidth', 8, {scope = 'local'}) + nvim('set_option_value', 'shiftwidth', NIL, {scope = 'local'}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + + -- Now try with options with a special "local is unset" value (e.g. 'undolevels') + nvim('set_option_value', 'undolevels', 1000, {}) + eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'})) + nvim('set_option_value', 'undolevels', NIL, {scope = 'local'}) + eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'})) + + nvim('set_option_value', 'autoread', true, {}) + eq(true, nvim('get_option_value', 'autoread', {scope = 'local'})) + nvim('set_option_value', 'autoread', NIL, {scope = 'local'}) + eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) + end) end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() @@ -1120,8 +1154,8 @@ describe('API', function() end) end) - describe('RPC (K_EVENT) #6166', function() - it('does not complete ("interrupt") normal-mode operator-pending', function() + describe('RPC (K_EVENT)', function() + it('does not complete ("interrupt") normal-mode operator-pending #6166', function() helpers.insert([[ FIRST LINE SECOND LINE]]) @@ -1157,7 +1191,7 @@ describe('API', function() ]]) end) - it('does not complete ("interrupt") normal-mode map-pending', function() + it('does not complete ("interrupt") normal-mode map-pending #6166', function() command("nnoremap dd :let g:foo='it worked...'<CR>") helpers.insert([[ FIRST LINE @@ -1173,7 +1207,8 @@ describe('API', function() SECOND LINE]]) eq('it worked...', helpers.eval('g:foo')) end) - it('does not complete ("interrupt") insert-mode map-pending', function() + + it('does not complete ("interrupt") insert-mode map-pending #6166', function() command('inoremap xx foo') command('set timeoutlen=9999') helpers.insert([[ @@ -1188,6 +1223,37 @@ describe('API', function() FIRST LINE SECOND LINfooE]]) end) + + it('does not interrupt Insert mode i_CTRL-O #10035', function() + feed('iHello World<c-o>') + eq({mode='niI', blocking=false}, meths.get_mode()) -- fast event + eq(2, eval('1+1')) -- causes K_EVENT key + eq({mode='niI', blocking=false}, meths.get_mode()) -- still in ctrl-o mode + feed('dd') + eq({mode='i', blocking=false}, meths.get_mode()) -- left ctrl-o mode + expect('') -- executed the command + end) + + it('does not interrupt Select mode v_CTRL-O #15688', function() + feed('iHello World<esc>gh<c-o>') + eq({mode='vs', blocking=false}, meths.get_mode()) -- fast event + eq({mode='vs', blocking=false}, meths.get_mode()) -- again #15288 + eq(2, eval('1+1')) -- causes K_EVENT key + eq({mode='vs', blocking=false}, meths.get_mode()) -- still in ctrl-o mode + feed('^') + eq({mode='s', blocking=false}, meths.get_mode()) -- left ctrl-o mode + feed('h') + eq({mode='i', blocking=false}, meths.get_mode()) -- entered insert mode + expect('h') -- selection is the whole line and is replaced + end) + + it('does not interrupt Insert mode i_0_CTRL-D #13997', function() + command('set timeoutlen=9999') + feed('i<Tab><Tab>a0') + eq(2, eval('1+1')) -- causes K_EVENT key + feed('<C-D>') + expect('a') -- recognized i_0_CTRL-D + end) end) describe('nvim_get_context', function() diff --git a/test/functional/autocmd/cursormoved_spec.lua b/test/functional/autocmd/cursormoved_spec.lua index d0f46e689b..9641d4b096 100644 --- a/test/functional/autocmd/cursormoved_spec.lua +++ b/test/functional/autocmd/cursormoved_spec.lua @@ -31,4 +31,12 @@ describe('CursorMoved', function() eq({'aaa'}, funcs.nvim_buf_get_lines(eval('g:buf'), 0, -1, true)) eq(0, eval('g:cursormoved')) end) + + it("is not triggered by cursor movement prior to first CursorMoved instantiation", function() + source([[ + let g:cursormoved = 0 + autocmd CursorMoved * let g:cursormoved += 1 + ]]) + eq(0, eval('g:cursormoved')) + end) end) diff --git a/test/functional/autocmd/recording_spec.lua b/test/functional/autocmd/recording_spec.lua new file mode 100644 index 0000000000..81152758de --- /dev/null +++ b/test/functional/autocmd/recording_spec.lua @@ -0,0 +1,52 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local source_vim = helpers.source + +describe('RecordingEnter', function() + before_each(clear) + it('works', function() + source_vim [[ + let g:recorded = 0 + autocmd RecordingEnter * let g:recorded += 1 + execute "normal! qqyyq" + ]] + eq(1, eval('g:recorded')) + end) + + it('gives a correct reg_recording()', function() + source_vim [[ + let g:recording = '' + autocmd RecordingEnter * let g:recording = reg_recording() + execute "normal! qqyyq" + ]] + eq('q', eval('g:recording')) + end) +end) + +describe('RecordingLeave', function() + before_each(clear) + it('works', function() + source_vim [[ + let g:recorded = 0 + autocmd RecordingLeave * let g:recorded += 1 + execute "normal! qqyyq" + ]] + eq(1, eval('g:recorded')) + end) + + it('gives the correct reg_recorded()', function() + source_vim [[ + let g:recorded = 'a' + let g:recording = '' + autocmd RecordingLeave * let g:recording = reg_recording() + autocmd RecordingLeave * let g:recorded = reg_recorded() + execute "normal! qqyyq" + ]] + eq('q', eval 'g:recording') + eq('', eval 'g:recorded') + eq('q', eval 'reg_recorded()') + end) +end) diff --git a/test/functional/autocmd/searchwrapped_spec.lua b/test/functional/autocmd/searchwrapped_spec.lua new file mode 100644 index 0000000000..46c2c99b3d --- /dev/null +++ b/test/functional/autocmd/searchwrapped_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed + +describe('autocmd SearchWrapped', function() + before_each(function() + clear() + command('set ignorecase') + command('let g:test = 0') + command('autocmd! SearchWrapped * let g:test += 1') + curbufmeths.set_lines(0, 1, false, { + 'The quick brown fox', + 'jumps over the lazy dog'}) + end) + + it('gets triggered when search wraps the end', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('n') + eq(1, eval('g:test')) + + feed('nn') + eq(2, eval('g:test')) + end) + + it('gets triggered when search wraps in reverse order', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(1, eval('g:test')) + + feed('NN') + eq(2, eval('g:test')) + end) + + it('does not get triggered on failed searches', function() + feed('/blargh<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + end) +end) diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua index 102d8fc723..c0c9256af2 100644 --- a/test/functional/editor/macro_spec.lua +++ b/test/functional/editor/macro_spec.lua @@ -6,6 +6,8 @@ local feed = helpers.feed local clear = helpers.clear local expect = helpers.expect local command = helpers.command +local insert = helpers.insert +local curbufmeths = helpers.curbufmeths describe('macros', function() before_each(clear) @@ -27,4 +29,29 @@ describe('macros', function() expect('llllll') eq(eval('@i'), 'lxxx') end) + + it('can be replayed with Q', function() + insert [[hello +hello +hello]] + feed [[gg]] + + feed [[qqAFOO<esc>q]] + eq({'helloFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false)) + + feed[[Q]] + eq({'helloFOOFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false)) + + feed[[G3Q]] + eq({'helloFOOFOO', 'hello', 'helloFOOFOOFOO'}, curbufmeths.get_lines(0, -1, false)) + end) +end) + +describe('reg_recorded()', function() + before_each(clear) + + it('returns the correct value', function() + feed [[qqyyq]] + eq('q', eval('reg_recorded()')) + end) end) diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index 46ab483036..f03508035d 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -6,7 +6,6 @@ local expect = helpers.expect local command = helpers.command local eq = helpers.eq local eval = helpers.eval -local meths = helpers.meths describe('insert-mode', function() before_each(function() @@ -75,15 +74,5 @@ describe('insert-mode', function() feed('ooo') expect('hello oooworld') end) - - it("doesn't cancel Ctrl-O mode when processing event", function() - feed('iHello World<c-o>') - eq({mode='niI', blocking=false}, meths.get_mode()) -- fast event - eq(2, eval('1+1')) -- causes K_EVENT key - eq({mode='niI', blocking=false}, meths.get_mode()) -- still in ctrl-o mode - feed('dd') - eq({mode='i', blocking=false}, meths.get_mode()) -- left ctrl-o mode - expect('') -- executed the command - end) end) end) diff --git a/test/functional/editor/mode_visual_spec.lua b/test/functional/editor/mode_visual_spec.lua deleted file mode 100644 index 468ae00e01..0000000000 --- a/test/functional/editor/mode_visual_spec.lua +++ /dev/null @@ -1,27 +0,0 @@ --- Visual-mode tests. - -local helpers = require('test.functional.helpers')(after_each) -local clear = helpers.clear -local eq = helpers.eq -local eval = helpers.eval -local expect = helpers.expect -local feed = helpers.feed -local meths = helpers.meths - -describe('visual-mode', function() - before_each(clear) - - it("select-mode Ctrl-O doesn't cancel Ctrl-O mode when processing event #15688", function() - feed('iHello World<esc>gh<c-o>') - eq({mode='vs', blocking=false}, meths.get_mode()) -- fast event - eq({mode='vs', blocking=false}, meths.get_mode()) -- again #15288 - eq(2, eval('1+1')) -- causes K_EVENT key - eq({mode='vs', blocking=false}, meths.get_mode()) -- still in ctrl-o mode - feed('^') - eq({mode='s', blocking=false}, meths.get_mode()) -- left ctrl-o mode - feed('h') - eq({mode='i', blocking=false}, meths.get_mode()) -- entered insert mode - expect('h') -- selection is the whole line and is replaced - end) -end) - diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index 023cdd4ae1..bd14f3bc53 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -83,4 +83,9 @@ describe('set', function() Press ENTER or type command to continue^ | ]]) end) + + it('foldcolumn and signcolumn to empty string is disallowed', function() + matches('E474: Invalid argument: fdc=', exc_exec('set fdc=')) + matches('E474: Invalid argument: scl=', exc_exec('set scl=')) + end) end) diff --git a/test/functional/lua/spell_spec.lua b/test/functional/lua/spell_spec.lua new file mode 100644 index 0000000000..7e831f16a7 --- /dev/null +++ b/test/functional/lua/spell_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local pcall_err = helpers.pcall_err + +describe('vim.spell', function() + before_each(function() + clear() + end) + + describe('.check', function() + local check = function(x, exp) + return eq(exp, exec_lua("return vim.spell.check(...)", x)) + end + + it('can handle nil', function() + eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'check' (expected string)]], + pcall_err(exec_lua, [[vim.spell.check(nil)]])) + end) + + it('can check spellings', function() + check('hello', {}) + + check( + 'helloi', + {{"helloi", "bad", 1}} + ) + + check( + 'hello therei', + {{"therei", "bad", 7}} + ) + + check( + 'hello. there', + {{"there", "caps", 8}} + ) + + check( + 'neovim cna chkc spellins. okay?', + { + {"neovim" , "bad" , 1}, + {"cna" , "bad" , 8}, + {"chkc" , "bad" , 12}, + {"spellins", "bad" , 17}, + {"okay" , "caps", 27} + } + ) + end) + + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 2515b37beb..317f92fcdc 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -131,9 +131,9 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.startswith("123", "2")')) eq(false, funcs.luaeval('vim.startswith("123", "1234")')) - eq("Error executing lua: vim/shared.lua:0: prefix: expected string, got nil", + matches("prefix: expected string, got nil", pcall_err(exec_lua, 'return vim.startswith("123", nil)')) - eq("Error executing lua: vim/shared.lua:0: s: expected string, got nil", + matches("s: expected string, got nil", pcall_err(exec_lua, 'return vim.startswith(nil, "123")')) end) @@ -147,9 +147,9 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.endswith("123", "2")')) eq(false, funcs.luaeval('vim.endswith("123", "1234")')) - eq("Error executing lua: vim/shared.lua:0: suffix: expected string, got nil", + matches("suffix: expected string, got nil", pcall_err(exec_lua, 'return vim.endswith("123", nil)')) - eq("Error executing lua: vim/shared.lua:0: s: expected string, got nil", + matches("s: expected string, got nil", pcall_err(exec_lua, 'return vim.endswith(nil, "123")')) end) @@ -220,9 +220,9 @@ describe('lua stdlib', function() eq({"yy","xx"}, exec_lua("return test_table")) -- Validates args. - eq('Error executing lua: vim.schedule: expected function', + matches('vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule('stringly')")) - eq('Error executing lua: vim.schedule: expected function', + matches('vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule()")) exec_lua([[ @@ -232,7 +232,7 @@ describe('lua stdlib', function() ]]) feed("<cr>") - eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', remove_trace(eval("v:errmsg"))) + matches('big failure\nvery async', remove_trace(eval("v:errmsg"))) local screen = Screen.new(60,5) screen:set_default_attr_ids({ @@ -300,16 +300,16 @@ describe('lua stdlib', function() } for _, t in ipairs(loops) do - eq("Error executing lua: vim/shared.lua:0: Infinite loop detected", pcall_err(split, t[1], t[2])) + matches("Infinite loop detected", pcall_err(split, t[1], t[2])) end -- Validates args. eq(true, pcall(split, 'string', 'string')) - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(split, 1, 'string')) - eq('Error executing lua: vim/shared.lua:0: sep: expected string, got number', + matches('sep: expected string, got number', pcall_err(split, 'string', 1)) - eq('Error executing lua: vim/shared.lua:0: kwargs: expected table, got number', + matches('kwargs: expected table, got number', pcall_err(split, 'string', 'string', 1)) end) @@ -330,7 +330,7 @@ describe('lua stdlib', function() end -- Validates args. - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(trim, 2)) end) @@ -410,7 +410,7 @@ describe('lua stdlib', function() return getmetatable(t2) == mt ]])) - eq('Error executing lua: vim/shared.lua:0: Cannot deepcopy object of type thread', + matches('Cannot deepcopy object of type thread', pcall_err(exec_lua, [[ local thread = coroutine.create(function () return 0 end) local t = {thr = thread} @@ -423,7 +423,7 @@ describe('lua stdlib', function() eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) -- Validates args. - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(exec_lua, [[return vim.pesc(2)]])) end) @@ -548,19 +548,19 @@ describe('lua stdlib', function() return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1 ]])) - eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', + matches('invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_extend() ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + matches('wrong number of arguments %(given 1, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep") ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + matches('wrong number of arguments %(given 2, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep", {}) ]]) @@ -661,19 +661,19 @@ describe('lua stdlib', function() return vim.tbl_deep_extend("force", a, b) ]]), {a = 123 }) - eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', + matches('invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_deep_extend() ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + matches('wrong number of arguments %(given 1, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep") ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + matches('wrong number of arguments %(given 2, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep", {}) ]]) @@ -706,7 +706,7 @@ describe('lua stdlib', function() it('vim.list_extend', function() eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]]) - eq('Error executing lua: vim/shared.lua:0: src: expected table, got nil', + matches('src: expected table, got nil', pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]])) eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]]) eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]]) @@ -730,7 +730,7 @@ describe('lua stdlib', function() assert(vim.deep_equal(a, { A = 1; [1] = 'A'; })) vim.tbl_add_reverse_lookup(a) ]] - matches('^Error executing lua: vim/shared%.lua:0: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"$', + matches('The reverse lookup found an existing value for "[1A]" while processing key "[1A]"$', pcall_err(exec_lua, code)) end) @@ -771,7 +771,7 @@ describe('lua stdlib', function() end) it('vim.fn should error when calling API function', function() - eq('Error executing lua: vim.lua:0: Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', + matches('Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', pcall_err(exec_lua, "vim.fn.nvim_get_current_line()")) end) @@ -907,37 +907,37 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") - eq('Error executing lua: [string "<nvim>"]:0: opt[1]: expected table, got number', + matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: x', + matches('invalid type name: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: 1', + matches('invalid type name: 1', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: nil', + matches('invalid type name: nil', pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}")) -- Validated parameters are required by default. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil', + matches('arg1: expected string, got nil', pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}")) -- Explicitly required. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil', + matches('arg1: expected string, got nil', pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected table, got number', + matches('arg1: expected table, got number', pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got number', + matches('arg2: expected string, got number', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil', + matches('arg2: expected string, got nil', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil', + matches('arg2: expected string, got nil', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected even number, got 3', + matches('arg1: expected even number, got 3', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3', + matches('arg1: expected %?, got 3', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) -- Pass an additional message back. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3. Info: TEST_MSG', + matches('arg1: expected %?, got 3. Info: TEST_MSG', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1, 'TEST_MSG' end}}")) end) @@ -982,7 +982,7 @@ describe('lua stdlib', function() ]] eq(NIL, funcs.luaeval "vim.g.to_delete") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) end) @@ -1009,7 +1009,7 @@ describe('lua stdlib', function() return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} ]]) - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.b[BUF][0].testing')) eq({hello="world"}, funcs.luaeval "vim.b.to_delete") @@ -1046,7 +1046,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.nonexistant") eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) eq({hello="world"}, funcs.luaeval "vim.w.to_delete") @@ -1078,7 +1078,7 @@ describe('lua stdlib', function() eq(123, funcs.luaeval "vim.t[0].other") eq(NIL, funcs.luaeval "vim.t[0].nonexistant") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) eq({hello="world"}, funcs.luaeval "vim.t.to_delete") @@ -1108,7 +1108,7 @@ describe('lua stdlib', function() eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath") eq(false, funcs.luaeval "vim.v['false']") eq(NIL, funcs.luaeval "vim.v.null") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) end) @@ -1128,9 +1128,9 @@ describe('lua stdlib', function() ]] eq('', funcs.luaeval "vim.bo.filetype") eq(true, funcs.luaeval "vim.bo[BUF].modifiable") - matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$", + matches("Invalid option name: 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) - matches("^Error executing lua: .*: Expected lua string$", + matches("Expected lua string$", pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) end) @@ -1147,9 +1147,9 @@ describe('lua stdlib', function() eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[1001].cole") - matches("^Error executing lua: .*: Invalid option name: 'notanopt'$", + matches("Invalid option name: 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) - matches("^Error executing lua: .*: Expected lua string$", + matches("Expected lua string$", pcall_err(exec_lua, 'return vim.wo[0][0].list')) eq(2, funcs.luaeval "vim.wo[1000].cole") exec_lua [[ diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index b567b3e20c..37de5d0ce6 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -156,7 +156,7 @@ describe('health.vim', function() test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() ======================================================================== - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 24]]) + function health#check, line 20]]) eq(expected, received) end) @@ -167,7 +167,7 @@ describe('health.vim', function() broken: health#broken#check ======================================================================== - ERROR: Failed to run healthcheck for "broken" plugin. Exception: - function health#check[24]..health#broken#check, line 1 + function health#check[20]..health#broken#check, line 1 caused an error ]]) end) @@ -186,7 +186,7 @@ describe('health.vim', function() test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() ======================================================================== - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 24]]) + function health#check, line 20]]) eq(expected, received) end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 5dd34e7665..4e3eddb960 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -164,6 +164,201 @@ describe('incremental synchronization', function() } test_edit({"a"}, {"rb"}, expected_text_changes, 'utf-16', '\n') end) + it('deleting a line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 12, + text = '' + } + } + test_edit({"hello world"}, {"dd"}, expected_text_changes, 'utf-16', '\n') + end) + it('deleting an empty line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 2 + } + }, + rangeLength = 1, + text = '' + } + } + test_edit({"hello world", ""}, {"jdd"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding a line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 0, + text = 'hello world\n' + } + } + test_edit({"hello world"}, {"yyp"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding an empty line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 0, + text = '\n' + } + } + test_edit({"hello world"}, {"o"}, expected_text_changes, 'utf-16', '\n') + end) + end) + describe('multi line edit', function() + it('deletion and insertion', function() + local expected_text_changes = { + -- delete "_fsda" from end of line 1 + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 1 + } + }, + rangeLength = 5, + text = '' + }, + -- delete "hello world\n" from line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 0, + line = 3 + } + }, + rangeLength = 12, + text = '' + }, + -- delete "1234" from beginning of line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 4, + line = 2 + } + }, + rangeLength = 4, + text = '' + }, + -- add " asdf" to end of line 1 + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 4, + line = 1 + } + }, + rangeLength = 0, + text = ' asdf' + }, + -- delete " asdf\n" from line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 0, + line = 3 + } + }, + rangeLength = 6, + text = '' + }, + -- undo entire deletion + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 1 + } + }, + rangeLength = 5, + text = "_fdsa\nhello world\n1234 asdf" + }, + -- redo entire deletion + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 3 + } + }, + rangeLength = 27, + text = ' asdf' + }, + } + local original_lines = { + "\\begin{document}", + "test_fdsa", + "hello world", + "1234 asdf", + "\\end{document}" + } + test_edit(original_lines, {"jf_vejjbhhdu<C-R>"}, expected_text_changes, 'utf-16', '\n') + end) end) describe('multi-operation edits', function() @@ -297,6 +492,80 @@ describe('incremental synchronization', function() } test_edit({"🔥"}, {"x"}, expected_text_changes, 'utf-16', '\n') end) + it('replacing a multibyte character with matching prefix', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 1, + line = 1 + } + }, + rangeLength = 1, + text = '⟩' + } + } + -- ⟨ is e29fa8, ⟩ is e29fa9 + local original_lines = { + "\\begin{document}", + "⟨", + "\\end{document}", + } + test_edit(original_lines, {"jr⟩"}, expected_text_changes, 'utf-16', '\n') + end) + it('replacing a multibyte character with matching suffix', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 1, + line = 1 + } + }, + rangeLength = 1, + text = 'ḟ' + } + } + -- ฟ is e0b89f, ḟ is e1b89f + local original_lines = { + "\\begin{document}", + "ฟ", + "\\end{document}", + } + test_edit(original_lines, {"jrḟ"}, expected_text_changes, 'utf-16', '\n') + end) + it('inserting before a multibyte character', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 0, + text = ' ' + } + } + local original_lines = { + "\\begin{document}", + "→", + "\\end{document}", + } + test_edit(original_lines, {"ji "}, expected_text_changes, 'utf-16', '\n') + end) it('deleting a multibyte character from a long line', function() local expected_text_changes = { { diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index b12d4227d5..1af31c38f8 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -301,6 +301,43 @@ describe('LSP', function() } end) + it('should detach buffer in response to nvim_buf_detach', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_finish"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + ]] + eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")) + eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)")) + exec_lua [[ + vim.api.nvim_command(BUFFER.."bwipeout") + ]] + end; + on_init = function(_client) + client = _client + client.notify('finish') + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + exec_lua("return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)") + eq(false, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)")) + client.stop() + end + end; + } + end) + it('client should return settings via workspace/configuration handler', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 7dcca231ee..f25cfa2039 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -6,9 +6,11 @@ local poke_eventloop = helpers.poke_eventloop local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file -local command= helpers.command +local command = helpers.command local exc_exec = helpers.exc_exec local matches = helpers.matches +local exec_lua = helpers.exec_lua +local sleep = helpers.sleep describe(':terminal buffer', function() local screen @@ -328,3 +330,37 @@ describe('No heap-buffer-overflow when', function() assert_alive() end) end) + +describe('on_lines does not emit out-of-bounds line indexes when', function() + before_each(function() + clear() + exec_lua([[ + function _G.register_callback(bufnr) + _G.cb_error = '' + vim.api.nvim_buf_attach(bufnr, false, { + on_lines = function(_, bufnr, _, firstline, _, _) + local status, msg = pcall(vim.api.nvim_buf_get_offset, bufnr, firstline) + if not status then + _G.cb_error = msg + end + end + }) + end + ]]) + end) + + it('creating a terminal buffer #16394', function() + feed_command([[autocmd TermOpen * ++once call v:lua.register_callback(expand("<abuf>"))]]) + feed_command('terminal') + sleep(500) + eq('', exec_lua([[return _G.cb_error]])) + end) + + it('deleting a terminal buffer #16394', function() + feed_command('terminal') + sleep(500) + feed_command('lua _G.register_callback(0)') + feed_command('bdelete!') + eq('', exec_lua([[return _G.cb_error]])) + end) +end) diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 7d37dcccc8..7223f5ba61 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local command = helpers.command -local exc_exec = helpers.exc_exec +local pcall_err = helpers.pcall_err local feed = helpers.feed local sleep = helpers.sleep local poke_eventloop = helpers.poke_eventloop @@ -13,24 +13,22 @@ describe('associated channel is closed and later freed for terminal', function() it('opened by nvim_open_term() and deleted by :bdelete!', function() command([[let id = nvim_open_term(0, {})]]) -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", exc_exec([[bdelete! | call chansend(id, 'test')]])) - -- process free_channel_event - poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[bdelete! | call chansend(id, 'test')]])) + -- channel has been freed after one main loop iteration + eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) end) it('opened by termopen(), exited, and deleted by pressing a key', function() command([[let id = termopen('echo')]]) sleep(500) -- process has exited - eq("Vim(call):Can't send data to closed stream", exc_exec([[call chansend(id, 'test')]])) + eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[call chansend(id, 'test')]])) -- delete terminal feed('i<CR>') - -- process term_delayed_free and free_channel_event + -- need to first process input poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + -- channel has been freed after another main loop iteration + eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) end) -- This indirectly covers #16264 @@ -38,12 +36,10 @@ describe('associated channel is closed and later freed for terminal', function() command([[let id = termopen('echo')]]) sleep(500) -- process has exited - eq("Vim(call):Can't send data to closed stream", exc_exec([[call chansend(id, 'test')]])) + eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[call chansend(id, 'test')]])) -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", exc_exec([[bdelete | call chansend(id, 'test')]])) - -- process term_delayed_free and free_channel_event - poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[bdelete | call chansend(id, 'test')]])) + -- channel has been freed after one main loop iteration + eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) end) end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index b932c58430..11bdc73a47 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -12,6 +12,8 @@ local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim local feed_data = thelpers.feed_data local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua +local assert_alive = helpers.assert_alive describe(':terminal scrollback', function() local screen @@ -527,3 +529,71 @@ describe("'scrollback' option", function() end) end) + +describe("pending scrollback line handling", function() + local screen + + before_each(function() + clear() + screen = Screen.new(30, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Brown}, + [2] = {reverse = true}, + [3] = {bold = true}, + } + end) + + it("does not crash after setting 'number' #14891", function() + exec_lua [[ + local a = vim.api + local buf = a.nvim_create_buf(true, true) + local chan = a.nvim_open_term(buf, {}) + a.nvim_win_set_option(0, "number", true) + a.nvim_chan_send(chan, ("a\n"):rep(11) .. "a") + a.nvim_win_set_buf(0, buf) + ]] + screen:expect [[ + {1: 1 }^a | + {1: 2 } a | + {1: 3 } a | + {1: 4 } a | + {1: 5 } a | + {1: 6 } a | + | + ]] + feed('G') + screen:expect [[ + {1: 7 } a | + {1: 8 } a | + {1: 9 } a | + {1: 10 } a | + {1: 11 } a | + {1: 12 } ^a | + | + ]] + assert_alive() + end) + + it("does not crash after nvim_buf_call #14891", function() + exec_lua [[ + local a = vim.api + local bufnr = a.nvim_create_buf(false, true) + a.nvim_buf_call(bufnr, function() + vim.fn.termopen({"echo", ("hi\n"):rep(11)}) + end) + a.nvim_win_set_buf(0, bufnr) + vim.cmd("startinsert") + ]] + screen:expect [[ + hi | + hi | + hi | + | + | + [Process exited 0]{2: } | + {3:-- TERMINAL --} | + ]] + assert_alive() + end) +end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 6b9586b4de..bf57b135cb 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -20,6 +20,7 @@ local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file +local exec_lua = helpers.exec_lua if helpers.pending_win32(pending) then return end @@ -580,21 +581,34 @@ describe('TUI', function() end) it("paste: 'nomodifiable' buffer", function() + local has_luajit = exec_lua('return jit ~= nil') child_session:request('nvim_command', 'set nomodifiable') child_session:request('nvim_exec_lua', [[ -- Stack traces for this test are non-deterministic, so disable them _G.debug.traceback = function(msg) return msg end ]], {}) feed_data('\027[200~fail 1\nfail 2\n\027[201~') - screen:expect{grid=[[ - | - {4:~ }| - {5: }| - {8:paste: Error executing lua: vim.lua:243: Vim:E21: }| - {8:Cannot make changes, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | - {3:-- TERMINAL --} | - ]]} + if has_luajit then + screen:expect{grid=[[ + | + {4:~ }| + {5: }| + {8:paste: Error executing lua: vim.lua:0: Vim:E21: Ca}| + {8:nnot make changes, 'modifiable' is off} | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} + else + screen:expect{grid=[[ + | + {4:~ }| + {5: }| + {8:paste: Error executing lua: Vim:E21: Cannot make c}| + {8:hanges, 'modifiable' is off} | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} + end feed_data('\n') -- <Enter> child_session:request('nvim_command', 'set modifiable') feed_data('\027[200~success 1\nsuccess 2\n\027[201~') @@ -677,8 +691,8 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 en{1:d} | - {5:[No Name] [+] 3000,13 Bot}| + item 3000 en{1:d}d | + {5:[No Name] [+] 5999,13 Bot}| | {3:-- TERMINAL --} | ]]) @@ -765,6 +779,44 @@ describe('TUI', function() ]]) end) + it('paste: streamed paste with isolated "stop paste" code', function() + child_session:request('nvim_exec_lua', [[ + _G.paste_phases = {} + vim.paste = (function(overridden) + return function(lines, phase) + table.insert(_G.paste_phases, phase) + overridden(lines, phase) + end + end)(vim.paste) + ]], {}) + feed_data('i') + feed_data('\027[200~pasted') -- phase 1 + screen:expect([[ + pasted{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(' from terminal') -- phase 2 + screen:expect([[ + pasted from terminal{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + -- Send isolated "stop paste" sequence. + feed_data('\027[201~') -- phase 3 + screen:expect_unchanged() + local _, rv = child_session:request('nvim_exec_lua', [[return _G.paste_phases]], {}) + eq({1, 2, 3}, rv) + end) + it('allows termguicolors to be set at runtime', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 1138cfbf4c..911fa017ab 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -227,6 +227,43 @@ void ui_refresh(void) }, res) end) + it('supports getting text of multiline node', function() + if pending_c_parser(pending) then return end + insert(test_text) + local res = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local tree = parser:parse()[1] + return vim.treesitter.get_node_text(tree:root(), 0) + ]]) + eq(test_text, res) + + local res2 = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local root = parser:parse()[1]:root() + return vim.treesitter.get_node_text(root:child(0):child(0), 0) + ]]) + eq('void', res2) + end) + + it('support getting text where start of node is past EOF', function() + local text = [[ +def run + a = <<~E +end]] + insert(text) + local result = exec_lua([[ + local fake_node = {} + function fake_node:start() + return 3, 0, 23 + end + function fake_node:end_() + return 3, 0, 23 + end + return vim.treesitter.get_node_text(fake_node, 0) == nil + ]]) + eq(true, result) + end) + it('can match special regex characters like \\ * + ( with `vim-match?`', function() insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 9c035c728b..03cd4bfd06 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -212,10 +212,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 58 + m.hl_id = 60 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 59 end + if m.id_lm then m.id_lm = 61 end end -- Assert the new expectation. diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 249686234c..3e0e15c2b7 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -41,6 +41,7 @@ describe("folded lines", function() [9] = {bold = true, foreground = Screen.colors.Brown}, [10] = {background = Screen.colors.LightGrey, underline = true}, [11] = {bold=true}, + [12] = {background = Screen.colors.Grey90}, }) end) @@ -84,6 +85,117 @@ describe("folded lines", function() end end) + it("highlights with CursorLineFold when 'cursorline' is set", function() + command("set cursorline foldcolumn=2 foldmethod=marker") + command("hi link CursorLineFold Search") + insert(content1) + feed("zf3j") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + | + ]]) + end + feed("k") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + command("set cursorlineopt=line") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + end) + it("highlighting with relative line numbers", function() command("set relativenumber cursorline cursorlineopt=number foldmethod=marker") feed_command("set foldcolumn=2") diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index aeba049557..4fc5c389e5 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -2213,4 +2213,134 @@ describe('builtin popupmenu', function() feed('<c-y>') assert_alive() end) + + it('truncates double-width character correctly when there is no scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=menuone,noselect') + feed('i' .. string.rep(' ', 13)) + funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'}) + screen:expect([[ + ^ | + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) + + it('truncates double-width character correctly when there is scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=noselect') + command('set pumheight=4') + feed('i' .. string.rep(' ', 12)) + local items = {} + for _ = 1, 8 do + table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1}) + end + funcs.complete(13, items) + screen:expect([[ + ^ | + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) +end) + +describe('builtin popupmenu with ui/ext_multigrid', function() + local screen + before_each(function() + clear() + screen = Screen.new(32, 20) + screen:attach({ext_multigrid=true}) + screen:set_default_attr_ids({ + -- popup selected item / scrollbar track + ['s'] = {background = Screen.colors.WebGray}, + -- popup non-selected item + ['n'] = {background = Screen.colors.LightMagenta}, + -- popup scrollbar knob + ['c'] = {background = Screen.colors.Grey0}, + [1] = {bold = true, foreground = Screen.colors.Blue}, + [2] = {bold = true}, + [3] = {reverse = true}, + [4] = {bold = true, reverse = true}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen}, + [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + }) + end) + + it('truncates double-width character correctly when there is no scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=menuone,noselect') + feed('i' .. string.rep(' ', 13)) + funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'}) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 哦哦哦哦哦哦哦哦哦>}| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 12, false, 100}}}) + end) + + it('truncates double-width character correctly when there is scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=noselect') + command('set pumheight=4') + feed('i' .. string.rep(' ', 12)) + local items = {} + for _ = 1, 8 do + table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1}) + end + funcs.complete(13, items) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {n: 哦哦哦哦哦哦哦哦哦>}{s: }| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}}) + end) end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 741b93043d..dcd31cfdb7 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -157,6 +157,99 @@ describe('Signs', function() ]]) end) + it('higlights the cursorline sign with culhl', function() + feed('ia<cr>b<cr>c<esc>') + command('sign define piet text=>> texthl=Search culhl=ErrorMsg') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=2 name=piet buffer=1') + command('sign place 3 line=3 name=piet buffer=1') + command('set cursorline') + screen:expect([[ + {1:>>}a | + {1:>>}b | + {8:>>}{3:^c }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('k') + screen:expect([[ + {1:>>}a | + {8:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set nocursorline') + screen:expect([[ + {1:>>}a | + {1:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorline cursorlineopt=line') + screen:expect([[ + {1:>>}a | + {1:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorlineopt=number') + screen:expect([[ + {1:>>}a | + {8:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('multiple signs #9295', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') diff --git a/test/functional/vimscript/screenpos_spec.lua b/test/functional/vimscript/screenpos_spec.lua new file mode 100644 index 0000000000..75e5c02298 --- /dev/null +++ b/test/functional/vimscript/screenpos_spec.lua @@ -0,0 +1,51 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eq, meths = helpers.clear, helpers.eq, helpers.meths +local command, funcs = helpers.command, helpers.funcs + +before_each(clear) + +describe('screenpos() function', function() + it('works in floating window with border', function() + local bufnr = meths.create_buf(false, true) + local opts = { + relative='editor', + height=8, + width=12, + row=6, + col=8, + anchor='NW', + style='minimal', + border='none', + focusable=1 + } + local float = meths.open_win(bufnr, false, opts) + command('redraw') + local pos = funcs.screenpos(bufnr, 1, 1) + eq(7, pos.row) + eq(9, pos.col) + + -- only left border + opts.border = {'', '', '', '', '', '', '', '|'} + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(7, pos.row) + eq(10, pos.col) + + -- only top border + opts.border = {'', '_', '', '', '', '', '', ''} + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(8, pos.row) + eq(9, pos.col) + + -- both left and top border + opts.border = 'single' + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(8, pos.row) + eq(10, pos.col) + end) +end) diff --git a/test/helpers.lua b/test/helpers.lua index 09b113c01d..87431e4342 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -741,9 +741,20 @@ function module.read_file_list(filename, start) if not file then return nil end + + -- There is no need to read more than the last 2MB of the log file, so seek + -- to that. + local file_size = file:seek("end") + local offset = file_size - 2000000 + if offset < 0 then + offset = 0 + end + file:seek("set", offset) + local lines = {} local i = 1 - for line in file:lines() do + local line = file:read("*l") + while line ~= nil do if i >= start then table.insert(lines, line) if #lines > maxlines then @@ -751,6 +762,7 @@ function module.read_file_list(filename, start) end end i = i + 1 + line = file:read("*l") end file:close() return lines diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua index a73fc8e47e..29a2b78491 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -4,7 +4,6 @@ local cimported = helpers.cimport( './src/nvim/os/shell.h', './src/nvim/option_defs.h', './src/nvim/main.h', - './src/nvim/misc1.h', './src/nvim/memory.h' ) local ffi, eq = helpers.ffi, helpers.eq |