patch 9.1.1202: Missing TabClosedPre autocommand
Problem: Missing TabClosedPre autocommand
(zoumi)
Solution: Add the TabClosedPre autcommand (Jim Zhou).
fixes: #16518
closes: #16855
Signed-off-by: Jim Zhou <jimzhouzzy@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/autocmd.c b/src/autocmd.c
index f0b573b..3921682 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -180,6 +180,7 @@
KEYVALUE_ENTRY(EVENT_SWAPEXISTS, "SwapExists"),
KEYVALUE_ENTRY(EVENT_SYNTAX, "Syntax"),
KEYVALUE_ENTRY(EVENT_TABCLOSED, "TabClosed"),
+ KEYVALUE_ENTRY(EVENT_TABCLOSEDPRE, "TabClosedPre"),
KEYVALUE_ENTRY(EVENT_TABENTER, "TabEnter"),
KEYVALUE_ENTRY(EVENT_TABLEAVE, "TabLeave"),
KEYVALUE_ENTRY(EVENT_TABNEW, "TabNew"),
@@ -2900,6 +2901,14 @@
return NULL;
}
+/*
+ * Return TRUE when there is a TabClosedPre autocommand defined.
+ */
+ int
+has_tabclosedpre(void)
+{
+ return (first_autopat[(int)EVENT_TABCLOSEDPRE] != NULL);
+}
#if defined(FEAT_EVAL) || defined(PROTO)
/*
diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro
index ed85603..ff42b60 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -47,4 +47,5 @@
void f_autocmd_add(typval_T *argvars, typval_T *rettv);
void f_autocmd_delete(typval_T *argvars, typval_T *rettv);
void f_autocmd_get(typval_T *argvars, typval_T *rettv);
+int has_tabclosedpre(void);
/* vim: set ft=c : */
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index 54558b0..138b852 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -4986,4 +4986,162 @@
call StopVimInTerminal(buf)
endfunc
+" Test that TabClosedPre and TabClosed are triggered when closing a tab.
+func Test_autocmd_tabclosedpre()
+ augroup testing
+ au TabClosedPre * call add(g:tabpagenr_pre, t:testvar)
+ au TabClosed * call add(g:tabpagenr_post, t:testvar)
+ augroup END
+
+ " Test 'tabclose' triggering
+ let g:tabpagenr_pre = []
+ let g:tabpagenr_post = []
+ let t:testvar = 1
+ tabnew
+ let t:testvar = 2
+ tabnew
+ let t:testvar = 3
+ tabnew
+ let t:testvar = 4
+ tabnext
+ tabclose
+ tabclose
+ tabclose
+ call assert_equal([1, 2, 3], g:tabpagenr_pre)
+ call assert_equal([2, 3, 4], g:tabpagenr_post)
+
+ " Test 'tabclose {count}' triggering
+ let g:tabpagenr_pre = []
+ let g:tabpagenr_post = []
+ let t:testvar = 1
+ tabnew
+ let t:testvar = 2
+ tabnew
+ let t:testvar = 3
+ tabclose 2
+ tabclose 2
+ call assert_equal([2, 3], g:tabpagenr_pre)
+ call assert_equal([3, 1], g:tabpagenr_post)
+
+ " Test 'tabonly' triggering
+ let g:tabpagenr_pre = []
+ let g:tabpagenr_post = []
+ let t:testvar = 1
+ tabnew
+ let t:testvar = 2
+ tabonly
+ call assert_equal([1], g:tabpagenr_pre)
+ call assert_equal([2], g:tabpagenr_post)
+
+ " Test 'q' and 'close' triggering (closing the last window in a tab)
+ let g:tabpagenr_pre = []
+ let g:tabpagenr_post = []
+ split
+ let t:testvar = 1
+ tabnew
+ let t:testvar = 2
+ split
+ vsplit
+ tabnew
+ let t:testvar = 3
+ tabnext
+ only
+ quit
+ quit
+ close
+ close
+ call assert_equal([1, 2], g:tabpagenr_pre)
+ call assert_equal([2, 3], g:tabpagenr_post)
+
+ func ClearAutomcdAndCreateTabs()
+ au! TabClosedPre
+ bw!
+ e Z
+ tabonly
+ tabnew A
+ tabnew B
+ tabnew C
+ endfunc
+
+ func GetTabs()
+ redir => tabsout
+ tabs
+ redir END
+ let tabsout = substitute(tabsout, '\n', '', 'g')
+ let tabsout = substitute(tabsout, 'Tab page ', '', 'g')
+ let tabsout = substitute(tabsout, ' ', '', 'g')
+ return tabsout
+ endfunc
+
+ call CleanUpTestAuGroup()
+
+ " Close tab in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabclose
+ call assert_fails('tabclose', 'E1312')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabclose
+ call assert_fails('tabclose 2', 'E1312')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabclose 1
+ call assert_fails('tabclose', 'E1312')
+
+ " Close other (all) tabs in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabonly
+ call assert_fails('tabclose', 'E1312')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabonly
+ call assert_fails('tabclose 2', 'E1312')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabclose 4
+ call assert_fails('tabclose 2', 'E1312')
+
+ " Open new tabs in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabnew D
+ call assert_fails('tabclose', 'E1312')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabnew D
+ call assert_fails('tabclose 1', 'E1312')
+
+ " Moving the tab page in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabmove 0
+ tabclose
+ call assert_equal('1Z2A3>B', GetTabs())
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabmove 0
+ tabclose 1
+ call assert_equal('1A2B3>C', GetTabs())
+ tabonly
+ call assert_equal('1>C', GetTabs())
+
+ " Switching tab page in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabnext | e Y
+ tabclose
+ call assert_equal('1Y2A3>B', GetTabs())
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * tabnext | e Y
+ tabclose 1
+ call assert_equal('1Y2B3>C', GetTabs())
+ tabonly
+ call assert_equal('1>Y', GetTabs())
+
+ " Create new windows in TabClosedPre autocmd
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * split | e X| vsplit | e Y | split | e Z
+ call assert_fails('tabclose', 'E242')
+ call ClearAutomcdAndCreateTabs()
+ au TabClosedPre * new X | new Y | new Z
+ call assert_fails('tabclose 1', 'E242')
+
+ " Clean up
+ au!
+ only
+ tabonly
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index b9fcedc..c129404 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1202,
+/**/
1201,
/**/
1200,
diff --git a/src/vim.h b/src/vim.h
index 212b7e7..85fad6c 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1435,6 +1435,7 @@
EVENT_SWAPEXISTS, // found existing swap file
EVENT_SYNTAX, // syntax selected
EVENT_TABCLOSED, // after closing a tab page
+ EVENT_TABCLOSEDPRE, // before closing a tab page
EVENT_TABENTER, // after entering a tab page
EVENT_TABLEAVE, // before leaving a tab page
EVENT_TABNEW, // when entering a new tab page
diff --git a/src/window.c b/src/window.c
index cc76f79..cce7f4c 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2978,6 +2978,33 @@
recursive = FALSE;
}
+ static void
+trigger_tabclosedpre(tabpage_T *tp)
+{
+ static int recursive = FALSE;
+ tabpage_T *ptp = curtab;
+
+ // Quickly return when no TabClosedPre autocommands to be executed or
+ // already executing
+ if (!has_tabclosedpre() || recursive)
+ return;
+
+ if (valid_tabpage(tp))
+ goto_tabpage_tp(tp, FALSE, FALSE);
+ recursive = TRUE;
+ window_layout_lock();
+ apply_autocmds(EVENT_TABCLOSEDPRE, NULL, NULL, FALSE, NULL);
+ window_layout_unlock();
+ recursive = FALSE;
+ // tabpage may have been modified or deleted by autocmds
+ if (valid_tabpage(ptp))
+ // try to recover the tappage first
+ goto_tabpage_tp(ptp, FALSE, FALSE);
+ else
+ // fall back to the first tappage
+ goto_tabpage_tp(first_tabpage, FALSE, FALSE);
+}
+
/*
* Make a snapshot of all the window scroll positions and sizes of the current
* tab page.
@@ -3353,6 +3380,14 @@
return;
}
+ if (tp->tp_firstwin == tp->tp_lastwin)
+ {
+ trigger_tabclosedpre(tp);
+ // autocmd may have freed the window already.
+ if (!win_valid_any_tab(win))
+ return;
+ }
+
if (win->w_buffer != NULL)
// Close the link to the buffer.
close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0,