diff options
Diffstat (limited to 'test')
52 files changed, 3048 insertions, 336 deletions
diff --git a/test/config/paths.lua.in b/test/config/paths.lua.in index 7fe5d8ad80..e3979981ba 100644 --- a/test/config/paths.lua.in +++ b/test/config/paths.lua.in @@ -19,5 +19,6 @@ if module.test_luajit_prg == '' then end end table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/include") +table.insert(module.include_paths, "${CMAKE_BINARY_DIR}/src/nvim/auto") return module diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 81fad206da..01fcfab543 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -707,4 +707,59 @@ describe('api/buf', function() eq({3, 0}, curbuf('get_mark', 'v')) end) end) + + describe('nvim_buf_set_mark', function() + it('works with buffer local marks', function() + curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'}) + eq(true, curbufmeths.set_mark('z', 1, 1)) + eq({1, 1}, curbufmeths.get_mark('z')) + end) + it('works with file/uppercase marks', function() + curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'}) + eq(true, curbufmeths.set_mark('Z', 3, 1)) + eq({3, 1}, curbufmeths.get_mark('Z')) + end) + it('fails when invalid marks names are used', function() + eq(false, pcall(curbufmeths.set_mark, '!', 1, 0)) + eq(false, pcall(curbufmeths.set_mark, 'fail', 1, 0)) + end) + it('fails when invalid buffer number is used', function() + eq(false, pcall(meths.buf_set_mark, 99, 'a', 1, 1)) + end) + end) + + describe('nvim_buf_del_mark', function() + it('works with buffer local marks', function() + curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'}) + curbufmeths.set_mark('z', 3, 1) + eq(true, curbufmeths.del_mark('z')) + eq({0, 0}, curbufmeths.get_mark('z')) + end) + it('works with file/uppercase marks', function() + curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'}) + curbufmeths.set_mark('Z', 3, 3) + eq(true, curbufmeths.del_mark('Z')) + eq({0, 0}, curbufmeths.get_mark('Z')) + end) + it('returns false in marks not set in this buffer', function() + local abuf = meths.create_buf(false,true) + bufmeths.set_lines(abuf, -1, -1, true, {'a', 'bit of', 'text'}) + bufmeths.set_mark(abuf, 'A', 2, 2) + eq(false, curbufmeths.del_mark('A')) + eq({2, 2}, bufmeths.get_mark(abuf, 'A')) + end) + it('returns false if mark was not deleted', function() + curbufmeths.set_lines(-1, -1, true, {'a', 'bit of', 'text'}) + curbufmeths.set_mark('z', 3, 1) + eq(true, curbufmeths.del_mark('z')) + eq(false, curbufmeths.del_mark('z')) -- Mark was already deleted + end) + it('fails when invalid marks names are used', function() + eq(false, pcall(curbufmeths.del_mark, '!')) + eq(false, pcall(curbufmeths.del_mark, 'fail')) + end) + it('fails when invalid buffer number is used', function() + eq(false, pcall(meths.buf_del_mark, 99, 'a')) + end) + end) end) diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 37331d11c7..6f929ad1ca 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -21,7 +21,7 @@ describe('nvim_get_commands', function() it('validates input', function() eq('builtin=true not implemented', pcall_err(meths.get_commands, {builtin=true})) - eq('unexpected key: foo', pcall_err(meths.get_commands, + eq("Invalid key: 'foo'", pcall_err(meths.get_commands, {foo='blah'})) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 4194945645..dd8eef7ca0 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -436,16 +436,16 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) it('error on invalid optnames', function() - eq('Invalid key: silentt', + eq("Invalid key: 'silentt'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true})) - eq('Invalid key: sidd', + eq("Invalid key: 'sidd'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {sidd = false})) - eq('Invalid key: nowaiT', + eq("Invalid key: 'nowaiT'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {nowaiT = false})) end) it('error on <buffer> option key', function() - eq('Invalid key: buffer', + eq("Invalid key: 'buffer'", pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {buffer = true})) end) @@ -454,8 +454,8 @@ describe('nvim_set_keymap, nvim_del_keymap', function() -- 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 - eq('Gave non-boolean value for an opt: '..opt, + opts[opt] = 'fooo' + eq(opt..' is not a boolean', pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', opts)) end) end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index ffef6a6066..c95c24fabe 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -12,14 +12,17 @@ local funcs = helpers.funcs local iswin = helpers.iswin local meths = helpers.meths local matches = helpers.matches +local mkdir_p = helpers.mkdir_p local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed 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 pcall_err = helpers.pcall_err local format_string = helpers.format_string @@ -89,6 +92,14 @@ describe('API', function() nvim('exec', 'set nowrap', false) eq('nowrap\n\tLast set from anonymous :source', nvim('exec', 'verbose set wrap?', true)) + + -- Using script var to force creation of a script item + nvim('exec', [[ + let s:a = 1 + set nowrap + ]], false) + eq('nowrap\n\tLast set from anonymous :source (script id 1)', + nvim('exec', 'verbose set wrap?', true)) end) it('multiline input', function() @@ -130,6 +141,43 @@ describe('API', function() -- try no spaces before continuations to catch off-by-one error nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false) eq({a = 98}, request('nvim_eval', 'g:ab')) + + -- Script scope (s:) + eq('ahoy! script-scoped varrrrr', nvim('exec', [[ + let s:pirate = 'script-scoped varrrrr' + function! s:avast_ye_hades(s) abort + return a:s .. ' ' .. s:pirate + endfunction + echo <sid>avast_ye_hades('ahoy!') + ]], true)) + + eq('ahoy! script-scoped varrrrr', nvim('exec', [[ + let s:pirate = 'script-scoped varrrrr' + function! Avast_ye_hades(s) abort + return a:s .. ' ' .. s:pirate + endfunction + echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1) + ]], true)) + + eq('Vim(call):E5555: API call: Vim(echo):E121: Undefined variable: s:pirate', + pcall_err(request, 'nvim_exec', [[ + let s:pirate = 'script-scoped varrrrr' + call nvim_exec('echo s:pirate', 1) + ]], false)) + + -- Script items are created only on script var access + eq('1\n0', nvim('exec', [[ + echo expand("<SID>")->empty() + let s:a = 123 + echo expand("<SID>")->empty() + ]], true)) + + eq('1\n0', nvim('exec', [[ + echo expand("<SID>")->empty() + function s:a() abort + endfunction + echo expand("<SID>")->empty() + ]], true)) end) it('non-ASCII input', function() @@ -1117,7 +1165,7 @@ describe('API', function() describe('nvim_get_context', function() it('validates args', function() - eq('unexpected key: blah', + eq("Invalid key: 'blah'", pcall_err(nvim, 'get_context', {blah={}})) eq('invalid value for key: types', pcall_err(nvim, 'get_context', {types=42})) @@ -1574,6 +1622,18 @@ describe('API', function() end) describe('nvim_list_runtime_paths', function() + setup(function() + local pathsep = helpers.get_pathsep() + mkdir_p('Xtest'..pathsep..'a') + mkdir_p('Xtest'..pathsep..'b') + end) + teardown(function() + rmdir 'Xtest' + end) + before_each(function() + meths.set_current_dir 'Xtest' + end) + it('returns nothing with empty &runtimepath', function() meths.set_option('runtimepath', '') eq({}, meths.list_runtime_paths()) @@ -1601,8 +1661,7 @@ describe('API', function() local long_path = ('/a'):rep(8192) meths.set_option('runtimepath', long_path) local paths_list = meths.list_runtime_paths() - neq({long_path}, paths_list) - eq({long_path:sub(1, #(paths_list[1]))}, paths_list) + eq({}, paths_list) end) end) @@ -2206,6 +2265,9 @@ describe('API', function() [2] = {background = tonumber('0xffff40'), bg_indexed = true}; [3] = {background = Screen.colors.Plum1, fg_indexed = true, foreground = tonumber('0x00e000')}; [4] = {bold = true, reverse = true, background = Screen.colors.Plum1}; + [5] = {foreground = Screen.colors.Blue, background = Screen.colors.LightMagenta, bold = true}; + [6] = {bold = true}; + [7] = {reverse = true, background = Screen.colors.LightMagenta}; }) end) @@ -2253,5 +2315,203 @@ describe('API', function() | ]]} end) + + it('can handle input', function() + screen:try_resize(50, 10) + eq({3, 2}, exec_lua [[ + buf = vim.api.nvim_create_buf(1,1) + + stream = '' + do_the_echo = false + function input(_,t1,b1,data) + stream = stream .. data + _G.vals = {t1, b1} + if do_the_echo then + vim.api.nvim_chan_send(t1, data) + end + end + + term = vim.api.nvim_open_term(buf, {on_input=input}) + vim.api.nvim_open_win(buf, true, {width=40, height=5, row=1, col=1, relative='editor'}) + return {term, buf} + ]]) + + screen:expect{grid=[[ + | + {0:~}{1:^ }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + + feed 'iba<c-x>bla' + screen:expect{grid=[[ + | + {0:~}{7: }{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {6:-- TERMINAL --} | + ]]} + + eq('ba\024bla', exec_lua [[ return stream ]]) + eq({3,2}, exec_lua [[ return vals ]]) + + exec_lua [[ do_the_echo = true ]] + feed 'herrejösses!' + + screen:expect{grid=[[ + | + {0:~}{1:herrejösses!}{7: }{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~}{1: }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {6:-- TERMINAL --} | + ]]} + eq('ba\024blaherrejösses!', exec_lua [[ return stream ]]) + end) + end) + + describe('nvim_del_mark', function() + it('works', function() + local buf = meths.create_buf(false,true) + meths.buf_set_lines(buf, -1, -1, true, {'a', 'bit of', 'text'}) + eq(true, meths.buf_set_mark(buf, 'F', 2, 2)) + eq(true, meths.del_mark('F')) + eq({0, 0}, meths.buf_get_mark(buf, 'F')) + end) + it('fails when invalid marks are used', function() + eq(false, pcall(meths.del_mark, 'f')) + eq(false, pcall(meths.del_mark, '!')) + eq(false, pcall(meths.del_mark, 'fail')) + end) + end) + describe('nvim_get_mark', function() + it('works', function() + local buf = meths.create_buf(false,true) + meths.buf_set_lines(buf, -1, -1, true, {'a', 'bit of', 'text'}) + meths.buf_set_mark(buf, 'F', 2, 2) + meths.buf_set_name(buf, "mybuf") + local mark = meths.get_mark('F') + -- Compare the path tail ony + assert(string.find(mark[4], "mybuf$")) + eq({2, 2, buf.id, mark[4]}, mark) + end) + it('fails when invalid marks are used', function() + eq(false, pcall(meths.del_mark, 'f')) + eq(false, pcall(meths.del_mark, '!')) + eq(false, pcall(meths.del_mark, 'fail')) + end) + it('returns the expected when mark is not set', function() + eq(true, meths.del_mark('A')) + eq({0, 0, 0, ''}, meths.get_mark('A')) + end) + it('works with deleted buffers', function() + local fname = tmpname() + write_file(fname, 'a\nbit of\text') + nvim("command", "edit " .. fname) + local buf = meths.get_current_buf() + + meths.buf_set_mark(buf, 'F', 2, 2) + nvim("command", "new") -- Create new buf to avoid :bd failing + nvim("command", "bd! " .. buf.id) + os.remove(fname) + + local mark = meths.get_mark('F') + -- To avoid comparing relative vs absolute path + local mfname = mark[4] + local tail_patt = [[[\/][^\/]*$]] + -- tail of paths should be equals + eq(fname:match(tail_patt), mfname:match(tail_patt)) + eq({2, 2, buf.id, mark[4]}, mark) + end) + end) + describe('nvim_eval_statusline', function() + it('works', function() + eq({ + str = '%StatusLineStringWithHighlights', + width = 31 + }, + meths.eval_statusline( + '%%StatusLineString%#WarningMsg#WithHighlights', + {})) + end) + it('doesn\'t exceed maxwidth', function() + eq({ + str = 'Should be trun>', + width = 15 + }, + meths.eval_statusline( + 'Should be truncated%<', + { maxwidth = 15 })) + end) + describe('highlight parsing', function() + it('works', function() + eq({ + str = "TextWithWarningHighlightTextWithUserHighlight", + width = 45, + highlights = { + { start = 0, group = 'WarningMsg' }, + { start = 24, group = 'User1' } + }, + }, + meths.eval_statusline( + '%#WarningMsg#TextWithWarningHighlight%1*TextWithUserHighlight', + { highlights = true })) + end) + it('works with no highlight', function() + eq({ + str = "TextWithNoHighlight", + width = 19, + highlights = { + { start = 0, group = 'StatusLine' }, + }, + }, + meths.eval_statusline( + 'TextWithNoHighlight', + { highlights = true })) + end) + it('works with inactive statusline', function() + command('split') + + eq({ + str = 'TextWithNoHighlightTextWithWarningHighlight', + width = 43, + highlights = { + { start = 0, group = 'StatusLineNC' }, + { start = 19, group = 'WarningMsg' } + } + }, + meths.eval_statusline( + 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', + { winid = meths.list_wins()[2].id, highlights = true })) + end) + it('works with tabline', function() + eq({ + str = 'TextWithNoHighlightTextWithWarningHighlight', + width = 43, + highlights = { + { start = 0, group = 'TabLineFill' }, + { start = 19, group = 'WarningMsg' } + } + }, + meths.eval_statusline( + 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', + { use_tabline = true, highlights = true })) + end) + end) end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index c49d6405f4..11755a9d97 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1,8 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, - ok, feed, insert, eval = helpers.clear, helpers.nvim, helpers.curbuf, + ok, feed, insert, eval, tabpage = helpers.clear, helpers.nvim, helpers.curbuf, helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq, - helpers.neq, helpers.ok, helpers.feed, helpers.insert, helpers.eval + helpers.neq, helpers.ok, helpers.feed, helpers.insert, helpers.eval, + helpers.tabpage local poke_eventloop = helpers.poke_eventloop local curwinmeths = helpers.curwinmeths local funcs = helpers.funcs @@ -11,6 +12,7 @@ local NIL = helpers.NIL local meths = helpers.meths local command = helpers.command local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive -- check if str is visible at the beginning of some line local function is_visible(str) @@ -206,7 +208,7 @@ describe('API/win', function() end) end) - describe('{get,set}_option', function() + describe('nvim_win_get_option, nvim_win_set_option', function() it('works', function() curwin('set_option', 'colorcolumn', '4,3') eq('4,3', curwin('get_option', 'colorcolumn')) @@ -224,6 +226,18 @@ describe('API/win', function() pcall_err(curwin, 'get_option', 'statusline')) eq('', eval('&l:statusline')) -- confirm local value was not copied end) + + it('after switching windows #15390', function() + nvim('command', 'tabnew') + local tab1 = unpack(nvim('list_tabpages')) + local win1 = unpack(tabpage('list_wins', tab1)) + window('set_option', win1, 'statusline', 'window-status') + nvim('command', 'split') + nvim('command', 'wincmd J') + nvim('command', 'wincmd j') + eq('window-status', window('get_option', win1, 'statusline')) + assert_alive() + end) end) describe('get_position', function() @@ -354,13 +368,13 @@ describe('API/win', function() local win = meths.open_win(0, true, { relative='editor', row=10, col=10, width=50, height=10 }) - local tabpage = eval('tabpagenr()') + local tab = eval('tabpagenr()') command('tabprevious') eq(1, eval('tabpagenr()')) meths.win_close(win, false) - eq(1001, meths.tabpage_get_win(tabpage).id) - helpers.assert_alive() + eq(1001, meths.tabpage_get_win(tab).id) + assert_alive() end) end) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 6f2da24cf2..f4a1642ebf 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -6,6 +6,7 @@ local command = h.command local eq = h.eq local eval = h.eval local request = h.request +local iswin = h.iswin describe('autocmd DirChanged', function() local curdir = string.gsub(lfs.currentdir(), '\\', '/') @@ -14,6 +15,11 @@ describe('autocmd DirChanged', function() curdir .. '/Xtest-functional-autocmd-dirchanged.dir2', curdir .. '/Xtest-functional-autocmd-dirchanged.dir3', } + local win_dirs = { + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR1', + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR2', + curdir .. '\\XTEST-FUNCTIONAL-AUTOCMD-DIRCHANGED.DIR3', + } setup(function() for _, dir in pairs(dirs) do h.mkdir(dir) end end) teardown(function() for _, dir in pairs(dirs) do h.rmdir(dir) end end) @@ -27,17 +33,20 @@ describe('autocmd DirChanged', function() command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]]) end) - it('sets v:event', function() + it('sets v:event and <amatch>', function() command('lcd '..dirs[1]) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatch')) eq(1, eval('g:cdcount')) command('tcd '..dirs[2]) - eq({cwd=dirs[2], scope='tab', changed_window=false}, eval('g:ev')) + eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) eq(2, eval('g:cdcount')) command('cd '..dirs[3]) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq('global', eval('g:amatch')) eq(3, eval('g:cdcount')) end) @@ -63,17 +72,6 @@ describe('autocmd DirChanged', function() eq(dirs[3], eval('getcwd()')) end) - it('sets <amatch> to CWD "scope"', function() - command('lcd '..dirs[1]) - eq('window', eval('g:amatch')) - - command('tcd '..dirs[2]) - eq('tab', eval('g:amatch')) - - command('cd '..dirs[3]) - eq('global', eval('g:amatch')) - end) - it('does not trigger if :cd fails', function() command('let g:ev = {}') @@ -106,13 +104,79 @@ describe('autocmd DirChanged', function() command('split '..dirs[1]..'/foo') eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) command('split '..dirs[2]..'/bar') eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) eq(2, eval('g:cdcount')) end) + it('does not trigger if directory has not changed', function() + command('lcd '..dirs[1]) + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatch')) + eq(1, eval('g:cdcount')) + command('let g:ev = {}') + command('lcd '..dirs[1]) + eq({}, eval('g:ev')) + eq(1, eval('g:cdcount')) + + if iswin() then + command('lcd '..win_dirs[1]) + eq({}, eval('g:ev')) + eq(1, eval('g:cdcount')) + end + + command('tcd '..dirs[2]) + eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) + eq(2, eval('g:cdcount')) + command('let g:ev = {}') + command('tcd '..dirs[2]) + eq({}, eval('g:ev')) + eq(2, eval('g:cdcount')) + + if iswin() then + command('tcd '..win_dirs[2]) + eq({}, eval('g:ev')) + eq(2, eval('g:cdcount')) + end + + command('cd '..dirs[3]) + eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq('global', eval('g:amatch')) + eq(3, eval('g:cdcount')) + command('let g:ev = {}') + command('cd '..dirs[3]) + eq({}, eval('g:ev')) + eq(3, eval('g:cdcount')) + + if iswin() then + command('cd '..win_dirs[3]) + eq({}, eval('g:ev')) + eq(3, eval('g:cdcount')) + end + + command('set autochdir') + + command('split '..dirs[1]..'/foo') + eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatch')) + eq(4, eval('g:cdcount')) + command('let g:ev = {}') + command('split '..dirs[1]..'/bar') + eq({}, eval('g:ev')) + eq(4, eval('g:cdcount')) + + if iswin() then + command('split '..win_dirs[1]..'/baz') + eq({}, eval('g:ev')) + eq(4, eval('g:cdcount')) + end + end) + it("is triggered by switching to win/tab with different CWD #6054", function() command('lcd '..dirs[3]) -- window 3 command('split '..dirs[2]..'/foo') -- window 2 @@ -122,6 +186,7 @@ describe('autocmd DirChanged', function() command('2wincmd w') -- window 2 eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatch')) eq(4, eval('g:cdcount')) command('tabnew') -- tab 2 (tab-local CWD) @@ -129,8 +194,10 @@ describe('autocmd DirChanged', function() command('tcd '..dirs[3]) command('tabnext') -- tab 1 (no tab-local CWD) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatch')) command('tabnext') -- tab 2 - eq({cwd=dirs[3], scope='tab', changed_window=true}, eval('g:ev')) + eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev')) + eq('tabpage', eval('g:amatch')) eq(7, eval('g:cdcount')) command('tabnext') -- tab 1 @@ -138,6 +205,31 @@ describe('autocmd DirChanged', function() eq(9, eval('g:cdcount')) command('tabnext') -- tab 2 (has the *same* CWD) eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + + if iswin() then + command('tabnew') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tcd '..win_dirs[3]) + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 2 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('lcd '..win_dirs[3]) -- window 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 2 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabnext') -- tab 1 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + command('tabprevious') -- tab 3 + eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event + end end) it('is triggered by nvim_set_current_dir()', function() diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index f057420dde..cc6e2c8067 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -20,6 +20,7 @@ local retry = helpers.retry local rmdir = helpers.rmdir local sleep = helpers.sleep local iswin = helpers.iswin +local startswith = helpers.startswith local write_file = helpers.write_file local meths = helpers.meths @@ -310,7 +311,8 @@ describe('startup', function() end) local function pack_clear(cmd) - clear{args={'--cmd', 'set packpath=test/functional/fixtures', '--cmd', cmd}, env={XDG_CONFIG_HOME='test/functional/fixtures/'}} + -- add packages after config dir in rtp but before config/after + clear{args={'--cmd', 'set packpath=test/functional/fixtures', '--cmd', 'let paths=split(&rtp, ",")', '--cmd', 'let &rtp = paths[0]..",test/functional/fixtures,test/functional/fixtures/middle,"..join(paths[1:],",")', '--cmd', cmd}, env={XDG_CONFIG_HOME='test/functional/fixtures/'}} end @@ -326,6 +328,15 @@ describe('startup', function() eq({9003, '\thowdy'}, exec_lua [[ return { _G.y, _G.z } ]]) end) + it("handles require from &packpath in an async handler", function() + -- NO! you cannot just speed things up by calling async functions during startup! + -- It doesn't make anything actually faster! NOOOO! + pack_clear [[ lua require'async_leftpad'('brrrr', 'async_res') ]] + + -- haha, async leftpad go brrrrr + eq('\tbrrrr', exec_lua [[ return _G.async_res ]]) + end) + it("handles :packadd during startup", function() -- control group: opt/bonus is not availabe by default pack_clear [[ @@ -351,12 +362,57 @@ describe('startup', function() it("handles the correct order with start packages and after/", function() pack_clear [[ lua _G.test_loadorder = {} vim.cmd "runtime! filen.lua" ]] - eq({'ordinary', 'FANCY', 'ordinary after', 'FANCY after'}, exec_lua [[ return _G.test_loadorder ]]) + eq({'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + end) + + it("handles the correct order with start packages and after/ after startup", function() + pack_clear [[ lua _G.test_loadorder = {} ]] + command [[ runtime! filen.lua ]] + eq({'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + end) + + it("handles the correct order with globpath(&rtp, ...)", function() + pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]] + command [[ + for x in globpath(&rtp, "filen.lua",1,1) + call v:lua.dofile(x) + endfor + ]] + eq({'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + + local rtp = meths.get_option'rtp' + ok(startswith(rtp, 'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'), 'rtp='..rtp) end) it("handles the correct order with opt packages and after/", function() pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! superspecial\nruntime! filen.lua" ]] - eq({'ordinary', 'SuperSpecial', 'FANCY', 'SuperSpecial after', 'ordinary after', 'FANCY after'}, exec_lua [[ return _G.test_loadorder ]]) + eq({'ordinary', 'SuperSpecial', 'FANCY', 'mittel', 'FANCY after', 'SuperSpecial after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + end) + + it("handles the correct order with opt packages and after/ after startup", function() + pack_clear [[ lua _G.test_loadorder = {} ]] + command [[ + packadd! superspecial + runtime! filen.lua + ]] + eq({'ordinary', 'SuperSpecial', 'FANCY', 'mittel', 'FANCY after', 'SuperSpecial after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + end) + + it("handles the correct order with opt packages and globpath(&rtp, ...)", function() + pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]] + command [[ + packadd! superspecial + for x in globpath(&rtp, "filen.lua",1,1) + call v:lua.dofile(x) + endfor + ]] + eq({'ordinary', 'SuperSpecial', 'FANCY', 'mittel', 'SuperSpecial after', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + end) + + it("handles the correct order with a package that changes packpath", function() + pack_clear [[ lua _G.test_loadorder = {} vim.cmd "packadd! funky\nruntime! filen.lua" ]] + eq({'ordinary', 'funky!', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) + eq({'ordinary', 'funky!', 'mittel', 'ordinary after'}, exec_lua [[ return _G.nested_order ]]) end) end) @@ -472,7 +528,7 @@ describe('user config init', function() clear{ args_rm={'-u' }, env=xenv } eq(1, eval('g:lua_rc')) - eq(init_lua_path, eval('$MYVIMRC')) + eq(funcs.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC')) end) describe 'with explicitly provided config'(function() diff --git a/test/functional/editor/meta_key_spec.lua b/test/functional/editor/meta_key_spec.lua index 2a9541ba96..c219204409 100644 --- a/test/functional/editor/meta_key_spec.lua +++ b/test/functional/editor/meta_key_spec.lua @@ -11,15 +11,20 @@ describe('meta-keys #8226 #13042', function() end) it('ALT/META, normal-mode', function() - -- Unmapped ALT-chords behave as ESC+c + -- Unmapped ALT-chord behaves as ESC+c. insert('hello') feed('0<A-x><M-x>') expect('llo') + -- Unmapped ALT-chord resolves isolated (non-ALT) ESC mapping. #13086 #15869 + command('nnoremap <ESC> A<lt>ESC><Esc>') + command('nnoremap ; A;<Esc>') + feed('<A-;><M-;>') + expect('llo<ESC>;<ESC>;') -- Mapped ALT-chord behaves as mapped. command('nnoremap <M-l> Ameta-l<Esc>') command('nnoremap <A-j> Aalt-j<Esc>') feed('<A-j><M-l>') - expect('lloalt-jmeta-l') + expect('llo<ESC>;<ESC>;alt-jmeta-l') end) it('ALT/META, visual-mode', function() @@ -27,11 +32,15 @@ describe('meta-keys #8226 #13042', function() insert('peaches') feed('viw<A-x>viw<M-x>') expect('peach') + -- Unmapped ALT-chord resolves isolated (non-ALT) ESC mapping. #13086 #15869 + command('vnoremap <ESC> A<lt>ESC>') + feed('viw<A-;><ESC>viw<M-;><ESC>') + expect('peach<ESC>;<ESC>;') -- Mapped ALT-chord behaves as mapped. command('vnoremap <M-l> Ameta-l<Esc>') command('vnoremap <A-j> Aalt-j<Esc>') feed('viw<A-j>viw<M-l>') - expect('peachalt-jmeta-l') + expect('peach<ESC>;<ESC>;alt-jmeta-l') end) it('ALT/META insert-mode', function() diff --git a/test/functional/editor/mode_visual_spec.lua b/test/functional/editor/mode_visual_spec.lua index e9c117a1e5..468ae00e01 100644 --- a/test/functional/editor/mode_visual_spec.lua +++ b/test/functional/editor/mode_visual_spec.lua @@ -14,6 +14,7 @@ describe('visual-mode', function() 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('^') diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index bdf6ae76d1..fa650d611b 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -25,12 +25,19 @@ describe(':source', function() let b = #{ \ k: "v" "\ (o_o) - \ }]]) + \ } + let c = expand("<SID>")->empty() + let s:s = 0zbeef.cafe + let d = s:s]]) command('source') eq('2', meths.exec('echo a', true)) eq("{'k': 'v'}", meths.exec('echo b', true)) + -- Script items are created only on script var access + eq("1", meths.exec('echo c', true)) + eq("0zBEEFCAFE", meths.exec('echo d', true)) + exec('set cpoptions+=C') eq('Vim(let):E15: Invalid expression: #{', exc_exec('source')) end) @@ -43,7 +50,11 @@ describe(':source', function() let b = #{ "\ (>_<) \ K: "V" - \ }]]) + \ } + function! s:C() abort + return expand("<SID>") .. "C()" + endfunction + let D = {-> s:C()}]]) -- Source the 2nd line only feed('ggjV') @@ -55,6 +66,11 @@ describe(':source', function() feed_command(':source') eq('4', meths.exec('echo a', true)) eq("{'K': 'V'}", meths.exec('echo b', true)) + eq("<SNR>1_C()", meths.exec('echo D()', true)) + + -- Source last line only + feed_command(':$source') + eq('Vim(echo):E117: Unknown function: s:C', exc_exec('echo D()')) exec('set cpoptions+=C') eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source")) diff --git a/test/functional/fixtures/autoload/health/full_render.vim b/test/functional/fixtures/autoload/health/full_render.vim new file mode 100644 index 0000000000..2064b8606e --- /dev/null +++ b/test/functional/fixtures/autoload/health/full_render.vim @@ -0,0 +1,8 @@ +function! health#full_render#check() + call health#report_start("report 1") + call health#report_ok("life is fine") + call health#report_warn("no what installed", ["pip what", "make what"]) + call health#report_start("report 2") + call health#report_info("stuff is stable") + call health#report_error("why no hardcopy", [":h :hardcopy", ":h :TOhtml"]) +endfunction diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 9579525502..9abf478070 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -43,11 +43,11 @@ end local function read_message() local line = io.read("*l") local length = line:lower():match("content%-length:%s*(%d+)") - return vim.fn.json_decode(io.read(2 + length):sub(2)) + return vim.json.decode(io.read(2 + length):sub(2)) end local function send(payload) - io.stdout:write(format_message_with_content_length(vim.fn.json_encode(payload))) + io.stdout:write(format_message_with_content_length(vim.json.encode(payload))) end local function respond(id, err, result) @@ -246,6 +246,35 @@ function tests.capabilities_for_client_supports_method() } end +function tests.check_forward_request_cancelled() + skeleton { + on_init = function(_) + return { capabilities = {} } + end; + body = function() + expect_request("error_code_test", function() + return {code = -32800}, nil, {method = "error_code_test", client_id=1} + end) + notify('finish') + end; + } +end + +function tests.check_forward_content_modified() + skeleton { + on_init = function(_) + return { capabilities = {} } + end; + body = function() + expect_request("error_code_test", function() + return {code = -32801}, nil, {method = "error_code_test", client_id=1} + end) + expect_notification('finish') + notify('finish') + end; + } +end + function tests.basic_finish() skeleton { on_init = function(params) @@ -564,6 +593,35 @@ function tests.decode_nil() } end + +function tests.code_action_with_resolve() + skeleton { + on_init = function() + return { + capabilities = { + codeActionProvider = { + resolveProvider = true + } + } + } + end; + body = function() + notify('start') + local cmd = { + title = 'Command 1', + command = 'dummy1' + } + expect_request('textDocument/codeAction', function() + return nil, { cmd, } + end) + expect_request('codeAction/resolve', function() + return nil, cmd + end) + notify('shutdown') + end; + } +end + -- Tests will be indexed by TEST_NAME local kill_timer = vim.loop.new_timer() diff --git a/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim b/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim new file mode 100644 index 0000000000..de05f56e9e --- /dev/null +++ b/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim @@ -0,0 +1,3 @@ +function! health#success1#check() + call health#report_start("If you see this I'm broken") +endfunction diff --git a/test/functional/fixtures/lua/test_plug/health/init.lua b/test/functional/fixtures/lua/test_plug/health/init.lua new file mode 100644 index 0000000000..d07632cff4 --- /dev/null +++ b/test/functional/fixtures/lua/test_plug/health/init.lua @@ -0,0 +1,11 @@ +local M = {} +local health = require("health") + +M.check = function() + health.report_start("report 1") + health.report_ok("everything is fine") + health.report_start("report 2") + health.report_ok("nothing to see here") +end + +return M diff --git a/test/functional/fixtures/lua/test_plug/submodule/health.lua b/test/functional/fixtures/lua/test_plug/submodule/health.lua new file mode 100644 index 0000000000..d07632cff4 --- /dev/null +++ b/test/functional/fixtures/lua/test_plug/submodule/health.lua @@ -0,0 +1,11 @@ +local M = {} +local health = require("health") + +M.check = function() + health.report_start("report 1") + health.report_ok("everything is fine") + health.report_start("report 2") + health.report_ok("nothing to see here") +end + +return M diff --git a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua new file mode 100644 index 0000000000..3a8af6ebb2 --- /dev/null +++ b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua @@ -0,0 +1,12 @@ +local M = {} +local health = require("health") + +M.check = function() + health.report_start("report 1") + health.report_ok("everything is fine") + health.report_warn("About to add a number to nil") + local a = nil + 2 + return a +end + +return M diff --git a/test/functional/fixtures/middle/filen.lua b/test/functional/fixtures/middle/filen.lua new file mode 100644 index 0000000000..fce50cc776 --- /dev/null +++ b/test/functional/fixtures/middle/filen.lua @@ -0,0 +1 @@ +table.insert(_G.test_loadorder, "mittel") diff --git a/test/functional/fixtures/pack/foo/opt/funky/filen.lua b/test/functional/fixtures/pack/foo/opt/funky/filen.lua new file mode 100644 index 0000000000..a33b83c2a7 --- /dev/null +++ b/test/functional/fixtures/pack/foo/opt/funky/filen.lua @@ -0,0 +1,12 @@ +table.insert(_G.test_loadorder, "funky!") + +if not _G.nesty then + _G.nesty = true + local save_order = _G.test_loadorder + _G.test_loadorder = {} + _G.vim.o.pp = "" -- funky! + vim.cmd [[runtime! filen.lua ]] + _G.nested_order = _G.test_loadorder + _G.test_loadorder = save_order + _G.nesty = nil +end diff --git a/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_y.lua b/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_y.lua new file mode 100644 index 0000000000..7daa7733a0 --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_y.lua @@ -0,0 +1 @@ +return "I am fancy_y.lua" diff --git a/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_z.lua b/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_z.lua new file mode 100644 index 0000000000..6e81afdd70 --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/fancyplugin/after/lua/fancy_z.lua @@ -0,0 +1 @@ +return "I am fancy_z.lua" diff --git a/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x.lua b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x.lua new file mode 100644 index 0000000000..1b897a96cc --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x.lua @@ -0,0 +1 @@ +return "I am fancy_x.lua" diff --git a/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x/init.lua b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x/init.lua new file mode 100644 index 0000000000..8c27a43cab --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_x/init.lua @@ -0,0 +1 @@ +return "I am init.lua of fancy_x!" diff --git a/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_y/init.lua b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_y/init.lua new file mode 100644 index 0000000000..b66cbee4f6 --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/fancyplugin/lua/fancy_y/init.lua @@ -0,0 +1,2 @@ + +return "I am init.lua of fancy_y!" diff --git a/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua b/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua new file mode 100644 index 0000000000..a312572c5b --- /dev/null +++ b/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua @@ -0,0 +1,3 @@ +return function (val, res) + vim.loop.new_async(function() _G[res] = require'leftpad'(val) end):send() +end diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index f2ced8942d..141d9583e6 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, source = helpers.clear, helpers.source local eq, eval, command = helpers.eq, helpers.eval, helpers.command +local exc_exec = helpers.exc_exec describe('Test for delete()', function() before_each(clear) @@ -38,7 +39,7 @@ describe('Test for delete()', function() eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')")) eq(1, eval("isdirectory('Xdir1/subdir')")) eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')")) - eq(1, eval("isdirectory('Xdir1/empty')")) + eq(1, eval("'Xdir1/empty'->isdirectory()")) eq(0, eval("delete('Xdir1', 'rf')")) eq(0, eval("isdirectory('Xdir1')")) eq(-1, eval("delete('Xdir1', 'd')")) @@ -114,4 +115,10 @@ describe('Test for delete()', function() eq(0, eval("delete('Xdir4/Xfile')")) eq(0, eval("delete('Xdir4', 'd')")) end) + + it('gives correct emsgs', function() + eq('Vim(call):E474: Invalid argument', exc_exec("call delete('')")) + eq('Vim(call):E15: Invalid expression: 0', + exc_exec("call delete('foo', 0)")) + end) end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index 3b407ce5f5..b5de5cd232 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -600,7 +600,6 @@ describe('eval', function() command([[call ErrExe('call setreg(1)')]]) command([[call ErrExe('call setreg(1, 2, 3, 4)')]]) command([=[call ErrExe('call setreg([], 2)')]=]) - command([[call ErrExe('call setreg(1, {})')]]) command([=[call ErrExe('call setreg(1, 2, [])')]=]) command([=[call ErrExe('call setreg("/", ["1", "2"])')]=]) command([=[call ErrExe('call setreg("=", ["1", "2"])')]=]) @@ -615,8 +614,6 @@ describe('eval', function() Vim(call):E118: Too many arguments for function: setreg Executing call setreg([], 2) Vim(call):E730: using List as a String - Executing call setreg(1, {}) - Vim(call):E731: using Dictionary as a String Executing call setreg(1, 2, []) Vim(call):E730: using List as a String Executing call setreg("/", ["1", "2"]) diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua index f238128b31..cd3713eabe 100644 --- a/test/functional/legacy/expand_spec.lua +++ b/test/functional/legacy/expand_spec.lua @@ -81,7 +81,7 @@ describe('expand file name', function() call assert_equal('e Xfile1', expandcmd('e %')) edit Xfile2 edit Xfile1 - call assert_equal('e Xfile2', expandcmd('e #')) + call assert_equal('e Xfile2', 'e #'->expandcmd()) edit Xfile2 edit Xfile3 edit Xfile4 diff --git a/test/functional/legacy/file_perm_spec.lua b/test/functional/legacy/file_perm_spec.lua index 8fdee95e91..ccdbfe0534 100644 --- a/test/functional/legacy/file_perm_spec.lua +++ b/test/functional/legacy/file_perm_spec.lua @@ -3,7 +3,7 @@ require('os') local helpers = require('test.functional.helpers')(after_each) local clear, call, eq = helpers.clear, helpers.call, helpers.eq -local neq, exc_exec = helpers.neq, helpers.exc_exec +local neq, exc_exec, eval = helpers.neq, helpers.exc_exec, helpers.eval describe('Test getting and setting file permissions', function() local tempfile = helpers.tmpname() @@ -14,11 +14,12 @@ describe('Test getting and setting file permissions', function() end) it('file permissions', function() + -- eval() is used to test VimL method syntax for setfperm() and getfperm() eq('', call('getfperm', tempfile)) - eq(0, call('setfperm', tempfile, 'r--------')) + eq(0, eval("'" .. tempfile .. "'->setfperm('r--------')")) call('writefile', {'one'}, tempfile) - eq(9, call('len', call('getfperm', tempfile))) + eq(9, eval("len('" .. tempfile .. "'->getfperm())")) eq(1, call('setfperm', tempfile, 'rwx------')) if helpers.is_os('win') then diff --git a/test/functional/legacy/fnamemodify_spec.lua b/test/functional/legacy/fnamemodify_spec.lua index 7e859bf0cf..6a5538c26f 100644 --- a/test/functional/legacy/fnamemodify_spec.lua +++ b/test/functional/legacy/fnamemodify_spec.lua @@ -60,12 +60,6 @@ describe('filename modifiers', function() call assert_equal("'abc\\\ndef'", fnamemodify("abc\ndef", ':S')) endif endfunc - - func Test_expand() - new - call assert_equal("", expand('%:S')) - quit - endfunc ]=]) end) @@ -73,9 +67,4 @@ describe('filename modifiers', function() call('Test_fnamemodify') expected_empty() end) - - it('works for :S in an unnamed buffer', function() - call('Test_expand') - expected_empty() - end) end) diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index fdf79d55b2..8551c3d2a0 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -194,6 +194,10 @@ describe('luaeval(vim.api.…)', function() exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]])) eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type', exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]])) + + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table', + exc_exec([[call luaeval("vim.api.nvim_set_keymap('', '', '', '')")]])) + -- TODO: check for errors with Tabpage argument -- TODO: check for errors with Window argument -- TODO: check for errors with Buffer argument diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index 073927bf22..c83a50b78b 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -1050,6 +1050,102 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("sends updates on U", function() + feed("ggiAAA<cr>BBB") + feed("<esc>gg$a CCC") + + local check_events = setup_eventcheck(verify, nil) + + feed("ggU") + + check_events { + { "test1", "bytes", 1, 6, 0, 7, 7, 0, 0, 0, 0, 3, 3 }; + } + end) + + it("delete in completely empty buffer", function() + local check_events = setup_eventcheck(verify, nil) + + command "delete" + check_events { } + end) + + it("delete the only line of a buffer", function() + local check_events = setup_eventcheck(verify, {"AAA"}) + + command "delete" + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 4, 1, 0, 1 }; + } + end) + + it("delete the last line of a buffer with two lines", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB"}) + + command "2delete" + check_events { + { "test1", "bytes", 1, 3, 1, 0, 4, 1, 0, 4, 0, 0, 0 }; + } + end) + + it(":sort lines", function() + local check_events = setup_eventcheck(verify, {"CCC", "BBB", "AAA"}) + + command "%sort" + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 3, 0, 12, 3, 0, 12 }; + } + end) + + it("handles already sorted lines", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB", "CCC"}) + + command "%sort" + check_events { } + end) + + local function test_lockmarks(mode) + local description = (mode ~= "") and mode or "(baseline)" + it("test_lockmarks " .. description .. " %delete _", function() + local check_events = setup_eventcheck(verify, {"AAA", "BBB", "CCC"}) + + command(mode .. " %delete _") + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 3, 0, 12, 1, 0, 1 }; + } + end) + + it("test_lockmarks " .. description .. " append()", function() + local check_events = setup_eventcheck(verify) + + command(mode .. " call append(0, 'CCC')") + check_events { + { "test1", "bytes", 1, 2, 0, 0, 0, 0, 0, 0, 1, 0, 4 }; + } + + command(mode .. " call append(1, 'BBBB')") + check_events { + { "test1", "bytes", 1, 3, 1, 0, 4, 0, 0, 0, 1, 0, 5 }; + } + + command(mode .. " call append(2, '')") + check_events { + { "test1", "bytes", 1, 4, 2, 0, 9, 0, 0, 0, 1, 0, 1 }; + } + + command(mode .. " $delete _") + check_events { + { "test1", "bytes", 1, 5, 3, 0, 10, 1, 0, 1, 0, 0, 0 }; + } + + eq("CCC|BBBB|", table.concat(meths.buf_get_lines(0, 0, -1, true), "|")) + end) + end + + -- check that behavior is identical with and without "lockmarks" + test_lockmarks "" + test_lockmarks "lockmarks" + teardown(function() os.remove "Xtest-reload" os.remove "Xtest-undofile" diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 29dd5c60da..1cbfa224cc 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -14,48 +14,32 @@ describe('vim.diagnostic', function() exec_lua [[ require('vim.diagnostic') - function make_error(msg, x1, y1, x2, y2) + function make_diagnostic(msg, x1, y1, x2, y2, severity, source) return { lnum = x1, col = y1, end_lnum = x2, end_col = y2, message = msg, - severity = vim.diagnostic.severity.ERROR, + severity = severity, + source = source, } end - function make_warning(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.WARN, - } + function make_error(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.ERROR, source) end - function make_info(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.INFO, - } + function make_warning(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.WARN, source) end - function make_hint(msg, x1, y1, x2, y2) - return { - lnum = x1, - col = y1, - end_lnum = x2, - end_col = y2, - message = msg, - severity = vim.diagnostic.severity.HINT, - } + function make_info(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.INFO, source) + end + + function make_hint(msg, x1, y1, x2, y2, source) + return make_diagnostic(msg, x1, y1, x2, y2, vim.diagnostic.severity.HINT, source) end function count_diagnostics(bufnr, severity, namespace) @@ -489,6 +473,78 @@ describe('vim.diagnostic', function() end) describe('config()', function() + it('works with global, namespace, and ephemeral options', function() + eq(1, exec_lua [[ + vim.diagnostic.config({ + virtual_text = false, + }) + + vim.diagnostic.config({ + virtual_text = true, + underline = false, + }, diagnostic_ns) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Some Error', 4, 4, 4, 4), + }) + + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + + eq(1, exec_lua [[ + vim.diagnostic.config({ + virtual_text = false, + }) + + vim.diagnostic.config({ + virtual_text = false, + underline = false, + }, diagnostic_ns) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Some Error', 4, 4, 4, 4), + }, {virtual_text = true}) + + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + + eq(0, exec_lua [[ + vim.diagnostic.config({ + virtual_text = false, + }) + + vim.diagnostic.config({ + virtual_text = {severity=vim.diagnostic.severity.ERROR}, + underline = false, + }, diagnostic_ns) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Some Warning', 4, 4, 4, 4), + }, {virtual_text = true}) + + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + + eq(1, exec_lua [[ + vim.diagnostic.config({ + virtual_text = false, + }) + + vim.diagnostic.config({ + virtual_text = {severity=vim.diagnostic.severity.ERROR}, + underline = false, + }, diagnostic_ns) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Some Warning', 4, 4, 4, 4), + }, { + virtual_text = {} -- An empty table uses default values + }) + + return count_extmarks(diagnostic_bufnr, diagnostic_ns) + ]]) + end) + it('can use functions for config values', function() exec_lua [[ vim.diagnostic.config({ @@ -590,6 +646,118 @@ describe('vim.diagnostic', function() eq({'Error', 'Warn', 'Info'}, result[1]) eq({'Info', 'Warn', 'Error'}, result[2]) end) + + it('can show diagnostic sources in virtual text', function() + local result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'always', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' source x: Some error', result) + + result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }, diagnostic_ns) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = extmarks[1][4].virt_text[2][1] + return virt_text + ]] + eq(' Some error', result) + + result = exec_lua [[ + local diagnostics = { + make_error('Some error', 0, 0, 0, 0, 'source x'), + make_error('Another error', 1, 1, 1, 1, 'source y'), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + source = 'if_many', + } + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + local virt_text = {extmarks[1][4].virt_text[2][1], extmarks[2][4].virt_text[2][1]} + return virt_text + ]] + eq(' source x: Some error', result[1]) + eq(' source y: Another error', result[2]) + end) + + it('supports a format function for diagnostic messages', function() + local result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + format = function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("🔥 %s", diagnostic.message) + end + return string.format("👀 %s", diagnostic.message) + end, + } + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Warning', 0, 0, 0, 0), + make_error('Error', 1, 0, 1, 0), + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} + ]] + eq(" 👀 Warning", result[1][2][1]) + eq(" 🔥 Error", result[2][2][1]) + end) + + it('includes source for formatted diagnostics', function() + local result = exec_lua [[ + vim.diagnostic.config({ + underline = false, + virtual_text = { + prefix = '', + source = 'always', + format = function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return string.format("🔥 %s", diagnostic.message) + end + return string.format("👀 %s", diagnostic.message) + end, + } + }) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_warning('Warning', 0, 0, 0, 0, 'some_linter'), + make_error('Error', 1, 0, 1, 0, 'another_linter'), + }) + + local extmarks = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, diagnostic_ns, 0, -1, {details = true}) + return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} + ]] + eq(" some_linter: 👀 Warning", result[1][2][1]) + eq(" another_linter: 🔥 Error", result[2][2][1]) + end) end) describe('set()', function() @@ -778,10 +946,124 @@ describe('vim.diagnostic', function() return count_extmarks(diagnostic_bufnr, diagnostic_ns) ]]) end) + + it('sets signs', function() + local result = exec_lua [[ + vim.diagnostic.config({ + signs = true, + }) + + local diagnostics = { + make_error('Error', 1, 1, 1, 2), + make_warning('Warning', 3, 3, 3, 3), + } + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + + return vim.fn.sign_getplaced(diagnostic_bufnr, {group = '*'})[1].signs + ]] + + eq({2, 'DiagnosticSignError'}, {result[1].lnum, result[1].name}) + eq({4, 'DiagnosticSignWarn'}, {result[2].lnum, result[2].name}) + end) end) - describe('show_line_diagnostics()', function() - it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function() + describe('open_float()', function() + it('can show diagnostics from the whole buffer', function() + eq({'1. Syntax error', '2. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + make_warning("Some warning", 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header = false}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) + + it('can show diagnostics from a single line', function() + -- Using cursor position + eq({'1. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + make_warning("Some warning", 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, {2, 1}) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="line"}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + -- With specified position + eq({'1. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + make_warning("Some warning", 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, {1, 1}) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="line", pos=1}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) + + it('can show diagnostics from a specific position', function() + -- Using cursor position + eq({'1. Syntax error'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 1, 1, 1, 2), + make_warning("Some warning", 1, 3, 1, 4), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, {2, 2}) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="cursor"}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + -- With specified position + eq({'1. Some warning'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 1, 1, 1, 2), + make_warning("Some warning", 1, 3, 1, 4), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, {1, 1}) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="cursor", pos={1,3}}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + -- With column position past the end of the line. #16062 + eq({'1. Syntax error'}, exec_lua [[ + local first_line_len = #vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, 1, true)[1] + local diagnostics = { + make_error("Syntax error", 0, first_line_len + 1, 1, 0), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, {1, 1}) + local float_bufnr, winnr = vim.diagnostic.open_float(0, {show_header=false, scope="cursor", pos={0,first_line_len}}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) + + it('creates floating window and returns float bufnr and winnr if current line contains diagnostics', function() -- Two lines: -- Diagnostic: -- 1. <msg> @@ -791,8 +1073,10 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() - return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {scope="line"}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines ]]) end) @@ -808,8 +1092,10 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, buf_1_diagnostics) vim.diagnostic.set(other_ns, other_bufnr, buf_2_diagnostics) - local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics() - return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + local float_bufnr, winnr = vim.diagnostic.open_float() + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines ]]) end) @@ -824,12 +1110,14 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diagnostics) vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diagnostics) - local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics({namespace = diagnostic_ns}) - return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {namespace = diagnostic_ns}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines ]]) end) - it('creates floating window and returns popup bufnr and winnr without header, if requested', function() + it('creates floating window and returns float bufnr and winnr without header, if requested', function() -- One line (since no header): -- 1. <msg> eq(1, exec_lua [[ @@ -838,39 +1126,105 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local popup_bufnr, winnr = vim.diagnostic.show_line_diagnostics {show_header = false} - return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {show_header = false}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines ]]) end) - end) - describe('set_signs()', function() - -- TODO(tjdevries): Find out why signs are not displayed when set from Lua...?? - pending('sets signs by default', function() - exec_lua [[ - vim.diagnostic.config({ - update_in_insert = true, - signs = true, + it('clamps diagnostic line numbers within the valid range', function() + eq(1, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 6, 0, 6, 0), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {show_header = false, scope = "line", pos = 5}) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + ]]) + end) + + it('can show diagnostic source', function() + exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + + eq({"1. Syntax error"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3, "source x"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { + show_header = false, + source = "if_many", }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. source x: Syntax error"}, exec_lua [[ + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { + show_header = false, + source = "always", + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + eq({"1. source x: Syntax error", "2. source y: Another error"}, exec_lua [[ local diagnostics = { - make_error('Delayed Diagnostic', 1, 1, 1, 2), - make_error('Delayed Diagnostic', 3, 3, 3, 3), + make_error("Syntax error", 0, 1, 0, 3, "source x"), + make_error("Another error", 0, 1, 0, 3, "source y"), + } + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { + show_header = false, + source = "if_many", + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + end) + + it('respects severity_sort', function() + exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + + eq({"1. Syntax error", "2. Info", "3. Error", "4. Warning"}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + make_info('Info', 0, 3, 0, 4), + make_error('Error', 0, 2, 0, 2), + make_warning('Warning', 0, 0, 0, 1), } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.diagnostic._set_signs(diagnostic_ns, diagnostic_bufnr, diagnostics) - -- return vim.fn.sign_getplaced() - ]] + vim.diagnostic.config({severity_sort = false}) - nvim("input", "o") - nvim("input", "<esc>") + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) + + eq({"1. Syntax error", "2. Error", "3. Warning", "4. Info"}, exec_lua [[ + vim.diagnostic.config({severity_sort = true}) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) - -- TODO(tjdevries): Find a way to get the signs to display in the test... - eq(nil, exec_lua [[ - return im.fn.sign_getplaced()[1].signs + eq({"1. Info", "2. Warning", "3. Error", "4. Syntax error"}, exec_lua [[ + vim.diagnostic.config({severity_sort = { reverse = true } }) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { show_header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines ]]) end) end) diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua new file mode 100644 index 0000000000..80c01a2b8c --- /dev/null +++ b/test/functional/lua/ffi_spec.lua @@ -0,0 +1,62 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local clear = helpers.clear + +before_each(clear) + +describe('ffi.cdef', function() + it('can use Neovim core functions', function() + if not exec_lua("return pcall(require, 'ffi')") then + pending('missing LuaJIT FFI') + end + + eq(12, exec_lua[[ + local ffi = require('ffi') + + ffi.cdef('int curwin_col_off(void);') + + vim.cmd('set number numberwidth=4 signcolumn=yes:4') + + return ffi.C.curwin_col_off() + ]]) + + eq(20, exec_lua[=[ + local ffi = require('ffi') + + ffi.cdef[[ + typedef unsigned char char_u; + typedef struct window_S win_T; + typedef struct {} stl_hlrec_t; + typedef struct {} StlClickRecord; + typedef struct {} Error; + + win_T *find_window_by_handle(int Window, Error *err); + + int build_stl_str_hl( + win_T *wp, + char_u *out, + size_t outlen, + char_u *fmt, + int use_sandbox, + char_u fillchar, + int maxwidth, + stl_hlrec_t **hltab, + StlClickRecord **tabtab + ); + ]] + + return ffi.C.build_stl_str_hl( + ffi.C.find_window_by_handle(0, ffi.new('Error')), + ffi.new('char_u[1024]'), + 1024, + ffi.cast('char_u*', 'StatusLineOfLength20'), + 0, + 0, + 0, + nil, + nil + ) + ]=]) + end) +end) diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua new file mode 100644 index 0000000000..fbb21bfd57 --- /dev/null +++ b/test/functional/lua/json_spec.lua @@ -0,0 +1,133 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local NIL = helpers.NIL +local exec_lua = helpers.exec_lua +local eq = helpers.eq + +describe('vim.json.decode function', function() + before_each(function() + clear() + end) + + it('parses null, true, false', function() + eq(NIL, exec_lua([[return vim.json.decode('null')]])) + eq(true, exec_lua([[return vim.json.decode('true')]])) + eq(false, exec_lua([[return vim.json.decode('false')]])) + end) + + it('parses integer numbers', function() + eq(100000, exec_lua([[return vim.json.decode('100000')]])) + eq(-100000, exec_lua([[return vim.json.decode('-100000')]])) + eq(100000, exec_lua([[return vim.json.decode(' 100000 ')]])) + eq(-100000, exec_lua([[return vim.json.decode(' -100000 ')]])) + eq(0, exec_lua([[return vim.json.decode('0')]])) + eq(0, exec_lua([[return vim.json.decode('-0')]])) + end) + + it('parses floating-point numbers', function() + -- This behavior differs from vim.fn.json_decode, which return '100000.0' + eq('100000', exec_lua([[return tostring(vim.json.decode('100000.0'))]])) + eq(100000.5, exec_lua([[return vim.json.decode('100000.5')]])) + eq(-100000.5, exec_lua([[return vim.json.decode('-100000.5')]])) + eq(-100000.5e50, exec_lua([[return vim.json.decode('-100000.5e50')]])) + eq(100000.5e50, exec_lua([[return vim.json.decode('100000.5e50')]])) + eq(100000.5e50, exec_lua([[return vim.json.decode('100000.5e+50')]])) + eq(-100000.5e-50, exec_lua([[return vim.json.decode('-100000.5e-50')]])) + eq(100000.5e-50, exec_lua([[return vim.json.decode('100000.5e-50')]])) + eq(100000e-50, exec_lua([[return vim.json.decode('100000e-50')]])) + eq(0.5, exec_lua([[return vim.json.decode('0.5')]])) + eq(0.005, exec_lua([[return vim.json.decode('0.005')]])) + eq(0.005, exec_lua([[return vim.json.decode('0.00500')]])) + eq(0.5, exec_lua([[return vim.json.decode('0.00500e+002')]])) + eq(0.00005, exec_lua([[return vim.json.decode('0.00500e-002')]])) + + eq(-0.0, exec_lua([[return vim.json.decode('-0.0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e+0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0.0e-0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e-0')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e-2')]])) + eq(-0.0, exec_lua([[return vim.json.decode('-0e+2')]])) + + eq(0.0, exec_lua([[return vim.json.decode('0.0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e+0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0.0e-0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e-0')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e-2')]])) + eq(0.0, exec_lua([[return vim.json.decode('0e+2')]])) + end) + + it('parses containers', function() + eq({1}, exec_lua([[return vim.json.decode('[1]')]])) + eq({NIL, 1}, exec_lua([[return vim.json.decode('[null, 1]')]])) + eq({['1']=2}, exec_lua([[return vim.json.decode('{"1": 2}')]])) + eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}}, + exec_lua([[return vim.json.decode('{"1": 2, "3": [{"4": {"5": [ [], 1]}}]}')]])) + end) + + it('parses strings properly', function() + eq('\n', exec_lua([=[return vim.json.decode([["\n"]])]=])) + eq('', exec_lua([=[return vim.json.decode([[""]])]=])) + eq('\\/"\t\b\n\r\f', exec_lua([=[return vim.json.decode([["\\\/\"\t\b\n\r\f"]])]=])) + eq('/a', exec_lua([=[return vim.json.decode([["\/a"]])]=])) + -- Unicode characters: 2-byte, 3-byte + eq('«',exec_lua([=[return vim.json.decode([["«"]])]=])) + eq('ફ',exec_lua([=[return vim.json.decode([["ફ"]])]=])) + end) + + it('parses surrogate pairs properly', function() + eq('\240\144\128\128', exec_lua([[return vim.json.decode('"\\uD800\\uDC00"')]])) + end) + + it('accepts all spaces in every position where space may be put', function() + local s = ' \t\n\r \t\r\n \n\t\r \n\r\t \r\t\n \r\n\t\t \n\r\t \r\n\t\n \r\t\n\r \t\r \n\t\r\n \n \t\r\n \r\t\n\t \r\n\t\r \n\r \t\n\r\t \r \t\n\r \n\t\r\t \n\r\t\n \r\n \t\r\n\t' + local str = ('%s{%s"key"%s:%s[%s"val"%s,%s"val2"%s]%s,%s"key2"%s:%s1%s}%s'):gsub('%%s', s) + eq({key={'val', 'val2'}, key2=1}, exec_lua([[return vim.json.decode(...)]], str)) + end) + +end) + +describe('vim.json.encode function', function() + before_each(function() + clear() + end) + + it('dumps strings', function() + eq('"Test"', exec_lua([[return vim.json.encode('Test')]])) + eq('""', exec_lua([[return vim.json.encode('')]])) + eq('"\\t"', exec_lua([[return vim.json.encode('\t')]])) + eq('"\\n"', exec_lua([[return vim.json.encode('\n')]])) + -- vim.fn.json_encode return \\u001B + eq('"\\u001b"', exec_lua([[return vim.json.encode('\27')]])) + eq('"þÿþ"', exec_lua([[return vim.json.encode('þÿþ')]])) + end) + + it('dumps numbers', function() + eq('0', exec_lua([[return vim.json.encode(0)]])) + eq('10', exec_lua([[return vim.json.encode(10)]])) + eq('-10', exec_lua([[return vim.json.encode(-10)]])) + end) + + it('dumps floats', function() + eq('10.5', exec_lua([[return vim.json.encode(10.5)]])) + eq('-10.5', exec_lua([[return vim.json.encode(-10.5)]])) + eq('-1e-05', exec_lua([[return vim.json.encode(-1e-5)]])) + end) + + it('dumps lists', function() + eq('[]', exec_lua([[return vim.json.encode({})]])) + eq('[[]]', exec_lua([[return vim.json.encode({{}})]])) + eq('[[],[]]', exec_lua([[return vim.json.encode({{}, {}})]])) + end) + + it('dumps dictionaries', function() + eq('{}', exec_lua([[return vim.json.encode(vim.empty_dict())]])) + eq('{"d":[]}', exec_lua([[return vim.json.encode({d={}})]])) + end) + + it('dumps vim.NIL', function() + eq('null', exec_lua([[return vim.json.encode(vim.NIL)]])) + end) + +end) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 255e99032f..0675ec9abd 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -86,14 +86,15 @@ describe('luaeval()', function() -- meaningful later. it('correctly evaluates scalars', function() + -- Also test method call (->) syntax eq(1, funcs.luaeval('1')) - eq(0, eval('type(luaeval("1"))')) + eq(0, eval('"1"->luaeval()->type()')) eq(1.5, funcs.luaeval('1.5')) - eq(5, eval('type(luaeval("1.5"))')) + eq(5, eval('"1.5"->luaeval()->type()')) eq("test", funcs.luaeval('"test"')) - eq(1, eval('type(luaeval("\'test\'"))')) + eq(1, eval('"\'test\'"->luaeval()->type()')) eq('', funcs.luaeval('""')) eq('\000', funcs.luaeval([['\0']])) diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua new file mode 100644 index 0000000000..94f1b5840b --- /dev/null +++ b/test/functional/lua/ui_spec.lua @@ -0,0 +1,46 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local clear = helpers.clear + +describe('vim.ui', function() + before_each(function() + clear() + end) + + + describe('select', function() + it('can select an item', function() + local result = exec_lua[[ + local items = { + { name = 'Item 1' }, + { name = 'Item 2' }, + } + local opts = { + format_item = function(entry) + return entry.name + end + } + local selected + local cb = function(item) + selected = item + end + -- inputlist would require input and block the test; + local choices + vim.fn.inputlist = function(x) + choices = x + return 1 + end + vim.ui.select(items, opts, cb) + vim.wait(100, function() return selected ~= nil end) + return {selected, choices} + ]] + eq({ name = 'Item 1' }, result[1]) + eq({ + 'Select one of:', + '1: Item 1', + '2: Item 2', + }, result[2]) + end) + end) +end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 052a8a1ecd..81f1820986 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local exec_lua = helpers.exec_lua local eq = helpers.eq +local write_file = require('test.helpers').write_file describe('URI methods', function() before_each(function() @@ -158,6 +159,22 @@ describe('URI methods', function() end) + describe('uri from bufnr', function() + it('Windows paths should not be treated as uris', function() + if not helpers.iswin() then return end + + local file = helpers.tmpname() + write_file(file, 'Test content') + local test_case = string.format([[ + local file = '%s' + return vim.uri_from_bufnr(vim.fn.bufadd(file)) + ]], file) + local expected_uri = 'file:///' .. file:gsub("\\", "/") + eq(expected_uri, exec_lua(test_case)) + os.remove(file) + end) + end) + describe('uri to bufnr', function() it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function() local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class' diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index f3d265cb92..3123063b8c 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -237,29 +237,34 @@ describe('lua stdlib', function() end) it("vim.split", function() - local split = function(str, sep, plain) - return exec_lua('return vim.split(...)', str, sep, plain) + local split = function(str, sep, kwargs) + return exec_lua('return vim.split(...)', str, sep, kwargs) end local tests = { - { "a,b", ",", false, { 'a', 'b' } }, - { ":aa::bb:", ":", false, { '', 'aa', '', 'bb', '' } }, - { "::ee::ff:", ":", false, { '', '', 'ee', '', 'ff', '' } }, - { "ab", ".", false, { '', '', '' } }, - { "a1b2c", "[0-9]", false, { 'a', 'b', 'c' } }, - { "xy", "", false, { 'x', 'y' } }, - { "here be dragons", " ", false, { "here", "be", "dragons"} }, - { "axaby", "ab?", false, { '', 'x', 'y' } }, - { "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } }, - { "", "", false, {} }, - { "", "a", false, { '' } }, - { "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } }, + { "a,b", ",", false, false, { 'a', 'b' } }, + { ":aa::bb:", ":", false, false, { '', 'aa', '', 'bb', '' } }, + { ":aa::bb:", ":", false, true, { 'aa', '', 'bb' } }, + { "::ee::ff:", ":", false, false, { '', '', 'ee', '', 'ff', '' } }, + { "::ee::ff:", ":", false, true, { 'ee', '', 'ff' } }, + { "ab", ".", false, false, { '', '', '' } }, + { "a1b2c", "[0-9]", false, false, { 'a', 'b', 'c' } }, + { "xy", "", false, false, { 'x', 'y' } }, + { "here be dragons", " ", false, false, { "here", "be", "dragons"} }, + { "axaby", "ab?", false, false, { '', 'x', 'y' } }, + { "f v2v v3v w2w ", "([vw])2%1", false, false, { 'f ', ' v3v ', ' ' } }, + { "", "", false, false, {} }, + { "", "a", false, false, { '' } }, + { "x*yz*oo*l", "*", true, false, { 'x', 'yz', 'oo', 'l' } }, } for _, t in ipairs(tests) do - eq(t[4], split(t[1], t[2], t[3])) + eq(t[5], split(t[1], t[2], {plain=t[3], trimempty=t[4]})) end + -- Test old signature + eq({'x', 'yz', 'oo', 'l'}, split("x*yz*oo*l", "*", true)) + local loops = { { "abc", ".-" }, } @@ -283,9 +288,8 @@ describe('lua stdlib', function() vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(split, 'string', 1)) eq(dedent([[ - Error executing lua: vim/shared.lua:0: plain: expected boolean, got number + Error executing lua: vim/shared.lua:0: kwargs: expected table, got number stack traceback: - vim/shared.lua:0: in function 'gsplit' vim/shared.lua:0: in function <vim/shared.lua:0>]]), pcall_err(split, 'string', 'string', 1)) end) @@ -992,6 +996,9 @@ describe('lua stdlib', function() vim.g.to_delete = nil ]] eq(NIL, funcs.luaeval "vim.g.to_delete") + + matches([[^Error executing lua: .*: attempt to index .* nil value]], + pcall_err(exec_lua, 'return vim.g[0].testing')) end) it('vim.b', function() @@ -1001,18 +1008,25 @@ describe('lua stdlib', function() vim.api.nvim_buf_set_var(0, "floaty", 5120.1) vim.api.nvim_buf_set_var(0, "nullvar", vim.NIL) vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"}) + BUF = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_var(BUF, "testing", "bye") ]] eq('hi', funcs.luaeval "vim.b.testing") + eq('bye', funcs.luaeval "vim.b[BUF].testing") eq(123, funcs.luaeval "vim.b.other") eq(5120.1, funcs.luaeval "vim.b.floaty") eq(NIL, funcs.luaeval "vim.b.nonexistant") + eq(NIL, funcs.luaeval "vim.b[BUF].nonexistant") eq(NIL, funcs.luaeval "vim.b.nullvar") -- lost over RPC, so test locally: eq({false, true}, exec_lua [[ return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} ]]) + matches([[^Error executing lua: .*: attempt to index .* nil value]], + pcall_err(exec_lua, 'return vim.b[BUF][0].testing')) + eq({hello="world"}, funcs.luaeval "vim.b.to_delete") exec_lua [[ vim.b.to_delete = nil @@ -1033,11 +1047,22 @@ describe('lua stdlib', function() vim.api.nvim_win_set_var(0, "testing", "hi") vim.api.nvim_win_set_var(0, "other", 123) vim.api.nvim_win_set_var(0, "to_delete", {hello="world"}) + BUF = vim.api.nvim_create_buf(false, true) + WIN = vim.api.nvim_open_win(BUF, false, { + width=10, height=10, + relative='win', row=0, col=0 + }) + vim.api.nvim_win_set_var(WIN, "testing", "bye") ]] eq('hi', funcs.luaeval "vim.w.testing") + eq('bye', funcs.luaeval "vim.w[WIN].testing") eq(123, funcs.luaeval "vim.w.other") eq(NIL, funcs.luaeval "vim.w.nonexistant") + eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") + + matches([[^Error executing lua: .*: attempt to index .* nil value]], + pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) eq({hello="world"}, funcs.luaeval "vim.w.to_delete") exec_lua [[ @@ -1064,6 +1089,12 @@ describe('lua stdlib', function() eq('hi', funcs.luaeval "vim.t.testing") eq(123, funcs.luaeval "vim.t.other") eq(NIL, funcs.luaeval "vim.t.nonexistant") + eq('hi', funcs.luaeval "vim.t[0].testing") + 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]], + pcall_err(exec_lua, 'return vim.t[0][0].testing')) eq({hello="world"}, funcs.luaeval "vim.t.to_delete") exec_lua [[ @@ -1092,6 +1123,8 @@ 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]], + pcall_err(exec_lua, 'return vim.v[0].progpath')) end) it('vim.bo', function() @@ -2255,7 +2288,7 @@ end) describe('lua: require("mod") from packages', function() before_each(function() - command('set rtp+=test/functional/fixtures') + command('set rtp+=test/functional/fixtures pp+=test/functional/fixtures') end) it('propagates syntax error', function() @@ -2266,4 +2299,13 @@ describe('lua: require("mod") from packages', function() matches("unexpected symbol", syntax_error_msg) end) + + it('uses the right order of mod.lua vs mod/init.lua', function() + -- lua/fancy_x.lua takes precedence over lua/fancy_x/init.lua + eq('I am fancy_x.lua', exec_lua [[ return require'fancy_x' ]]) + -- but lua/fancy_y/init.lua takes precedence over after/lua/fancy_y.lua + eq('I am init.lua of fancy_y!', exec_lua [[ return require'fancy_y' ]]) + -- safety check: after/lua/fancy_z.lua is still loaded + eq('I am fancy_z.lua', exec_lua [[ return require'fancy_z' ]]) + end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 85c67be8f9..b84e9d1533 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_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') local clear = helpers.clear @@ -35,6 +36,7 @@ describe(':checkhealth', function() clear() eq('nvim', getcompletion('nvim', 'checkhealth')[1]) eq('provider', getcompletion('prov', 'checkhealth')[1]) + eq('vim.lsp', getcompletion('vim.ls', 'checkhealth')[1]) end) end) @@ -48,42 +50,34 @@ describe('health.vim', function() command("set runtimepath+=test/functional/fixtures") end) - it("health#report_*()", function() - helpers.source([[ - let g:health_report = execute([ - \ "call health#report_start('Check Bar')", - \ "call health#report_ok('Bar status')", - \ "call health#report_ok('Other Bar status')", - \ "call health#report_warn('Zub')", - \ "call health#report_start('Baz')", - \ "call health#report_warn('Zim', ['suggestion 1', 'suggestion 2'])" - \ ]) - ]]) - local result = helpers.eval("g:health_report") - - helpers.eq(helpers.dedent([[ - - - ## Check Bar - - OK: Bar status - - OK: Other Bar status - - WARNING: Zub - - ## Baz - - WARNING: Zim + describe(":checkhealth", function() + it("functions health#report_*() render correctly", function() + command("checkhealth full_render") + helpers.expect([[ + + full_render: health#full_render#check + ======================================================================== + ## report 1 + - OK: life is fine + - WARNING: no what installed - ADVICE: - - suggestion 1 - - suggestion 2]]), - result) - end) + - pip what + - make what + ## report 2 + - INFO: stuff is stable + - ERROR: why no hardcopy + - ADVICE: + - :help |:hardcopy| + - :help |:TOhtml| + ]]) + end) - describe(":checkhealth", function() it("concatenates multiple reports", function() - command("checkhealth success1 success2") + command("checkhealth success1 success2 test_plug") helpers.expect([[ - health#success1#check + success1: health#success1#check ======================================================================== ## report 1 - OK: everything is fine @@ -91,25 +85,111 @@ describe('health.vim', function() ## report 2 - OK: nothing to see here - health#success2#check + success2: health#success2#check ======================================================================== ## another 1 - OK: ok + + test_plug: require("test_plug.health").check() + ======================================================================== + ## report 1 + - OK: everything is fine + + ## report 2 + - OK: nothing to see here + ]]) + end) + + it("lua plugins, skips vimscript healthchecks with the same name", function() + command("checkhealth test_plug") + -- Existing file in test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim + -- and the Lua healthcheck is used instead. + helpers.expect([[ + + test_plug: require("test_plug.health").check() + ======================================================================== + ## report 1 + - OK: everything is fine + + ## report 2 + - OK: nothing to see here ]]) end) + it("lua plugins submodules", function() + command("checkhealth test_plug.submodule") + helpers.expect([[ + + test_plug.submodule: require("test_plug.submodule.health").check() + ======================================================================== + ## report 1 + - OK: everything is fine + + ## report 2 + - OK: nothing to see here + ]]) + end) + + it("lua plugins submodules with expression '*'", function() + command("checkhealth test_plug*") + local buf_lines = helpers.curbuf('get_lines', 0, -1, true) + -- avoid dealing with path separators + local received = table.concat(buf_lines, '\n', 1, #buf_lines - 2) + local expected = helpers.dedent([[ + + test_plug: require("test_plug.health").check() + ======================================================================== + ## report 1 + - OK: everything is fine + + ## report 2 + - OK: nothing to see here + + test_plug.submodule: require("test_plug.submodule.health").check() + ======================================================================== + ## report 1 + - OK: everything is fine + + ## report 2 + - OK: nothing to see here + + 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]]) + eq(expected, received) + end) + it("gracefully handles broken healthcheck", function() command("checkhealth broken") helpers.expect([[ - health#broken#check + broken: health#broken#check ======================================================================== - ERROR: Failed to run healthcheck for "broken" plugin. Exception: - function health#check[21]..health#broken#check, line 1 + function health#check[24]..health#broken#check, line 1 caused an error ]]) end) + it("gracefully handles broken lua healthcheck", function() + command("checkhealth test_plug.submodule_failed") + local buf_lines = helpers.curbuf('get_lines', 0, -1, true) + local received = table.concat(buf_lines, '\n', 1, #buf_lines - 2) + -- avoid dealing with path separators + local lua_err = "attempt to perform arithmetic on a nil value" + local last_line = buf_lines[#buf_lines - 1] + assert(string.find(last_line, lua_err) ~= nil, "Lua error not present") + + local expected = global_helpers.dedent([[ + + 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]]) + eq(expected, received) + end) + it("highlights OK, ERROR", function() local screen = Screen.new(72, 10) screen:attach() @@ -126,23 +206,24 @@ describe('health.vim', function() command("set laststatus=0") screen:expect{grid=[[ ^ | - {Heading:health#foo#check} | + {Heading:foo: } | {Bar:========================================================================}| - {Bullet: -} {Error:ERROR:} No healthcheck found for "foo" plugin. | + {Bullet: -} {Error:ERROR}: No healthcheck found for "foo" plugin. | | - {Heading:health#success1#check} | + {Heading:success1: health#success1#check} | {Bar:========================================================================}| {Heading2:##}{Heading: report 1} | - {Bullet: -} {Ok:OK:} everything is fine | + {Bullet: -} {Ok:OK}: everything is fine | | ]]} end) it("gracefully handles invalid healthcheck", function() command("checkhealth non_existent_healthcheck") + -- luacheck: ignore 613 helpers.expect([[ - health#non_existent_healthcheck#check + non_existent_healthcheck: ======================================================================== - ERROR: No healthcheck found for "non_existent_healthcheck" plugin. ]]) diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua index e48a0ad260..c8b75e65fc 100644 --- a/test/functional/plugin/lsp/codelens_spec.lua +++ b/test/functional/plugin/lsp/codelens_spec.lua @@ -58,5 +58,33 @@ describe('vim.lsp.codelens', function() ]], bufnr) eq({[1] = {'Lens1', 'LspCodeLens'}}, virtual_text_chunks) + + end) + it('codelens uses client commands', function() + local fake_uri = "file:///fake/uri" + local cmd = exec_lua([[ + fake_uri = ... + local bufnr = vim.uri_to_bufnr(fake_uri) + vim.fn.bufload(bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'}) + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 8 } + }, + command = { title = 'Lens1', command = 'Dummy' } + }, + } + vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) + local cmd_called = nil + vim.lsp.commands['Dummy'] = function(command) + cmd_called = command + end + vim.api.nvim_set_current_buf(bufnr) + vim.lsp.codelens.run() + return cmd_called + ]], fake_uri) + eq({ command = 'Dummy', title = 'Lens1' }, cmd) end) end) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 353de667ea..243ad6bdb8 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -428,6 +428,32 @@ describe('vim.lsp.diagnostic', function() end) end) end) + + it('maintains LSP information when translating diagnostics', function() + local result = exec_lua [[ + local diagnostics = { + make_error("Error 1", 1, 1, 1, 5), + } + + diagnostics[1].code = 42 + diagnostics[1].tags = {"foo", "bar"} + diagnostics[1].data = "Hello world" + + vim.lsp.diagnostic.on_publish_diagnostics(nil, { + uri = fake_uri, + diagnostics = diagnostics, + }, {client_id=1}) + + return { + vim.diagnostic.get(diagnostic_bufnr, {lnum=1})[1], + vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)[1], + } + ]] + eq({code = 42, tags = {"foo", "bar"}, data = "Hello world"}, result[1].user_data.lsp) + eq(42, result[2].code) + eq({"foo", "bar"}, result[2].tags) + eq("Hello world", result[2].data) + end) end) describe("vim.lsp.diagnostic.get_line_diagnostics", function() diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 6ad37110c7..ce50abb50d 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -132,37 +132,38 @@ local function test_rpc_server(config) end describe('LSP', function() - describe('server_name specified', function() - before_each(function() - clear_notrace() - -- Run an instance of nvim on the file which contains our "scripts". - -- Pass TEST_NAME to pick the script. - local test_name = "basic_init" - exec_lua([=[ - lsp = require('vim.lsp') - local test_name, fixture_filename, logfile = ... - function test__start_client() - return lsp.start_client { - cmd_env = { - NVIM_LOG_FILE = logfile; - }; - cmd = { - vim.v.progpath, '-Es', '-u', 'NONE', '--headless', - "-c", string.format("lua TEST_NAME = %q", test_name), - "-c", "luafile "..fixture_filename; - }; - root_dir = vim.loop.cwd(); - } - end - TEST_CLIENT1 = test__start_client() - ]=], test_name, fake_lsp_code, fake_lsp_logfile) - end) + before_each(function() + clear_notrace() - after_each(function() - exec_lua("lsp._vim_exit_handler()") - -- exec_lua("lsp.stop_all_clients(true)") - end) + -- Run an instance of nvim on the file which contains our "scripts". + -- Pass TEST_NAME to pick the script. + local test_name = "basic_init" + exec_lua([=[ + lsp = require('vim.lsp') + local test_name, fixture_filename, logfile = ... + function test__start_client() + return lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = logfile; + }; + cmd = { + vim.v.progpath, '-Es', '-u', 'NONE', '--headless', + "-c", string.format("lua TEST_NAME = %q", test_name), + "-c", "luafile "..fixture_filename; + }; + root_dir = vim.loop.cwd(); + } + end + TEST_CLIENT1 = test__start_client() + ]=], test_name, fake_lsp_code, fake_lsp_logfile) + end) + after_each(function() + exec_lua("lsp._vim_exit_handler()") + -- exec_lua("lsp.stop_all_clients(true)") + end) + + describe('server_name specified', function() it('start_client(), stop_client()', function() retry(nil, 4000, function() eq(1, exec_lua('return #lsp.get_active_clients()')) @@ -334,7 +335,6 @@ describe('LSP', function() } end) it('workspace/configuration returns NIL per section if client was started without config.settings', function() - clear_notrace() fake_lsp_server_setup('workspace/configuration no settings') eq({ NIL, NIL, }, exec_lua [[ local result = { @@ -405,7 +405,7 @@ describe('LSP', function() } end) - it('should call unsupported_method when trying to call an unsupported method', function() + it('should not call unsupported_method when trying to call an unsupported method', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; } @@ -415,24 +415,12 @@ describe('LSP', function() exec_lua([=[ BUFFER = vim.api.nvim_get_current_buf() lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.lsp.handlers['textDocument/typeDefinition'] = function(err, result, ctx) - local method = ctx.method - vim.lsp._last_lsp_handler = { err = err; method = method } - end - vim.lsp._unsupported_method = function(method) - vim.lsp._last_unsupported_method = method - return 'fake-error' - end - vim.lsp.buf.type_definition() + vim.lsp.handlers['textDocument/typeDefinition'] = function() end ]=]) end; on_init = function(client) client.stop() - local method = exec_lua("return vim.lsp._last_unsupported_method") - eq("textDocument/typeDefinition", method) - local lsp_cb_call = exec_lua("return vim.lsp._last_lsp_handler") - eq("fake-error", lsp_cb_call.err) - eq("textDocument/typeDefinition", lsp_cb_call.method) + exec_lua("vim.lsp.buf.type_definition()") exec_lua [[ vim.api.nvim_command(BUFFER.."bwipeout") ]] @@ -447,7 +435,7 @@ describe('LSP', function() } end) - it('shouldn\'t call unsupported_method when no client and trying to call an unsupported method', function() + it('should not call unsupported_method when no client and trying to call an unsupported method', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; } @@ -455,20 +443,12 @@ describe('LSP', function() test_name = "capabilities_for_client_supports_method"; on_setup = function() exec_lua([=[ - vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method) - vim.lsp._last_lsp_handler = { err = err; method = method } - end - vim.lsp._unsupported_method = function(method) - vim.lsp._last_unsupported_method = method - return 'fake-error' - end - vim.lsp.buf.type_definition() + vim.lsp.handlers['textDocument/typeDefinition'] = function() end ]=]) end; on_init = function(client) client.stop() - eq(NIL, exec_lua("return vim.lsp._last_unsupported_method")) - eq(NIL, exec_lua("return vim.lsp._last_lsp_handler")) + exec_lua("vim.lsp.buf.type_definition()") end; on_exit = function(code, signal) eq(0, code, "exit code", fake_lsp_logfile) @@ -480,6 +460,55 @@ describe('LSP', function() } end) + it('should not forward RequestCancelled to callback', function() + local expected_handlers = { + {NIL, {}, {method="finish", client_id=1}}; + } + local client + test_rpc_server { + test_name = "check_forward_request_cancelled"; + on_init = function(_client) + _client.request("error_code_test") + client = _client + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, #expected_handlers, "did not call expected handler") + end; + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), {err, {}, ctx}, "expected handler") + if ctx.method == 'finish' then client.stop() end + end; + } + end) + + it('should forward ContentModified to callback', function() + local expected_handlers = { + {NIL, {}, {method="finish", client_id=1}}; + {{code = -32801}, NIL, {method = "error_code_test", client_id=1}}; + } + local client + test_rpc_server { + test_name = "check_forward_content_modified"; + on_init = function(_client) + _client.request("error_code_test") + client = _client + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, #expected_handlers, "did not call expected handler") + end; + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), {err, _, ctx}, "expected handler") + -- if ctx.method == 'error_code_test' then client.notify("finish") end + if ctx.method ~= 'finish' then client.notify('finish') end + if ctx.method == 'finish' then client.stop() end + end; + } + end) + it('should not send didOpen if the buffer closes before init', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; @@ -1135,10 +1164,11 @@ describe('LSP', function() eq({ 2, 6 }, funcs.nvim_win_get_cursor(0)) end) - it('fix the cursor to the valid column if the content was removed', function() + it('fix the cursor to the valid col if the content was removed', function() funcs.nvim_win_set_cursor(0, { 2, 6 }) local edits = { - make_edit(1, 0, 1, 19, '') + make_edit(1, 0, 1, 6, ''), + make_edit(1, 6, 1, 19, '') } exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) eq({ @@ -1151,6 +1181,19 @@ describe('LSP', function() eq({ 2, 0 }, funcs.nvim_win_get_cursor(0)) end) + it('fix the cursor to the valid row if the content was removed', function() + funcs.nvim_win_set_cursor(0, { 2, 6 }) + local edits = { + make_edit(1, 0, 1, 6, ''), + make_edit(0, 18, 5, 0, '') + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + }, buf_lines(1)) + eq({ 1, 6 }, funcs.nvim_win_get_cursor(0)) + end) + it('fix the cursor row', function() funcs.nvim_win_set_cursor(0, { 3, 0 }) local edits = { @@ -2022,83 +2065,6 @@ describe('LSP', function() end) end) - describe('lsp.util.make_floating_popup_options', function() - before_each(function() - exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local winheight = vim.fn.winheight(0) - for i = 1, winheight do - vim.api.nvim_buf_set_lines(bufnr, 0, 0, false, {''}) - end - vim.api.nvim_win_set_buf(0, bufnr) - vim.api.nvim_win_set_cursor(0, {winheight, 0}) - ]] - end) - - local function popup_row(opts) - return exec_lua([[ - return vim.lsp.util.make_floating_popup_options(...).row - ]], 2, 2, opts) - end - - local err_pattern = "^Error executing lua: %.%.%./util%.lua:0: invalid floating preview border: .*%. :help vim%.api%.nvim_open_win%(%)$" - - it('calculates default border height correctly', function() - eq(0, popup_row()) - end) - - it('calculates string border height correctly', function() - eq(0, popup_row({border = 'none'})) - eq(-2, popup_row({border = 'single'})) - eq(-2, popup_row({border = 'double'})) - eq(-2, popup_row({border = 'rounded'})) - eq(-2, popup_row({border = 'solid'})) - eq(-1, popup_row({border = 'shadow'})) - end) - - it('error on invalid string border', function() - matches(err_pattern, pcall_err(popup_row, {border = ''})) - matches(err_pattern, pcall_err(popup_row, {border = 'invalid'})) - end) - - it('error on invalid array border length', function() - matches(err_pattern, pcall_err(popup_row, {border = {}})) - matches(err_pattern, pcall_err(popup_row, {border = {'', '', ''}})) - matches(err_pattern, pcall_err(popup_row, {border = {'', '', '', '', ''}})) - end) - - it('error on invalid array border member type', function() - matches(err_pattern, pcall_err(popup_row, {border = {0}})) - end) - - it('calculates 8-array border height correctly', function() - eq(0, popup_row({border = {'', '', '', '', '', '', '', ''}})) - eq(-2, popup_row({border = {'', '~', '', '~', '', '~', '', '~'}})) - eq(-1, popup_row({border = {'', '', '', '~', '', '~', '', ''}})) - eq(0, popup_row({border = {'', '', '', {'~', 'NormalFloat'}, '', '', '', {'~', 'NormalFloat'}}})) - eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}, '', '', '', {'~', 'NormalFloat'}, '', ''}})) - end) - - it('calculates 4-array border height correctly', function() - eq(0, popup_row({border = {'', '', '', ''}})) - eq(-2, popup_row({border = {'', '~', '', '~'}})) - eq(0, popup_row({border = {'', '', '', {'~', 'NormalFloat'}}})) - eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}, '', ''}})) - end) - - it('calculates 2-array border height correctly', function() - eq(0, popup_row({border = {'', ''}})) - eq(-2, popup_row({border = {'', '~'}})) - eq(-2, popup_row({border = {'', {'~', 'NormalFloat'}}})) - end) - - it('calculates 1-array border height correctly', function() - eq(0, popup_row({border = {''}})) - eq(-2, popup_row({border = {'~'}})) - eq(-2, popup_row({border = {{'~', 'NormalFloat'}}})) - end) - end) - describe('lsp.util._make_floating_popup_size', function() before_each(function() exec_lua [[ contents = @@ -2353,6 +2319,10 @@ describe('LSP', function() eq(0, signal, "exit signal", fake_lsp_logfile) end; on_handler = function(err, result, ctx) + -- Don't compare & assert params, they're not relevant for the testcase + -- This allows us to be lazy and avoid declaring them + ctx.params = nil + eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler") if ctx.method == 'start' then exec_lua("vim.lsp.buf.rename()") @@ -2370,4 +2340,59 @@ describe('LSP', function() end end) + describe('vim.lsp.buf.code_action', function() + it('Calls client side command if available', function() + local client + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + test_rpc_server { + test_name = 'code_action_with_resolve', + on_init = function(client_) + client = client_ + end, + on_setup = function() + 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}) + if ctx.method == 'start' then + exec_lua([[ + vim.lsp.commands['dummy1'] = function(cmd) + vim.lsp.commands['dummy2'] = function() + end + end + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.fn.inputlist = function() + return 1 + end + vim.lsp.buf.code_action() + ]]) + elseif ctx.method == 'shutdown' then + eq('function', exec_lua[[return type(vim.lsp.commands['dummy2'])]]) + client.stop() + end + end + } + end) + end) + describe('vim.lsp.commands', function() + it('Accepts only string keys', function() + matches( + '.*The key for commands in `vim.lsp.commands` must be a string', + pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') + ) + end) + it('Accepts only function values', function() + matches( + '.*Command added to `vim.lsp.commands` must be a function', + pcall_err(exec_lua, 'vim.lsp.commands.dummy = 10') + ) + end) + end) end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 103ae59b8e..7dcca231ee 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -22,14 +22,28 @@ describe(':terminal buffer', function() it('terminal-mode forces various options', function() feed([[<C-\><C-N>]]) - command('setlocal cursorline cursorcolumn scrolloff=4 sidescrolloff=7') - eq({ 1, 1, 4, 7 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) - eq('n', eval('mode()')) + command('setlocal cursorline cursorlineopt=both cursorcolumn scrolloff=4 sidescrolloff=7') + eq({ 'both', 1, 1, 4, 7 }, eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + eq('nt', eval('mode(1)')) -- Enter terminal-mode ("insert" mode in :terminal). feed('i') - eq('t', eval('mode()')) - eq({ 0, 0, 0, 0 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + eq('t', eval('mode(1)')) + eq({ 'number', 1, 0, 0, 0 }, eval('[&l:cursorlineopt, &l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + end) + + it('terminal-mode does not change cursorlineopt if cursorline is disabled', function() + feed([[<C-\><C-N>]]) + command('setlocal nocursorline cursorlineopt=both') + feed('i') + eq({ 0, 'both' }, eval('[&l:cursorline, &l:cursorlineopt]')) + end) + + it('terminal-mode disables cursorline when cursorlineopt is only set to "line', function() + feed([[<C-\><C-N>]]) + command('setlocal cursorline cursorlineopt=line') + feed('i') + eq({ 0, 'line' }, eval('[&l:cursorline, &l:cursorlineopt]')) end) describe('when a new file is edited', function() diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 707c355069..065dd72485 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -96,19 +96,28 @@ describe(':terminal', function() eq(3, #jumps) end) + it('nvim_get_mode() in :terminal', function() + command(':terminal') + eq({ blocking=false, mode='nt' }, nvim('get_mode')) + feed('i') + eq({ blocking=false, mode='t' }, nvim('get_mode')) + feed([[<C-\><C-N>]]) + eq({ blocking=false, mode='nt' }, nvim('get_mode')) + end) + it(':stopinsert RPC request exits terminal-mode #7807', function() command(':terminal') feed('i[tui] insert-mode') eq({ blocking=false, mode='t' }, nvim('get_mode')) command('stopinsert') - eq({ blocking=false, mode='n' }, nvim('get_mode')) + eq({ blocking=false, mode='nt' }, 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')) + eq({ blocking=false, mode='nt' }, nvim('get_mode')) command(':stopinsert') - eq({ blocking=false, mode='n' }, nvim('get_mode')) + eq({ blocking=false, mode='nt' }, nvim('get_mode')) feed('a') eq({ blocking=false, mode='t' }, nvim('get_mode')) end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 0eb5901b3b..3d8441b93c 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -33,16 +33,16 @@ describe(':terminal mouse', function() describe('when the terminal has focus', function() it('will exit focus on mouse-scroll', function() - eq('t', eval('mode()')) + eq('t', eval('mode(1)')) feed('<ScrollWheelUp><0,0>') - eq('n', eval('mode()')) + eq('nt', eval('mode(1)')) end) it('will exit focus on <C-\\> + mouse-scroll', function() - eq('t', eval('mode()')) + eq('t', eval('mode(1)')) feed('<C-\\>') feed('<ScrollWheelUp><0,0>') - eq('n', eval('mode()')) + eq('nt', eval('mode(1)')) end) describe('with mouse events enabled by the program', function() @@ -94,7 +94,7 @@ describe(':terminal mouse', function() -- When the display area such as a number is clicked, it returns to the -- normal mode. feed('<LeftMouse><3,0>') - eq('n', eval('mode()')) + eq('nt', eval('mode(1)')) screen:expect([[ {7: 11 }^line28 | {7: 12 }line29 | diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 188afa1e84..c92107082e 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -111,7 +111,7 @@ describe(':terminal', function() command('terminal') feed('a<Cmd>wincmd j<CR>') eq(2, eval("winnr()")) - eq('t', eval('mode()')) + eq('t', eval('mode(1)')) end) end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 4373d17890..8074f91215 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -790,3 +790,511 @@ end]] helpers.assert_alive() end) end) + +describe('decorations: virtual lines', function() + local screen, ns + before_each(function() + clear() + screen = Screen.new(50, 12) + screen:attach() + screen:set_default_attr_ids { + [1] = {bold=true, foreground=Screen.colors.Blue}; + [2] = {foreground = Screen.colors.Cyan4}; + [3] = {background = Screen.colors.Yellow1}; + [4] = {bold = true}; + [5] = {background = Screen.colors.Yellow, foreground = Screen.colors.Blue}; + [6] = {foreground = Screen.colors.Blue}; + [7] = {foreground = Screen.colors.SlateBlue}; + [8] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue}; + [9] = {foreground = Screen.colors.Brown}; + } + + ns = meths.create_namespace 'test' + end) + + local example_text = [[ +if (h->n_buckets < new_n_buckets) { // expand + khkey_t *new_keys = (khkey_t *)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); + h->keys = new_keys; + if (kh_is_map && val_size) { + char *new_vals = krealloc( h->vals_buf, new_n_buckets * val_size); + h->vals_buf = new_vals; + } +}]] + + it('works with one line', function() + insert(example_text) + feed 'gg' + meths.buf_set_extmark(0, ns, 1, 33, { + virt_lines={ {{">> ", "NonText"}, {"krealloc", "Identifier"}, {": change the size of an allocation"}}}; + virt_lines_above=true; + }) + + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + {1:>> }{2:krealloc}: change the size of an allocation | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + + feed '/krealloc<cr>' + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + {1:>> }{2:krealloc}: change the size of an allocation | + khkey_t *new_keys = (khkey_t *){3:^krealloc}((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + /krealloc | + ]]} + + -- virtual line remains anchored to the extmark + feed 'i<cr>' + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *) | + {1:>> }{2:krealloc}: change the size of an allocation | + {3:^krealloc}((void *)h->keys, new_n_buckets * sizeof(k| + hkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + {4:-- INSERT --} | + ]]} + + feed '<esc>3+' + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *) | + {1:>> }{2:krealloc}: change the size of an allocation | + {3:krealloc}((void *)h->keys, new_n_buckets * sizeof(k| + hkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + | + ]]} + + meths.buf_set_extmark(0, ns, 5, 0, { + virt_lines = { {{"^^ REVIEW:", "Todo"}, {" new_vals variable seems unneccesary?", "Comment"}} }; + }) + -- TODO: what about the cursor?? + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *) | + {3:krealloc}((void *)h->keys, new_n_buckets * sizeof(k| + hkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buck^ets * val_size); | + {5:^^ REVIEW:}{6: new_vals variable seems unneccesary?} | + h->vals_buf = new_vals; | + } | + | + ]]} + + meths.buf_clear_namespace(0, ns, 0, -1) + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *) | + {3:krealloc}((void *)h->keys, new_n_buckets * sizeof(k| + hkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = {3:krealloc}( h->vals_buf, new_n_| + buck^ets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + end) + + + it('works with text at the beginning of the buffer', function() + insert(example_text) + feed 'gg' + + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + {1:~ }| + | + ]]} + + meths.buf_set_extmark(0, ns, 0, 0, { + virt_lines={ + {{"refactor(khash): ", "Special"}, {"take size of values as parameter"}}; + {{"Author: Dev Devsson, "}, {"Tue Aug 31 10:13:37 2021", "Comment"}}; + }; + virt_lines_above=true; + right_gravity=false; + }) + + -- placing virt_text on topline does not automatically cause a scroll + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + {1:~ }| + | + ]], unchanged=true} + + feed '<c-b>' + screen:expect{grid=[[ + {7:refactor(khash): }take size of values as parameter | + Author: Dev Devsson, {6:Tue Aug 31 10:13:37 2021} | + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + | + ]]} + end) + + it('works with text et the end of the buffer', function() + insert(example_text) + feed 'G' + + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + ^} | + {1:~ }| + | + ]]} + + local id = meths.buf_set_extmark(0, ns, 7, 0, { + virt_lines={{{"Grugg"}}}; + right_gravity=false; + }) + + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + ^} | + Grugg | + | + ]]} + + meths.buf_del_extmark(0, ns, id) + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + ^} | + {1:~ }| + | + ]]} + end) + + it('works with a block scrolling up', function() + screen:try_resize(30, 7) + insert("aa\nbb\ncc\ndd\nee\nff\ngg\nhh") + feed 'gg' + + meths.buf_set_extmark(0, ns, 6, 0, { + virt_lines={ + {{"they see me"}}; + {{"scrolling", "Special"}}; + {{"they"}}; + {{"hatin'", "Special"}}; + }; + }) + + screen:expect{grid=[[ + ^aa | + bb | + cc | + dd | + ee | + ff | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^bb | + cc | + dd | + ee | + ff | + gg | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^cc | + dd | + ee | + ff | + gg | + they see me | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^dd | + ee | + ff | + gg | + they see me | + {7:scrolling} | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^ee | + ff | + gg | + they see me | + {7:scrolling} | + they | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^ff | + gg | + they see me | + {7:scrolling} | + they | + {7:hatin'} | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^gg | + they see me | + {7:scrolling} | + they | + {7:hatin'} | + hh | + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + they see me | + {7:scrolling} | + they | + {7:hatin'} | + ^hh | + {1:~ }| + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + {7:scrolling} | + they | + {7:hatin'} | + ^hh | + {1:~ }| + {1:~ }| + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + they | + {7:hatin'} | + ^hh | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + {7:hatin'} | + ^hh | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed '<c-e>' + screen:expect{grid=[[ + ^hh | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) + + it('works with sign and numbercolumns', function() + insert(example_text) + feed 'gg' + command 'set number signcolumn=yes' + screen:expect{grid=[[ + {8: }{9: 1 }^if (h->n_buckets < new_n_buckets) { // expan| + {8: }{9: }d | + {8: }{9: 2 } khkey_t *new_keys = (khkey_t *)krealloc((v| + {8: }{9: }oid *)h->keys, new_n_buckets * sizeof(khkey_| + {8: }{9: }t)); | + {8: }{9: 3 } h->keys = new_keys; | + {8: }{9: 4 } if (kh_is_map && val_size) { | + {8: }{9: 5 } char *new_vals = krealloc( h->vals_buf, | + {8: }{9: }new_n_buckets * val_size); | + {8: }{9: 6 } h->vals_buf = new_vals; | + {8: }{9: 7 } } | + | + ]]} + + meths.buf_set_extmark(0, ns, 2, 0, { + virt_lines={ + {{"Some special", "Special"}}; + {{"remark about codes", "Comment"}}; + }; + }) + + screen:expect{grid=[[ + {8: }{9: 1 }^if (h->n_buckets < new_n_buckets) { // expan| + {8: }{9: }d | + {8: }{9: 2 } khkey_t *new_keys = (khkey_t *)krealloc((v| + {8: }{9: }oid *)h->keys, new_n_buckets * sizeof(khkey_| + {8: }{9: }t)); | + {8: }{9: 3 } h->keys = new_keys; | + {8: }{9: }{7:Some special} | + {8: }{9: }{6:remark about codes} | + {8: }{9: 4 } if (kh_is_map && val_size) { | + {8: }{9: 5 } char *new_vals = krealloc( h->vals_buf, | + {8: }{9: }new_n_buckets * val_size); | + | + ]]} + + meths.buf_set_extmark(0, ns, 2, 0, { + virt_lines={ + {{"Some special", "Special"}}; + {{"remark about codes", "Comment"}}; + }; + virt_lines_leftcol=true; + }) + screen:expect{grid=[[ + {8: }{9: 1 }^if (h->n_buckets < new_n_buckets) { // expan| + {8: }{9: }d | + {8: }{9: 2 } khkey_t *new_keys = (khkey_t *)krealloc((v| + {8: }{9: }oid *)h->keys, new_n_buckets * sizeof(khkey_| + {8: }{9: }t)); | + {8: }{9: 3 } h->keys = new_keys; | + {7:Some special} | + {6:remark about codes} | + {8: }{9: 4 } if (kh_is_map && val_size) { | + {8: }{9: 5 } char *new_vals = krealloc( h->vals_buf, | + {8: }{9: }new_n_buckets * val_size); | + | + ]]} + end) + + + it('works with hard tabs', function() + insert(example_text) + feed 'gg' + meths.buf_set_extmark(0, ns, 1, 0, { + virt_lines={ {{">>", "NonText"}, {"\tvery\ttabby", "Identifier"}, {"text\twith\ttabs"}}}; + }) + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + {1:>>}{2: very tabby}text with tabs | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + + command 'set tabstop=4' + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + {1:>>}{2: very tabby}text with tabs | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + end) + +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index ccf5f963d1..6c2c4b398a 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -46,6 +46,7 @@ describe('float window', function() [24] = {foreground = Screen.colors.Black, background = Screen.colors.Grey80}; [25] = {blend = 100, background = Screen.colors.Gray0}; [26] = {blend = 80, background = Screen.colors.Gray0}; + [27] = {background = Screen.colors.LightGray}; } it('behavior', function() @@ -109,6 +110,21 @@ describe('float window', function() assert_alive() end) + it('closed immediately by autocmd after win_enter #15548', function() + eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', + pcall_err(exec_lua, [[ + vim.cmd "autocmd BufLeave * ++once quit!" + local buf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_open_win(buf, true, { + relative = "win", + row = 0, col = 0, + width = 1, height = 1, + noautocmd = false, + }) + ]])) + assert_alive() + end) + it('opened with correct height', function() local height = exec_lua([[ vim.api.nvim_set_option("winheight", 20) @@ -1514,7 +1530,7 @@ describe('float window', function() it('API has proper error messages', function() local buf = meths.create_buf(false,false) - eq("Invalid key 'bork'", + eq("Invalid key: 'bork'", pcall_err(meths.open_win,buf, false, {width=20,height=2,bork=true})) eq("'win' key is only valid with relative='win'", pcall_err(meths.open_win,buf, false, {width=20,height=2,relative='editor',row=0,col=0,win=0})) @@ -1527,13 +1543,15 @@ describe('float window', function() eq("'relative' requires 'row'/'col' or 'bufpos'", pcall_err(meths.open_win,buf, false, {width=20,height=2,relative='editor'})) eq("'width' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=-1,height=2,relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=-1,height=2,relative='editor', row=0, col=0})) eq("'height' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=20,height=-1,relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=20,height=-1,relative='editor', row=0, col=0})) eq("'height' key must be a positive Integer", - pcall_err(meths.open_win,buf, false, {width=20,height=0,relative='editor'})) - eq("Must specify 'width' and 'height'", - pcall_err(meths.open_win,buf, false, {relative='editor'})) + pcall_err(meths.open_win,buf, false, {width=20,height=0,relative='editor', row=0, col=0})) + eq("Must specify 'width'", + pcall_err(meths.open_win,buf, false, {relative='editor', row=0, col=0})) + eq("Must specify 'height'", + pcall_err(meths.open_win,buf, false, {relative='editor', row=0, col=0, width=2})) end) it('can be placed relative window or cursor', function() @@ -1866,6 +1884,293 @@ describe('float window', function() end end) + it('always anchor to corner including border', function() + screen:try_resize(40,13) + meths.buf_set_lines(0, 0, -1, true, {'just some example text', 'some more example text'}) + feed('ggeee') + command('below split') + if multigrid then + screen:expect([[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ]]) + else + screen:expect([[ + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {5:[No Name] [+] }| + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {' halloj! ', + ' BORDAA '}) + local win = meths.open_win(buf, false, {relative='cursor', width=9, height=2, row=1, col=-2, border="double"}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 6 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [6] = {{id = 1003}, "NW", 4, 1, 14, true} + }} + else + screen:expect([[ + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {5:[No Name] [+] }| + just some exampl^e text | + some more exam{5:╔═════════╗} | + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_set_config(win, {relative='cursor', row=0, col=-2, anchor='NE'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 6 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [6] = {{id = 1003}, "NE", 4, 0, 14, true} + }} + else + screen:expect([[ + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {5:[No Name] [+] }| + jus{5:╔═════════╗}pl^e text | + som{5:║}{1: halloj! }{5:║}ple text | + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_set_config(win, {relative='cursor', row=1, col=-2, anchor='SE'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 6 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [6] = {{id = 1003}, "SE", 4, 1, 14, true} + }} + else + screen:expect([[ + just some example text | + some more example text | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {5:[No║}{1: BORDAA }{5:║ }| + jus{5:╚═════════╝}pl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_set_config(win, {relative='cursor', row=0, col=-2, anchor='SW'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + just some example text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 6 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], float_pos={ + [6] = {{id = 1003}, "SW", 4, 0, 14, true} + }} + else + screen:expect([[ + just some example text | + some more example text | + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {5:[No Name] [+] ╚═════════╝ }| + just some exampl^e text | + some more example text | + {0:~ }| + {0:~ }| + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + end) + it('can be placed relative text in a window', function() screen:try_resize(30,5) local firstwin = meths.get_current_win().id @@ -5870,6 +6175,132 @@ describe('float window', function() end) end) + it("left drag changes visual selection in float window", function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar'}) + meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=5}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:foo }| + {1:bar }| + {2:~ }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2}; + }} + meths.input_mouse('left', 'press', '', 5, 0, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {1:^foo }| + {1:bar }| + {2:~ }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2}; + }} + meths.input_mouse('left', 'drag', '', 5, 1, 2) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- VISUAL --} | + ## grid 5 + {27:foo}{1: }| + {27:ba}{1:^r }| + {2:~ }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 2}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }{1:foo }{0: }| + {0:~ }{1:bar }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'press', '', 0, 2, 5) + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }{1:^foo }{0: }| + {0:~ }{1:bar }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'drag', '', 0, 3, 7) + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }{27:foo}{1: }{0: }| + {0:~ }{27:ba}{1:^r }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {3:-- VISUAL --} | + ]]} + end + end) + it("'winblend' option", function() screen:try_resize(50,9) screen:set_default_attr_ids({ diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 712c1f377a..b6e2f2311f 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -1487,6 +1487,29 @@ describe("inccommand=nosplit", function() ]]) eq(eval('v:null'), eval('v:exiting')) end) + + it("does not break bar-separated command #8796", function() + source([[ + function! F() + if v:false | return | endif + endfun + ]]) + command('call timer_start(10, {-> F()}, {"repeat":-1})') + feed(':%s/') + sleep(20) -- Allow some timer activity. + screen:expect([[ + Inc substitution on | + two lines | + Inc substitution on | + two lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :%s/^ | + ]]) + end) end) describe(":substitute, 'inccommand' with a failing expression", function() diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 7bca741ae3..baacef358f 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -585,6 +585,69 @@ describe('ui/mouse/input', function() ]]) end) + it('left drag changes visual selection in split layout', function() + screen:try_resize(53,14) + command('set mouse=a') + command('vsplit') + command('wincmd l') + command('below split') + command('enew') + feed('ifoo\nbar<esc>') + + screen:expect{grid=[[ + testing {4:│}testing | + mouse {4:│}mouse | + support and selection {4:│}support and selection | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│[No Name] [+] }| + {0:~ }{4:│}foo{0:$} | + {0:~ }{4:│}ba^r{0:$} | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ]]} + + meths.input_mouse('left', 'press', '', 0, 6, 27) + screen:expect{grid=[[ + testing {4:│}testing | + mouse {4:│}mouse | + support and selection {4:│}support and selection | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│[No Name] [+] }| + {0:~ }{4:│}^foo{0:$} | + {0:~ }{4:│}bar{0:$} | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ]]} + meths.input_mouse('left', 'drag', '', 0, 7, 30) + + screen:expect{grid=[[ + testing {4:│}testing | + mouse {4:│}mouse | + support and selection {4:│}support and selection | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│[No Name] [+] }| + {0:~ }{4:│}{1:foo}{3:$} | + {0:~ }{4:│}{1:bar}{0:^$} | + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {0:~ }{4:│}{0:~ }| + {4:[No Name] [+] }{5:[No Name] [+] }| + {2:-- VISUAL --} | + ]]} + end) + it('two clicks will select the word and enter VISUAL', function() feed('<LeftMouse><2,2><LeftMouse><2,2>') screen:expect([[ @@ -1384,4 +1447,128 @@ describe('ui/mouse/input', function() end) -- level 3 - wrapped end) + + it('getmousepos works correctly', function() + local winwidth = meths.get_option('winwidth') + -- Set winwidth=1 so that window sizes don't change. + meths.set_option('winwidth', 1) + command('tabedit') + local tabpage = meths.get_current_tabpage() + insert('hello') + command('vsplit') + local opts = { + relative='editor', + width=12, + height=1, + col=8, + row=1, + anchor='NW', + style='minimal', + border='single', + focusable=1 + } + local float = meths.open_win(meths.get_current_buf(), false, opts) + command('redraw') + local lines = meths.get_option('lines') + local columns = meths.get_option('columns') + + -- Test that screenrow and screencol are set properly for all positions. + for row = 0, lines - 1 do + for col = 0, columns - 1 do + -- Skip the X button that would close the tab. + if row ~= 0 or col ~= columns - 1 then + meths.input_mouse('left', 'press', '', 0, row, col) + meths.set_current_tabpage(tabpage) + local mousepos = funcs.getmousepos() + eq(row + 1, mousepos.screenrow) + eq(col + 1, mousepos.screencol) + -- All other values should be 0 when clicking on the command line. + if row == lines - 1 then + eq(0, mousepos.winid) + eq(0, mousepos.winrow) + eq(0, mousepos.wincol) + eq(0, mousepos.line) + eq(0, mousepos.column) + end + end + end + end + + -- Test that mouse position values are properly set for the floating window + -- with a border. 1 is added to the height and width to account for the + -- border. + for win_row = 0, opts.height + 1 do + for win_col = 0, opts.width + 1 do + local row = win_row + opts.row + local col = win_col + opts.col + meths.input_mouse('left', 'press', '', 0, row, col) + local mousepos = funcs.getmousepos() + eq(float.id, mousepos.winid) + eq(win_row + 1, mousepos.winrow) + eq(win_col + 1, mousepos.wincol) + local line = 0 + local column = 0 + if win_row > 0 and win_row < opts.height + 1 + and win_col > 0 and win_col < opts.width + 1 then + -- Because of border, win_row and win_col don't need to be + -- incremented by 1. + line = math.min(win_row, funcs.line('$')) + column = math.min(win_col, #funcs.getline(line) + 1) + end + eq(line, mousepos.line) + eq(column, mousepos.column) + end + end + + -- Test that mouse position values are properly set for the floating + -- window, after removing the border. + opts.border = 'none' + meths.win_set_config(float, opts) + command('redraw') + for win_row = 0, opts.height - 1 do + for win_col = 0, opts.width - 1 do + local row = win_row + opts.row + local col = win_col + opts.col + meths.input_mouse('left', 'press', '', 0, row, col) + local mousepos = funcs.getmousepos() + eq(float.id, mousepos.winid) + eq(win_row + 1, mousepos.winrow) + eq(win_col + 1, mousepos.wincol) + local line = math.min(win_row + 1, funcs.line('$')) + local column = math.min(win_col + 1, #funcs.getline(line) + 1) + eq(line, mousepos.line) + eq(column, mousepos.column) + end + end + + -- Test that mouse position values are properly set for ordinary windows. + -- Set the float to be unfocusable instead of closing, to additionally test + -- that getmousepos does not consider unfocusable floats. (see discussion + -- in PR #14937 for details). + opts.focusable = false + meths.win_set_config(float, opts) + command('redraw') + for nr = 1, 2 do + for win_row = 0, funcs.winheight(nr) - 1 do + for win_col = 0, funcs.winwidth(nr) - 1 do + local row = win_row + funcs.win_screenpos(nr)[1] - 1 + local col = win_col + funcs.win_screenpos(nr)[2] - 1 + meths.input_mouse('left', 'press', '', 0, row, col) + local mousepos = funcs.getmousepos() + eq(funcs.win_getid(nr), mousepos.winid) + eq(win_row + 1, mousepos.winrow) + eq(win_col + 1, mousepos.wincol) + local line = math.min(win_row + 1, funcs.line('$')) + local column = math.min(win_col + 1, #funcs.getline(line) + 1) + eq(line, mousepos.line) + eq(column, mousepos.column) + end + end + end + + -- Restore state and release mouse. + command('tabclose!') + meths.set_option('winwidth', winwidth) + meths.input_mouse('left', 'release', '', 0, 0, 0) + end) end) diff --git a/test/functional/vimscript/json_functions_spec.lua b/test/functional/vimscript/json_functions_spec.lua index c3b607b544..5d1597f53d 100644 --- a/test/functional/vimscript/json_functions_spec.lua +++ b/test/functional/vimscript/json_functions_spec.lua @@ -168,7 +168,8 @@ describe('json_decode() function', function() end) it('parses floating-point numbers', function() - eq('100000.0', eval('string(json_decode("100000.0"))')) + -- Also test method call (->) syntax + eq('100000.0', eval('"100000.0"->json_decode()->string()')) eq(100000.5, funcs.json_decode('100000.5')) eq(-100000.5, funcs.json_decode('-100000.5')) eq(-100000.5e50, funcs.json_decode('-100000.5e50')) @@ -549,11 +550,12 @@ describe('json_encode() function', function() end) it('dumps floats', function() - eq('0.0', eval('json_encode(0.0)')) + -- Also test method call (->) syntax + eq('0.0', eval('0.0->json_encode()')) eq('10.5', funcs.json_encode(10.5)) eq('-10.5', funcs.json_encode(-10.5)) eq('-1.0e-5', funcs.json_encode(-1e-5)) - eq('1.0e50', eval('json_encode(1.0e50)')) + eq('1.0e50', eval('1.0e50->json_encode()')) end) it('fails to dump NaN and infinite values', function() |