commit 0d3ed7904a17ede7041ea00b89c0f64210426a46
parent eb86bbf83ac9e7ef5f3510f15fd0c182f19e7a3a
Author: zeertzjq <zeertzjq@outlook.com>
Date: Mon, 19 Jan 2026 10:18:54 +0800
vim-patch:9.1.1211: TabClosedPre is triggered just before the tab is being freed
Problem: TabClosedPre is triggered just before the tab is being freed,
which limited its functionality.
Solution: Trigger it a bit earlier and also on :tabclose and :tabonly
(Jim Zhou)
closes: vim/vim#16890
https://github.com/vim/vim/commit/bcf66e014141982192e2743829bceef60ce77727
Co-authored-by: Jim Zhou <jimzhouzzy@gmail.com>
Diffstat:
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
@@ -5150,6 +5150,8 @@ void tabpage_close(int forceit)
return;
}
+ trigger_tabclosedpre(curtab, true);
+
// First close all the windows but the current one. If that worked then
// close the last window in this tab, that will close it.
while (curwin->w_floating) {
@@ -5172,6 +5174,8 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
int done = 0;
char prev_idx[NUMBUFLEN];
+ trigger_tabclosedpre(tp, true);
+
// Limit to 1000 windows, autocommands may add a window while we close
// one. OK, so I'm paranoid...
while (++done < 1000) {
diff --git a/src/nvim/window.c b/src/nvim/window.c
@@ -3102,9 +3102,12 @@ static void do_autocmd_winclosed(win_T *win)
recursive = false;
}
-static void trigger_tabclosedpre(tabpage_T *tp)
+/// directly is true if the window is closed by ':tabclose' or ':tabonly'.
+/// This allows saving the session before closing multi-window tab.
+void trigger_tabclosedpre(tabpage_T *tp, bool directly)
{
static bool recursive = false;
+ static bool skip = false;
tabpage_T *ptp = curtab;
// Quickly return when no TabClosedPre autocommands to be executed or
@@ -3113,8 +3116,17 @@ static void trigger_tabclosedpre(tabpage_T *tp)
return;
}
+ // Skip if the event have been triggered by ':tabclose' recently
+ if (skip) {
+ skip = false;
+ return;
+ }
+
if (valid_tabpage(tp)) {
goto_tabpage_tp(tp, false, false);
+ if (directly) {
+ skip = true;
+ }
}
recursive = true;
window_layout_lock();
@@ -3187,7 +3199,7 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
}
if (tp->tp_firstwin == tp->tp_lastwin) {
- trigger_tabclosedpre(tp);
+ trigger_tabclosedpre(tp, false);
// autocmd may have freed the window already.
if (!win_valid_any_tab(win)) {
return false;
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
@@ -4760,7 +4760,7 @@ func Test_autocmd_tabclosedpre()
call ClearAutomcdAndCreateTabs()
au TabClosedPre * tabmove 0
tabclose
- call assert_equal('1Z2A3>B', GetTabs())
+ call assert_equal('1>Z2A3B', GetTabs())
call ClearAutomcdAndCreateTabs()
au TabClosedPre * tabmove 0
tabclose 1
@@ -4788,7 +4788,33 @@ func Test_autocmd_tabclosedpre()
au TabClosedPre * new X | new Y | new Z
call assert_fails('tabclose 1', 'E242')
+ " Test directly closing the tab page with ':tabclose'
+ au!
+ tabonly
+ bw!
+ e Z
+ au TabClosedPre * mksession!
+ tabnew A
+ sp
+ tabclose
+ source Session.vim
+ call assert_equal('1Z2>AA', GetTabs())
+
+ " Test directly closing the tab page with ':tabonly'
+ " Z is closed before A. Hence A overwrites the session.
+ au!
+ tabonly
+ bw!
+ e Z
+ au TabClosedPre * mksession!
+ tabnew A
+ tabnew B
+ tabonly
+ source Session.vim
+ call assert_equal('1>A2B', GetTabs())
+
" Clean up
+ call delete('Session.vim')
au!
only
tabonly