diff options
Diffstat (limited to 'test')
28 files changed, 1064 insertions, 113 deletions
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index d26cb0dbb3..9d6cfb99ab 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -39,6 +39,41 @@ describe('api/buf', function() eq(1, curbuf_depr('line_count')) end) + it('cursor position is maintained after lines are inserted #9961', function() + -- replace the buffer contents with these three lines. + request('nvim_buf_set_lines', 0, 0, -1, 1, {"line1", "line2", "line3", "line4"}) + -- Set the current cursor to {3, 2}. + curwin('set_cursor', {3, 2}) + + -- add 2 lines and delete 1 line above the current cursor position. + request('nvim_buf_set_lines', 0, 1, 2, 1, {"line5", "line6"}) + -- check the current set of lines in the buffer. + eq({"line1", "line5", "line6", "line3", "line4"}, buffer('get_lines', 0, 0, -1, 1)) + -- cursor should be moved below by 1 line. + eq({4, 2}, curwin('get_cursor')) + + -- add a line after the current cursor position. + request('nvim_buf_set_lines', 0, 5, 5, 1, {"line7"}) + -- check the current set of lines in the buffer. + eq({"line1", "line5", "line6", "line3", "line4", "line7"}, buffer('get_lines', 0, 0, -1, 1)) + -- cursor position is unchanged. + eq({4, 2}, curwin('get_cursor')) + + -- overwrite current cursor line. + request('nvim_buf_set_lines', 0, 3, 5, 1, {"line8", "line9"}) + -- check the current set of lines in the buffer. + eq({"line1", "line5", "line6", "line8", "line9", "line7"}, buffer('get_lines', 0, 0, -1, 1)) + -- cursor position is unchanged. + eq({4, 2}, curwin('get_cursor')) + + -- delete current cursor line. + request('nvim_buf_set_lines', 0, 3, 5, 1, {}) + -- check the current set of lines in the buffer. + eq({"line1", "line5", "line6", "line7"}, buffer('get_lines', 0, 0, -1, 1)) + -- cursor position is unchanged. + eq({4, 2}, curwin('get_cursor')) + end) + it('line_count has defined behaviour for unloaded buffers', function() -- we'll need to know our bufnr for when it gets unloaded local bufnr = curbuf('get_number') diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index f52372bee3..c150787425 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -1,15 +1,19 @@ local helpers = require('test.functional.helpers')(after_each) local global_helpers = require('test.helpers') +local bufmeths = helpers.bufmeths local clear = helpers.clear local command = helpers.command local curbufmeths = helpers.curbufmeths -local eq = helpers.eq +local eq, neq = helpers.eq, helpers.neq +local expect_err = helpers.expect_err +local feed = helpers.feed local funcs = helpers.funcs local meths = helpers.meths local source = helpers.source local shallowcopy = global_helpers.shallowcopy +local sleep = global_helpers.sleep describe('nvim_get_keymap', function() before_each(clear) @@ -308,3 +312,502 @@ describe('nvim_get_keymap', function() eq({space_table}, meths.get_keymap('n')) end) end) + +describe('nvim_set_keymap, nvim_del_keymap', function() + before_each(clear) + + -- `generate_expected` is truthy: for generating an expected output for + -- maparg(), which does not accept "!" (though it returns "!" in its output + -- if getting a mapping set with |:map!|). + local function normalize_mapmode(mode, generate_expected) + if not generate_expected and mode == '!' then + -- Cannot retrieve mapmode-ic mappings with "!", but can with "i" or "c". + mode = 'i' + elseif mode == '' then + mode = generate_expected and ' ' or mode + end + return mode + end + + -- Generate a mapargs dict, for comparison against the mapping that was + -- actually set + local function generate_mapargs(mode, lhs, rhs, opts) + if not opts then + opts = {} + end + + local to_return = {} + to_return.mode = normalize_mapmode(mode, true) + to_return.noremap = not opts.noremap and 0 or 1 + to_return.lhs = lhs + to_return.rhs = rhs + to_return.silent = not opts.silent and 0 or 1 + to_return.nowait = not opts.nowait and 0 or 1 + to_return.expr = not opts.expr and 0 or 1 + to_return.sid = not opts.sid and 0 or opts.sid + to_return.buffer = not opts.buffer and 0 or opts.buffer + + -- mode 't' doesn't print when calling maparg + if mode == 't' then + to_return.mode = '' + end + + return to_return + end + + -- Gets a maparg() dict from Nvim, if one exists. + local function get_mapargs(mode, lhs) + return funcs.maparg(lhs, normalize_mapmode(mode), false, true) + end + + it('error on empty LHS', function() + -- escape parentheses in lua string, else comparison fails erroneously + expect_err('Invalid %(empty%) LHS', + meths.set_keymap, '', '', 'rhs', {}) + expect_err('Invalid %(empty%) LHS', + meths.set_keymap, '', '', '', {}) + + expect_err('Invalid %(empty%) LHS', meths.del_keymap, '', '') + end) + + it('error if LHS longer than MAXMAPLEN', function() + -- assume MAXMAPLEN of 50 chars, as declared in vim.h + local MAXMAPLEN = 50 + local lhs = '' + for i=1,MAXMAPLEN do + lhs = lhs..(i % 10) + end + + -- exactly 50 chars should be fine + meths.set_keymap('', lhs, 'rhs', {}) + + -- del_keymap should unmap successfully + meths.del_keymap('', lhs) + eq({}, get_mapargs('', lhs)) + + -- 51 chars should produce an error + lhs = lhs..'1' + expect_err('LHS exceeds maximum map length: '..lhs, + meths.set_keymap, '', lhs, 'rhs', {}) + expect_err('LHS exceeds maximum map length: '..lhs, + meths.del_keymap, '', lhs) + end) + + it('does not throw errors when rhs is longer than MAXMAPLEN', function() + local MAXMAPLEN = 50 + local rhs = '' + for i=1,MAXMAPLEN do + rhs = rhs..(i % 10) + end + rhs = rhs..'1' + meths.set_keymap('', 'lhs', rhs, {}) + eq(generate_mapargs('', 'lhs', rhs), + get_mapargs('', 'lhs')) + end) + + it('throws errors when given too-long mode shortnames', function() + expect_err('Shortname is too long: map', + meths.set_keymap, 'map', 'lhs', 'rhs', {}) + + expect_err('Shortname is too long: vmap', + meths.set_keymap, 'vmap', 'lhs', 'rhs', {}) + + expect_err('Shortname is too long: xnoremap', + meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}) + + expect_err('Shortname is too long: map', meths.del_keymap, 'map', 'lhs') + expect_err('Shortname is too long: vmap', meths.del_keymap, 'vmap', 'lhs') + expect_err('Shortname is too long: xnoremap', meths.del_keymap, 'xnoremap', 'lhs') + end) + + it('error on invalid mode shortname', function() + expect_err('Invalid mode shortname: " "', + meths.set_keymap, ' ', 'lhs', 'rhs', {}) + expect_err('Invalid mode shortname: "m"', + meths.set_keymap, 'm', 'lhs', 'rhs', {}) + expect_err('Invalid mode shortname: "?"', + meths.set_keymap, '?', 'lhs', 'rhs', {}) + expect_err('Invalid mode shortname: "y"', + meths.set_keymap, 'y', 'lhs', 'rhs', {}) + expect_err('Invalid mode shortname: "p"', + meths.set_keymap, 'p', 'lhs', 'rhs', {}) + expect_err('Invalid mode shortname: "?"', meths.del_keymap, '?', 'lhs') + expect_err('Invalid mode shortname: "y"', meths.del_keymap, 'y', 'lhs') + expect_err('Invalid mode shortname: "p"', meths.del_keymap, 'p', 'lhs') + end) + + it('error on invalid optnames', function() + expect_err('Invalid key: silentt', + meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true}) + expect_err('Invalid key: sidd', + meths.set_keymap, 'n', 'lhs', 'rhs', {sidd = false}) + expect_err('Invalid key: nowaiT', + meths.set_keymap, 'n', 'lhs', 'rhs', {nowaiT = false}) + end) + + it('error on <buffer> option key', function() + expect_err('Invalid key: buffer', + meths.set_keymap, 'n', 'lhs', 'rhs', {buffer = true}) + end) + + local optnames = {'nowait', 'silent', 'script', 'expr', 'unique'} + for _, opt in ipairs(optnames) do + -- note: need '%' to escape hyphens, which have special meaning in lua + it('throws an error when given non-boolean value for '..opt, function() + local opts = {} + opts[opt] = 2 + expect_err('Gave non%-boolean value for an opt: '..opt, + meths.set_keymap, 'n', 'lhs', 'rhs', opts) + end) + end + + -- Perform tests of basic functionality + it('sets ordinary mappings', function() + meths.set_keymap('n', 'lhs', 'rhs', {}) + eq(generate_mapargs('n', 'lhs', 'rhs'), get_mapargs('n', 'lhs')) + + meths.set_keymap('v', 'lhs', 'rhs', {}) + eq(generate_mapargs('v', 'lhs', 'rhs'), get_mapargs('v', 'lhs')) + end) + + it('does not throw when LHS or RHS have leading/trailing whitespace', function() + meths.set_keymap('n', ' lhs', 'rhs', {}) + eq(generate_mapargs('n', '<Space><Space><Space>lhs', 'rhs'), + get_mapargs('n', ' lhs')) + + meths.set_keymap('n', 'lhs ', 'rhs', {}) + eq(generate_mapargs('n', 'lhs<Space><Space><Space><Space>', 'rhs'), + get_mapargs('n', 'lhs ')) + + meths.set_keymap('v', ' lhs ', '\trhs\t\f', {}) + eq(generate_mapargs('v', '<Space>lhs<Space><Space>', '\trhs\t\f'), + get_mapargs('v', ' lhs ')) + end) + + it('can set noremap mappings', function() + meths.set_keymap('x', 'lhs', 'rhs', {noremap = true}) + eq(generate_mapargs('x', 'lhs', 'rhs', {noremap = true}), + get_mapargs('x', 'lhs')) + + meths.set_keymap('t', 'lhs', 'rhs', {noremap = true}) + eq(generate_mapargs('t', 'lhs', 'rhs', {noremap = true}), + get_mapargs('t', 'lhs')) + end) + + it('can unmap mappings', function() + meths.set_keymap('v', 'lhs', 'rhs', {}) + meths.del_keymap('v', 'lhs') + eq({}, get_mapargs('v', 'lhs')) + + meths.set_keymap('t', 'lhs', 'rhs', {noremap = true}) + meths.del_keymap('t', 'lhs') + eq({}, get_mapargs('t', 'lhs')) + end) + + -- Test some edge cases + it('"!" and empty string are synonyms for mapmode-nvo', function() + local nvo_shortnames = {'', '!'} + for _, name in ipairs(nvo_shortnames) do + meths.set_keymap(name, 'lhs', 'rhs', {}) + meths.del_keymap(name, 'lhs') + eq({}, get_mapargs(name, 'lhs')) + end + end) + + local special_chars = {'<C-U>', '<S-Left>', '<F12><F2><Tab>', '<Space><Tab>'} + for _, lhs in ipairs(special_chars) do + for _, rhs in ipairs(special_chars) do + local mapmode = '!' + it('can set mappings with special characters, lhs: '..lhs..', rhs: '..rhs, + function() + meths.set_keymap(mapmode, lhs, rhs, {}) + eq(generate_mapargs(mapmode, lhs, rhs), get_mapargs(mapmode, lhs)) + end) + end + end + + it('can set mappings containing literal keycodes', function() + meths.set_keymap('n', '\n\r\n', 'rhs', {}) + local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs') + eq(expected, get_mapargs('n', '<C-j><CR><C-j>')) + end) + + it('can set mappings whose RHS is a <Nop>', function() + meths.set_keymap('i', 'lhs', '<Nop>', {}) + command('normal ilhs') + eq({''}, curbufmeths.get_lines(0, -1, 0)) -- imap to <Nop> does nothing + eq(generate_mapargs('i', 'lhs', '<Nop>', {}), + get_mapargs('i', 'lhs')) + + -- also test for case insensitivity + meths.set_keymap('i', 'lhs', '<nOp>', {}) + command('normal ilhs') + eq({''}, curbufmeths.get_lines(0, -1, 0)) + -- note: RHS in returned mapargs() dict reflects the original RHS + -- provided by the user + eq(generate_mapargs('i', 'lhs', '<nOp>', {}), + get_mapargs('i', 'lhs')) + + meths.set_keymap('i', 'lhs', '<NOP>', {}) + command('normal ilhs') + eq({''}, curbufmeths.get_lines(0, -1, 0)) + eq(generate_mapargs('i', 'lhs', '<NOP>', {}), + get_mapargs('i', 'lhs')) + end) + + it('treats an empty RHS in a mapping like a <Nop>', function() + meths.set_keymap('i', 'lhs', '', {}) + command('normal ilhs') + eq({''}, curbufmeths.get_lines(0, -1, 0)) + eq(generate_mapargs('i', 'lhs', '', {}), + get_mapargs('i', 'lhs')) + end) + + it('can set and unset <M-">', function() + -- Taken from the legacy test: test_mapping.vim. Exposes a bug in which + -- replace_termcodes changes the length of the mapping's LHS, but + -- do_map continues to use the *old* length of LHS. + meths.set_keymap('i', '<M-">', 'foo', {}) + meths.del_keymap('i', '<M-">') + eq({}, get_mapargs('i', '<M-">')) + end) + + it('interprets control sequences in expr-quotes correctly when called ' + ..'inside vim', function() + command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]]) + eq(generate_mapargs('i', '<Space>', '\t', {}), + get_mapargs('i', '<Space>')) + feed('i ') + eq({'\t'}, curbufmeths.get_lines(0, -1, 0)) + end) + + it('throws appropriate error messages when setting <unique> maps', function() + meths.set_keymap('l', 'lhs', 'rhs', {}) + expect_err('E227: mapping already exists for lhs', + meths.set_keymap, 'l', 'lhs', 'rhs', {unique = true}) + -- different mapmode, no error should be thrown + meths.set_keymap('t', 'lhs', 'rhs', {unique = true}) + end) + + it('can set <expr> mappings whose RHS change dynamically', function() + meths.command_output([[ + function! FlipFlop() abort + if !exists('g:flip') | let g:flip = 0 | endif + let g:flip = !g:flip + return g:flip + endfunction + ]]) + eq(1, meths.call_function('FlipFlop', {})) + eq(0, meths.call_function('FlipFlop', {})) + eq(1, meths.call_function('FlipFlop', {})) + eq(0, meths.call_function('FlipFlop', {})) + + meths.set_keymap('i', 'lhs', 'FlipFlop()', {expr = true}) + command('normal ilhs') + eq({'1'}, curbufmeths.get_lines(0, -1, 0)) + + command('normal! ggVGd') + + command('normal ilhs') + eq({'0'}, curbufmeths.get_lines(0, -1, 0)) + end) + + it('can set mappings that do trigger other mappings', function() + meths.set_keymap('i', 'mhs', 'rhs', {}) + meths.set_keymap('i', 'lhs', 'mhs', {}) + + command('normal imhs') + eq({'rhs'}, curbufmeths.get_lines(0, -1, 0)) + + command('normal! ggVGd') + + command('normal ilhs') + eq({'rhs'}, curbufmeths.get_lines(0, -1, 0)) + end) + + it("can set noremap mappings that don't trigger other mappings", function() + meths.set_keymap('i', 'mhs', 'rhs', {}) + meths.set_keymap('i', 'lhs', 'mhs', {noremap = true}) + + command('normal imhs') + eq({'rhs'}, curbufmeths.get_lines(0, -1, 0)) + + command('normal! ggVGd') + + command('normal ilhs') -- shouldn't trigger mhs-to-rhs mapping + eq({'mhs'}, curbufmeths.get_lines(0, -1, 0)) + end) + + it("can set nowait mappings that fire without waiting", function() + meths.set_keymap('i', '123456', 'longer', {}) + meths.set_keymap('i', '123', 'shorter', {nowait = true}) + + -- feed keys one at a time; if all keys arrive atomically, the longer + -- mapping will trigger + local keys = 'i123456' + for c in string.gmatch(keys, '.') do + feed(c) + sleep(5) + end + eq({'shorter456'}, curbufmeths.get_lines(0, -1, 0)) + end) + + -- Perform exhaustive tests of basic functionality + local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', ''} + for _, mapmode in ipairs(mapmodes) do + it('can set/unset normal mappings in mapmode '..mapmode, function() + meths.set_keymap(mapmode, 'lhs', 'rhs', {}) + eq(generate_mapargs(mapmode, 'lhs', 'rhs'), + get_mapargs(mapmode, 'lhs')) + + -- some mapmodes (like 'o') will prevent other mapmodes (like '!') from + -- taking effect, so unmap after each mapping + meths.del_keymap(mapmode, 'lhs') + eq({}, get_mapargs(mapmode, 'lhs')) + end) + end + + for _, mapmode in ipairs(mapmodes) do + it('can set/unset noremap mappings using mapmode '..mapmode, function() + meths.set_keymap(mapmode, 'lhs', 'rhs', {noremap = true}) + eq(generate_mapargs(mapmode, 'lhs', 'rhs', {noremap = true}), + get_mapargs(mapmode, 'lhs')) + + meths.del_keymap(mapmode, 'lhs') + eq({}, get_mapargs(mapmode, 'lhs')) + end) + end + + -- Test map-arguments, using optnames from above + -- remove some map arguments that are harder to test, or were already tested + optnames = {'nowait', 'silent', 'expr', 'noremap'} + for _, mapmode in ipairs(mapmodes) do + local printable_mode = normalize_mapmode(mapmode) + + -- Test with single mappings + for _, maparg in ipairs(optnames) do + it('can set/unset '..printable_mode..'-mappings with maparg: '..maparg, + function() + meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = true}) + eq(generate_mapargs(mapmode, 'lhs', 'rhs', {[maparg] = true}), + get_mapargs(mapmode, 'lhs')) + meths.del_keymap(mapmode, 'lhs') + eq({}, get_mapargs(mapmode, 'lhs')) + end) + it ('can set/unset '..printable_mode..'-mode mappings with maparg '.. + maparg..', whose value is false', function() + meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = false}) + eq(generate_mapargs(mapmode, 'lhs', 'rhs'), + get_mapargs(mapmode, 'lhs')) + meths.del_keymap(mapmode, 'lhs') + eq({}, get_mapargs(mapmode, 'lhs')) + end) + end + + -- Test with triplets of mappings, one of which is false + for i = 1, (#optnames - 2) do + local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2] + it('can set/unset '..printable_mode..'-mode mappings with mapargs '.. + opt1..', '..opt2..', '..opt3, function() + local opts = {[opt1] = true, [opt2] = false, [opt3] = true} + meths.set_keymap(mapmode, 'lhs', 'rhs', opts) + eq(generate_mapargs(mapmode, 'lhs', 'rhs', opts), + get_mapargs(mapmode, 'lhs')) + meths.del_keymap(mapmode, 'lhs') + eq({}, get_mapargs(mapmode, 'lhs')) + end) + end + end +end) + +describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() + before_each(clear) + + -- nvim_set_keymap is implemented as a wrapped call to nvim_buf_set_keymap, + -- so its tests also effectively test nvim_buf_set_keymap + + -- here, we mainly test for buffer specificity and other special cases + + -- switch to the given buffer, abandoning any changes in the current buffer + local function switch_to_buf(bufnr) + command(bufnr..'buffer!') + end + + -- `set hidden`, then create two buffers and return their bufnr's + -- If start_from_first is truthy, the first buffer will be open when + -- the function returns; if falsy, the second buffer will be open. + local function make_two_buffers(start_from_first) + command('set hidden') + + local first_buf = meths.call_function('bufnr', {'%'}) + command('new') + local second_buf = meths.call_function('bufnr', {'%'}) + neq(second_buf, first_buf) -- sanity check + + if start_from_first then + switch_to_buf(first_buf) + end + + return first_buf, second_buf + end + + it('rejects negative bufnr values', function() + expect_err('Wrong type for argument 1, expecting Buffer', + bufmeths.set_keymap, -1, '', 'lhs', 'rhs', {}) + end) + + it('can set mappings active in the current buffer but not others', function() + local first, second = make_two_buffers(true) + + bufmeths.set_keymap(0, '', 'lhs', 'irhs<Esc>', {}) + command('normal lhs') + eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) + + -- mapping should have no effect in new buffer + switch_to_buf(second) + command('normal lhs') + eq({''}, bufmeths.get_lines(0, 0, 1, 1)) + + -- mapping should remain active in old buffer + switch_to_buf(first) + command('normal ^lhs') + eq({'rhsrhs'}, bufmeths.get_lines(0, 0, 1, 1)) + end) + + it('can set local mappings in buffer other than current', function() + local first = make_two_buffers(false) + bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) + + -- shouldn't do anything + command('normal lhs') + eq({''}, bufmeths.get_lines(0, 0, 1, 1)) + + -- should take effect + switch_to_buf(first) + command('normal lhs') + eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) + end) + + it('can disable mappings made in another buffer, inside that buffer', function() + local first = make_two_buffers(false) + bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) + bufmeths.del_keymap(first, '', 'lhs') + switch_to_buf(first) + + -- shouldn't do anything + command('normal lhs') + eq({''}, bufmeths.get_lines(0, 0, 1, 1)) + end) + + it("can't disable mappings given wrong buffer handle", function() + local first, second = make_two_buffers(false) + bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) + expect_err('E31: No such mapping', + bufmeths.del_keymap, second, '', 'lhs') + + -- should still work + switch_to_buf(first) + command('normal lhs') + eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1)) + end) +end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index c508ca37db..2178abab53 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1284,7 +1284,7 @@ describe('API', function() end) it('returns attached UIs', function() local screen = Screen.new(20, 4) - screen:attach() + screen:attach({override=true}) local expected = { { chan = 1, @@ -1299,6 +1299,7 @@ describe('API', function() ext_messages = false, height = 4, rgb = true, + override = true, width = 20, } } @@ -1308,6 +1309,7 @@ describe('API', function() screen = Screen.new(44, 99) screen:attach({ rgb = false }) expected[1].rgb = false + expected[1].override = false expected[1].width = 44 expected[1].height = 99 eq(expected, nvim("list_uis")) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 814624d8e2..337c5442ef 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local dedent = helpers.dedent local eq = helpers.eq @@ -6,11 +7,13 @@ local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear local meths = helpers.meths +local meth_pcall = helpers.meth_pcall local funcs = helpers.funcs local expect = helpers.expect local command = helpers.command local exc_exec = helpers.exc_exec local curbufmeths = helpers.curbufmeths +local source = helpers.source describe('autocmd', function() before_each(clear) @@ -144,4 +147,117 @@ describe('autocmd', function() --- Autocommands ---]]), funcs.execute('autocmd Tabnew')) end) + + it('window works', function() + -- Nvim uses a special window to execute certain actions for an invisible buffer, + -- internally called autcmd_win and mentioned in the docs at :help E813 + -- Do some safety checks for redrawing and api accesses to this window. + + local screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {background = Screen.colors.LightMagenta}, + [3] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, + }) + + source([[ + function! Doit() + let g:winid = nvim_get_current_win() + redraw! + echo getchar() + " API functions work when aucmd_win is in scope + let g:had_value = has_key(w:, "testvar") + call nvim_win_set_var(g:winid, "testvar", 7) + let g:test = w:testvar + endfunction + set hidden + " add dummy text to not discard the buffer + call setline(1,"bb") + autocmd User <buffer> call Doit() + ]]) + screen:expect([[ + ^bb | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + + feed(":enew | doautoall User<cr>") + screen:expect([[ + {2:bb }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ^:enew | doautoall User | + ]]) + + feed('<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + 13 | + ]]) + eq(7, eval('g:test')) + + -- API calls are blocked when aucmd_win is not in scope + eq({false, 'Vim(call):Invalid window id'}, + meth_pcall(command, "call nvim_set_current_win(g:winid)")) + + -- second time aucmd_win is needed, a different code path is invoked + -- to reuse the same window, so check again + command("let g:test = v:null") + command("let g:had_value = v:null") + feed(":doautoall User<cr>") + screen:expect([[ + {2:bb }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ^:doautoall User | + ]]) + + feed('<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + 13 | + ]]) + -- win vars in aucmd_win should have been reset + eq(0, eval('g:had_value')) + eq(7, eval('g:test')) + + eq({false, 'Vim(call):Invalid window id'}, + meth_pcall(command, "call nvim_set_current_win(g:winid)")) + end) end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 7b89172f92..0137092eb5 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -1,3 +1,5 @@ +local global_helpers = require('test.helpers') +local uname = global_helpers.uname local helpers = require('test.functional.helpers')(after_each) local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq, @@ -7,6 +9,7 @@ local sleep = helpers.sleep local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv local set_session = helpers.set_session local nvim_prog = helpers.nvim_prog +local os_name = helpers.os_name local retry = helpers.retry local expect_twostreams = helpers.expect_twostreams @@ -138,9 +141,8 @@ describe('channels', function() command("call chansend(id, 'incomplet\004')") - local is_freebsd = eval("system('uname') =~? 'FreeBSD'") == 1 - local bsdlike = is_freebsd or (helpers.os_name() == "osx") - print("bsdlike:", bsdlike) + local is_freebsd = (string.lower(uname()) == 'freebsd') + local bsdlike = is_freebsd or (os_name() == "osx") local extra = bsdlike and "^D\008\008" or "" expect_twoline(id, "stdout", "incomplet"..extra, "[1, ['incomplet'], 'stdin']", true) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index a0981e9207..b793e531c9 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -7,19 +7,9 @@ local feed = helpers.feed local eval = helpers.eval local clear = helpers.clear local funcs = helpers.funcs -local nvim_prog = helpers.nvim_prog +local nvim_prog_abs = helpers.nvim_prog_abs local write_file = helpers.write_file -local function nvim_prog_abs() - -- system(['build/bin/nvim']) does not work for whatever reason. It needs to - -- either be executable searched in $PATH or something starting with / or ./. - if nvim_prog:match('[/\\]') then - return funcs.fnamemodify(nvim_prog, ':p') - else - return nvim_prog - end -end - describe('Command-line option', function() describe('-s', function() local fname = 'Xtest-functional-core-main-s' diff --git a/test/functional/spell/spellfile_spec.lua b/test/functional/core/spellfile_spec.lua index afd2c1bce4..afd2c1bce4 100644 --- a/test/functional/spell/spellfile_spec.lua +++ b/test/functional/core/spellfile_spec.lua diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 76e242b1e2..f77af836a6 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -13,9 +13,7 @@ local nvim_set = helpers.nvim_set local read_file = helpers.read_file local retry = helpers.retry local rmdir = helpers.rmdir -local set_session = helpers.set_session local sleep = helpers.sleep -local spawn = helpers.spawn local iswin = helpers.iswin local write_file = helpers.write_file @@ -228,10 +226,6 @@ describe('sysinit', function() local vimdir = 'Xvim' local xhome = 'Xhome' local pathsep = helpers.get_pathsep() - local argv = { - nvim_prog, '--headless', '--embed', '-i', 'NONE', '-n', - '--cmd', 'set nomore undodir=. directory=. belloff=' - } before_each(function() rmdir(xdgdir) @@ -260,19 +254,21 @@ describe('sysinit', function() end) it('prefers XDG_CONFIG_DIRS over VIM', function() - set_session(spawn(argv, nil, - { 'HOME='..xhome, - 'XDG_CONFIG_DIRS='..xdgdir, - 'VIM='..vimdir })) + clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='}, + args_rm={'-u', '--cmd'}, + env={ HOME=xhome, + XDG_CONFIG_DIRS=xdgdir, + VIM=vimdir }} eq('loaded 1 xdg 1 vim 0', eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) it('uses VIM if XDG_CONFIG_DIRS unset', function() - set_session(spawn(argv, nil, - { 'HOME='..xhome, - 'XDG_CONFIG_DIRS=', - 'VIM='..vimdir })) + clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='}, + args_rm={'-u', '--cmd'}, + env={ HOME=xhome, + XDG_CONFIG_DIRS='', + VIM=vimdir }} eq('loaded 1 xdg 0 vim 1', eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua index 6f440c7d82..40d06b599f 100644 --- a/test/functional/eval/api_functions_spec.lua +++ b/test/functional/eval/api_functions_spec.lua @@ -6,7 +6,7 @@ local clear, curbufmeths = helpers.clear, helpers.curbufmeths local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval local insert = helpers.insert -describe('api functions', function() +describe('eval-API', function() before_each(clear) it("work", function() diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua index e2958c2924..802c3f68c6 100644 --- a/test/functional/ex_cmds/oldfiles_spec.lua +++ b/test/functional/ex_cmds/oldfiles_spec.lua @@ -1,18 +1,18 @@ local Screen = require('test.functional.ui.screen') local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command -local feed, nvim_prog, wait = helpers.feed, helpers.nvim_prog, helpers.wait -local ok, set_session, spawn = helpers.ok, helpers.set_session, helpers.spawn +local feed, wait = helpers.feed, helpers.wait +local ok = helpers.ok local eval = helpers.eval local shada_file = 'Xtest.shada' local function _clear() - set_session(spawn({nvim_prog, '--embed', '--headless', '-u', 'NONE', - -- Need shada for these tests. - '-i', shada_file, - '--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'})) + clear{args={'-i', shada_file, -- Need shada for these tests. + '--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}, + args_rm={'-i', '--cmd'}} end describe(':oldfiles', function() diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua index bf10f80401..3392a90270 100644 --- a/test/functional/ex_cmds/quickfix_commands_spec.lua +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -37,9 +37,9 @@ for _, c in ipairs({'l', 'c'}) do -- Second line of each entry (i.e. `nr=-1, …`) was obtained from actual -- results. First line (i.e. `{lnum=…`) was obtained from legacy test. local list = { - {lnum=700, col=10, text='Line 700', + {lnum=700, col=10, text='Line 700', module='', nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''}, - {lnum=800, col=15, text='Line 800', + {lnum=800, col=15, text='Line 800', module='', nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''}, } eq(list, getlist()) @@ -58,7 +58,7 @@ for _, c in ipairs({'l', 'c'}) do ]]):format(file)) command(('%s %s'):format(addfcmd, file)) list[#list + 1] = { - lnum=900, col=30, text='Line 900', + lnum=900, col=30, text='Line 900', module='', nr=-1, bufnr=5, valid=1, pattern='', vcol=0, ['type']='', } eq(list, getlist()) @@ -71,9 +71,9 @@ for _, c in ipairs({'l', 'c'}) do command('enew!') command(('%s %s'):format(getfcmd, file)) list = { - {lnum=222, col=77, text='Line 222', + {lnum=222, col=77, text='Line 222', module='', nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''}, - {lnum=333, col=88, text='Line 333', + {lnum=333, col=88, text='Line 333', module='', nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''}, } eq(list, getlist()) diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua index df0b9df5dd..7c00daf1d7 100644 --- a/test/functional/ex_cmds/wviminfo_spec.lua +++ b/test/functional/ex_cmds/wviminfo_spec.lua @@ -1,23 +1,21 @@ local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') -local command, eq, neq, spawn, nvim_prog, set_session, write_file = - helpers.command, helpers.eq, helpers.neq, helpers.spawn, - helpers.nvim_prog, helpers.set_session, helpers.write_file +local clear = helpers.clear +local command, eq, neq, write_file = + helpers.command, helpers.eq, helpers.neq, helpers.write_file local iswin = helpers.iswin local read_file = helpers.read_file describe(':wshada', function() local shada_file = 'wshada_test' - local session before_each(function() - -- Override the default session because we need 'swapfile' for these tests. - session = spawn({nvim_prog, '-u', 'NONE', '-i', iswin() and 'nul' or '/dev/null', '--embed', - '--cmd', 'set swapfile'}) - set_session(session) + clear{args={'-i', iswin() and 'nul' or '/dev/null', + -- Need 'swapfile' for these tests. + '--cmd', 'set swapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}, + args_rm={'-n', '-i', '--cmd'}} end) after_each(function () - session:close() os.remove(shada_file) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 7f9b5fe5fc..fd10a6afcd 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -38,7 +38,7 @@ local nvim_prog = ( local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent' ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' ..' belloff= noshowcmd noruler nomore' -local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N', +local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', nvim_set, '--embed'} -- Directory containing nvim. local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "") @@ -238,6 +238,16 @@ local function stop() session:stop() end +local function nvim_prog_abs() + -- system(['build/bin/nvim']) does not work for whatever reason. It must + -- be executable searched in $PATH or something starting with / or ./. + if nvim_prog:match('[/\\]') then + return request('nvim_call_function', 'fnamemodify', {nvim_prog, ':p'}) + else + return nvim_prog + end +end + -- Executes an ex-command. VimL errors manifest as client (lua) errors, but -- v:errmsg will not be updated. local function nvim_command(cmd) @@ -312,6 +322,43 @@ local function merge_args(...) return argv end +-- Removes Nvim startup args from `args` matching items in `args_rm`. +-- +-- "-u", "-i", "--cmd" are treated specially: their "values" are also removed. +-- Example: +-- args={'--headless', '-u', 'NONE'} +-- args_rm={'--cmd', '-u'} +-- Result: +-- {'--headless'} +-- +-- All cases are removed. +-- Example: +-- args={'--cmd', 'foo', '-N', '--cmd', 'bar'} +-- args_rm={'--cmd', '-u'} +-- Result: +-- {'-N'} +local function remove_args(args, args_rm) + local new_args = {} + local skip_following = {'-u', '-i', '-c', '--cmd', '-s', '--listen'} + if not args_rm or #args_rm == 0 then + return {unpack(args)} + end + for _, v in ipairs(args_rm) do + assert(type(v) == 'string') + end + local last = '' + for _, arg in ipairs(args) do + if table_contains(skip_following, last) then + last = '' + elseif table_contains(args_rm, arg) then + last = arg + else + table.insert(new_args, arg) + end + end + return new_args +end + local function spawn(argv, merge, env) local child_stream = ChildProcessStream.spawn( merge and merge_args(prepend_argv, argv) or argv, @@ -350,20 +397,25 @@ local function retry(max, max_ms, fn) end -- Starts a new global Nvim session. +-- -- Parameters are interpreted as startup args, OR a map with these keys: --- args: Merged with the default `nvim_argv` set. --- env : Defines the environment of the new session. +-- args: List: Args appended to the default `nvim_argv` set. +-- args_rm: List: Args removed from the default set. All cases are +-- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" +-- (and its value) from the default set. +-- env: Map: Defines the environment of the new session. -- -- Example: -- clear('-e') --- clear({args={'-e'}, env={TERM=term}}) +-- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} local function clear(...) local args = {unpack(nvim_argv)} + table.insert(args, '--headless') local new_args local env = nil local opts = select(1, ...) - local headless = true if type(opts) == 'table' then + args = remove_args(args, opts.args_rm) if opts.env then local env_tbl = {} for k, v in pairs(opts.env) do @@ -374,7 +426,8 @@ local function clear(...) for _, k in ipairs({ 'HOME', 'ASAN_OPTIONS', - 'LD_LIBRARY_PATH', 'PATH', + 'LD_LIBRARY_PATH', + 'PATH', 'NVIM_LOG_FILE', 'NVIM_RPLUGIN_MANIFEST', }) do @@ -388,15 +441,9 @@ local function clear(...) end end new_args = opts.args or {} - if opts.headless == false then - headless = false - end else new_args = {...} end - if headless then - table.insert(args, '--headless') - end for _, arg in ipairs(new_args) do table.insert(args, arg) end @@ -789,6 +836,7 @@ local module = { nvim_async = nvim_async, nvim_dir = nvim_dir, nvim_prog = nvim_prog, + nvim_prog_abs = nvim_prog_abs, nvim_set = nvim_set, ok = ok, os_name = os_name, diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua index e17b463e30..f7f074c61a 100644 --- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua +++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua @@ -2,9 +2,9 @@ local helpers = require('test.functional.helpers')(after_each) local lfs = require('lfs') -local clear, command, eq, neq, eval, wait, spawn = +local clear, command, eq, neq, eval, wait = helpers.clear, helpers.command, helpers.eq, helpers.neq, helpers.eval, - helpers.wait, helpers.spawn + helpers.wait describe('storing global variables in ShaDa files', function() local tempname = 'Xtest-functional-legacy-074' @@ -14,9 +14,7 @@ describe('storing global variables in ShaDa files', function() end) it('is working', function() - local nvim2 = spawn({helpers.nvim_prog, '-u', 'NONE', - '-i', 'Xviminfo', '--embed'}) - helpers.set_session(nvim2) + clear{args_rm={'-i'}, args={'-i', 'Xviminfo'}} local test_dict = {foo = 1, bar = 0, longvarible = 1000} local test_list = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 10703465aa..8df2d89b70 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -18,6 +18,15 @@ describe('assert function:', function() clear() end) + describe('assert_beeps', function() + it('works', function() + call('assert_beeps', 'normal h') + expected_empty() + call('assert_beeps', 'normal 0') + expected_errors({'command did not beep: normal 0'}) + end) + end) + -- assert_equal({expected}, {actual}, [, {msg}]) describe('assert_equal', function() it('should not change v:errors when expected is equal to actual', function() diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index f6f3f02f45..0fc2876d00 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local global_helpers = require('test.helpers') local Screen = require('test.functional.ui.screen') @@ -15,6 +16,7 @@ local neq = helpers.neq local mkdir = helpers.mkdir local rmdir = helpers.rmdir local alter_slashes = helpers.alter_slashes +local table_contains = global_helpers.table_contains describe('startup defaults', function() describe(':filetype', function() @@ -160,20 +162,39 @@ describe('startup defaults', function() end) end) - describe("'packpath'", function() - it('defaults to &runtimepath', function() - eq(meths.get_option('runtimepath'), meths.get_option('packpath')) - end) + it("'shadafile' ('viminfofile')", function() + local env = {XDG_DATA_HOME='Xtest-userdata', XDG_CONFIG_HOME='Xtest-userconfig'} + clear{args={}, args_rm={'-i'}, env=env} + -- Default 'shadafile' is empty. + -- This means use the default location. :help shada-file-name + eq('', meths.get_option('shadafile')) + eq('', meths.get_option('viminfofile')) + -- Check that shada data (such as v:oldfiles) is saved/restored. + command('edit Xtest-foo') + command('write') + local f = eval('fnamemodify(@%,":p")') + assert(string.len(f) > 3) + command('qall') + clear{args={}, args_rm={'-i'}, env=env} + eq({ f }, eval('v:oldfiles')) + os.remove('Xtest-foo') + rmdir('Xtest-userdata') + end) - it('does not follow modifications to runtimepath', function() - meths.command('set runtimepath+=foo') - neq(meths.get_option('runtimepath'), meths.get_option('packpath')) - meths.command('set packpath+=foo') - eq(meths.get_option('runtimepath'), meths.get_option('packpath')) - end) + it("'packpath'", function() + clear() + -- Defaults to &runtimepath. + eq(meths.get_option('runtimepath'), meths.get_option('packpath')) + + -- Does not follow modifications to runtimepath. + meths.command('set runtimepath+=foo') + neq(meths.get_option('runtimepath'), meths.get_option('packpath')) + meths.command('set packpath+=foo') + eq(meths.get_option('runtimepath'), meths.get_option('packpath')) end) it('v:progpath is set to the absolute path', function() + clear() eq(eval("fnamemodify(v:progpath, ':p')"), eval('v:progpath')) end) @@ -231,6 +252,23 @@ describe('XDG-based defaults', function() -- Need separate describe() blocks to not run clear() twice. -- Do not put before_each() here for the same reasons. + it("&runtimepath data-dir matches stdpath('data') #9910", function() + clear() + local rtp = eval('split(&runtimepath, ",")') + local rv = {} + local expected = (iswin() + and { [[\nvim-data\site]], [[\nvim-data\site\after]], } + or { '/nvim/site', '/nvim/site/after', }) + + for _,v in ipairs(rtp) do + local m = string.match(v, [=[[/\]nvim[^/\]*[/\]site.*$]=]) + if m and not table_contains(rv, m) then + table.insert(rv, m) + end + end + eq(expected, rv) + end) + describe('with empty/broken environment', function() it('sets correct defaults', function() clear({env={ diff --git a/test/functional/clipboard/clipboard_provider_spec.lua b/test/functional/provider/clipboard_spec.lua index 2bbc678a02..b2d75db745 100644 --- a/test/functional/clipboard/clipboard_provider_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -236,7 +236,7 @@ describe('clipboard', function() end) end) -describe('clipboard', function() +describe('clipboard (with fake clipboard.vim)', function() local function reset(...) clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...) end @@ -664,4 +664,20 @@ describe('clipboard', function() the a sourcetarget]]) end) + it('setreg("*") with clipboard=unnamed #5646', function() + source([=[ + function! Paste_without_yank(direction) range + let [reg_save,regtype_save] = [getreg('*'), getregtype('*')] + normal! gvp + call setreg('*', reg_save, regtype_save) + endfunction + xnoremap p :call Paste_without_yank('p')<CR> + set clipboard=unnamed + ]=]) + insert('some words') + feed('gg0yiw') + feed('wviwp') + expect('some some') + eq('some', eval('getreg("*")')) + end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index ca5737db7f..f3849709e3 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -106,6 +106,15 @@ describe(':terminal', function() command('stopinsert') eq({ blocking=false, mode='n' }, nvim('get_mode')) end) + + it(':stopinsert in normal mode doesn\'t break insert mode #9889', function() + command(':terminal') + eq({ blocking=false, mode='n' }, nvim('get_mode')) + command(':stopinsert') + eq({ blocking=false, mode='n' }, nvim('get_mode')) + feed('a') + eq({ blocking=false, mode='t' }, nvim('get_mode')) + end) end) describe(':terminal (with fake shell)', function() diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 9579e0ea0b..48fedd5927 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -3,9 +3,12 @@ local Screen = require('test.functional.ui.screen') local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local nvim_dir, command = helpers.nvim_dir, helpers.command +local nvim_prog_abs = helpers.nvim_prog_abs local eq, eval = helpers.eq, helpers.eval +local funcs = helpers.funcs +local nvim_set = helpers.nvim_set -describe(':terminal window highlighting', function() +describe(':terminal highlight', function() local screen before_each(function() @@ -112,8 +115,51 @@ describe(':terminal window highlighting', function() end) end) +it(':terminal highlight has lower precedence than editor #9964', function() + clear() + local screen = Screen.new(30, 4) + screen:set_default_attr_ids({ + -- "Normal" highlight emitted by the child nvim process. + N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40')}, + -- "Search" highlight emitted by the child nvim process. + S_child = {background = tonumber('0xffff40'), italic = true, foreground = tonumber('0x4040ff')}, + -- "Search" highlight in the parent nvim process. + S = {background = Screen.colors.Green, italic = true, foreground = Screen.colors.Red}, + -- "Question" highlight in the parent nvim process. + Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4}, + }) + screen:attach({rgb=true}) + -- Child nvim process in :terminal (with cterm colors). + funcs.termopen({ + nvim_prog_abs(), '-n', '-u', 'NORC', '-i', 'NONE', '--cmd', nvim_set, + '+hi Normal ctermfg=Blue ctermbg=Yellow', + '+norm! ichild nvim', + '+norm! oline 2', + }) + screen:expect([[ + {N_child:^child nvim }| + {N_child:line 2 }| + {N_child: }| + | + ]]) + command('hi Search gui=italic guifg=Red guibg=Green cterm=italic ctermfg=Red ctermbg=Green') + feed('/nvim<cr>') + screen:expect([[ + {N_child:child }{S:^nvim}{N_child: }| + {N_child:line 2 }| + {N_child: }| + /nvim | + ]]) + command('syntax keyword Question line') + screen:expect([[ + {N_child:child }{S:^nvim}{N_child: }| + {Q:line}{N_child: 2 }| + {N_child: }| + /nvim | + ]]) +end) -describe('terminal window highlighting with custom palette', function() +describe(':terminal highlight with custom palette', function() local screen before_each(function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 9d0eb5e40e..469d088c57 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -255,14 +255,14 @@ describe('TUI', function() ]]) end) - it('shows up in nvim_list_uis', function() + it('is included in nvim_list_uis()', function() feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\013') screen:expect([=[ | {4:~ }| {5: }| - [[['height', 6], ['rgb', v:false], ['width', 50]]]| - | + [[['height', 6], ['override', v:false], ['rgb', v:| + false], ['width', 50]]] | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | ]=]) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 121e71c3c2..45808b3b1b 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -983,7 +983,7 @@ describe('Expressions coloring support', function() {EOB:~ }| {EOB:~ }| {EOB:~ }| - ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^" | + ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^ | ]]) end) end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 16be846647..5d563895d6 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -116,6 +116,31 @@ local function test_cmdline(linegrid) }}} end) + it('from normal mode when : is mapped', function() + command('nnoremap ; :') + + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {3:n }| + | + ]]} + + feed(';') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {3:c }| + | + ]], cmdline={{ + firstc = ":", + content = {{""}}, + pos = 0, + }}} + end) + it('but not with scrolled messages', function() screen:try_resize(35,10) feed(':echoerr doesnotexist<cr>') diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua index 10dbc68672..9196c8af40 100644 --- a/test/functional/ui/embed_spec.lua +++ b/test/functional/ui/embed_spec.lua @@ -8,7 +8,7 @@ local clear = helpers.clear local function test_embed(ext_linegrid) local screen local function startup(...) - clear{headless=false, args={...}} + clear{args_rm={'--headless'}, args={...}} -- attach immediately after startup, for early UI screen = Screen.new(60, 8) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index efa776762b..d49d2f0316 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -22,8 +22,129 @@ describe('ui/ext_messages', function() [6] = {bold = true, reverse = true}, }) end) + after_each(function() + os.remove('Xtest') + end) + + it('msg_show kind=confirm,confirm_sub,emsg,wmsg', function() + feed('iline 1\nline 2<esc>') + + -- kind=confirm + feed(':echo confirm("test")<cr>') + screen:expect{grid=[[ + line 1 | + line ^2 | + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ { + content = {{"\ntest\n[O]k: ", 4}}, + kind = 'confirm', + }}} + feed('<cr><cr>') + screen:expect{grid=[[ + line 1 | + line ^2 | + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ { + content = { { "\ntest\n[O]k: ", 4 } }, + kind = "confirm" + }, { + content = { { "1" } }, + kind = "echo" + }, { + content = { { "Press ENTER or type command to continue", 4 } }, + kind = "return_prompt" + } }} + feed('<cr><cr>') + + -- kind=confirm_sub + feed(':%s/i/X/gc<cr>') + screen:expect{grid=[[ + l{7:i}ne 1 | + l{8:i}ne ^2 | + {1:~ }| + {1:~ }| + {1:~ }| + ]], attr_ids={ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {foreground = Screen.colors.Blue1}, + [6] = {bold = true, reverse = true}, + [7] = {reverse = true}, + [8] = {background = Screen.colors.Yellow}, + }, messages={ { + content = { { "replace with X (y/n/a/q/l/^E/^Y)?", 4 } }, + kind = "confirm_sub" + } }} + feed('nq') + + -- kind=wmsg (editing readonly file) + command('write Xtest') + command('set readonly nohls') + feed('G$x') + screen:expect{grid=[[ + line 1 | + {IGNORE}| + {1:~ }| + {1:~ }| + {1:~ }| + ]], attr_ids={ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [7] = {foreground = Screen.colors.Red}, + }, messages={ { + content = { { "W10: Warning: Changing a readonly file", 7 } }, + kind = "wmsg" + } + }} + + -- kind=wmsg ('wrapscan' after search reaches EOF) + feed('uG$/i<cr>') + screen:expect{grid=[[ + l^ine 1 | + line 2 | + {1:~ }| + {1:~ }| + {1:~ }| + ]], attr_ids={ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {foreground = Screen.colors.Blue1}, + [6] = {bold = true, reverse = true}, + [7] = {foreground = Screen.colors.Red}, + }, messages={ { + content = { { "search hit BOTTOM, continuing at TOP", 7 } }, + kind = "wmsg" + } }} + + -- kind=emsg after :throw + feed(':throw "foo"<cr>') + screen:expect{grid=[[ + l^ine 1 | + line 2 | + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ { + content = { { "Error detected while processing :", 2 } }, + kind = "emsg" + }, { + content = { { "E605: Exception not caught: foo", 2 } }, + kind = "" + }, { + content = { { "Press ENTER or type command to continue", 4 } }, + kind = "return_prompt" + } } + } + end) - it('supports :echoerr', function() + it(':echoerr', function() feed(':echoerr "raa"<cr>') screen:expect{grid=[[ ^ | @@ -142,7 +263,7 @@ describe('ui/ext_messages', function() }} end) - it('supports showmode', function() + it('&showmode', function() command('imap <f2> <cmd>echomsg "stuff"<cr>') feed('i') screen:expect{grid=[[ @@ -230,7 +351,7 @@ describe('ui/ext_messages', function() }} end) - it('supports showmode with recording message', function() + it('&showmode with macro-recording message', function() feed('qq') screen:expect{grid=[[ ^ | @@ -268,7 +389,7 @@ describe('ui/ext_messages', function() ]]) end) - it('shows recording message with noshowmode', function() + it('shows macro-recording message with &noshowmode', function() command("set noshowmode") feed('qq') -- also check mode to avoid immediate success @@ -308,7 +429,7 @@ describe('ui/ext_messages', function() ]], mode="normal"} end) - it('supports showcmd and ruler', function() + it('supports &showcmd and &ruler', function() command('set showcmd ruler') screen:expect{grid=[[ ^ | @@ -529,7 +650,7 @@ describe('ui/ext_messages', function() local screen before_each(function() - clear{headless=false, args={"--cmd", "set shortmess-=I"}} + clear{args_rm={'--headless'}, args={"--cmd", "set shortmess-=I"}} screen = Screen.new(80, 24) screen:attach({rgb=true, ext_messages=true, ext_popupmenu=true}) screen:set_default_attr_ids({ diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index c54d608ec4..c5a23e4661 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -11,7 +11,7 @@ describe('ext_multigrid', function() local screen before_each(function() - clear{headless=false, args={'--cmd', 'set laststatus=2'}} + clear{args_rm={'--headless'}, args={'--cmd', 'set laststatus=2'}} screen = Screen.new(53,14) screen:attach({ext_multigrid=true}) screen:set_default_attr_ids({ diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index ed630259be..7ce21f5d76 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -115,7 +115,8 @@ describe('ui receives option updates', function() end) local function startup_test(headless) - local expected = reset(nil,{headless=headless,args={'--cmd', 'set guifont=Comic\\ Sans\\ 12'}}) + local expected = reset(nil, {args_rm=(headless and {} or {'--headless'}), + args={'--cmd', 'set guifont=Comic\\ Sans\\ 12'}}) expected.guifont = "Comic Sans 12" screen:expect(function() eq(expected, screen.options) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 983a2f3d40..53b6642207 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -74,6 +74,7 @@ local global_helpers = require('test.helpers') local deepcopy = global_helpers.deepcopy local shallowcopy = global_helpers.shallowcopy +local concat_tables = global_helpers.concat_tables local helpers = require('test.functional.helpers')(nil) local request, run_session = helpers.request, helpers.run_session local eq = helpers.eq @@ -413,26 +414,23 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end end - -- Extension features. The default expectations should cover the case of + -- UI extensions. The default expectations should cover the case of -- the ext_ feature being disabled, or the feature currently not activated - -- (for instance no external cmdline visible). Some extensions require + -- (e.g. no external cmdline visible). Some extensions require -- preprocessing to represent highlights in a reproducible way. local extstate = self:_extstate_repr(attr_state) - - -- convert assertion errors into invalid screen state descriptions - local status, res = pcall(function() - for _, k in ipairs(ext_keys) do - -- Empty states is considered the default and need not be mentioned - if not (expected[k] == nil and isempty(extstate[k])) then - eq(expected[k], extstate[k], k) + if expected['mode'] ~= nil then + extstate['mode'] = self.mode + end + -- Convert assertion errors into invalid screen state descriptions. + for _, k in ipairs(concat_tables(ext_keys, {'mode'})) 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) + if not status then + return (tostring(res)..'\nHint: full state of "'..k..'":\n '..inspect(extstate[k])) end end - if expected.mode ~= nil then - eq(expected.mode, self.mode, "mode") - end - end) - if not status then - return tostring(res) end end, expected) end diff --git a/test/functional/viml/errorlist_spec.lua b/test/functional/viml/errorlist_spec.lua index 6c5a63e6b1..c5390cbd12 100644 --- a/test/functional/viml/errorlist_spec.lua +++ b/test/functional/viml/errorlist_spec.lua @@ -26,7 +26,7 @@ describe('setqflist()', function() it('sets w:quickfix_title', function() setqflist({''}, 'r', 'foo') command('copen') - eq(':foo', get_cur_win_var('quickfix_title')) + eq('foo', get_cur_win_var('quickfix_title')) setqflist({''}, 'r', {['title'] = 'qf_title'}) eq('qf_title', get_cur_win_var('quickfix_title')) end) @@ -34,7 +34,7 @@ describe('setqflist()', function() it('allows string {what} for backwards compatibility', function() setqflist({}, 'r', '5') command('copen') - eq(':5', get_cur_win_var('quickfix_title')) + eq('5', get_cur_win_var('quickfix_title')) end) it('requires a dict for {what}', function() @@ -64,8 +64,8 @@ describe('setloclist()', function() setloclist(1, {}, 'r', 'foo') setloclist(2, {}, 'r', 'bar') command('lopen') - eq(':bar', get_cur_win_var('quickfix_title')) + eq('bar', get_cur_win_var('quickfix_title')) command('lclose | wincmd w | lopen') - eq(':foo', get_cur_win_var('quickfix_title')) + eq('foo', get_cur_win_var('quickfix_title')) end) end) |