diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-05-24 19:18:11 +0000 |
commit | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (patch) | |
tree | 729bbcb92231538fa61dab6c3d890b025484b7f5 /test/functional/api | |
parent | 376914f419eb08fdf4c1a63a77e1f035898a0f10 (diff) | |
parent | 28c04948a1c887a1cc0cb64de79fa32631700466 (diff) | |
download | rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.gz rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.bz2 rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.zip |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'test/functional/api')
-rw-r--r-- | test/functional/api/autocmd_spec.lua | 78 | ||||
-rw-r--r-- | test/functional/api/buffer_spec.lua | 193 | ||||
-rw-r--r-- | test/functional/api/buffer_updates_spec.lua | 32 | ||||
-rw-r--r-- | test/functional/api/command_spec.lua | 25 | ||||
-rw-r--r-- | test/functional/api/extmark_spec.lua | 82 | ||||
-rw-r--r-- | test/functional/api/highlight_spec.lua | 22 | ||||
-rw-r--r-- | test/functional/api/keymap_spec.lua | 203 | ||||
-rw-r--r-- | test/functional/api/menu_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/api/proc_spec.lua | 19 | ||||
-rw-r--r-- | test/functional/api/server_notifications_spec.lua | 40 | ||||
-rw-r--r-- | test/functional/api/server_requests_spec.lua | 65 | ||||
-rw-r--r-- | test/functional/api/tabpage_spec.lua | 42 | ||||
-rw-r--r-- | test/functional/api/ui_spec.lua | 22 | ||||
-rw-r--r-- | test/functional/api/version_spec.lua | 8 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 441 | ||||
-rw-r--r-- | test/functional/api/window_spec.lua | 1162 |
16 files changed, 1952 insertions, 490 deletions
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index e89abf6c64..3f9883f43f 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -1,14 +1,15 @@ -local helpers = require('test.functional.helpers')(after_each) - -local clear = helpers.clear -local command = helpers.command -local eq = helpers.eq -local neq = helpers.neq -local exec_lua = helpers.exec_lua -local matches = helpers.matches -local api = helpers.api -local source = helpers.source -local pcall_err = helpers.pcall_err +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear = n.clear +local command = n.command +local eq = t.eq +local neq = t.neq +local exec_lua = n.exec_lua +local matches = t.matches +local api = n.api +local source = n.source +local pcall_err = t.pcall_err before_each(clear) @@ -355,6 +356,44 @@ describe('autocmd api', function() test({ 'list' }) test({ foo = 'bar' }) end) + + it('function in arbitrary data is passed to all autocmds #28353', function() + eq( + 1303, + exec_lua([[ + local res = 1 + + local fun = function(m, x) + res = res * m + x + end + + local group = vim.api.nvim_create_augroup('MyTest', { clear = false }) + + vim.api.nvim_create_autocmd('User', { + group = group, + callback = function(payload) + payload.data.fun(10, payload.data.x) + end, + pattern = 'MyEvent', + }) + vim.api.nvim_create_autocmd('User', { + group = group, + callback = function(payload) + payload.data.fun(100, payload.data.x) + end, + pattern = 'MyEvent', + }) + + vim.api.nvim_exec_autocmds('User', { + group = group, + pattern = 'MyEvent', + data = { x = 3, fun = fun }, + }) + + return res + ]]) + ) + end) end) describe('nvim_get_autocmds', function() @@ -610,15 +649,17 @@ describe('autocmd api', function() it('can retrieve a callback from an autocmd', function() local content = 'I Am A Callback' api.nvim_set_var('content', content) - - local result = exec_lua([[ + exec_lua([[ local cb = function() return vim.g.content end vim.api.nvim_create_autocmd("User", { pattern = "TestTrigger", desc = "A test autocommand with a callback", callback = cb, }) - local aus = vim.api.nvim_get_autocmds({ event = 'User', pattern = 'TestTrigger'}) + ]]) + + local result = exec_lua([[ + local aus = vim.api.nvim_get_autocmds({ event = 'User', pattern = 'TestTrigger' }) local first = aus[1] return { cb = { @@ -627,9 +668,14 @@ describe('autocmd api', function() } } ]]) + eq({ cb = { type = 'function', can_retrieve = true } }, result) - eq('function', result.cb.type) - eq(true, result.cb.can_retrieve) + -- Also test with Vimscript + source([[ + let s:aus = nvim_get_autocmds({'event': 'User', 'pattern': 'TestTrigger'}) + let g:result = s:aus[0].callback() + ]]) + eq(content, api.nvim_get_var('result')) end) it( diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 78d220ff57..cf69958fd8 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -1,21 +1,23 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local clear = helpers.clear -local eq = helpers.eq -local ok = helpers.ok -local describe_lua_and_rpc = helpers.describe_lua_and_rpc(describe) -local api = helpers.api -local fn = helpers.fn -local request = helpers.request -local exc_exec = helpers.exc_exec -local exec_lua = helpers.exec_lua -local feed_command = helpers.feed_command -local insert = helpers.insert + +local clear = n.clear +local eq = t.eq +local ok = t.ok +local describe_lua_and_rpc = n.describe_lua_and_rpc(describe) +local api = n.api +local fn = n.fn +local request = n.request +local exc_exec = n.exc_exec +local exec_lua = n.exec_lua +local feed_command = n.feed_command +local insert = n.insert local NIL = vim.NIL -local command = helpers.command -local feed = helpers.feed -local pcall_err = helpers.pcall_err -local assert_alive = helpers.assert_alive +local command = n.command +local feed = n.feed +local pcall_err = t.pcall_err +local assert_alive = n.assert_alive describe('api/buf', function() before_each(clear) @@ -121,6 +123,65 @@ describe('api/buf', function() eq({ 5, 2 }, api.nvim_win_get_cursor(win2)) end) + it('cursor position is maintained consistently with viewport', function() + local screen = Screen.new(20, 12) + screen:set_default_attr_ids { + [1] = { bold = true, foreground = Screen.colors.Blue1 }, + [2] = { reverse = true, bold = true }, + [3] = { reverse = true }, + } + screen:attach() + + local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' } + local buf = api.nvim_get_current_buf() + + api.nvim_buf_set_lines(buf, 0, -1, true, lines) + + command('6') + command('new') + screen:expect { + grid = [[ + ^ | + {1:~ }|*4 + {2:[No Name] }| + line5 | + line6 | + {1:~ }|*2 + {3:[No Name] [+] }| + | + ]], + } + + lines[5] = 'boogalo 5' + api.nvim_buf_set_lines(buf, 0, -1, true, lines) + screen:expect { + grid = [[ + ^ | + {1:~ }|*4 + {2:[No Name] }| + boogalo 5 | + line6 | + {1:~ }|*2 + {3:[No Name] [+] }| + | + ]], + } + + command('wincmd w') + screen:expect { + grid = [[ + | + {1:~ }|*4 + {3:[No Name] }| + boogalo 5 | + ^line6 | + {1:~ }|*2 + {2:[No Name] [+] }| + | + ]], + } + end) + it('line_count has defined behaviour for unloaded buffers', function() -- we'll need to know our bufnr for when it gets unloaded local bufnr = api.nvim_buf_get_number(0) @@ -323,20 +384,20 @@ describe('api/buf', function() ]], } - -- inserting just before topline scrolls up api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) screen:expect { grid = [[ ^ | {1:~ }|*4 {2:[No Name] }| - mmm | wwweeee | xxx | yyy | + zzz | {3:[No Name] [+] }| | ]], + unchanged = true, } end) @@ -402,7 +463,6 @@ describe('api/buf', function() ]], } - -- inserting just before topline scrolls up api.nvim_buf_set_lines(buf, 3, 3, true, { 'mmm' }) screen:expect { grid = [[ @@ -412,10 +472,10 @@ describe('api/buf', function() mmm | wwweeee | {2:[No Name] [+] }| - mmm | wwweeee | xxx | yyy | + zzz | {3:[No Name] [+] }| | ]], @@ -1316,12 +1376,7 @@ describe('api/buf', function() -- immediate call to nvim_win_get_cursor should have returned the same position eq({ 2, 12 }, cursor) -- coladd should be 0 - eq( - 0, - exec_lua([[ - return vim.fn.winsaveview().coladd - ]]) - ) + eq(0, fn.winsaveview().coladd) end) it('does not change cursor screen column when cursor >EOL and row got shorter', function() @@ -1335,9 +1390,7 @@ describe('api/buf', function() -- turn on virtualedit command('set virtualedit=all') -- move cursor after eol - exec_lua([[ - vim.fn.winrestview({ coladd = 5 }) - ]]) + fn.winrestview({ coladd = 5 }) local cursor = exec_lua([[ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { @@ -1356,12 +1409,7 @@ describe('api/buf', function() -- immediate call to nvim_win_get_cursor should have returned the same position eq({ 2, 26 }, cursor) -- coladd should be increased so that cursor stays in the same screen column - eq( - 13, - exec_lua([[ - return vim.fn.winsaveview().coladd - ]]) - ) + eq(13, fn.winsaveview().coladd) end) it( @@ -1377,9 +1425,7 @@ describe('api/buf', function() -- turn on virtualedit command('set virtualedit=all') -- move cursor after eol - exec_lua([[ - vim.fn.winrestview({ coladd = 21 }) - ]]) + fn.winrestview({ coladd = 21 }) local cursor = exec_lua([[ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { @@ -1398,12 +1444,7 @@ describe('api/buf', function() -- immediate call to nvim_win_get_cursor should have returned the same position eq({ 1, 38 }, cursor) -- coladd should be increased so that cursor stays in the same screen column - eq( - 2, - exec_lua([[ - return vim.fn.winsaveview().coladd - ]]) - ) + eq(2, fn.winsaveview().coladd) end ) @@ -1420,9 +1461,7 @@ describe('api/buf', function() -- turn on virtualedit command('set virtualedit=all') -- move cursor after eol just a bit - exec_lua([[ - vim.fn.winrestview({ coladd = 3 }) - ]]) + fn.winrestview({ coladd = 3 }) local cursor = exec_lua([[ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, { @@ -1441,12 +1480,7 @@ describe('api/buf', function() -- immediate call to nvim_win_get_cursor should have returned the same position eq({ 1, 22 }, cursor) -- coladd should become 0 - eq( - 0, - exec_lua([[ - return vim.fn.winsaveview().coladd - ]]) - ) + eq(0, fn.winsaveview().coladd) end ) @@ -1464,9 +1498,7 @@ describe('api/buf', function() -- turn on virtualedit command('set virtualedit=all') -- move cursor after eol - exec_lua([[ - vim.fn.winrestview({ coladd = 28 }) - ]]) + fn.winrestview({ coladd = 28 }) local cursor = exec_lua([[ vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, { @@ -1485,12 +1517,7 @@ describe('api/buf', function() -- immediate call to nvim_win_get_cursor should have returned the same position eq({ 2, 26 }, cursor) -- coladd should be increased so that cursor stays in the same screen column - eq( - 13, - exec_lua([[ - return vim.fn.winsaveview().coladd - ]]) - ) + eq(13, fn.winsaveview().coladd) end ) end) @@ -1686,12 +1713,11 @@ describe('api/buf', function() api.nvim_buf_set_text(0, 0, 0, 1, 3, { 'XXX', 'YYY' }) screen:expect([[ - XXX | - YYY | - ^ | - ~ | - | - + XXX | + YYY | + ^ | + {1:~ }| + | ]]) end) @@ -2024,6 +2050,37 @@ describe('api/buf', function() eq(1, fn.filereadable(new_name)) os.remove(new_name) end) + + describe("with 'autochdir'", function() + local topdir + local oldbuf + local newbuf + + before_each(function() + command('set shellslash') + topdir = fn.getcwd() + t.mkdir(topdir .. '/Xacd') + + oldbuf = api.nvim_get_current_buf() + command('vnew') + newbuf = api.nvim_get_current_buf() + command('set autochdir') + end) + + after_each(function() + n.rmdir(topdir .. '/Xacd') + end) + + it('does not change cwd with non-current buffer', function() + api.nvim_buf_set_name(oldbuf, topdir .. '/Xacd/foo.txt') + eq(topdir, fn.getcwd()) + end) + + it('changes cwd with current buffer', function() + api.nvim_buf_set_name(newbuf, topdir .. '/Xacd/foo.txt') + eq(topdir .. '/Xacd', fn.getcwd()) + end) + end) end) describe('nvim_buf_is_loaded', function() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 262ca40e28..e030b45396 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -1,13 +1,15 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear = helpers.clear -local eq, ok = helpers.eq, helpers.ok -local fn = helpers.fn -local api = helpers.api -local command, eval, next_msg = helpers.command, helpers.eval, helpers.next_msg -local nvim_prog = helpers.nvim_prog -local pcall_err = helpers.pcall_err +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear = n.clear +local eq, ok = t.eq, t.ok +local fn = n.fn +local api = n.api +local command, eval, next_msg = n.command, n.eval, n.next_msg +local nvim_prog = n.nvim_prog +local pcall_err = t.pcall_err local sleep = vim.uv.sleep -local write_file = helpers.write_file +local write_file = t.write_file local origlines = { 'original line 1', @@ -34,7 +36,7 @@ local function sendkeys(keys) end local function open(activate, lines) - local filename = helpers.tmpname() + local filename = t.tmpname() write_file(filename, table.concat(lines, '\n') .. '\n', true) command('edit ' .. filename) local b = api.nvim_get_current_buf() @@ -511,11 +513,11 @@ describe('API: buffer events:', function() -- create several new sessions, in addition to our main API local sessions = {} - local pipe = helpers.new_pipename() + local pipe = n.new_pipename() eval("serverstart('" .. pipe .. "')") - sessions[1] = helpers.connect(pipe) - sessions[2] = helpers.connect(pipe) - sessions[3] = helpers.connect(pipe) + sessions[1] = n.connect(pipe) + sessions[2] = n.connect(pipe) + sessions[3] = n.connect(pipe) local function request(sessionnr, method, ...) local status, rv = sessions[sessionnr]:request(method, ...) @@ -814,7 +816,7 @@ describe('API: buffer events:', function() clear() sleep(250) -- response - eq(true, helpers.request('nvim_buf_attach', 0, false, {})) + eq(true, n.request('nvim_buf_attach', 0, false, {})) -- notification eq({ [1] = 'notification', diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index f73b9c8b13..a16c6a88e3 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -1,17 +1,18 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local NIL = vim.NIL -local clear = helpers.clear -local command = helpers.command -local eq = helpers.eq -local api = helpers.api -local matches = helpers.matches -local source = helpers.source -local pcall_err = helpers.pcall_err -local exec_lua = helpers.exec_lua -local assert_alive = helpers.assert_alive -local feed = helpers.feed -local fn = helpers.fn +local clear = n.clear +local command = n.command +local eq = t.eq +local api = n.api +local matches = t.matches +local source = n.source +local pcall_err = t.pcall_err +local exec_lua = n.exec_lua +local assert_alive = n.assert_alive +local feed = n.feed +local fn = n.fn describe('nvim_get_commands', function() local cmd_dict = { diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 2acfbfc949..7b2fe209ba 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1,20 +1,21 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local request = helpers.request -local eq = helpers.eq -local ok = helpers.ok -local pcall_err = helpers.pcall_err -local insert = helpers.insert -local feed = helpers.feed -local clear = helpers.clear -local command = helpers.command -local exec = helpers.exec -local api = helpers.api -local assert_alive = helpers.assert_alive +local request = n.request +local eq = t.eq +local ok = t.ok +local pcall_err = t.pcall_err +local insert = n.insert +local feed = n.feed +local clear = n.clear +local command = n.command +local exec = n.exec +local api = n.api +local assert_alive = n.assert_alive local function expect(contents) - return eq(contents, helpers.curbuf_contents()) + return eq(contents, n.curbuf_contents()) end local function set_extmark(ns_id, id, line, col, opts) @@ -460,7 +461,7 @@ describe('API/extmarks', function() -- This shouldn't seg fault screen:expect([[ 12345^ 1 | - ~ |*8 + {1:~ }|*8 | ]]) end) @@ -513,7 +514,7 @@ describe('API/extmarks', function() insert('abc') screen:expect([[ ab^c12345 | - ~ |*8 + {1:~ }|*8 | ]]) local rv = get_extmark_by_id(ns, marks[1]) @@ -1568,7 +1569,7 @@ describe('API/extmarks', function() sign_text = '>>', spell = true, virt_lines = { - { { 'lines', 'Macro' }, { '???' } }, + { { 'lines', 'Macro' }, { '???' }, { ';;;', '' } }, { { 'stack', { 'Type', 'Search' } }, { '!!!' } }, }, virt_lines_above = true, @@ -1603,7 +1604,7 @@ describe('API/extmarks', function() sign_text = '>>', spell = true, virt_lines = { - { { 'lines', 'Macro' }, { '???' } }, + { { 'lines', 'Macro' }, { '???' }, { ';;;', '' } }, { { 'stack', { 'Type', 'Search' } }, { '!!!' } }, }, virt_lines_above = true, @@ -1734,16 +1735,17 @@ describe('API/extmarks', function() command('d2') screen:expect([[ S2^aaa bbb ccc | - aaa bbb ccc |*3 - |*2 + {7: }aaa bbb ccc |*3 + {7: } | + | ]]) -- mark is restored with undo_restore == true command('silent undo') screen:expect([[ - S1 ^aaa bbb ccc | - S1S2aaa bbb ccc | - S2 aaa bbb ccc | - aaa bbb ccc |*2 + S1{7: }^aaa bbb ccc | + S2S1aaa bbb ccc | + S2{7: }aaa bbb ccc | + {7: }aaa bbb ccc |*2 | ]]) -- decor is not removed twice @@ -1894,6 +1896,24 @@ describe('Extmarks buffer api with many marks', function() end eq(ns_marks[ns1], get_marks(ns1)) eq(ns_marks[ns2], get_marks(ns2)) + + api.nvim_buf_clear_namespace(0, ns1, 0, 10) + for id, mark in pairs(ns_marks[ns1]) do + if mark[1] < 10 then + ns_marks[ns1][id] = nil + end + end + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) + + api.nvim_buf_clear_namespace(0, ns1, 20, -1) + for id, mark in pairs(ns_marks[ns1]) do + if mark[1] >= 20 then + ns_marks[ns1][id] = nil + end + end + eq(ns_marks[ns1], get_marks(ns1)) + eq(ns_marks[ns2], get_marks(ns2)) end) it('can delete line', function() @@ -1964,7 +1984,7 @@ describe('API/win_extmark', function() grid = [[ non ui-watched line | ui-watched lin^e | - ~ | + {1:~ }| | ]], extmarks = { @@ -2052,7 +2072,7 @@ describe('API/win_extmark', function() grid = [[ ui-watched linupdat^e| e | - ~ | + {1:~ }| | ]], extmarks = { @@ -2079,9 +2099,9 @@ describe('API/win_extmark', function() grid = [[ ## grid 1 [4:--------------------]|*3 - [No Name] [+] | + {3:[No Name] [+] }| [2:--------------------]|*2 - [No Name] [+] | + {2:[No Name] [+] }| [3:--------------------]| ## grid 2 non ui-watched line | @@ -2091,7 +2111,7 @@ describe('API/win_extmark', function() ## grid 4 non ui-watched line | ui-watched lin^e | - ~ | + {1:~ }| ]], extmarks = { [2] = { @@ -2112,13 +2132,13 @@ describe('API/win_extmark', function() grid = [[ ## grid 1 [4:--------------------]|*3 - [No Name] [+] | + {3:[No Name] [+] }| [2:--------------------]|*2 - [No Name] [+] | + {2:[No Name] [+] }| [3:--------------------]| ## grid 2 non ui-watched line | - ui-watched linupd@@@| + ui-watched linupd{1:@@@}| ## grid 3 | ## grid 4 diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 1973d3e1c7..dd0611f184 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -1,14 +1,16 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear = helpers.clear +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local eq, eval = helpers.eq, helpers.eval -local command = helpers.command -local exec_capture = helpers.exec_capture -local api = helpers.api -local fn = helpers.fn -local pcall_err = helpers.pcall_err -local ok = helpers.ok -local assert_alive = helpers.assert_alive + +local clear = n.clear +local eq, eval = t.eq, n.eval +local command = n.command +local exec_capture = n.exec_capture +local api = n.api +local fn = n.fn +local pcall_err = t.pcall_err +local ok = t.ok +local assert_alive = n.assert_alive describe('API: highlight', function() clear() diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 0decd710e9..995711507f 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -1,17 +1,19 @@ -local helpers = require('test.functional.helpers')(after_each) - -local clear = helpers.clear -local command = helpers.command -local eq, neq = helpers.eq, helpers.neq -local exec_lua = helpers.exec_lua -local exec = helpers.exec -local feed = helpers.feed -local fn = helpers.fn -local api = helpers.api -local source = helpers.source -local pcall_err = helpers.pcall_err - -local shallowcopy = helpers.shallowcopy +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear = n.clear +local command = n.command +local eq, neq = t.eq, t.neq +local exec_lua = n.exec_lua +local exec = n.exec +local feed = n.feed +local fn = n.fn +local api = n.api +local matches = t.matches +local source = n.source +local pcall_err = t.pcall_err + +local shallowcopy = t.shallowcopy local sleep = vim.uv.sleep local sid_api_client = -9 @@ -398,7 +400,9 @@ describe('nvim_get_keymap', function() 0, exec_lua([[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]]) ) @@ -738,7 +742,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end end - it('can set mappings containing literal keycodes', function() + it('can set mappings containing C0 control codes', function() api.nvim_set_keymap('n', '\n\r\n', 'rhs', {}) local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs') eq(expected, get_mapargs('n', '<NL><CR><NL>')) @@ -951,7 +955,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -963,34 +969,38 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it(':map command shows lua mapping correctly', function() exec_lua [[ - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() print('jkl;') end, + }) ]] - assert.truthy( - string.match( - exec_lua [[return vim.api.nvim_exec2(':nmap asdf', { output = true }).output]], - '^\nn asdf <Lua %d+>' - ) + matches( + '^\nn asdf <Lua %d+>', + exec_lua [[return vim.api.nvim_exec2(':nmap asdf', { output = true }).output]] ) end) it('mapcheck() returns lua mapping correctly', function() exec_lua [[ - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() print('jkl;') end, + }) ]] - assert.truthy(string.match(fn.mapcheck('asdf', 'n'), '^<Lua %d+>')) + matches('^<Lua %d+>', fn.mapcheck('asdf', 'n')) end) - it('maparg() returns lua mapping correctly', function() + it('maparg() and maplist() return lua mapping correctly', function() eq( 0, exec_lua([[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]]) ) - assert.truthy(string.match(fn.maparg('asdf', 'n'), '^<Lua %d+>')) + matches('^<Lua %d+>', fn.maparg('asdf', 'n')) local mapargs = fn.maparg('asdf', 'n', false, true) mapargs.callback = nil @@ -1010,11 +1020,20 @@ describe('nvim_set_keymap, nvim_del_keymap', function() call maparg('asdf', 'n', v:false, v:true).callback() ]]) eq(2, exec_lua([[return GlobalCount]])) + + api.nvim_eval([[ + maplist()->filter({_, m -> m.lhs == 'asdf'})->foreach({_, m -> m.callback()}) + ]]) + eq(3, exec_lua([[return GlobalCount]])) end) it('can make lua expr mappings replacing keycodes', function() exec_lua [[ - vim.api.nvim_set_keymap('n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true }) + vim.api.nvim_set_keymap('n', 'aa', '', { + callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, + expr = true, + replace_keycodes = true, + }) ]] feed('aa') @@ -1024,7 +1043,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('can make lua expr mappings without replacing keycodes', function() exec_lua [[ - vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return '<space>' end, expr = true }) + vim.api.nvim_set_keymap('i', 'aa', '', { + callback = function() return '<space>' end, + expr = true, + }) ]] feed('iaa<esc>') @@ -1034,7 +1056,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('lua expr mapping returning nil is equivalent to returning an empty string', function() exec_lua [[ - vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return nil end, expr = true }) + vim.api.nvim_set_keymap('i', 'aa', '', { + callback = function() return nil end, + expr = true, + }) ]] feed('iaa<esc>') @@ -1047,7 +1072,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ VisibleCount = 0 - vim.api.nvim_set_keymap('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end}) + vim.api.nvim_set_keymap('i', '<F2>', '', { + callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end, + }) return VisibleCount ]] ) @@ -1060,7 +1087,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ OpCount = 0 - vim.api.nvim_set_keymap('o', '<F2>', '', {callback = function() OpCount = OpCount + 1 end}) + vim.api.nvim_set_keymap('o', '<F2>', '', { + callback = function() OpCount = OpCount + 1 end, + }) return OpCount ]] ) @@ -1075,7 +1104,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1085,7 +1116,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) exec_lua [[ - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount - 1 end, + }) ]] feed('asdf\n') @@ -1098,7 +1131,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1107,14 +1142,12 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) - exec_lua [[ - vim.api.nvim_del_keymap('n', 'asdf' ) - ]] + exec_lua [[vim.api.nvim_del_keymap('n', 'asdf' )]] feed('asdf\n') eq(1, exec_lua [[return GlobalCount]]) - eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + eq('\nNo mapping found', n.exec_capture('nmap asdf')) end) it('no double-free when unmapping simplifiable lua mappings', function() @@ -1122,7 +1155,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('n', '<C-I>', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_set_keymap('n', '<C-I>', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1131,29 +1166,30 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) - exec_lua [[ - vim.api.nvim_del_keymap('n', '<C-I>') - ]] + exec_lua [[vim.api.nvim_del_keymap('n', '<C-I>')]] feed('<C-I>\n') eq(1, exec_lua [[return GlobalCount]]) - eq('\nNo mapping found', helpers.exec_capture('nmap <C-I>')) + eq('\nNo mapping found', n.exec_capture('nmap <C-I>')) end) it('can set descriptions on mappings', function() api.nvim_set_keymap('n', 'lhs', 'rhs', { desc = 'map description' }) eq(generate_mapargs('n', 'lhs', 'rhs', { desc = 'map description' }), get_mapargs('n', 'lhs')) - eq('\nn lhs rhs\n map description', helpers.exec_capture('nmap lhs')) + eq('\nn lhs rhs\n map description', n.exec_capture('nmap lhs')) end) it('can define !-mode abbreviations with lua callbacks', function() exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('!a', 'foo', '', {expr = true, callback = function() - GlobalCount = GlobalCount + 1 - return tostring(GlobalCount) - end}) + vim.api.nvim_set_keymap('!a', 'foo', '', { + expr = true, + callback = function() + GlobalCount = GlobalCount + 1 + return tostring(GlobalCount) + end, + }) ]] feed 'iThe foo and the bar and the foo again<esc>' @@ -1166,10 +1202,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('can define insert mode abbreviations with lua callbacks', function() exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('ia', 'foo', '', {expr = true, callback = function() - GlobalCount = GlobalCount + 1 - return tostring(GlobalCount) - end}) + vim.api.nvim_set_keymap('ia', 'foo', '', { + expr = true, + callback = function() + GlobalCount = GlobalCount + 1 + return tostring(GlobalCount) + end, + }) ]] feed 'iThe foo and the bar and the foo again<esc>' @@ -1182,10 +1221,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('can define cmdline mode abbreviations with lua callbacks', function() exec_lua [[ GlobalCount = 0 - vim.api.nvim_set_keymap('ca', 'foo', '', {expr = true, callback = function() - GlobalCount = GlobalCount + 1 - return tostring(GlobalCount) - end}) + vim.api.nvim_set_keymap('ca', 'foo', '', { + expr = true, + callback = function() + GlobalCount = GlobalCount + 1 + return tostring(GlobalCount) + end, + }) ]] feed 'iThe foo and the bar and the foo again<esc>' @@ -1290,7 +1332,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() it('does not crash when setting mapping in a non-existing buffer #13541', function() pcall_err(api.nvim_buf_set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) - helpers.assert_alive() + n.assert_alive() end) it('can make lua mappings', function() @@ -1298,7 +1340,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1310,7 +1354,11 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() it('can make lua expr mappings replacing keycodes', function() exec_lua [[ - vim.api.nvim_buf_set_keymap(0, 'n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true }) + vim.api.nvim_buf_set_keymap(0, 'n', 'aa', '', { + callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, + expr = true, + replace_keycodes = true, + }) ]] feed('aa') @@ -1320,7 +1368,10 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() it('can make lua expr mappings without replacing keycodes', function() exec_lua [[ - vim.api.nvim_buf_set_keymap(0, 'i', 'aa', '', {callback = function() return '<space>' end, expr = true }) + vim.api.nvim_buf_set_keymap(0, 'i', 'aa', '', { + callback = function() return '<space>' end, + expr = true, + }) ]] feed('iaa<esc>') @@ -1333,7 +1384,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1343,7 +1396,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) exec_lua [[ - vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount - 1 end, + }) ]] feed('asdf\n') @@ -1356,7 +1411,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1365,14 +1422,12 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) - exec_lua [[ - vim.api.nvim_buf_del_keymap(0, 'n', 'asdf' ) - ]] + exec_lua [[vim.api.nvim_buf_del_keymap(0, 'n', 'asdf' )]] feed('asdf\n') eq(1, exec_lua [[return GlobalCount]]) - eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + eq('\nNo mapping found', n.exec_capture('nmap asdf')) end) it('no double-free when unmapping simplifiable lua mappings', function() @@ -1380,7 +1435,9 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() 0, exec_lua [[ GlobalCount = 0 - vim.api.nvim_buf_set_keymap(0, 'n', '<C-I>', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + vim.api.nvim_buf_set_keymap(0, 'n', '<C-I>', '', { + callback = function() GlobalCount = GlobalCount + 1 end, + }) return GlobalCount ]] ) @@ -1389,13 +1446,11 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq(1, exec_lua [[return GlobalCount]]) - exec_lua [[ - vim.api.nvim_buf_del_keymap(0, 'n', '<C-I>') - ]] + exec_lua [[vim.api.nvim_buf_del_keymap(0, 'n', '<C-I>')]] feed('<C-I>\n') eq(1, exec_lua [[return GlobalCount]]) - eq('\nNo mapping found', helpers.exec_capture('nmap <C-I>')) + eq('\nNo mapping found', n.exec_capture('nmap <C-I>')) end) end) diff --git a/test/functional/api/menu_spec.lua b/test/functional/api/menu_spec.lua index 44b9039393..76eef164c9 100644 --- a/test/functional/api/menu_spec.lua +++ b/test/functional/api/menu_spec.lua @@ -1,9 +1,9 @@ -local helpers = require('test.functional.helpers')(after_each) +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local clear = helpers.clear -local command = helpers.command -local feed = helpers.feed +local clear = n.clear +local command = n.command +local feed = n.feed describe('update_menu notification', function() local screen diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index 50c441792c..d2dd655b17 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -1,14 +1,15 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() -local clear = helpers.clear -local eq = helpers.eq -local fn = helpers.fn -local neq = helpers.neq -local nvim_argv = helpers.nvim_argv -local request = helpers.request -local retry = helpers.retry +local clear = n.clear +local eq = t.eq +local fn = n.fn +local neq = t.neq +local nvim_argv = n.nvim_argv +local request = n.request +local retry = t.retry local NIL = vim.NIL -local is_os = helpers.is_os +local is_os = t.is_os describe('API', function() before_each(clear) diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index d1608a951c..7b4c4e8312 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -1,11 +1,12 @@ -local helpers = require('test.functional.helpers')(after_each) -local assert_log = helpers.assert_log -local eq, clear, eval, command, next_msg = - helpers.eq, helpers.clear, helpers.eval, helpers.command, helpers.next_msg -local api = helpers.api -local exec_lua = helpers.exec_lua -local retry = helpers.retry -local assert_alive = helpers.assert_alive +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local eq, clear, eval, command, next_msg = t.eq, n.clear, n.eval, n.command, n.next_msg +local api = n.api +local exec_lua = n.exec_lua +local retry = t.retry +local assert_alive = n.assert_alive +local check_close = n.check_close local testlog = 'Xtest-server-notify-log' @@ -18,6 +19,7 @@ describe('notify', function() end) after_each(function() + check_close() os.remove(testlog) end) @@ -31,18 +33,18 @@ describe('notify', function() end) end) - describe('passing 0 as the channel id', function() - it('sends the notification/args to all subscribed channels', function() - api.nvim_subscribe('event2') + describe('channel id 0', function() + it('broadcasts the notification/args to all channels', function() eval('rpcnotify(0, "event1", 1, 2, 3)') eval('rpcnotify(0, "event2", 4, 5, 6)') eval('rpcnotify(0, "event2", 7, 8, 9)') + eq({ 'notification', 'event1', { 1, 2, 3 } }, next_msg()) eq({ 'notification', 'event2', { 4, 5, 6 } }, next_msg()) eq({ 'notification', 'event2', { 7, 8, 9 } }, next_msg()) - api.nvim_unsubscribe('event2') - api.nvim_subscribe('event1') + eval('rpcnotify(0, "event2", 10, 11, 12)') eval('rpcnotify(0, "event1", 13, 14, 15)') + eq({ 'notification', 'event2', { 10, 11, 12 } }, next_msg()) eq({ 'notification', 'event1', { 13, 14, 15 } }, next_msg()) end) @@ -75,17 +77,6 @@ describe('notify', function() end) end) - it('unsubscribe non-existing event #8745', function() - clear { env = { - NVIM_LOG_FILE = testlog, - } } - api.nvim_subscribe('event1') - api.nvim_unsubscribe('doesnotexist') - assert_log("tried to unsubscribe unknown event 'doesnotexist'", testlog, 10) - api.nvim_unsubscribe('event1') - assert_alive() - end) - it('cancels stale events on channel close', function() local catchan = eval("jobstart(['cat'], {'rpc': v:true})") local catpath = eval('exepath("cat")') @@ -94,7 +85,6 @@ describe('notify', function() exec_lua( [[ vim.rpcnotify(..., "nvim_call_function", 'chanclose', {..., 'rpc'}) - vim.rpcnotify(..., "nvim_subscribe", "daily_rant") return vim.api.nvim_get_chan_info(...) ]], catchan diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 298dbac217..bdd340f6c6 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -1,17 +1,18 @@ -- Test server -> client RPC scenarios. Note: unlike `rpcnotify`, to evaluate -- `rpcrequest` calls we need the client event loop to be running. -local helpers = require('test.functional.helpers')(after_each) - -local clear, eval = helpers.clear, helpers.eval -local eq, neq, run, stop = helpers.eq, helpers.neq, helpers.run, helpers.stop -local nvim_prog, command, fn = helpers.nvim_prog, helpers.command, helpers.fn -local source, next_msg = helpers.source, helpers.next_msg -local ok = helpers.ok -local api = helpers.api -local spawn, merge_args = helpers.spawn, helpers.merge_args -local set_session = helpers.set_session -local pcall_err = helpers.pcall_err -local assert_alive = helpers.assert_alive +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear, eval = n.clear, n.eval +local eq, neq, run, stop = t.eq, t.neq, n.run, n.stop +local nvim_prog, command, fn = n.nvim_prog, n.command, n.fn +local source, next_msg = n.source, n.next_msg +local ok = t.ok +local api = n.api +local spawn, merge_args = n.spawn, n.merge_args +local set_session = n.set_session +local pcall_err = t.pcall_err +local assert_alive = n.assert_alive describe('server -> client', function() local cid @@ -91,19 +92,19 @@ describe('server -> client', function() local function on_request(method, args) eq('rcall', method) - local n = unpack(args) * 2 - if n <= 16 then + local _n = unpack(args) * 2 + if _n <= 16 then local cmd - if n == 4 then - cmd = 'let g:result2 = rpcrequest(' .. cid .. ', "rcall", ' .. n .. ')' - elseif n == 8 then - cmd = 'let g:result3 = rpcrequest(' .. cid .. ', "rcall", ' .. n .. ')' - elseif n == 16 then - cmd = 'let g:result4 = rpcrequest(' .. cid .. ', "rcall", ' .. n .. ')' + if _n == 4 then + cmd = 'let g:result2 = rpcrequest(' .. cid .. ', "rcall", ' .. _n .. ')' + elseif _n == 8 then + cmd = 'let g:result3 = rpcrequest(' .. cid .. ', "rcall", ' .. _n .. ')' + elseif _n == 16 then + cmd = 'let g:result4 = rpcrequest(' .. cid .. ', "rcall", ' .. _n .. ')' end command(cmd) end - return n + return _n end run(on_request, nil, on_setup) end) @@ -259,7 +260,7 @@ describe('server -> client', function() pcall(fn.jobstop, jobid) end) - if helpers.skip(helpers.is_os('win')) then + if t.skip(t.is_os('win')) then return end @@ -280,7 +281,7 @@ describe('server -> client', function() end) describe('connecting to another (peer) nvim', function() - local nvim_argv = merge_args(helpers.nvim_argv, { '--headless' }) + local nvim_argv = merge_args(n.nvim_argv, { '--headless' }) local function connect_test(server, mode, address) local serverpid = fn.getpid() local client = spawn(nvim_argv, false, nil, true) @@ -363,6 +364,24 @@ describe('server -> client', function() server:close() client:close() end) + + it('via stdio, with many small flushes does not crash #23781', function() + source([[ + let chan = jobstart([v:progpath, '--embed', '--headless', '-n', '-u', 'NONE', '-i', 'NONE'], { 'rpc':v:false }) + call chansend(chan, 0Z94) + sleep 50m + call chansend(chan, 0Z00) + call chansend(chan, 0Z01) + call chansend(chan, 0ZAC) + call chansend(chan, 0Z6E76696D5F636F6D6D616E64) + call chansend(chan, 0Z91) + call chansend(chan, 0ZA5) + call chansend(chan, 0Z71616C6C21) + let g:statuses = jobwait([chan]) + ]]) + eq(eval('g:statuses'), { 0 }) + assert_alive() + end) end) describe('connecting to its own pipe address', function() diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 36955c4ace..74858475c8 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -1,11 +1,15 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok -local api = helpers.api -local fn = helpers.fn -local request = helpers.request +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear, eq, ok = n.clear, t.eq, t.ok +local exec = n.exec +local feed = n.feed +local api = n.api +local fn = n.fn +local request = n.request local NIL = vim.NIL -local pcall_err = helpers.pcall_err -local command = helpers.command +local pcall_err = t.pcall_err +local command = n.command describe('api/tabpage', function() before_each(clear) @@ -86,6 +90,30 @@ describe('api/tabpage', function() pcall_err(api.nvim_tabpage_set_win, tab1, win3) ) end) + + it('does not switch window when textlocked or in the cmdwin', function() + local target_win = api.nvim_get_current_win() + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Vim:E11: Invalid in command-line window; <CR> executes, CTRL-C quits', + pcall_err(api.nvim_tabpage_set_win, 0, target_win) + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_tabpage_set_win(0,%d) + ]]):format(target_win)) + cur_win = api.nvim_get_current_win() + eq( + 'Vim(normal):E5555: API call: Vim:E565: Not allowed to change text or change window', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) end) describe('{get,set,del}_var', function() diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua index 3e1f1ec965..2145db7f8a 100644 --- a/test/functional/api/ui_spec.lua +++ b/test/functional/api/ui_spec.lua @@ -1,14 +1,16 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local clear = helpers.clear -local command = helpers.command -local eq = helpers.eq -local eval = helpers.eval -local exec = helpers.exec -local feed = helpers.feed -local api = helpers.api -local request = helpers.request -local pcall_err = helpers.pcall_err + +local clear = n.clear +local command = n.command +local eq = t.eq +local eval = n.eval +local exec = n.exec +local feed = n.feed +local api = n.api +local request = n.request +local pcall_err = t.pcall_err describe('nvim_ui_attach()', function() before_each(function() diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index c304f1aa88..5dad9978b7 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -1,6 +1,8 @@ -local helpers = require('test.functional.helpers')(after_each) -local clear, fn, eq = helpers.clear, helpers.fn, helpers.eq -local api = helpers.api +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local clear, fn, eq = n.clear, n.fn, t.eq +local api = n.api local function read_mpack_file(fname) local fd = io.open(fname, 'rb') diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 9a4a457637..fd0535aa51 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,42 +1,43 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') local uv = vim.uv local fmt = string.format -local dedent = helpers.dedent -local assert_alive = helpers.assert_alive +local dedent = t.dedent +local assert_alive = n.assert_alive local NIL = vim.NIL -local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq -local command = helpers.command -local command_output = helpers.api.nvim_command_output -local exec = helpers.exec -local exec_capture = helpers.exec_capture -local eval = helpers.eval -local expect = helpers.expect -local fn = helpers.fn -local api = helpers.api -local matches = helpers.matches +local clear, eq, neq = n.clear, t.eq, t.neq +local command = n.command +local command_output = n.api.nvim_command_output +local exec = n.exec +local exec_capture = n.exec_capture +local eval = n.eval +local expect = n.expect +local fn = n.fn +local api = n.api +local matches = t.matches local pesc = vim.pesc -local mkdir_p = helpers.mkdir_p -local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed -local async_meths = helpers.async_meths -local is_os = helpers.is_os -local parse_context = helpers.parse_context -local request = helpers.request -local rmdir = helpers.rmdir -local source = helpers.source -local next_msg = helpers.next_msg -local tmpname = helpers.tmpname -local write_file = helpers.write_file -local exec_lua = helpers.exec_lua -local exc_exec = helpers.exc_exec -local insert = helpers.insert -local skip = helpers.skip - -local pcall_err = helpers.pcall_err +local mkdir_p = n.mkdir_p +local ok, nvim_async, feed = t.ok, n.nvim_async, n.feed +local async_meths = n.async_meths +local is_os = t.is_os +local parse_context = n.parse_context +local request = n.request +local rmdir = n.rmdir +local source = n.source +local next_msg = n.next_msg +local tmpname = t.tmpname +local write_file = t.write_file +local exec_lua = n.exec_lua +local exc_exec = n.exc_exec +local insert = n.insert +local skip = t.skip + +local pcall_err = t.pcall_err local format_string = require('test.format_string').format_string -local intchar2lua = helpers.intchar2lua -local mergedicts_copy = helpers.mergedicts_copy +local intchar2lua = t.intchar2lua +local mergedicts_copy = t.mergedicts_copy local endswith = vim.endswith describe('API', function() @@ -559,6 +560,16 @@ describe('API', function() eq('Vim:E121: Undefined variable: bogus', pcall_err(request, 'nvim_eval', 'bogus expression')) eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) + + it('can return Lua function to Lua code', function() + eq( + [["a string with \"double quotes\" and 'single quotes'"]], + exec_lua([=[ + local fun = vim.api.nvim_eval([[luaeval('string.format')]]) + return fun('%q', [[a string with "double quotes" and 'single quotes']]) + ]=]) + ) + end) end) describe('nvim_call_function', function() @@ -624,6 +635,16 @@ describe('API', function() pcall_err(request, 'nvim_call_function', 'Foo', too_many_args) ) end) + + it('can return Lua function to Lua code', function() + eq( + [["a string with \"double quotes\" and 'single quotes'"]], + exec_lua([=[ + local fun = vim.api.nvim_call_function('luaeval', { 'string.format' }) + return fun('%q', [[a string with "double quotes" and 'single quotes']]) + ]=]) + ) + end) end) describe('nvim_call_dict_function', function() @@ -702,18 +723,18 @@ describe('API', function() end) after_each(function() - helpers.rmdir('Xtestdir') + n.rmdir('Xtestdir') end) it('works', function() api.nvim_set_current_dir('Xtestdir') - eq(fn.getcwd(), start_dir .. helpers.get_pathsep() .. 'Xtestdir') + eq(start_dir .. n.get_pathsep() .. 'Xtestdir', fn.getcwd()) end) it('sets previous directory', function() api.nvim_set_current_dir('Xtestdir') command('cd -') - eq(fn.getcwd(), start_dir) + eq(start_dir, fn.getcwd()) end) end) @@ -1269,7 +1290,7 @@ describe('API', function() api.nvim_paste('', true, 3) screen:expect([[ | - ~ |*2 + {1:~ }|*2 :Foo^ | ]]) end) @@ -1280,8 +1301,8 @@ describe('API', function() api.nvim_paste('normal! \023\022\006\027', true, -1) screen:expect([[ | - ~ |*2 - :normal! ^W^V^F^[^ | + {1:~ }|*2 + :normal! {18:^W^V^F^[}^ | ]]) end) it('crlf=false does not break lines at CR, CRLF', function() @@ -1467,7 +1488,7 @@ describe('API', function() eq(NIL, api.nvim_get_var('Unknown_script_func')) -- Check if autoload works properly - local pathsep = helpers.get_pathsep() + local pathsep = n.get_pathsep() local xconfig = 'Xhome' .. pathsep .. 'Xconfig' local xdata = 'Xhome' .. pathsep .. 'Xdata' local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep) @@ -1593,14 +1614,12 @@ describe('API', function() api.nvim_set_option_value('equalalways', false, {}) local status, rv = pcall(command_output, 'verbose set equalalways?') eq(true, status) - ok( - nil ~= string.find(rv, 'noequalalways\n' .. '\tLast set from API client %(channel id %d+%)') - ) + matches('noequalalways\n' .. '\tLast set from API client %(channel id %d+%)', rv) api.nvim_exec_lua('vim.api.nvim_set_option_value("equalalways", true, {})', {}) status, rv = pcall(command_output, 'verbose set equalalways?') eq(true, status) - eq(' equalalways\n\tLast set from Lua', rv) + eq(' equalalways\n\tLast set from Lua (run Nvim with -V1 for more details)', rv) end) it('updates whether the option has ever been set #25025', function() @@ -1953,7 +1972,7 @@ describe('API', function() describe('RPC (K_EVENT)', function() it('does not complete ("interrupt") normal-mode operator-pending #6166', function() - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('gg') @@ -1981,16 +2000,16 @@ describe('API', function() -- Make any RPC request (can be non-async: op-pending does not block). api.nvim_get_current_buf() screen:expect([[ - ^a$ | - b$ | - c$ | + ^a{1:$} | + b{1:$} | + c{1:$} | | ]]) end) it('does not complete ("interrupt") normal-mode map-pending #6166', function() command("nnoremap dd :let g:foo='it worked...'<CR>") - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('gg') @@ -2002,13 +2021,13 @@ describe('API', function() expect([[ FIRST LINE SECOND LINE]]) - eq('it worked...', helpers.eval('g:foo')) + eq('it worked...', n.eval('g:foo')) end) it('does not complete ("interrupt") insert-mode map-pending #6166', function() command('inoremap xx foo') command('set timeoutlen=9999') - helpers.insert([[ + n.insert([[ FIRST LINE SECOND LINE]]) api.nvim_input('ix') @@ -2155,35 +2174,32 @@ describe('API', function() describe('nvim_replace_termcodes', function() it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() - eq('\128\254X', helpers.api.nvim_replace_termcodes('\128', true, true, true)) + eq('\128\254X', n.api.nvim_replace_termcodes('\128', true, true, true)) end) it('leaves non-K_SPECIAL string unchanged', function() - eq('abc', helpers.api.nvim_replace_termcodes('abc', true, true, true)) + eq('abc', n.api.nvim_replace_termcodes('abc', true, true, true)) end) it('converts <expressions>', function() - eq('\\', helpers.api.nvim_replace_termcodes('<Leader>', true, true, true)) + eq('\\', n.api.nvim_replace_termcodes('<Leader>', true, true, true)) end) it('converts <LeftMouse> to K_SPECIAL KS_EXTRA KE_LEFTMOUSE', function() -- K_SPECIAL KS_EXTRA KE_LEFTMOUSE -- 0x80 0xfd 0x2c -- 128 253 44 - eq('\128\253\44', helpers.api.nvim_replace_termcodes('<LeftMouse>', true, true, true)) + eq('\128\253\44', n.api.nvim_replace_termcodes('<LeftMouse>', true, true, true)) end) it('converts keycodes', function() - eq( - '\nx\27x\rx<x', - helpers.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true) - ) + eq('\nx\27x\rx<x', n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true)) end) it('does not convert keycodes if special=false', function() eq( '<NL>x<Esc>x<CR>x<lt>x', - helpers.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false) + n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false) ) end) @@ -2212,18 +2228,18 @@ describe('API', function() api.nvim_feedkeys(':let x1="…"\n', '', true) -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80 - local inp = helpers.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true) + local inp = n.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true) api.nvim_feedkeys(inp, '', true) -- escape_ks=true -- nvim_feedkeys with K_SPECIAL escaping disabled - inp = helpers.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true) + inp = n.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true) api.nvim_feedkeys(inp, '', false) -- escape_ks=false - helpers.stop() + n.stop() end -- spin the loop a bit - helpers.run(nil, nil, on_setup) + n.run(nil, nil, on_setup) eq('…', api.nvim_get_var('x1')) -- Because of the double escaping this is neq @@ -2366,7 +2382,7 @@ describe('API', function() {0:~ }|*6 {1:very fail} | ]]) - helpers.poke_eventloop() + n.poke_eventloop() -- shows up to &cmdheight lines async_meths.nvim_err_write('more fail\ntoo fail\n') @@ -2678,7 +2694,7 @@ describe('API', function() describe('nvim_list_runtime_paths', function() setup(function() - local pathsep = helpers.get_pathsep() + local pathsep = n.get_pathsep() mkdir_p('Xtest' .. pathsep .. 'a') mkdir_p('Xtest' .. pathsep .. 'b') end) @@ -2723,7 +2739,7 @@ describe('API', function() it('can throw exceptions', function() local status, err = pcall(api.nvim_get_option_value, 'invalid-option', {}) eq(false, status) - ok(err:match("Unknown option 'invalid%-option'") ~= nil) + matches("Unknown option 'invalid%-option'", err) end) it('does not truncate error message <1 MB #5984', function() @@ -2736,10 +2752,7 @@ describe('API', function() it('does not leak memory on incorrect argument types', function() local status, err = pcall(api.nvim_set_current_dir, { 'not', 'a', 'dir' }) eq(false, status) - ok( - err:match(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String') - ~= nil - ) + matches(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String', err) end) describe('nvim_parse_expression', function() @@ -3135,10 +3148,60 @@ describe('API', function() -- nowadays this works because we don't execute any spurious autocmds at all #24824 assert_alive() end) + + it('no memory leak when autocommands load the buffer immediately', function() + exec([[ + autocmd BufNew * ++once call bufload(expand("<abuf>")->str2nr()) + \| let loaded = bufloaded(expand("<abuf>")->str2nr()) + ]]) + api.nvim_create_buf(false, true) + eq(1, eval('g:loaded')) + end) + + it('creating scratch buffer where autocommands set &swapfile works', function() + exec([[ + autocmd BufNew * ++once execute expand("<abuf>") "buffer" + \| file foobar + \| setlocal swapfile + ]]) + local new_buf = api.nvim_create_buf(false, true) + neq('', fn.swapname(new_buf)) + end) + + it('fires expected autocommands', function() + exec([=[ + " Append the &buftype to check autocommands trigger *after* the buffer was configured to be + " scratch, if applicable. + autocmd BufNew * let fired += [["BufNew", expand("<abuf>")->str2nr(), + \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] + autocmd BufAdd * let fired += [["BufAdd", expand("<abuf>")->str2nr(), + \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] + + " Don't want to see OptionSet; buffer options set from passing true for "scratch", etc. + " should be configured invisibly, and before autocommands. + autocmd OptionSet * let fired += [["OptionSet", expand("<amatch>")]] + + let fired = [] + ]=]) + local new_buf = api.nvim_create_buf(false, false) + eq({ { 'BufNew', new_buf, '' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(false, true) + eq({ { 'BufNew', new_buf, 'nofile' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(true, false) + eq({ { 'BufNew', new_buf, '' }, { 'BufAdd', new_buf, '' } }, eval('g:fired')) + + command('let fired = []') + new_buf = api.nvim_create_buf(true, true) + eq({ { 'BufNew', new_buf, 'nofile' }, { 'BufAdd', new_buf, 'nofile' } }, eval('g:fired')) + end) end) describe('nvim_get_runtime_file', function() - local p = helpers.alter_slashes + local p = n.alter_slashes it('can find files', function() eq({}, api.nvim_get_runtime_file('bork.borkbork', false)) eq({}, api.nvim_get_runtime_file('bork.borkbork', true)) @@ -3365,13 +3428,13 @@ describe('API', function() {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}}, } - for _, t in pairs(tests) do - it(t.desc, function() + for _, test in pairs(tests) do + it(test.desc, function() -- Switch to the target buffer/window so that curbuf/curwin are used. api.nvim_set_current_win(wins[2]) - local info = api.nvim_get_option_info2(unpack(t.args)) - eq(t.linenr, info.last_set_linenr) - eq(t.sid, info.last_set_sid) + local info = api.nvim_get_option_info2(unpack(test.args)) + eq(test.linenr, info.last_set_linenr) + eq(test.sid, info.last_set_sid) end) end @@ -3492,9 +3555,9 @@ describe('API', function() false, { width = 79, height = 31, row = 1, col = 1, relative = 'editor' } ) - local t = api.nvim_open_term(b, {}) + local term = api.nvim_open_term(b, {}) - api.nvim_chan_send(t, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a')) + api.nvim_chan_send(term, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a')) screen:expect { grid = [[ ^ | @@ -3620,7 +3683,7 @@ describe('API', function() api.nvim_buf_set_name(buf, 'mybuf') local mark = api.nvim_get_mark('F', {}) -- Compare the path tail only - assert(string.find(mark[4], 'mybuf$')) + matches('mybuf$', mark[4]) eq({ 2, 2, buf, mark[4] }, mark) end) it('validation', function() @@ -3855,13 +3918,13 @@ describe('API', function() norm 4G ]]) eq({ - str = '││aabb 4 ', + str = '││bbaa 4 ', width = 9, highlights = { { group = 'CursorLineFold', start = 0 }, { group = 'Normal', start = 6 }, - { group = 'IncSearch', start = 6 }, - { group = 'ErrorMsg', start = 8 }, + { group = 'ErrorMsg', start = 6 }, + { group = 'IncSearch', start = 8 }, { group = 'Normal', start = 10 }, }, }, api.nvim_eval_statusline( @@ -4953,4 +5016,218 @@ describe('API', function() eq(false, exec_lua('return _G.success')) end) end) + + it('nvim__redraw', function() + local screen = Screen.new(60, 5) + screen:attach() + local win = api.nvim_get_current_win() + eq('at least one action required', pcall_err(api.nvim__redraw, {})) + eq('at least one action required', pcall_err(api.nvim__redraw, { buf = 0 })) + eq('at least one action required', pcall_err(api.nvim__redraw, { win = 0 })) + eq("cannot use both 'buf' and 'win'", pcall_err(api.nvim__redraw, { buf = 0, win = 0 })) + feed(':echo getchar()<CR>') + fn.setline(1, 'foobar') + command('vnew') + fn.setline(1, 'foobaz') + -- Can flush pending screen updates + api.nvim__redraw({ flush = true }) + screen:expect({ + grid = [[ + foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + ^:echo getchar() | + ]], + }) + -- Can update the grid cursor position #20793 + api.nvim__redraw({ cursor = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + -- Also in non-current window + api.nvim__redraw({ cursor = true, win = win }) + screen:expect({ + grid = [[ + foobaz │^foobar | + {1:~ }│{1:~ }|*2 + {3:[No Name] [+] }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + -- Can update the 'statusline' in a single window + api.nvim_set_option_value('statusline', 'statusline1', { win = 0 }) + api.nvim_set_option_value('statusline', 'statusline2', { win = win }) + api.nvim__redraw({ cursor = true, win = 0, statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline1 }{2:[No Name] [+] }| + :echo getchar() | + ]], + }) + api.nvim__redraw({ win = win, statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline1 }{2:statusline2 }| + :echo getchar() | + ]], + }) + -- Can update the 'statusline' in all windows + api.nvim_set_option_value('statusline', '', { win = win }) + api.nvim_set_option_value('statusline', 'statusline3', {}) + api.nvim__redraw({ statusline = true }) + screen:expect({ + grid = [[ + ^foobaz │foobar | + {1:~ }│{1:~ }|*2 + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'statuscolumn' + api.nvim_set_option_value('statuscolumn', 'statuscolumn', { win = win }) + api.nvim__redraw({ statuscolumn = true }) + screen:expect({ + grid = [[ + ^foobaz │{8:statuscolumn}foobar | + {1:~ }│{1:~ }|*2 + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'winbar' + api.nvim_set_option_value('winbar', 'winbar', { win = 0 }) + api.nvim__redraw({ win = 0, winbar = true }) + screen:expect({ + grid = [[ + {5:^winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {1:~ }│{1:~ }| + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update the 'tabline' + api.nvim_set_option_value('showtabline', 2, {}) + api.nvim_set_option_value('tabline', 'tabline', {}) + api.nvim__redraw({ tabline = true }) + screen:expect({ + grid = [[ + {2:^tabline }| + {5:winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {3:statusline3 }{2:statusline3 }| + :echo getchar() | + ]], + }) + -- Can update multiple status widgets + api.nvim_set_option_value('tabline', 'tabline2', {}) + api.nvim_set_option_value('statusline', 'statusline4', {}) + api.nvim__redraw({ statusline = true, tabline = true }) + screen:expect({ + grid = [[ + {2:^tabline2 }| + {5:winbar }│{8:statuscolumn}foobar | + foobaz │{1:~ }| + {3:statusline4 }{2:statusline4 }| + :echo getchar() | + ]], + }) + -- Can update all status widgets + api.nvim_set_option_value('tabline', 'tabline3', {}) + api.nvim_set_option_value('statusline', 'statusline5', {}) + api.nvim_set_option_value('statuscolumn', 'statuscolumn2', {}) + api.nvim_set_option_value('winbar', 'winbar2', {}) + api.nvim__redraw({ statuscolumn = true, statusline = true, tabline = true, winbar = true }) + screen:expect({ + grid = [[ + {2:^tabline3 }| + {5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}foobaz │{8:statuscolumn}foobar | + {3:statusline5 }{2:statusline5 }| + :echo getchar() | + ]], + }) + -- Can update status widget for a specific window + feed('<CR><CR>') + command('let g:status=0') + api.nvim_set_option_value('statusline', '%{%g:status%}', { win = 0 }) + command('vsplit') + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:0 }{2:0 statusline5 }| + 13 | + ]], + }) + command('let g:status=1') + api.nvim__redraw({ win = 0, statusline = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:1 }{2:0 statusline5 }| + 13 | + ]], + }) + -- Can update status widget for a specific buffer + command('let g:status=2') + api.nvim__redraw({ buf = 0, statusline = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | + {3:2 }{2:2 statusline5 }| + 13 | + ]], + }) + -- valid = true does not draw any lines on its own + exec_lua([[ + _G.lines = 0 + ns = vim.api.nvim_create_namespace('') + vim.api.nvim_set_decoration_provider(ns, { + on_win = function() + if _G.do_win then + vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'IncSearch', end_col = 6 }) + end + end, + on_line = function() + _G.lines = _G.lines + 1 + end, + }) + ]]) + local lines = exec_lua('return lines') + api.nvim__redraw({ buf = 0, valid = true, flush = true }) + eq(lines, exec_lua('return _G.lines')) + -- valid = false does + api.nvim__redraw({ buf = 0, valid = false, flush = true }) + neq(lines, exec_lua('return _G.lines')) + -- valid = true does redraw lines if affected by on_win callback + exec_lua('_G.do_win = true') + api.nvim__redraw({ buf = 0, valid = true, flush = true }) + screen:expect({ + grid = [[ + {2:tabline3 }| + {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| + {8:statuscolumn2}{2:^foobaz} │{8:statuscolumn2}{2:foobaz}│{8:statuscolumn}foobar | + {3:2 }{2:2 statusline5 }| + 13 | + ]], + }) + -- takes buffer line count from correct buffer with "win" and {0, -1} "range" + api.nvim__redraw({ win = 0, range = { 0, -1 } }) + n.assert_alive() + end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 097a546ef2..15b9b0945c 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1,27 +1,29 @@ -local helpers = require('test.functional.helpers')(after_each) +local t = require('test.testutil') +local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') + local clear, curbuf, curbuf_contents, curwin, eq, neq, matches, ok, feed, insert, eval = - helpers.clear, - helpers.api.nvim_get_current_buf, - helpers.curbuf_contents, - helpers.api.nvim_get_current_win, - helpers.eq, - helpers.neq, - helpers.matches, - helpers.ok, - helpers.feed, - helpers.insert, - helpers.eval -local poke_eventloop = helpers.poke_eventloop -local exec = helpers.exec -local exec_lua = helpers.exec_lua -local fn = helpers.fn -local request = helpers.request + n.clear, + n.api.nvim_get_current_buf, + n.curbuf_contents, + n.api.nvim_get_current_win, + t.eq, + t.neq, + t.matches, + t.ok, + n.feed, + n.insert, + n.eval +local poke_eventloop = n.poke_eventloop +local exec = n.exec +local exec_lua = n.exec_lua +local fn = n.fn +local request = n.request local NIL = vim.NIL -local api = helpers.api -local command = helpers.command -local pcall_err = helpers.pcall_err -local assert_alive = helpers.assert_alive +local api = n.api +local command = n.command +local pcall_err = t.pcall_err +local assert_alive = n.assert_alive describe('API/win', function() before_each(clear) @@ -111,6 +113,44 @@ describe('API/win', function() api.nvim_win_set_buf(new_win, next_buf) eq(next_buf, api.nvim_win_get_buf(new_win)) end) + + describe("with 'autochdir'", function() + local topdir + local otherbuf + local oldwin + local newwin + + before_each(function() + command('set shellslash') + topdir = fn.getcwd() + t.mkdir(topdir .. '/Xacd') + t.mkdir(topdir .. '/Xacd/foo') + otherbuf = api.nvim_create_buf(false, true) + api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt') + + command('set autochdir') + command('edit Xacd/foo/bar.txt') + eq(topdir .. '/Xacd/foo', fn.getcwd()) + + oldwin = api.nvim_get_current_win() + command('vsplit') + newwin = api.nvim_get_current_win() + end) + + after_each(function() + n.rmdir(topdir .. '/Xacd') + end) + + it('does not change cwd with non-current window', function() + api.nvim_win_set_buf(oldwin, otherbuf) + eq(topdir .. '/Xacd/foo', fn.getcwd()) + end) + + it('changes cwd with current window', function() + api.nvim_win_set_buf(newwin, otherbuf) + eq(topdir .. '/Xacd', fn.getcwd()) + end) + end) end) describe('{get,set}_cursor', function() @@ -1147,27 +1187,6 @@ describe('API/win', function() end) describe('open_win', function() - it('noautocmd option works', function() - command('autocmd BufEnter,BufLeave,BufWinEnter * let g:fired = 1') - api.nvim_open_win(api.nvim_create_buf(true, true), true, { - relative = 'win', - row = 3, - col = 3, - width = 12, - height = 3, - noautocmd = true, - }) - eq(0, fn.exists('g:fired')) - api.nvim_open_win(api.nvim_create_buf(true, true), true, { - relative = 'win', - row = 3, - col = 3, - width = 12, - height = 3, - }) - eq(1, fn.exists('g:fired')) - end) - it('disallowed in cmdwin if enter=true or buf=cmdwin_buf', function() local new_buf = api.nvim_create_buf(true, true) feed('q:') @@ -1233,81 +1252,151 @@ describe('API/win', function() eq(wins_before, api.nvim_list_wins()) end) - it('creates a split window', function() - local win = api.nvim_open_win(0, true, { - vertical = false, - }) - eq('', api.nvim_win_get_config(win).relative) + describe('creates a split window above', function() + local function test_open_win_split_above(key, val) + local initial_win = api.nvim_get_current_win() + local win = api.nvim_open_win(0, true, { + [key] = val, + height = 10, + }) + eq('', api.nvim_win_get_config(win).relative) + eq(10, api.nvim_win_get_height(win)) + local layout = fn.winlayout() + eq({ + 'col', + { + { 'leaf', win }, + { 'leaf', initial_win }, + }, + }, layout) + end + + it("with split = 'above'", function() + test_open_win_split_above('split', 'above') + end) + + it("with vertical = false and 'nosplitbelow'", function() + api.nvim_set_option_value('splitbelow', false, {}) + test_open_win_split_above('vertical', false) + end) end) - it('creates split windows in the correct direction', function() - local initial_win = api.nvim_get_current_win() - local win = api.nvim_open_win(0, true, { - vertical = true, - }) - eq('', api.nvim_win_get_config(win).relative) + describe('creates a split window below', function() + local function test_open_win_split_below(key, val) + local initial_win = api.nvim_get_current_win() + local win = api.nvim_open_win(0, true, { + [key] = val, + height = 15, + }) + eq('', api.nvim_win_get_config(win).relative) + eq(15, api.nvim_win_get_height(win)) + local layout = fn.winlayout() + eq({ + 'col', + { + { 'leaf', initial_win }, + { 'leaf', win }, + }, + }, layout) + end - local layout = fn.winlayout() + it("with split = 'below'", function() + test_open_win_split_below('split', 'below') + end) - eq({ - 'row', - { - { 'leaf', win }, - { 'leaf', initial_win }, - }, - }, layout) + it("with vertical = false and 'splitbelow'", function() + api.nvim_set_option_value('splitbelow', true, {}) + test_open_win_split_below('vertical', false) + end) end) - it("respects the 'split' option", function() - local initial_win = api.nvim_get_current_win() - local win = api.nvim_open_win(0, true, { - split = 'below', - }) - eq('', api.nvim_win_get_config(win).relative) + describe('creates a split window to the left', function() + local function test_open_win_split_left(key, val) + local initial_win = api.nvim_get_current_win() + local win = api.nvim_open_win(0, true, { + [key] = val, + width = 25, + }) + eq('', api.nvim_win_get_config(win).relative) + eq(25, api.nvim_win_get_width(win)) + local layout = fn.winlayout() + eq({ + 'row', + { + { 'leaf', win }, + { 'leaf', initial_win }, + }, + }, layout) + end - local layout = fn.winlayout() + it("with split = 'left'", function() + test_open_win_split_left('split', 'left') + end) - eq({ - 'col', - { - { 'leaf', initial_win }, - { 'leaf', win }, - }, - }, layout) + it("with vertical = true and 'nosplitright'", function() + api.nvim_set_option_value('splitright', false, {}) + test_open_win_split_left('vertical', true) + end) end) - it( - "doesn't change tp_curwin when splitting window in non-current tab with enter=false", - function() - local tab1 = api.nvim_get_current_tabpage() - local tab1_win = api.nvim_get_current_win() + describe('creates a split window to the right', function() + local function test_open_win_split_right(key, val) + local initial_win = api.nvim_get_current_win() + local win = api.nvim_open_win(0, true, { + [key] = val, + width = 30, + }) + eq('', api.nvim_win_get_config(win).relative) + eq(30, api.nvim_win_get_width(win)) + local layout = fn.winlayout() + eq({ + 'row', + { + { 'leaf', initial_win }, + { 'leaf', win }, + }, + }, layout) + end + + it("with split = 'right'", function() + test_open_win_split_right('split', 'right') + end) - helpers.command('tabnew') - local tab2 = api.nvim_get_current_tabpage() - local tab2_win = api.nvim_get_current_win() + it("with vertical = true and 'splitright'", function() + api.nvim_set_option_value('splitright', true, {}) + test_open_win_split_right('vertical', true) + end) + end) - eq({ tab1_win, tab2_win }, api.nvim_list_wins()) - eq({ tab1, tab2 }, api.nvim_list_tabpages()) + it("doesn't change tp_curwin when splitting window in another tab with enter=false", function() + local tab1 = api.nvim_get_current_tabpage() + local tab1_win = api.nvim_get_current_win() - api.nvim_set_current_tabpage(tab1) - eq(tab1_win, api.nvim_get_current_win()) + n.command('tabnew') + local tab2 = api.nvim_get_current_tabpage() + local tab2_win = api.nvim_get_current_win() - local tab2_prevwin = fn.tabpagewinnr(tab2, '#') + eq({ tab1_win, tab2_win }, api.nvim_list_wins()) + eq({ tab1, tab2 }, api.nvim_list_tabpages()) - -- split in tab2 whine in tab2, with enter = false - local tab2_win2 = api.nvim_open_win(api.nvim_create_buf(false, true), false, { - win = tab2_win, - split = 'right', - }) - eq(tab1_win, api.nvim_get_current_win()) -- we should still be in the first tp - eq(tab1_win, api.nvim_tabpage_get_win(tab1)) + api.nvim_set_current_tabpage(tab1) + eq(tab1_win, api.nvim_get_current_win()) - eq(tab2_win, api.nvim_tabpage_get_win(tab2)) -- tab2's tp_curwin should not have changed - eq(tab2_prevwin, fn.tabpagewinnr(tab2, '#')) -- tab2's tp_prevwin should not have changed - eq({ tab1_win, tab2_win, tab2_win2 }, api.nvim_list_wins()) - eq({ tab2_win, tab2_win2 }, api.nvim_tabpage_list_wins(tab2)) - end - ) + local tab2_prevwin = fn.tabpagewinnr(tab2, '#') + + -- split in tab2 whine in tab2, with enter = false + local tab2_win2 = api.nvim_open_win(api.nvim_create_buf(false, true), false, { + win = tab2_win, + split = 'right', + }) + eq(tab1_win, api.nvim_get_current_win()) -- we should still be in the first tp + eq(tab1_win, api.nvim_tabpage_get_win(tab1)) + + eq(tab2_win, api.nvim_tabpage_get_win(tab2)) -- tab2's tp_curwin should not have changed + eq(tab2_prevwin, fn.tabpagewinnr(tab2, '#')) -- tab2's tp_prevwin should not have changed + eq({ tab1_win, tab2_win, tab2_win2 }, api.nvim_list_wins()) + eq({ tab2_win, tab2_win2 }, api.nvim_tabpage_list_wins(tab2)) + end) it('creates splits in the correct location', function() local first_win = api.nvim_get_current_win() @@ -1364,6 +1453,400 @@ describe('API/win', function() }, }, layout) end) + + it('opens floating windows in other tabpages', function() + local first_win = api.nvim_get_current_win() + local first_tab = api.nvim_get_current_tabpage() + + command('tabnew') + local new_tab = api.nvim_get_current_tabpage() + local win = api.nvim_open_win(0, false, { + relative = 'win', + win = first_win, + width = 5, + height = 5, + row = 1, + col = 1, + }) + eq(api.nvim_win_get_tabpage(win), first_tab) + eq(api.nvim_get_current_tabpage(), new_tab) + end) + + it('switches to new windows in non-current tabpages when enter=true', function() + local first_win = api.nvim_get_current_win() + local first_tab = api.nvim_get_current_tabpage() + command('tabnew') + local win = api.nvim_open_win(0, true, { + relative = 'win', + win = first_win, + width = 5, + height = 5, + row = 1, + col = 1, + }) + eq(api.nvim_win_get_tabpage(win), first_tab) + eq(api.nvim_get_current_tabpage(), first_tab) + end) + + local function setup_tabbed_autocmd_test() + local info = {} + info.orig_buf = api.nvim_get_current_buf() + info.other_buf = api.nvim_create_buf(true, true) + info.tab1_curwin = api.nvim_get_current_win() + info.tab1 = api.nvim_get_current_tabpage() + command('tab split | split') + info.tab2_curwin = api.nvim_get_current_win() + info.tab2 = api.nvim_get_current_tabpage() + exec([=[ + tabfirst + let result = [] + autocmd TabEnter * let result += [["TabEnter", nvim_get_current_tabpage()]] + autocmd TabLeave * let result += [["TabLeave", nvim_get_current_tabpage()]] + autocmd WinEnter * let result += [["WinEnter", win_getid()]] + autocmd WinLeave * let result += [["WinLeave", win_getid()]] + autocmd WinNew * let result += [["WinNew", win_getid()]] + autocmd WinClosed * let result += [["WinClosed", str2nr(expand("<afile>"))]] + autocmd BufEnter * let result += [["BufEnter", win_getid(), bufnr()]] + autocmd BufLeave * let result += [["BufLeave", win_getid(), bufnr()]] + autocmd BufWinEnter * let result += [["BufWinEnter", win_getid(), bufnr()]] + autocmd BufWinLeave * let result += [["BufWinLeave", win_getid(), bufnr()]] + ]=]) + return info + end + + it('noautocmd option works', function() + local info = setup_tabbed_autocmd_test() + + api.nvim_open_win( + info.other_buf, + true, + { split = 'left', win = info.tab2_curwin, noautocmd = true } + ) + eq({}, eval('result')) + + api.nvim_open_win( + info.orig_buf, + true, + { relative = 'editor', row = 0, col = 0, width = 10, height = 10, noautocmd = true } + ) + eq({}, eval('result')) + end) + + it('fires expected autocmds when creating splits without entering', function() + local info = setup_tabbed_autocmd_test() + + -- For these, don't want BufWinEnter if visiting the same buffer, like :{s}buffer. + -- Same tabpage, same buffer. + local new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Other tabpage, same buffer. + command('let result = []') + new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Same tabpage, other buffer. + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + + -- Other tabpage, other buffer. + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + eq(info.tab1_curwin, api.nvim_get_current_win()) + end) + + it('fires expected autocmds when creating and entering splits', function() + local info = setup_tabbed_autocmd_test() + + -- Same tabpage, same buffer. + local new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'WinEnter', new_win }, + }, eval('result')) + + -- Same tabpage, other buffer. + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab1_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'WinEnter', new_win }, + { 'BufLeave', new_win, info.orig_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + + -- For these, the other tabpage's prevwin and curwin will change like we switched from its old + -- curwin to the new window, so the extra events near TabEnter reflect that. + -- Other tabpage, same buffer. + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + }, eval('result')) + + -- Other tabpage, other buffer. + api.nvim_set_current_win(info.tab2_curwin) + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + + { 'BufLeave', new_win, info.orig_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + + -- Other tabpage, other buffer; but other tabpage's curwin has a new buffer active. + api.nvim_set_current_win(info.tab2_curwin) + local new_buf = api.nvim_create_buf(true, true) + api.nvim_set_current_buf(new_buf) + api.nvim_set_current_win(info.tab1_curwin) + command('let result = []') + new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) + eq({ + { 'WinNew', new_win }, + { 'BufLeave', info.tab1_curwin, info.orig_buf }, + { 'WinLeave', info.tab1_curwin }, + { 'TabLeave', info.tab1 }, + + { 'WinEnter', info.tab2_curwin }, + { 'TabEnter', info.tab2 }, + { 'BufEnter', info.tab2_curwin, new_buf }, + { 'WinLeave', info.tab2_curwin }, + { 'WinEnter', new_win }, + { 'BufLeave', new_win, new_buf }, + { 'BufEnter', new_win, info.other_buf }, + { 'BufWinEnter', new_win, info.other_buf }, + }, eval('result')) + end) + + it('OK when new window is moved to other tabpage by autocommands', function() + -- Use nvim_win_set_config in the autocommands, as other methods of moving a window to a + -- different tabpage (e.g: wincmd T) actually creates a new window. + local tab0 = api.nvim_get_current_tabpage() + local tab0_win = api.nvim_get_current_win() + command('tabnew') + local new_buf = api.nvim_create_buf(true, true) + local tab1 = api.nvim_get_current_tabpage() + local tab1_parent = api.nvim_get_current_win() + command( + 'tabfirst | autocmd WinNew * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + local new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab1, api.nvim_get_current_tabpage()) + eq(new_win, api.nvim_get_current_win()) + eq(new_buf, api.nvim_get_current_buf()) + + -- nvim_win_set_config called after entering. It doesn't follow a curwin that is moved to a + -- different tabpage, but instead moves to the win filling the space, which is tab0_win. + command( + 'tabfirst | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab0, api.nvim_get_current_tabpage()) + eq(tab0_win, api.nvim_get_current_win()) + eq(tab1, api.nvim_win_get_tabpage(new_win)) + eq(new_buf, api.nvim_win_get_buf(new_win)) + + command( + 'tabfirst | autocmd BufEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' + .. tab1_parent + .. '})' + ) + new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(tab0, api.nvim_get_current_tabpage()) + eq(tab0_win, api.nvim_get_current_win()) + eq(tab1, api.nvim_win_get_tabpage(new_win)) + eq(new_buf, api.nvim_win_get_buf(new_win)) + end) + + it('does not fire BufWinEnter if win_set_buf fails', function() + exec([[ + set nohidden modified + autocmd WinNew * ++once only! + let fired = v:false + autocmd BufWinEnter * ++once let fired = v:true + ]]) + eq( + 'Failed to set buffer 2', + pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' }) + ) + eq(false, eval('fired')) + end) + + it('fires Buf* autocommands when `!enter` if window is entered via autocommands', function() + exec([[ + autocmd WinNew * ++once only! + let fired = v:false + autocmd BufEnter * ++once let fired = v:true + ]]) + api.nvim_open_win(api.nvim_create_buf(true, true), false, { split = 'left' }) + eq(true, eval('fired')) + end) + + it('no heap-use-after-free if target buffer deleted by autocommands', function() + local cur_buf = api.nvim_get_current_buf() + local new_buf = api.nvim_create_buf(true, true) + command('autocmd WinNew * ++once call nvim_buf_delete(' .. new_buf .. ', #{force: 1})') + api.nvim_open_win(new_buf, true, { split = 'left' }) + eq(cur_buf, api.nvim_get_current_buf()) + end) + + it('checks if splitting disallowed', function() + command('split | autocmd WinEnter * ++once call nvim_open_win(0, 0, #{split: "right"})') + matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) + + command('only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left"})') + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(command, 'new | quit') + ) + + local w = api.nvim_get_current_win() + command( + 'only | new | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: ' + .. w + .. '})' + ) + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(api.nvim_win_close, w, true) + ) + + -- OK when using window to different buffer than `win`s. + w = api.nvim_get_current_win() + command( + 'only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: ' + .. w + .. '})' + ) + command('new | quit') + end) + + it('restores last known cursor position if BufWinEnter did not move it', function() + -- This test mostly exists to ensure BufWinEnter is executed before enter_buffer's epilogue. + local buf = api.nvim_get_current_buf() + insert([[ + foo + bar baz .etc + i love autocommand bugs! + supercalifragilisticexpialidocious + marvim is actually a human + llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch + ]]) + api.nvim_win_set_cursor(0, { 5, 2 }) + command('set nostartofline | enew') + local new_win = api.nvim_open_win(buf, false, { split = 'left' }) + eq({ 5, 2 }, api.nvim_win_get_cursor(new_win)) + + exec([[ + only! + autocmd BufWinEnter * ++once normal! j6l + ]]) + new_win = api.nvim_open_win(buf, false, { split = 'left' }) + eq({ 2, 6 }, api.nvim_win_get_cursor(new_win)) + end) + + it('does not block all win_set_buf autocommands if !enter and !noautocmd', function() + local new_buf = fn.bufadd('foobarbaz') + exec([[ + let triggered = "" + autocmd BufReadCmd * ++once let triggered = bufname() + ]]) + api.nvim_open_win(new_buf, false, { split = 'left' }) + eq('foobarbaz', eval('triggered')) + end) + + it('sets error when no room', function() + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'above', win = 0 }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_open_win, 0, true, { split = 'below', win = 0 }) + ) + end) + + describe("with 'autochdir'", function() + local topdir + local otherbuf + + before_each(function() + command('set shellslash') + topdir = fn.getcwd() + t.mkdir(topdir .. '/Xacd') + t.mkdir(topdir .. '/Xacd/foo') + otherbuf = api.nvim_create_buf(false, true) + api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt') + + command('set autochdir') + command('edit Xacd/foo/bar.txt') + eq(topdir .. '/Xacd/foo', fn.getcwd()) + end) + + after_each(function() + n.rmdir(topdir .. '/Xacd') + end) + + it('does not change cwd with enter=false #15280', function() + api.nvim_open_win( + otherbuf, + false, + { relative = 'editor', height = 5, width = 5, row = 5, col = 5 } + ) + eq(topdir .. '/Xacd/foo', fn.getcwd()) + end) + + it('changes cwd with enter=true', function() + api.nvim_open_win( + otherbuf, + true, + { relative = 'editor', height = 5, width = 5, row = 5, col = 5 } + ) + eq(topdir .. '/Xacd', fn.getcwd()) + end) + end) end) describe('set_config', function() @@ -1471,6 +1954,15 @@ describe('API/win', function() config = api.nvim_win_get_config(win) eq('', config.relative) eq('below', config.split) + + eq( + "non-float with 'win' requires at least 'split' or 'vertical'", + pcall_err(api.nvim_win_set_config, 0, { win = 0 }) + ) + eq( + "non-float with 'win' requires at least 'split' or 'vertical'", + pcall_err(api.nvim_win_set_config, 0, { win = 0, relative = '' }) + ) end) it('creates top-level splits', function() @@ -1663,6 +2155,474 @@ describe('API/win', function() }, }, fn.winlayout()) end) + + it('closing new curwin when moving window to other tabpage works', function() + command('split | tabnew') + local t2_win = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * ++once quit') + local t1_move_win = api.nvim_get_current_win() + -- win_set_config fails to switch away from "t1_move_win" because the WinEnter autocmd that + -- closed the window we're switched to returns us to "t1_move_win", as it filled the space. + eq( + 'Failed to switch away from window ' .. t1_move_win, + pcall_err(api.nvim_win_set_config, t1_move_win, { win = t2_win, split = 'left' }) + ) + eq(t1_move_win, api.nvim_get_current_win()) + + command('split | split | autocmd WinEnter * ++once quit') + t1_move_win = api.nvim_get_current_win() + -- In this case, we closed the window that we got switched to, but doing so didn't switch us + -- back to "t1_move_win", which is fine. + api.nvim_win_set_config(t1_move_win, { win = t2_win, split = 'left' }) + neq(t1_move_win, api.nvim_get_current_win()) + end) + + it('messing with "win" or "parent" when moving "win" to other tabpage', function() + command('split | tabnew') + local t2 = api.nvim_get_current_tabpage() + local t2_win1 = api.nvim_get_current_win() + command('split') + local t2_win2 = api.nvim_get_current_win() + command('split') + local t2_win3 = api.nvim_get_current_win() + + command('tabfirst | autocmd WinEnter * ++once call nvim_win_close(' .. t2_win1 .. ', 1)') + local cur_win = api.nvim_get_current_win() + eq( + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win1, split = 'left' }) + ) + eq(cur_win, api.nvim_get_current_win()) + + command('split | autocmd WinLeave * ++once quit!') + cur_win = api.nvim_get_current_win() + eq( + 'Windows to split were closed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win2, split = 'left' }) + ) + neq(cur_win, api.nvim_get_current_win()) + + exec([[ + split + autocmd WinLeave * ++once + \ call nvim_win_set_config(0, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]) + cur_win = api.nvim_get_current_win() + eq( + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) + ) + eq('editor', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + command('autocmd WinLeave * ++once wincmd J') + cur_win = api.nvim_get_current_win() + eq( + 'Floating state of windows to split changed', + pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) + ) + eq('', api.nvim_win_get_config(0).relative) + eq(cur_win, api.nvim_get_current_win()) + + -- Try to make "parent" floating. This should give the same error as before, but because + -- changing a split from another tabpage into a float isn't supported yet, check for that + -- error instead for now. + -- Use ":silent!" to avoid the one second delay from printing the error message. + exec(([[ + autocmd WinLeave * ++once silent! + \ call nvim_win_set_config(%d, #{relative:'editor', row:0, col:0, width:5, height:5}) + ]]):format(t2_win3)) + cur_win = api.nvim_get_current_win() + api.nvim_win_set_config(0, { win = t2_win3, split = 'left' }) + matches( + 'Cannot change window from different tabpage into float$', + api.nvim_get_vvar('errmsg') + ) + -- The error doesn't abort moving the window (or maybe it should, if that's wanted?) + neq(cur_win, api.nvim_get_current_win()) + eq(t2, api.nvim_win_get_tabpage(cur_win)) + end) + + it('expected autocmds when moving window to other tabpage', function() + local new_curwin = api.nvim_get_current_win() + command('split') + local win = api.nvim_get_current_win() + command('tabnew') + local parent = api.nvim_get_current_win() + exec([[ + tabfirst + let result = [] + autocmd WinEnter * let result += ["Enter", win_getid()] + autocmd WinLeave * let result += ["Leave", win_getid()] + autocmd WinNew * let result += ["New", win_getid()] + ]]) + api.nvim_win_set_config(0, { win = parent, split = 'left' }) + -- Shouldn't see WinNew, as we're not creating any new windows, just moving existing ones. + eq({ 'Leave', win, 'Enter', new_curwin }, eval('result')) + end) + + it('no autocmds when moving window within same tabpage', function() + local parent = api.nvim_get_current_win() + exec([[ + split + let result = [] + autocmd WinEnter * let result += ["Enter", win_getid()] + autocmd WinLeave * let result += ["Leave", win_getid()] + autocmd WinNew * let result += ["New", win_getid()] + ]]) + api.nvim_win_set_config(0, { win = parent, split = 'left' }) + -- Shouldn't see any of those events, as we remain in the same window. + eq({}, eval('result')) + end) + + it('checks if splitting disallowed', function() + command('split | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "right"})') + matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) + + command('autocmd BufHidden * ++once call nvim_win_set_config(0, #{split: "left"})') + matches( + 'E1159: Cannot split a window when closing the buffer$', + pcall_err(command, 'new | quit') + ) + + -- OK when using window to different buffer. + local w = api.nvim_get_current_win() + command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})') + command('new | quit') + end) + + --- Returns a function to get information about the window layout, sizes and positions of a + --- tabpage. + local function define_tp_info_function() + exec_lua([[ + function tp_info(tp) + return { + layout = vim.fn.winlayout(vim.api.nvim_tabpage_get_number(tp)), + pos_sizes = vim.tbl_map( + function(w) + local pos = vim.fn.win_screenpos(w) + return { + row = pos[1], + col = pos[2], + width = vim.fn.winwidth(w), + height = vim.fn.winheight(w) + } + end, + vim.api.nvim_tabpage_list_wins(tp) + ) + } + end + ]]) + + return function(tp) + return exec_lua('return tp_info(...)', tp) + end + end + + it('attempt to move window with no room', function() + -- Fill the 2nd tabpage full of windows until we run out of room. + -- Use &laststatus=0 to ensure restoring missing statuslines doesn't affect things. + command('set laststatus=0 | tabnew') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit | wincmd | | wincmd p') + local t2 = api.nvim_get_current_tabpage() + local t2_cur_win = api.nvim_get_current_win() + local t2_top_split = fn.win_getid(1) + local t2_bot_split = fn.win_getid(fn.winnr('$')) + local t2_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 0, col = 0, width = 10, height = 10 } + ) + local t2_float_config = api.nvim_win_get_config(t2_float) + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t2_cur_win, api.nvim_get_current_win()) + eq(t2_info, tp_info(t2)) + eq(t2_float_config, api.nvim_win_get_config(t2_float)) + + -- Try to move windows from the 1st tabpage to the 2nd. + command('tabfirst | split | wincmd _') + local t1 = api.nvim_get_current_tabpage() + local t1_cur_win = api.nvim_get_current_win() + local t1_float = api.nvim_open_win( + 0, + false, + { relative = 'editor', row = 5, col = 3, width = 7, height = 6 } + ) + local t1_float_config = api.nvim_win_get_config(t1_float) + local t1_info = tp_info(t1) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'below' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'above' }) + ) + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'below' }) + ) + eq(t1_cur_win, api.nvim_get_current_win()) + eq(t1_info, tp_info(t1)) + eq(t1_float_config, api.nvim_win_get_config(t1_float)) + end) + + it('attempt to move window from other tabpage with no room', function() + -- Fill up the 1st tabpage with horizontal splits, then create a 2nd with only a few. Go back + -- to the 1st and try to move windows from the 2nd (while it's non-current) to it. Check that + -- window positions and sizes in the 2nd are unchanged. + command('set laststatus=0') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + + command('tab split') + local t2 = api.nvim_get_current_tabpage() + local t2_top = api.nvim_get_current_win() + command('belowright split') + local t2_mid_left = api.nvim_get_current_win() + command('belowright vsplit') + local t2_mid_right = api.nvim_get_current_win() + command('split | wincmd J') + local t2_bot = api.nvim_get_current_win() + local tp_info = define_tp_info_function() + local t2_info = tp_info(t2) + eq({ + 'col', + { + { 'leaf', t2_top }, + { + 'row', + { + { 'leaf', t2_mid_left }, + { 'leaf', t2_mid_right }, + }, + }, + { 'leaf', t2_bot }, + }, + }, t2_info.layout) + + local function try_move_t2_wins_to_t1() + for _, w in ipairs({ t2_bot, t2_mid_left, t2_mid_right, t2_top }) do + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, w, { win = 0, split = 'below' }) + ) + eq(t2_info, tp_info(t2)) + end + end + command('tabfirst') + try_move_t2_wins_to_t1() + -- Go to the 2nd tabpage to ensure nothing changes after win_comp_pos, last_status, .etc. + -- from enter_tabpage. + command('tabnext') + eq(t2_info, tp_info(t2)) + + -- Check things are fine with the global statusline too, for good measure. + -- Set it while the 2nd tabpage is current, so last_status runs for it. + command('set laststatus=3') + t2_info = tp_info(t2) + command('tabfirst') + try_move_t2_wins_to_t1() + end) + + it('handles cmdwin and textlock restrictions', function() + command('tabnew') + local t2 = api.nvim_get_current_tabpage() + local t2_win = api.nvim_get_current_win() + command('tabfirst') + local t1_move_win = api.nvim_get_current_win() + command('split') + + -- Can't move the cmdwin, or its old curwin to a different tabpage. + local old_curwin = api.nvim_get_current_win() + feed('q:') + eq( + 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', + pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) + ) + eq( + 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', + pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win }) + ) + -- But we can move other windows. + api.nvim_win_set_config(t1_move_win, { split = 'left', win = t2_win }) + eq(t2, api.nvim_win_get_tabpage(t1_move_win)) + command('quit!') + + -- Can't configure windows such that the cmdwin would become the only non-float. + command('only!') + feed('q:') + eq( + 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- old_curwin is now no longer the only other non-float, so we can make it floating now. + local t1_new_win = api.nvim_open_win( + api.nvim_create_buf(true, true), + false, + { split = 'left', win = old_curwin } + ) + api.nvim_win_set_config( + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + eq('editor', api.nvim_win_get_config(old_curwin).relative) + -- ...which means we shouldn't be able to also make the new window floating too! + eq( + 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- Nothing ought to stop us from making the cmdwin itself floating, though... + api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }) + eq('editor', api.nvim_win_get_config(0).relative) + -- We can't make our new window from before floating too, as it's now the only non-float. + eq( + 'Cannot change last window into float', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + command('quit!') + + -- Can't switch away from window before moving it to a different tabpage during textlock. + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) + ]]):format(t2_win)) + local cur_win = api.nvim_get_current_win() + matches( + 'E565: Not allowed to change text or change window$', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) + + it('updates statusline when moving bottom split', function() + local screen = Screen.new(10, 10) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText + [1] = { bold = true, reverse = true }, -- StatusLine + }) + screen:attach() + exec([[ + set laststatus=0 + belowright split + call nvim_win_set_config(0, #{split: 'above', win: win_getid(winnr('#'))}) + ]]) + screen:expect([[ + ^ | + {0:~ }|*3 + {1:[No Name] }| + | + {0:~ }|*3 + | + ]]) + end) + + it("updates tp_curwin of moved window's original tabpage", function() + local t1 = api.nvim_get_current_tabpage() + command('tab split | split') + local t2 = api.nvim_get_current_tabpage() + local t2_alt_win = api.nvim_get_current_win() + command('vsplit') + local t2_cur_win = api.nvim_get_current_win() + command('tabprevious') + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + -- tp_curwin is unchanged when moved within the same tabpage. + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = t2_alt_win }) + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + -- Also unchanged if the move failed. + command('let &winwidth = &columns | let &winminwidth = &columns') + matches( + 'E36: Not enough room$', + pcall_err(api.nvim_win_set_config, t2_cur_win, { split = 'left', win = 0 }) + ) + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + command('set winminwidth& winwidth&') + + -- But is changed if successfully moved to a different tabpage. + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) + eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) + eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) + + -- Now do it for a float, which has different altwin logic. + command('tabnext') + t2_cur_win = + api.nvim_open_win(0, true, { relative = 'editor', row = 5, col = 5, width = 5, height = 5 }) + eq(t2_alt_win, fn.win_getid(fn.winnr('#'))) + command('tabprevious') + eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) + + api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) + eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) + eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) + end) end) describe('get_config', function() |