autocmd: add WinClosed event

- only fire once, just before freeing mem
- trigger when on a different buffer
- avoid recursive calls in another tab
This commit is contained in:
Marcos ALMEIDA
2018-09-29 20:40:53 +02:00
committed by Justin M. Keyes
parent fb8b0503ba
commit 757aad92e8
5 changed files with 105 additions and 5 deletions

View File

@@ -317,6 +317,7 @@ Name triggered by ~
|CursorMoved| the cursor was moved in Normal mode |CursorMoved| the cursor was moved in Normal mode
|CursorMovedI| the cursor was moved in Insert mode |CursorMovedI| the cursor was moved in Insert mode
|WinClosed| after closing a window
|WinNew| after creating a new window |WinNew| after creating a new window
|WinEnter| after entering another window |WinEnter| after entering another window
|WinLeave| before leaving a 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. VimResume After Nvim resumes from |suspend| state.
*VimSuspend* *VimSuspend*
VimSuspend Before Nvim enters |suspend| state. VimSuspend Before Nvim enters |suspend| state.
*WinClosed*
WinClosed After closing a window.
*WinEnter* *WinEnter*
WinEnter After entering another window. Not done for WinEnter After entering another window. Not done for
the first window, when Vim has just started. 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 executes the BufLeave autocommands before the
WinLeave autocommands (but not for ":new"). WinLeave autocommands (but not for ":new").
Not used for ":qa" or ":q" when exiting Vim. Not used for ":qa" or ":q" when exiting Vim.
*WinNew* *WinNew*
WinNew When a new window was created. Not done for WinNew When a new window was created. Not done for
the first window, when Vim has just started. the first window, when Vim has just started.

View File

@@ -159,6 +159,7 @@ Events:
|UILeave| |UILeave|
|VimResume| |VimResume|
|VimSuspend| |VimSuspend|
|WinClosed|
Functions: Functions:
|dictwatcheradd()| notifies a callback whenever a |Dict| is modified |dictwatcheradd()| notifies a callback whenever a |Dict| is modified

View File

@@ -110,6 +110,7 @@ return {
'WinEnter', -- after entering a window 'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window 'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window 'WinNew', -- when entering a new window
'WinClosed', -- after closing a window
}, },
aliases = { aliases = {
BufCreate = 'BufAdd', BufCreate = 'BufAdd',
@@ -129,5 +130,6 @@ return {
TermOpen=true, TermOpen=true,
UIEnter=true, UIEnter=true,
UILeave=true, UILeave=true,
WinClosed=true,
}, },
} }

View File

@@ -2502,9 +2502,10 @@ int win_close(win_T *win, bool free_buf)
return FAIL; return FAIL;
} }
win->w_closing = true; win->w_closing = true;
apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf); apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) if (!win_valid(win)) {
return FAIL; return FAIL;
}
win->w_closing = false; win->w_closing = false;
if (last_window()) if (last_window())
return FAIL; 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. */ /* Free independent synblock before the buffer is freed. */
if (win->w_buffer != NULL) 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); win_close_othertab(win, false, prev_curtab);
return FAIL; return FAIL;
} }
// Autocommands may have closed the window already, or closed the only // Autocommands may have closed the window already, or closed the only
// other window or moved to another tab page. // other window or moved to another tab page.
if (!win_valid(win) || (!win->w_floating && last_window()) 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 // let terminal buffers know that this window dimensions may be ignored
win->w_closing = true; 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); wp = win_free_mem(win, &dir, NULL);
if (help_window) { if (help_window) {
@@ -2678,6 +2687,19 @@ int win_close(win_T *win, bool free_buf)
return OK; 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. * 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, * 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 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) { if (win->w_buffer != NULL) {
// Close the link to the buffer. // Close the link to the buffer.
close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false); close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false);

View File

@@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local dedent = helpers.dedent local dedent = helpers.dedent
local neq = helpers.neq
local eq = helpers.eq local eq = helpers.eq
local eval = helpers.eval local eval = helpers.eval
local feed = helpers.feed local feed = helpers.feed
@@ -40,6 +41,71 @@ describe('autocmd', function()
assert.same(expected, eval('g:foo')) assert.same(expected, eval('g:foo'))
end) 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() it('v:vim_did_enter is 1 after VimEnter', function()
eq(1, eval('v:vim_did_enter')) eq(1, eval('v:vim_did_enter'))
end) end)