From bcb70eeac48040fd6d6bfc20cf7fb6f41374a67c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 00:42:36 +0000 Subject: fix(api): win_set_config autocmds crash when moving win to other tabpage Problem: win_enter autocommands can close new_curwin, crashing if it was the last window in its tabpage after removing win, or can close parent, crashing when attempting to split it later. Solution: remove win first, check that parent is valid after win_enter. NOTE: This isn't actually quite right, as this means win is not in the window list or even has a frame when triggering enter autocommands (so it's not considered valid in the tabpage). This is addressed in later commits. --- test/functional/api/window_spec.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 097a546ef2..a812d502eb 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1663,6 +1663,27 @@ describe('API/win', function() }, }, fn.winlayout()) end) + + it('closing new curwin when moving window to other tabpage works', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + local t = api.nvim_get_current_tabpage() + command('tabfirst | autocmd WinEnter * ++once quit') + api.nvim_win_set_config(0, { win = w, split = 'left' }) + -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. + eq(t, api.nvim_get_current_tabpage()) + eq({ t }, api.nvim_list_tabpages()) + end) + + it('closing split parent when moving window to other tabpage aborts', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + eq( + 'Window to split was closed', + pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + ) + end) end) describe('get_config', function() -- cgit From 233649bc757743f7677b2ae414779192a94aa2ae Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 01:50:49 +0000 Subject: fix(api): win_set_config fires unnecessary autocmds Problem: win_set_config should have the observable effect of moving an existing window to another place, but instead fires autocommands as if a new window was created and entered (and does not fire autocommands reflecting a "return" to the original window). Solution: do not fire win_enter-related autocommands when splitting the window, but continue to fire them when entering the window that fills the new space when moving a window to a different tabpage, as the new curwin changes. Also, remove "++once" from the WinEnter autocmd in the other test, as omitting it also crashed Nvim before this fix. --- test/functional/api/window_spec.lua | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index a812d502eb..3914090814 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1668,7 +1668,7 @@ describe('API/win', function() command('split | tabnew') local w = api.nvim_get_current_win() local t = api.nvim_get_current_tabpage() - command('tabfirst | autocmd WinEnter * ++once quit') + command('tabfirst | autocmd WinEnter * quit') api.nvim_win_set_config(0, { win = w, split = 'left' }) -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. eq(t, api.nvim_get_current_tabpage()) @@ -1684,6 +1684,38 @@ describe('API/win', function() pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) ) 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) end) describe('get_config', function() -- cgit From a873f33993ef84e3f954127038e559e1ac1cac43 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 02:59:26 +0000 Subject: fix(api): open_win fire BufWinEnter for other buffer when !enter && !noautocmd Problem: BufWinEnter is not fired when not entering a new window, even when a different buffer is specified and buffer-related autocommands are unblocked (!noautocmd). Solution: fire it in the context of the new window and buffer. Do not do it if the buffer is unchanged, like :{s}buffer. Be wary of autocommands! For example, it's possible for nvim_win_set_config to be used in an autocommand to move a window to a different tabpage (in contrast, things like wincmd T actually create a *new* window, so it may not have been possible before, meaning other parts of Nvim could assume windows can't do this... I'd be especially cautious of logic that restores curwin and curtab without checking if curwin is still valid in curtab, if any such logic exists). Also, bail early from win_set_buf if setting the temp curwin fails; this shouldn't be possible, as the callers check that wp is valid, but in case that's not true, win_set_buf will no longer continue setting a buffer for the wrong window. Note that pum_create_float_preview also uses win_set_buf, but from a glance, doesn't look like it properly checks for autocmds screwing things up (win_enter, nvim_create_buf...). I haven't addressed that here. Also adds some test coverage for nvim_open_win autocommands. Closes #27121. --- test/functional/api/window_spec.lua | 207 ++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 3914090814..8966c3b086 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1364,6 +1364,213 @@ describe('API/win', function() }, }, layout) 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(""))]] + 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('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) end) describe('set_config', function() -- cgit From e55a502ed413d2bc8954b5227acfb34c8689f979 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 18:45:56 +0000 Subject: fix(api): open_win fire Buf* events when !enter && !noautocmd if entered early Problem: if switch_win{_noblock} fails to restore the old curwin after WinNew (e.g: it was closed), wp will become the new curwin, but win_set_buf enter events would still be blocked if !enter && !noautocmd. Solution: fire them, as we've actually entered the new window. Note: there's a problem of switch_win{_noblock} failing to restore the old curwin, leaving us in wp without triggering WinEnter/WinLeave, but this affects all callers of switch_win{_noblock} anyways. (It's also not clear how WinLeave can be called if the old curwin was closed already). --- test/functional/api/window_spec.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 8966c3b086..e0cb66de41 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1571,6 +1571,16 @@ describe('API/win', function() ) 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) end) describe('set_config', function() -- cgit From b1e24f240baeea80dcf4a3d8453fed0230fb88fd Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 20:15:47 +0000 Subject: fix(api): avoid open_win UAF if target buf deleted by autocmds Problem: WinNew and win_enter autocommands can delete the target buffer to switch to, causing a heap-use-after-free. Solution: store a bufref to the buffer, check it before attempting to switch. --- test/functional/api/window_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index e0cb66de41..2f6a02b5d5 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1581,6 +1581,14 @@ describe('API/win', function() 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) end) describe('set_config', function() -- cgit From 5d58136cccc760f6d95eb45b46f2ad60f06b103b Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 17:17:44 +0000 Subject: fix(api): make open_win/win_set_config check if splitting allowed Problem: splitting is disallowed in some cases to prevent the window layout changes while a window is closing, but it's not checked for. Solution: check for this, and set the API error message directly. (Also sneak in a change to tui.c that got lost from #27352; it's a char* buf, and the memset is assuming one byte each anyway) --- test/functional/api/window_spec.lua | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 2f6a02b5d5..30235d6b84 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1589,6 +1589,37 @@ describe('API/win', function() 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) end) describe('set_config', function() @@ -1941,6 +1972,22 @@ describe('API/win', function() -- 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) end) describe('get_config', function() -- cgit From b1577d371a6db43222de9e3a525def82320ebdb1 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 21:44:42 +0000 Subject: fix(api): make win_set_config with "win" for splits need "split/vertical" Problem: currently, for splits, nvim_win_set_config accepts win without any of split or vertical set, which has little effect and seems error-prone. Solution: require at least one of split or vertical to also be set for splits. Also, update nvim_win_set_config docs, as it's no longer limited to just floating and external windows. --- test/functional/api/window_spec.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 30235d6b84..246ff50ddc 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1727,6 +1727,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() -- cgit From a70eae57bd44208a77b5ac29839e8a39ab3c9cd8 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 22:53:37 +0000 Subject: fix(api): make open_win block only enter/leave events if !enter && !noautocmd Problem: nvim_open_win blocking all win_set_buf autocommands when !enter && !noautocmd is too aggressive. Solution: temporarily block WinEnter/Leave and BufEnter/Leave events when setting the buffer. Delegate the firing of BufWinEnter back to win_set_buf, which also has the advantage of keeping the timing consistent (e.g: before the epilogue in enter_buffer, which also handles restoring the cursor position if autocommands didn't change it, among other things). Reword the documentation for noautocmd a bit. I pondered modifying do_buffer and callees to allow for BufEnter/Leave being conditionally disabled, but it seems too invasive (and potentially error-prone, especially if new code paths to BufEnter/Leave are added in the future). Unfortunately, doing this has the drawback of blocking ALL such events for the duration, which also means blocking unrelated such events; like if window switching occurs in a ++nested autocmd fired by win_set_buf. If this turns out to be a problem in practice, a different solution specialized for nvim_open_win could be considered. :-) --- test/functional/api/window_spec.lua | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 246ff50ddc..6e20c81fc2 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1620,6 +1620,40 @@ describe('API/win', function() ) 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) end) describe('set_config', function() -- cgit From d942c2b9432d81e4b509519bd48fa886e37e9ca8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:51:31 +0000 Subject: fix(api): handle win_split_ins failure properly Problem: nvim_win_set_config does not handle failure in win_split_ins properly yet, which can cause all sorts of issues. Also nvim_open_win and nvim_win_set_config do not set the error message to the one from win_split_ins. Solution: handle failure by undoing winframe_remove, like in win_splitmove. Make sure autocommands from switching to the altwin fire within a valid window, and ensure they don't screw things up. Set the error message to that of win_split_ins, if any. Also change a few other small things, including: - adjust win_append to take a tabpage_T * argument, which is more consistent with win_remove (and also allows us to undo a call to win_remove). - allow winframe_restore to restore window positions. Useful if `wp` was in a different tabpage, as a call to win_comp_pos (which only works for the current tabpage) after winframe_restore should no longer be needed. Though enter_tabpage calls win_comp_pos anyway, this has the advantage of ensuring w_winrow/col remains accurate even before entering the tabpage (useful for stuff like win_screenpos, if used on a window in another tabpage). (This change should probably also be PR'd to Vim later, even though it doesn't use winframe_restore for a `wp` in a different tabpage yet). --- test/functional/api/window_spec.lua | 319 ++++++++++++++++++++++++++++++++++-- 1 file changed, 307 insertions(+), 12 deletions(-) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 6e20c81fc2..abe1ca9344 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1654,6 +1654,18 @@ describe('API/win', function() 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) end) describe('set_config', function() @@ -1965,23 +1977,89 @@ describe('API/win', function() it('closing new curwin when moving window to other tabpage works', function() command('split | tabnew') - local w = api.nvim_get_current_win() - local t = api.nvim_get_current_tabpage() - command('tabfirst | autocmd WinEnter * quit') - api.nvim_win_set_config(0, { win = w, split = 'left' }) - -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. - eq(t, api.nvim_get_current_tabpage()) - eq({ t }, api.nvim_list_tabpages()) + 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('closing split parent when moving window to other tabpage aborts', function() + it('messing with "win" or "parent" when moving "win" to other tabpage', function() command('split | tabnew') - local w = api.nvim_get_current_win() - command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + 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( - 'Window to split was closed', - pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + '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() @@ -2031,6 +2109,223 @@ describe('API/win', function() 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('does not switch window when textlocked or in the cmdwin', function() + command('tabnew') + local t2_win = api.nvim_get_current_win() + command('tabfirst') + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Failed to switch away from window ' .. cur_win, + pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) + ) + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + api.nvim_get_vvar('errmsg') + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) + ]]):format(t2_win)) + 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) end) describe('get_config', function() -- cgit From e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:25:44 +0000 Subject: fix(api): patch some cmdwin/textlock holes Problem: there are new ways to escape textlock or break the cmdwin in nvim_win_set_config and nvim_tabpage_set_win. Solution: fix them. Use win_goto to check it in nvim_tabpage_set_win and use the try_start/end pattern like with similar functions such as nvim_set_current_win (which uses the existing msg_list, if set). Careful not to use `wp->handle` when printing the window ID in the error message for nvim_tabpage_set_win, as win_goto autocommands may have freed the window. On a related note, I have a feeling some API functions ought to be checking curbuf_locked... --- test/functional/api/window_spec.lua | 65 +++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index abe1ca9344..ae8a519f11 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2297,29 +2297,82 @@ describe('API/win', function() try_move_t2_wins_to_t1() end) - it('does not switch window when textlocked or in the cmdwin', function() + 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:') - local cur_win = api.nvim_get_current_win() eq( - 'Failed to switch away from window ' .. cur_win, + 'E11: Invalid in command-line window; executes, CTRL-C quits', pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) ) eq( 'E11: Invalid in command-line window; executes, CTRL-C quits', - api.nvim_get_vvar('errmsg') + pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win }) ) - eq(cur_win, api.nvim_get_current_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; 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; 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)) - cur_win = api.nvim_get_current_win() + local cur_win = api.nvim_get_current_win() matches( 'E565: Not allowed to change text or change window$', pcall_err(command, 'normal! ==') -- cgit From 54022a2946aca5de991e7fa1ebc2954340ec20a8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 01:00:33 +0000 Subject: fix(api): win_set_config update statuslines after removing splits Problem: nvim_win_set_config does not update statuslines after removing a split. Solution: call last_status. Didn't realize this was missing in the original nvim_win_set_config for splits PR. As it can only be done for the current tabpage, do it if win_tp == curtab; enter_tabpage will eventually call last_status anyway when the user enters another tabpage. --- test/functional/api/window_spec.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index ae8a519f11..6b7c550ca8 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2379,6 +2379,28 @@ describe('API/win', function() ) 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) end) describe('get_config', function() -- cgit From b52d15853e89149472c1ecd9cce3a84e4af0785a Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:56:32 +0000 Subject: fix(api): win_set_config set tp_curwin of win moved from other tabpage Problem: nvim_win_set_config does not update the tp_curwin of win's original tabpage when moving it to another. Solution: update it if win was the tp_curwin. Add a test. --- test/functional/api/window_spec.lua | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'test/functional/api/window_spec.lua') diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 6b7c550ca8..a10c8f48ef 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2401,6 +2401,47 @@ describe('API/win', function() | ]]) 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() -- cgit