mirror of
https://github.com/neovim/neovim.git
synced 2026-02-21 09:50:19 +10:00
fix(autocmd): fire TabClosed after freeing tab page
Problem: TabClosed is fired after close_buffer is called (after b_nwindows is decremented) and after the tab page is removed from the list, but before it's freed. This causes inconsistencies such as the removed tabpage having a valid handle and functions like nvim_tabpage_get_number returning nonsense. Solution: fire it after free_tabpage. Try to maintain the Nvim-specific behaviour of setting `<amatch>` to the old tab page number, and the (undocumented) behaviour of setting `<abuf>` to the buffer it was showing (close_buffer sets w_buffer to NULL if it was freed, so it should be OK pass it to apply_autocmds_group, similar to before).
This commit is contained in:
@@ -3045,15 +3045,11 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
|
||||
goto leave_open;
|
||||
}
|
||||
|
||||
bool free_tp = false;
|
||||
int free_tp_idx = 0;
|
||||
|
||||
// When closing the last window in a tab page remove the tab page.
|
||||
if (tp->tp_firstwin == tp->tp_lastwin) {
|
||||
char prev_idx[NUMBUFLEN];
|
||||
if (has_event(EVENT_TABCLOSED)) {
|
||||
vim_snprintf(prev_idx, NUMBUFLEN, "%i", tabpage_index(tp));
|
||||
}
|
||||
|
||||
free_tp_idx = tabpage_index(tp);
|
||||
int h = tabline_height();
|
||||
|
||||
if (tp == first_tabpage) {
|
||||
@@ -3070,23 +3066,25 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
|
||||
}
|
||||
ptp->tp_next = tp->tp_next;
|
||||
}
|
||||
free_tp = true;
|
||||
redraw_tabline = true;
|
||||
if (h != tabline_height()) {
|
||||
win_new_screen_rows();
|
||||
}
|
||||
|
||||
if (has_event(EVENT_TABCLOSED)) {
|
||||
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Free the memory used for the window.
|
||||
buf_T *buf = win->w_buffer;
|
||||
int dir;
|
||||
win_free_mem(win, &dir, tp);
|
||||
|
||||
if (free_tp) {
|
||||
if (free_tp_idx > 0) {
|
||||
free_tabpage(tp);
|
||||
|
||||
if (has_event(EVENT_TABCLOSED)) {
|
||||
char prev_idx[NUMBUFLEN];
|
||||
vim_snprintf(prev_idx, NUMBUFLEN, "%i", free_tp_idx);
|
||||
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, buf);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ local n = require('test.functional.testnvim')()
|
||||
local clear, eq = n.clear, t.eq
|
||||
local api = n.api
|
||||
local command = n.command
|
||||
local eval = n.eval
|
||||
local exec = n.exec
|
||||
|
||||
describe('TabClosed', function()
|
||||
before_each(clear)
|
||||
@@ -48,6 +50,36 @@ describe('TabClosed', function()
|
||||
eq('tabclosed:2:2:2', api.nvim_exec('bdelete Xtestfile2', true))
|
||||
eq('Xtestfile1', api.nvim_eval('bufname("")'))
|
||||
end)
|
||||
|
||||
it('triggers after tab page is properly freed', function()
|
||||
exec([[
|
||||
let s:tp = nvim_get_current_tabpage()
|
||||
let g:buf = bufnr()
|
||||
|
||||
setlocal bufhidden=wipe
|
||||
tabnew
|
||||
au TabClosed * ++once let g:tp_valid = nvim_tabpage_is_valid(s:tp)
|
||||
\| let g:abuf = expand('<abuf>')
|
||||
|
||||
call nvim_buf_delete(g:buf, #{force: 1})
|
||||
]])
|
||||
eq(false, eval('g:tp_valid'))
|
||||
eq(false, eval('nvim_buf_is_valid(g:buf)'))
|
||||
eq('', eval('g:abuf'))
|
||||
|
||||
exec([[
|
||||
tabnew
|
||||
let g:buf = bufnr()
|
||||
let s:win = win_getid()
|
||||
|
||||
tabfirst
|
||||
au TabClosed * ++once let g:abuf = expand('<abuf>')
|
||||
|
||||
call nvim_win_close(s:win, 1)
|
||||
]])
|
||||
eq(true, eval('nvim_buf_is_valid(g:buf)'))
|
||||
eq(eval('g:buf'), tonumber(eval('g:abuf')))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with NR as <afile>', function()
|
||||
|
||||
Reference in New Issue
Block a user