diff options
Diffstat (limited to 'test/functional/api/vim_spec.lua')
-rw-r--r-- | test/functional/api/vim_spec.lua | 441 |
1 files changed, 359 insertions, 82 deletions
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 9a4a457637..fd0535aa51 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,42 +1,43 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') local uv = vim.uv local fmt = string.format -local dedent = helpers.dedent -local assert_alive = helpers.assert_alive +local dedent = t.dedent +local assert_alive = n.assert_alive local NIL = vim.NIL -local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq -local command = helpers.command -local command_output = helpers.api.nvim_command_output -local exec = helpers.exec -local exec_capture = helpers.exec_capture -local eval = helpers.eval -local expect = helpers.expect -local fn = helpers.fn -local api = helpers.api -local matches = helpers.matches +local clear, eq, neq = n.clear, t.eq, t.neq +local command = n.command +local command_output = n.api.nvim_command_output +local exec = n.exec +local exec_capture = n.exec_capture +local eval = n.eval +local expect = n.expect +local fn = n.fn +local api = n.api +local matches = t.matches local pesc = vim.pesc -local mkdir_p = helpers.mkdir_p -local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed -local async_meths = helpers.async_meths -local is_os = helpers.is_os -local parse_context = helpers.parse_context -local request = helpers.request -local rmdir = helpers.rmdir -local source = helpers.source -local next_msg = helpers.next_msg -local tmpname = helpers.tmpname -local write_file = helpers.write_file -local exec_lua = helpers.exec_lua -local exc_exec = helpers.exc_exec -local insert = helpers.insert -local skip = helpers.skip - -local pcall_err = helpers.pcall_err +local mkdir_p = n.mkdir_p +local ok, nvim_async, feed = t.ok, n.nvim_async, n.feed +local async_meths = n.async_meths +local is_os = t.is_os +local parse_context = n.parse_context +local request = n.request +local rmdir = n.rmdir +local source = n.source +local next_msg = n.next_msg +local tmpname = t.tmpname +local write_file = t.write_file +local exec_lua = n.exec_lua +local exc_exec = n.exc_exec +local insert = n.insert +local skip = t.skip + +local pcall_err = t.pcall_err local format_string = require('test.format_string').format_string -local intchar2lua = helpers.intchar2lua -local mergedicts_copy = helpers.mergedicts_copy +local intchar2lua = t.intchar2lua +local mergedicts_copy = t.mergedicts_copy local endswith = vim.endswith describe('API', function() @@ -559,6 +560,16 @@ describe('API', function() eq('Vim:E121: Undefined variable: bogus', pcall_err(request, 'nvim_eval', 'bogus expression')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) + + it('can return Lua function to Lua code', function() + eq( + [["a string with \"double quotes\" and 'single quotes'"]], + exec_lua([=[ + local fun = vim.api.nvim_eval([[luaeval('string.format')]]) + return fun('%q', [[a string with "double quotes" and 'single quotes']]) + ]=]) + ) + end) end) describe('nvim_call_function', function() @@ -624,6 +635,16 @@ describe('API', function() pcall_err(request, 'nvim_call_function', 'Foo', too_many_args) ) end) + + it('can return Lua function to Lua code', function() + eq( + [["a string with \"double quotes\" and 'single quotes'"]], + exec_lua([=[ + local fun = vim.api.nvim_call_function('luaeval', { 'string.format' }) + return fun('%q', [[a string with "double quotes" and 'single quotes']]) + ]=]) + ) + end) end) describe('nvim_call_dict_function', function() @@ -702,18 +723,18 @@ describe('API', function() end) after_each(function() - helpers.rmdir('Xtestdir') + n.rmdir('Xtestdir') end) it('works', function() api.nvim_set_current_dir('Xtestdir') - eq(fn.getcwd(), start_dir .. helpers.get_pathsep() .. 'Xtestdir') + eq(start_dir .. n.get_pathsep() .. 'Xtestdir', fn.getcwd()) end) it('sets previous directory', function() api.nvim_set_current_dir('Xtestdir') command('cd -') - eq(fn.getcwd(), start_dir) + eq(start_dir, fn.getcwd()) end) end) @@ -1269,7 +1290,7 @@ describe('API', function() api.nvim_paste('', true, 3) screen:expect([[ | - ~ |*2 + {1:~ }|*2 :Foo^ | ]]) end) @@ -1280,8 +1301,8 @@ describe('API', function() api.nvim_paste('normal! \023\022\006\027', true, -1) screen:expect([[ | - ~ |*2 - :normal! ^W^V^F^[^ | + {1:~ }|*2 + :normal! {18:^W^V^F^[}^ | ]]) end) it('crlf=false does not break lines at CR, CRLF', function() @@ -1467,7 +1488,7 @@ describe('API', function() eq(NIL, api.nvim_get_var('Unknown_script_func')) -- Check if autoload works properly - local pathsep = helpers.get_pathsep() + local pathsep = n.get_pathsep() local xconfig = 'Xhome' .. pathsep .. 'Xconfig' local xdata = 'Xhome' .. pathsep .. 'Xdata' local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep) @@ -1593,14 +1614,12 @@ describe('API', function() api.nvim_set_option_value('equalalways', false, {}) local status, rv = pcall(command_output, 'verbose set equalalways?') eq(true, status) - ok( - nil ~= string.find(rv, 'noequalalways\n' .. '\tLast set from API client %(channel id %d+%)') - ) + matches('noequalalways\n' .. '\tLast set from API client %(channel id %d+%)', rv) api.nvim_exec_lua('vim.api.nvim_set_option_value("equalalways", true, {})', {}) status, rv = pcall(command_output, 'verbose set equalalways?') eq(true, status) - eq(' equalalways\n\tLast set from Lua', rv) + eq(' equalalways\n\tLast set from Lua (run Nvim with -V1 for more details)', rv) end) it('updates whether the option has ever been set #25025', function() @@ -1953,7 +1972,7 @@ describe('API', function() describe('RPC (K_EVENT)', function() it('does not complete ("interrupt") normal-mode operator-pending #6166', function() - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('gg') @@ -1981,16 +2000,16 @@ describe('API', function() -- Make any RPC request (can be non-async: op-pending does not block). api.nvim_get_current_buf() screen:expect([[ - ^a$ | - b$ | - c$ | + ^a{1:$} | + b{1:$} | + c{1:$} | | ]]) end) it('does not complete ("interrupt") normal-mode map-pending #6166', function() command("nnoremap dd :let g:foo='it worked...'<CR>") - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('gg') @@ -2002,13 +2021,13 @@ describe('API', function() expect([[ FIRST LINE SECOND LINE]]) - eq('it worked...', helpers.eval('g:foo')) + eq('it worked...', n.eval('g:foo')) end) it('does not complete ("interrupt") insert-mode map-pending #6166', function() command('inoremap xx foo') command('set timeoutlen=9999') - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('ix') @@ -2155,35 +2174,32 @@ describe('API', function() describe('nvim_replace_termcodes', function() it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() - eq('\128\254X', helpers.api.nvim_replace_termcodes('\128', true, true, true)) + eq('\128\254X', n.api.nvim_replace_termcodes('\128', true, true, true)) end) it('leaves non-K_SPECIAL string unchanged', function() - eq('abc', helpers.api.nvim_replace_termcodes('abc', true, true, true)) + eq('abc', n.api.nvim_replace_termcodes('abc', true, true, true)) end) it('converts <expressions>', function() - eq('\\', helpers.api.nvim_replace_termcodes('<Leader>', true, true, true)) + eq('\\', n.api.nvim_replace_termcodes('<Leader>', true, true, true)) end) it('converts <LeftMouse> to K_SPECIAL KS_EXTRA KE_LEFTMOUSE', function() -- K_SPECIAL KS_EXTRA KE_LEFTMOUSE -- 0x80 0xfd 0x2c -- 128 253 44 - eq('\128\253\44', helpers.api.nvim_replace_termcodes('<LeftMouse>', true, true, true)) + eq('\128\253\44', n.api.nvim_replace_termcodes('<LeftMouse>', true, true, true)) end) it('converts keycodes', function() - eq( - '\nx\27x\rx<x', - helpers.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true) - ) + eq('\nx\27x\rx<x', n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true)) end) it('does not convert keycodes if special=false', function() eq( '<NL>x<Esc>x<CR>x<lt>x', - helpers.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false) + n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false) ) end) @@ -2212,18 +2228,18 @@ describe('API', function() api.nvim_feedkeys(':let x1="…"\n', '', true) -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80 - local inp = helpers.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true) + local inp = n.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true) api.nvim_feedkeys(inp, '', true) -- escape_ks=true -- nvim_feedkeys with K_SPECIAL escaping disabled - inp = helpers.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true) + inp = n.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true) api.nvim_feedkeys(inp, '', false) -- escape_ks=false - helpers.stop() + n.stop() end -- spin the loop a bit - helpers.run(nil, nil, on_setup) + n.run(nil, nil, on_setup) eq('…', api.nvim_get_var('x1')) -- Because of the double escaping this is neq @@ -2366,7 +2382,7 @@ describe('API', function() {0:~ }|*6 {1:very fail} | ]]) - helpers.poke_eventloop() + n.poke_eventloop() -- shows up to &cmdheight lines async_meths.nvim_err_write('more fail\ntoo fail\n') @@ -2678,7 +2694,7 @@ describe('API', function() describe('nvim_list_runtime_paths', function() setup(function() - local pathsep = helpers.get_pathsep() + local pathsep = n.get_pathsep() mkdir_p('Xtest' .. pathsep .. 'a') mkdir_p('Xtest' .. pathsep .. 'b') end) @@ -2723,7 +2739,7 @@ describe('API', function() it('can throw exceptions', function() local status, err = pcall(api.nvim_get_option_value, 'invalid-option', {}) eq(false, status) - ok(err:match("Unknown option 'invalid%-option'") ~= nil) + matches("Unknown option 'invalid%-option'", err) end) it('does not truncate error message <1 MB #5984', function() @@ -2736,10 +2752,7 @@ describe('API', function() it('does not leak memory on incorrect argument types', function() local status, err = pcall(api.nvim_set_current_dir, { 'not', 'a', 'dir' }) eq(false, status) - ok( - err:match(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String') - ~= nil - ) + matches(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String', err) end) describe('nvim_parse_expression', function() @@ -3135,10 +3148,60 @@ describe('API', function() -- nowadays this works because we don't execute any spurious autocmds at all #24824 assert_alive() end) + + it('no memory leak when autocommands load the buffer immediately', function() + exec([[ + autocmd BufNew * ++once call bufload(expand("<abuf>")->str2nr()) + \| let loaded = bufloaded(expand("<abuf>")->str2nr()) + ]]) + api.nvim_create_buf(false, true) + eq(1, eval('g:loaded')) + end) + + it('creating scratch buffer where autocommands set &swapfile works', function() + exec([[ + autocmd BufNew * ++once execute expand("<abuf>") "buffer" + \| file foobar + \| setlocal swapfile + ]]) + local new_buf = api.nvim_create_buf(false, true) + neq('', fn.swapname(new_buf)) + end) + + it('fires expected autocommands', function() + exec([=[ + " Append the &buftype to check autocommands trigger *after* the buffer was configured to be + " scratch, if applicable. + autocmd BufNew * let fired += [["BufNew", expand("<abuf>")->str2nr(), + \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] + autocmd BufAdd * let fired += [["BufAdd", expand("<abuf>")->str2nr(), + \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] + + " Don't want to see OptionSet; buffer options set from passing true for "scratch", etc. + " should be configured invisibly, and before autocommands. + autocmd OptionSet * let fired += [["OptionSet", expand("<amatch>")]] + + let fired = [] + ]=]) + local new_buf = api.nvim_create_buf(false, false) + eq({ { 'BufNew', new_buf, '' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(false, true) + eq({ { 'BufNew', new_buf, 'nofile' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(true, false) + eq({ { 'BufNew', new_buf, '' }, { 'BufAdd', new_buf, '' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(true, true) + eq({ { 'BufNew', new_buf, 'nofile' }, { 'BufAdd', new_buf, 'nofile' } }, eval('g:fired')) + end) end) describe('nvim_get_runtime_file', function() - local p = helpers.alter_slashes + local p = n.alter_slashes it('can find files', function() eq({}, api.nvim_get_runtime_file('bork.borkbork', false)) eq({}, api.nvim_get_runtime_file('bork.borkbork', true)) @@ -3365,13 +3428,13 @@ describe('API', function() {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}}, } - for _, t in pairs(tests) do - it(t.desc, function() + for _, test in pairs(tests) do + it(test.desc, function() -- Switch to the target buffer/window so that curbuf/curwin are used. api.nvim_set_current_win(wins[2]) - local info = api.nvim_get_option_info2(unpack(t.args)) - eq(t.linenr, info.last_set_linenr) - eq(t.sid, info.last_set_sid) + local info = api.nvim_get_option_info2(unpack(test.args)) + eq(test.linenr, info.last_set_linenr) + eq(test.sid, info.last_set_sid) end) end @@ -3492,9 +3555,9 @@ describe('API', function() false, { width = 79, height = 31, row = 1, col = 1, relative = 'editor' } ) - local t = api.nvim_open_term(b, {}) + local term = api.nvim_open_term(b, {}) - api.nvim_chan_send(t, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a')) + api.nvim_chan_send(term, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a')) screen:expect { grid = [[ ^ | @@ -3620,7 +3683,7 @@ describe('API', function() api.nvim_buf_set_name(buf, 'mybuf') local mark = api.nvim_get_mark('F', {}) -- Compare the path tail only - assert(string.find(mark[4], 'mybuf$')) + matches('mybuf$', mark[4]) eq({ 2, 2, buf, mark[4] }, mark) end) it('validation', function() @@ -3855,13 +3918,13 @@ describe('API', function() norm 4G ]]) eq({ - str = '││aabb 4 ', + str = '││bbaa 4 ', width = 9, highlights = { { group = 'CursorLineFold', start = 0 }, { group = 'Normal', start = 6 }, - { group = 'IncSearch', start = 6 }, - { group = 'ErrorMsg', start = 8 }, + { group = 'ErrorMsg', start = 6 }, + { group = 'IncSearch', start = 8 }, { group = 'Normal', start = 10 }, }, }, api.nvim_eval_statusline( @@ -4953,4 +5016,218 @@ describe('API', function() eq(false, exec_lua('return _G.success')) end) end) + + it('nvim__redraw', function() + local screen = Screen.new(60, 5) + screen:attach() + local win = api.nvim_get_current_win() + eq('at least one action required', pcall_err(api.nvim__redraw, {})) + eq('at least one action required', pcall_err(api.nvim__redraw, { buf = 0 })) + eq('at least one action required', pcall_err(api.nvim__redraw, { win = 0 })) + eq("cannot use both 'buf' and 'win'", pcall_err(api.nvim__redraw, { buf = 0, win = 0 })) + feed(':echo getchar()<CR>') + fn.setline(1, 'foobar') + command('vnew') + fn.setline(1, 'foobaz') + -- Can flush pending screen updates + api.nvim__redraw({ flush = true }) + screen:expect({ + grid = [[ + foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + ^:echo getchar() | + ]], + }) + -- Can update the grid cursor position #20793 + api.nvim__redraw({ cursor = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + -- Also in non-current window + api.nvim__redraw({ cursor = true, win = win }) + screen:expect({ + grid = [[ + foobaz │^foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + -- Can update the 'statusline' in a single window + api.nvim_set_option_value('statusline', 'statusline1', { win = 0 }) + api.nvim_set_option_value('statusline', 'statusline2', { win = win }) + api.nvim__redraw({ cursor = true, win = 0, statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline1 }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + api.nvim__redraw({ win = win, statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline1 }{2:statusline2 }| + :echo getchar() | + ]], + }) + -- Can update the 'statusline' in all windows + api.nvim_set_option_value('statusline', '', { win = win }) + api.nvim_set_option_value('statusline', 'statusline3', {}) + api.nvim__redraw({ statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'statuscolumn' + api.nvim_set_option_value('statuscolumn', 'statuscolumn', { win = win }) + api.nvim__redraw({ statuscolumn = true }) + screen:expect({ + grid = [[ + ^foobaz │{8:statuscolumn}foobar | + {1:~ }│{1:~ }|*2 + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'winbar' + api.nvim_set_option_value('winbar', 'winbar', { win = 0 }) + api.nvim__redraw({ win = 0, winbar = true }) + screen:expect({ + grid = [[ + {5:^winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {1:~ }│{1:~ }| + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'tabline' + api.nvim_set_option_value('showtabline', 2, {}) + api.nvim_set_option_value('tabline', 'tabline', {}) + api.nvim__redraw({ tabline = true }) + screen:expect({ + grid = [[ + {2:^tabline }| + {5:winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update multiple status widgets + api.nvim_set_option_value('tabline', 'tabline2', {}) + api.nvim_set_option_value('statusline', 'statusline4', {}) + api.nvim__redraw({ statusline = true, tabline = true }) + screen:expect({ + grid = [[ + {2:^tabline2 }| + {5:winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {3:statusline4 }{2:statusline4 }| + :echo getchar() | + ]], + }) + -- Can update all status widgets + api.nvim_set_option_value('tabline', 'tabline3', {}) + api.nvim_set_option_value('statusline', 'statusline5', {}) + api.nvim_set_option_value('statuscolumn', 'statuscolumn2', {}) + api.nvim_set_option_value('winbar', 'winbar2', {}) + api.nvim__redraw({ statuscolumn = true, statusline = true, tabline = true, winbar = true }) + screen:expect({ + grid = [[ + {2:^tabline3 }| + {5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}foobaz │{8:statuscolumn}foobar | + {3:statusline5 }{2:statusline5 }| + :echo getchar() | + ]], + }) + -- Can update status widget for a specific window + feed('<CR><CR>') + command('let g:status=0') + api.nvim_set_option_value('statusline', '%{%g:status%}', { win = 0 }) + command('vsplit') + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:0 }{2:0 statusline5 }| + 13 | + ]], + }) + command('let g:status=1') + api.nvim__redraw({ win = 0, statusline = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:1 }{2:0 statusline5 }| + 13 | + ]], + }) + -- Can update status widget for a specific buffer + command('let g:status=2') + api.nvim__redraw({ buf = 0, statusline = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:2 }{2:2 statusline5 }| + 13 | + ]], + }) + -- valid = true does not draw any lines on its own + exec_lua([[ + _G.lines = 0 + ns = vim.api.nvim_create_namespace('') + vim.api.nvim_set_decoration_provider(ns, { + on_win = function() + if _G.do_win then + vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'IncSearch', end_col = 6 }) + end + end, + on_line = function() + _G.lines = _G.lines + 1 + end, + }) + ]]) + local lines = exec_lua('return lines') + api.nvim__redraw({ buf = 0, valid = true, flush = true }) + eq(lines, exec_lua('return _G.lines')) + -- valid = false does + api.nvim__redraw({ buf = 0, valid = false, flush = true }) + neq(lines, exec_lua('return _G.lines')) + -- valid = true does redraw lines if affected by on_win callback + exec_lua('_G.do_win = true') + api.nvim__redraw({ buf = 0, valid = true, flush = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}{2:^foobaz} │{8:statuscolumn2}{2:foobaz}│{8:statuscolumn}foobar | + {3:2 }{2:2 statusline5 }| + 13 | + ]], + }) + -- takes buffer line count from correct buffer with "win" and {0, -1} "range" + api.nvim__redraw({ win = 0, range = { 0, -1 } }) + n.assert_alive() + end) end) |