aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/autocmd.txt4
-rw-r--r--runtime/doc/vim_diff.txt1
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/window.c37
-rw-r--r--test/functional/autocmd/autocmd_spec.lua66
5 files changed, 105 insertions, 5 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 774e6a5d92..d15f5e7900 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -317,6 +317,7 @@ Name triggered by ~
|CursorMoved| the cursor was moved in Normal mode
|CursorMovedI| the cursor was moved in Insert mode
+|WinClosed| after closing a window
|WinNew| after creating a new window
|WinEnter| after entering another window
|WinLeave| before leaving a window
@@ -1131,6 +1132,8 @@ VimResized After the Vim window was resized, thus 'lines'
VimResume After Nvim resumes from |suspend| state.
*VimSuspend*
VimSuspend Before Nvim enters |suspend| state.
+ *WinClosed*
+WinClosed After closing a window.
*WinEnter*
WinEnter After entering another window. Not done for
the first window, when Vim has just started.
@@ -1148,7 +1151,6 @@ WinLeave Before leaving a window. If the window to be
executes the BufLeave autocommands before the
WinLeave autocommands (but not for ":new").
Not used for ":qa" or ":q" when exiting Vim.
-
*WinNew*
WinNew When a new window was created. Not done for
the first window, when Vim has just started.
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 2bb798a6e6..5835c7f314 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -159,6 +159,7 @@ Events:
|UILeave|
|VimResume|
|VimSuspend|
+ |WinClosed|
Functions:
|dictwatcheradd()| notifies a callback whenever a |Dict| is modified
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index a52789c795..d596edf551 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -110,6 +110,7 @@ return {
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window
+ 'WinClosed', -- after closing a window
},
aliases = {
BufCreate = 'BufAdd',
@@ -129,5 +130,6 @@ return {
TermOpen=true,
UIEnter=true,
UILeave=true,
+ WinClosed=true,
},
}
diff --git a/src/nvim/window.c b/src/nvim/window.c
index e913d33de0..4743dca3ff 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -2502,9 +2502,10 @@ int win_close(win_T *win, bool free_buf)
return FAIL;
}
win->w_closing = true;
- apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf);
- if (!win_valid(win))
+ apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
+ if (!win_valid(win)) {
return FAIL;
+ }
win->w_closing = false;
if (last_window())
return FAIL;
@@ -2534,6 +2535,12 @@ int win_close(win_T *win, bool free_buf)
}
}
+ // Fire WinClosed just before starting to free window-related resources.
+ do_autocmd_winclosed(win);
+ // autocmd may have freed the window already.
+ if (!win_valid_any_tab(win)) {
+ return OK;
+ }
/* Free independent synblock before the buffer is freed. */
if (win->w_buffer != NULL)
@@ -2576,6 +2583,7 @@ int win_close(win_T *win, bool free_buf)
win_close_othertab(win, false, prev_curtab);
return FAIL;
}
+
// Autocommands may have closed the window already, or closed the only
// other window or moved to another tab page.
if (!win_valid(win) || (!win->w_floating && last_window())
@@ -2585,8 +2593,9 @@ int win_close(win_T *win, bool free_buf)
// let terminal buffers know that this window dimensions may be ignored
win->w_closing = true;
- /* Free the memory used for the window and get the window that received
- * the screen space. */
+
+ // Free the memory used for the window and get the window that received
+ // the screen space.
wp = win_free_mem(win, &dir, NULL);
if (help_window) {
@@ -2678,6 +2687,19 @@ int win_close(win_T *win, bool free_buf)
return OK;
}
+static void do_autocmd_winclosed(win_T *win)
+ FUNC_ATTR_NONNULL_ALL
+{
+ static bool recursive = false;
+ if (recursive || !has_event(EVENT_WINCLOSED)) {
+ return;
+ }
+ recursive = true;
+ apply_autocmds(EVENT_WINCLOSED, win->w_buffer->b_fname,
+ win->w_buffer->b_fname, false, win->w_buffer);
+ recursive = false;
+}
+
/*
* Close window "win" in tab page "tp", which is not the current tab page.
* This may be the last window in that tab page and result in closing the tab,
@@ -2698,6 +2720,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
return; // window is already being closed
}
+ // Fire WinClosed just before starting to free window-related resources.
+ do_autocmd_winclosed(win);
+ // 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, false);
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index 43534c9e7e..31c6edb940 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local dedent = helpers.dedent
+local neq = helpers.neq
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
@@ -40,6 +41,71 @@ describe('autocmd', function()
assert.same(expected, eval('g:foo'))
end)
+ it(':close triggers WinClosed event', function()
+ command('let g:triggered = 0')
+ command('new')
+ command('autocmd WinClosed <buffer> :let g:triggered+=1')
+ eq(0, eval('g:triggered'))
+ command('close')
+ eq(1, eval('g:triggered'))
+ end)
+
+ it(':bdelete triggers WinClosed event', function()
+ command('let g:triggered = 0')
+ command('autocmd WinClosed <buffer> :let g:triggered+=1')
+ local first_buffer = eval("bufnr('%')")
+ command('new')
+ command('bdelete ' .. first_buffer )
+ eq(1, eval('g:triggered'))
+ end)
+
+ it(':close triggers WinClosed event in another tab', function()
+ command('let g:triggered = 0')
+ local current_buffer = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :let g:triggered+=1')
+ command('tabnew')
+ command('bdelete ' .. current_buffer)
+ eq(1, eval('g:triggered'))
+ end)
+
+ it('WinClosed events are not recursive in different window', function()
+ command('let g:triggered = 0')
+ local first_buffer = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :let g:triggered+=1')
+ command('new')
+ local second_buffer = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :bdelete ' .. first_buffer)
+ command('new')
+ neq(-1, funcs.bufwinnr(first_buffer))
+ command('bdelete ' .. second_buffer )
+ eq(0, eval('g:triggered'))
+
+ -- first event was triggered, second wasn't
+ eq(-1, funcs.bufwinnr(first_buffer))
+ end)
+
+ it('WinClosed events are not recursive in the same window', function()
+ command('let g:triggered = 0')
+ command('new')
+ local second_buffer = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :let g:triggered+=1 | bdelete ' .. second_buffer)
+ neq(-1, funcs.bufwinnr(second_buffer))
+ eq(0, eval('g:triggered'))
+ command('bdelete ' .. second_buffer )
+ eq(-1, funcs.bufwinnr(second_buffer))
+ eq(1, eval('g:triggered'))
+ end)
+
+ it('WinClosed events are not recursive in different tab', function()
+ command('let g:triggered = 0')
+ command('new')
+ local second_buffer = eval("bufnr('%')")
+ command('autocmd WinClosed <buffer> :let g:triggered+=1 | bdelete ' .. second_buffer)
+ command('tabnew')
+ command('bdelete ' .. second_buffer )
+ eq(1, eval('g:triggered'))
+ end)
+
it('v:vim_did_enter is 1 after VimEnter', function()
eq(1, eval('v:vim_did_enter'))
end)