patch 9.1.1202: Missing TabClosedPre autocommand

Commit: 
https://github.com/vim/vim/commit/5606ca5349982fe53cc6a2ec6345aa66f0613d40
Author: Jim Zhou <jimzhou...@gmail.com>
Date:   Thu Mar 13 21:58:25 2025 +0100

    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 <jimzhou...@gmail.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 8532fc956..372d1acbc 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -387,6 +387,7 @@ Name                        triggered by ~
 |TabNew|               after creating a new tab page
 |WinClosed|            after closing a window
 |TabClosed|            after closing a tab page
+|TabClosedPre|         before closing a tab page
 |WinEnter|             after entering another window
 |WinLeave|             before leaving a window
 |TabEnter|             after entering another tab page
@@ -1232,6 +1233,10 @@ Syntax                           When the 'syntax' 
option has been set.  The
                                See |:syn-on|.
                                                        *TabClosed*
 TabClosed                      After closing a tab page.
+                                                       *TabClosedPre*
+TabClosedPre                   Before closing a tab page.  The window layout
+                               is locked, thus opening and closing of windows
+                               is prohibited.
                                                        *TabEnter*
 TabEnter                       Just after entering a tab page. |tab-page|
                                After triggering the WinEnter and before
diff --git a/runtime/doc/tags b/runtime/doc/tags
index a91b4af0b..3bd49d90d 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -5759,6 +5759,7 @@ TSQL      ft_sql.txt      /*TSQL*
 TTpro-telnet   syntax.txt      /*TTpro-telnet*
 Tab    intro.txt       /*Tab*
 TabClosed      autocmd.txt     /*TabClosed*
+TabClosedPre   autocmd.txt     /*TabClosedPre*
 TabEnter       autocmd.txt     /*TabEnter*
 TabLeave       autocmd.txt     /*TabLeave*
 TabNew autocmd.txt     /*TabNew*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index f12bcd4c3..6d41e342f 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Mar 06
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Mar 13
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41687,6 +41687,7 @@ Autocommands: ~
 |CursorMovedC|         after the cursor was moved in the command-line
 |KeyInputPre|          before processing any key event in any mode
 |SessionWritePost|     after writing the session file |:mksession|
+|TabClosedPre|         before closing a |tabpage|.
 |TermResponseAll|      after the terminal response to |t_RV| and others is
                        received
 |WinNewPre|            before creating a new window
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index f537e0397..d469134db 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -2,7 +2,7 @@
 " Language:       Vim script
 " Maintainer:     Hirohito Higashi <h.east.727 ATMARK gmail.com>
 "         Doug Kearns <dougkea...@gmail.com>
-" Last Change:    2025 Mar 10
+" Last Change:    2025 Mar 13
 " Former Maintainer: Charles E. Campbell
 
 " DO NOT CHANGE DIRECTLY.
@@ -116,7 +116,7 @@ syn keyword vimErrSetting contained invakm invaltkeymap 
invanti invantialias inv
 syn case ignore
 " GEN_SYN_VIM: vimAutoEvent, START_STR='syn keyword vimAutoEvent contained', 
END_STR=''
 syn keyword vimAutoEvent contained BufAdd BufCreate BufDelete BufEnter 
BufFilePost BufFilePre BufHidden BufLeave BufNew BufNewFile BufRead BufReadCmd 
BufReadPost BufReadPre BufUnload BufWinEnter BufWinLeave BufWipeout BufWrite 
BufWriteCmd BufWritePost BufWritePre CmdlineChanged CmdlineEnter CmdlineLeave 
CmdUndefined CmdwinEnter CmdwinLeave ColorScheme ColorSchemePre CompleteChanged 
CompleteDone CompleteDonePre CursorHold CursorHoldI CursorMoved CursorMovedC 
CursorMovedI DiffUpdated DirChanged DirChangedPre EncodingChanged ExitPre 
FileAppendCmd FileAppendPost FileAppendPre FileChangedRO FileChangedShell 
FileChangedShellPost FileEncoding FileReadCmd FileReadPost FileReadPre FileType 
FileWriteCmd FileWritePost FileWritePre FilterReadPost FilterReadPre 
FilterWritePost
-syn keyword vimAutoEvent contained FilterWritePre FocusGained FocusLost 
FuncUndefined GUIEnter GUIFailed InsertChange InsertCharPre InsertEnter 
InsertLeave InsertLeavePre KeyInputPre MenuPopup ModeChanged OptionSet 
QuickFixCmdPost QuickFixCmdPre QuitPre RemoteReply SafeState SafeStateAgain 
SessionLoadPost SessionWritePost ShellCmdPost ShellFilterPost SigUSR1 SourceCmd 
SourcePost SourcePre SpellFileMissing StdinReadPost StdinReadPre SwapExists 
Syntax TabClosed TabEnter TabLeave TabNew TermChanged TerminalOpen 
TerminalWinOpen TermResponse TermResponseAll TextChanged TextChangedI 
TextChangedP TextChangedT TextYankPost User VimEnter VimLeave VimLeavePre 
VimResized VimResume VimSuspend WinClosed WinEnter WinLeave WinNew WinNewPre 
WinResized WinScrolled
+syn keyword vimAutoEvent contained FilterWritePre FocusGained FocusLost 
FuncUndefined GUIEnter GUIFailed InsertChange InsertCharPre InsertEnter 
InsertLeave InsertLeavePre KeyInputPre MenuPopup ModeChanged OptionSet 
QuickFixCmdPost QuickFixCmdPre QuitPre RemoteReply SafeState SafeStateAgain 
SessionLoadPost SessionWritePost ShellCmdPost ShellFilterPost SigUSR1 SourceCmd 
SourcePost SourcePre SpellFileMissing StdinReadPost StdinReadPre SwapExists 
Syntax TabClosed TabClosedPre TabEnter TabLeave TabNew TermChanged TerminalOpen 
TerminalWinOpen TermResponse TermResponseAll TextChanged TextChangedI 
TextChangedP TextChangedT TextYankPost User VimEnter VimLeave VimLeavePre 
VimResized VimResume VimSuspend WinClosed WinEnter WinLeave WinNew WinNewPre 
WinResized WinScrolled
 
 " Highlight commonly used Groupnames {{{2
 syn keyword vimGroup contained Comment Constant String Character Number 
Boolean Float Identifier Function Statement Conditional Repeat Label Operator 
Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass 
Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug 
Underlined Ignore Error Todo
@@ -1153,7 +1153,7 @@ syn keyword       vimSyncCcomment contained       
ccomment        skipwhite       nextgroup=vimGroupName
 syn keyword    vimSyncClear    contained       clear   skipwhite       
nextgroup=vimSyncGroupName
 syn keyword    vimSyncFromstart        contained       fromstart
 syn keyword    vimSyncMatch    contained       match   skipwhite       
nextgroup=vimSyncGroupName
-syn keyword    vimSyncRegion   contained       region  skipwhite       
nextgroup=vimSynRegion
+syn keyword    vimSyncRegion   contained       region  skipwhite       
nextgroup=vimSynReg
 syn match      vimSyncLinebreak        contained       "\<linebreaks="         
nextgroup=vimNumber
 syn keyword    vimSyncLinecont contained       linecont        skipwhite       
nextgroup=vimSynRegPat
 syn match      vimSyncLines    contained       "\<lines="              
nextgroup=vimNumber
diff --git a/src/autocmd.c b/src/autocmd.c
index f0b573bf5..392168269 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -180,6 +180,7 @@ static keyvalue_T event_tab[NUM_EVENTS] = {
     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 @@ get_event_name_no_group(expand_T *xp UNUSED, int idx, 
int win)
     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 ed85603e3..ff42b6002 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -47,4 +47,5 @@ int au_exists(char_u *arg);
 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 54558b043..138b852f0 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -4986,4 +4986,162 @@ func Test_WinScrolled_Resized_eiw()
   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, '
', '', '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 b9fcedc01..c129404aa 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1202,
 /**/
     1201,
 /**/
diff --git a/src/vim.h b/src/vim.h
index 212b7e774..85fad6ce4 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1435,6 +1435,7 @@ enum auto_event
     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 cc76f7951..cce7f4c79 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2978,6 +2978,33 @@ trigger_winclosed(win_T *win)
     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 @@ win_close_othertab(win_T *win, int free_buf, tabpage_T 
*tp)
            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,

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1tsptQ-008keq-7I%40256bit.org.

Raspunde prin e-mail lui