commit d6e5286d2c533d97d4efe4644559b81702d3caba
parent 992eaac08218c037ffa8980f36a628f5b293355b
Author: zeertzjq <zeertzjq@outlook.com>
Date: Wed, 14 Jan 2026 15:37:16 +0800
vim-patch:9.1.0059: No event triggered before creating a window
Problem: No event is triggered before creating a window.
(Sergey Vlasov)
Solution: Add the WinNewPre event (Sergey Vlasov)
fixes: vim/vim#10635
closes: vim/vim#12761
https://github.com/vim/vim/commit/1f47db75fdc8c53c5c778b26ecfa0942ac801f22
Not sure if this should be triggered before creating a floating window,
as its use case is related to window layout.
Co-authored-by: Sergey Vlasov <sergey@vlasov.me>
Diffstat:
7 files changed, 113 insertions(+), 6 deletions(-)
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
@@ -1232,6 +1232,18 @@ WinLeave Before leaving a window. If the window to be
WinLeave autocommands (but not for ":new").
Not used for ":qa" or ":q" when exiting Vim.
Before WinClosed.
+ *WinNewPre*
+WinNewPre Before creating a new window. Triggered
+ before commands that modify window layout by
+ creating a split or new tab page. Not done for
+ the first window, when Vim has just started.
+ It is not allowed to modify window layout
+ while executing commands for the WinNewPre
+ event.
+ Most useful to store current window layout
+ and compare it with the new layout after the
+ Window has been created.
+
*WinNew*
WinNew When a new window was created. Not done for
the first window, when Vim has just started.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
@@ -2580,7 +2580,8 @@ A jump table for the options with a short description can be found at |Q_op|.
|VimResized|,
|VimResume|,
|VimSuspend|,
- |WinNew|
+ |WinNew|,
+ |WinNewPre|
*'expandtab'* *'et'* *'noexpandtab'* *'noet'*
'expandtab' 'et' boolean (default off)
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
@@ -218,6 +218,7 @@ error('Cannot require a meta file')
--- |'WinEnter'
--- |'WinLeave'
--- |'WinNew'
+--- |'WinNewPre'
--- |'WinResized'
--- |'WinScrolled'
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
@@ -2258,7 +2258,8 @@ vim.go.ei = vim.go.eventignore
--- `VimResized`,
--- `VimResume`,
--- `VimSuspend`,
---- `WinNew`
+--- `WinNew`,
+--- `WinNewPre`
---
--- @type string
vim.o.eventignorewin = ""
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
@@ -138,7 +138,8 @@ return {
WinClosed = true, -- after closing a window
WinEnter = true, -- after entering a window
WinLeave = true, -- before leaving a window
- WinNew = false, -- when entering a new window
+ WinNewPre = false, -- before creating a new window
+ WinNew = false, -- after creating a new window
WinResized = true, -- after a window was resized
WinScrolled = true, -- after a window was scrolled or resized
},
diff --git a/src/nvim/window.c b/src/nvim/window.c
@@ -1127,6 +1127,8 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
return NULL;
}
+ trigger_winnewpre();
+
win_T *oldwin;
if (flags & WSP_TOP) {
oldwin = firstwin;
@@ -3069,6 +3071,13 @@ int win_close(win_T *win, bool free_buf, bool force)
return OK;
}
+static void trigger_winnewpre(void)
+{
+ window_layout_lock();
+ apply_autocmds(EVENT_WINNEWPRE, NULL, NULL, false, NULL);
+ window_layout_unlock();
+}
+
static void do_autocmd_winclosed(win_T *win)
FUNC_ATTR_NONNULL_ALL
{
@@ -4380,6 +4389,8 @@ int win_new_tabpage(int after, char *filename)
curtab = newtp;
+ trigger_winnewpre();
+
// Create a new empty window.
if (win_alloc_firstwin(old_curtab->tp_curwin) == OK) {
// Make the new Tab page the new topframe.
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
@@ -277,6 +277,7 @@ func Test_win_tab_autocmd()
let g:record = []
augroup testing
+ au WinNewPre * call add(g:record, 'WinNewPre')
au WinNew * call add(g:record, 'WinNew')
au WinClosed * call add(g:record, 'WinClosed')
au WinEnter * call add(g:record, 'WinEnter')
@@ -293,8 +294,8 @@ func Test_win_tab_autocmd()
close
call assert_equal([
- \ 'WinLeave', 'WinNew', 'WinEnter',
- \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter',
+ \ 'WinLeave', 'TabLeave', 'WinNewPre', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
\ 'WinLeave', 'TabLeave', 'WinClosed', 'TabClosed', 'WinEnter', 'TabEnter',
\ 'WinLeave', 'WinClosed', 'WinEnter'
\ ], g:record)
@@ -305,17 +306,96 @@ func Test_win_tab_autocmd()
bwipe somefile
call assert_equal([
- \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
+ \ 'WinLeave', 'TabLeave', 'WinNewPre', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter',
\ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter',
\ 'WinClosed', 'TabClosed'
\ ], g:record)
+ let g:record = []
+ copen
+ help
+ tabnext
+ vnew
+
+ call assert_equal([
+ \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter',
+ \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter',
+ \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter'
+ \ ], g:record)
+
augroup testing
au!
augroup END
unlet g:record
endfunc
+func Test_WinNewPre()
+ " Test that the old window layout can be accessed before a new window is created.
+ let g:layouts_pre = []
+ let g:layouts_post = []
+ augroup testing
+ au WinNewPre * call add(g:layouts_pre, winlayout())
+ au WinNew * call add(g:layouts_post, winlayout())
+ augroup END
+ split
+ call assert_notequal(g:layouts_pre[0], g:layouts_post[0])
+ split
+ call assert_equal(g:layouts_pre[1], g:layouts_post[0])
+ call assert_notequal(g:layouts_pre[1], g:layouts_post[1])
+ tabnew
+ call assert_notequal(g:layouts_pre[2], g:layouts_post[1])
+ call assert_notequal(g:layouts_pre[2], g:layouts_post[2])
+ augroup testing
+ au!
+ augroup END
+ unlet g:layouts_pre
+ unlet g:layouts_post
+
+ " Test modifying window layout during WinNewPre throws.
+ let g:caught = 0
+ augroup testing
+ au!
+ au WinNewPre * split
+ augroup END
+ try
+ vnew
+ catch
+ let g:caught += 1
+ endtry
+ augroup testing
+ au!
+ au WinNewPre * tabnew
+ augroup END
+ try
+ vnew
+ catch
+ let g:caught += 1
+ endtry
+ augroup testing
+ au!
+ au WinNewPre * close
+ augroup END
+ try
+ vnew
+ catch
+ let g:caught += 1
+ endtry
+ augroup testing
+ au!
+ au WinNewPre * tabclose
+ augroup END
+ try
+ vnew
+ catch
+ let g:caught += 1
+ endtry
+ call assert_equal(4, g:caught)
+ augroup testing
+ au!
+ augroup END
+ unlet g:caught
+endfunc
+
func Test_WinResized()
CheckRunVimInTerminal