aboutsummaryrefslogtreecommitdiff
path: root/test/functional/api/window_spec.lua
diff options
context:
space:
mode:
authorSean Dewar <seandewar@users.noreply.github.com>2024-02-04 02:59:26 +0000
committerSean Dewar <6256228+seandewar@users.noreply.github.com>2024-03-08 23:23:55 +0000
commita873f33993ef84e3f954127038e559e1ac1cac43 (patch)
tree732e38f3a4db9af8403ee9633bbc88f53e16d570 /test/functional/api/window_spec.lua
parent233649bc757743f7677b2ae414779192a94aa2ae (diff)
downloadrneovim-a873f33993ef84e3f954127038e559e1ac1cac43.tar.gz
rneovim-a873f33993ef84e3f954127038e559e1ac1cac43.tar.bz2
rneovim-a873f33993ef84e3f954127038e559e1ac1cac43.zip
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.
Diffstat (limited to 'test/functional/api/window_spec.lua')
-rw-r--r--test/functional/api/window_spec.lua207
1 files changed, 207 insertions, 0 deletions
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("<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('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()