diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 090c216fcb..e99b7ab8b4 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -294,6 +294,7 @@ Name triggered by ~ |CursorMoved| the cursor was moved in Normal mode |CursorMovedI| the cursor was moved in Insert mode +|WinNew| after creating a new window |WinEnter| after entering another window |WinLeave| before leaving a window |TabEnter| after entering another tab page @@ -992,6 +993,11 @@ WinLeave Before leaving a window. If the window to be 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 fist window, when Vim has just started. + Before a WinEnter event. + ============================================================================== 6. Patterns *autocmd-patterns* *{pat}* diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 8d891effae..b405928577 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -89,6 +89,7 @@ return { 'VimLeave', -- before exiting Vim 'VimLeavePre', -- before exiting Vim and writing ShaDa file 'VimResized', -- after Vim window was resized + 'WinNew', -- when entering a new window 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window }, diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 8909fdd57a..600cf575fc 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -409,9 +409,6 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) buf->b_nwindows = nwindows; buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); - if (win_valid_any_tab(win) && win->w_buffer == buf) { - win->w_buffer = NULL; // make sure we don't use the buffer now - } /* Autocommands may have deleted the buffer. */ if (!buf_valid(buf)) @@ -419,11 +416,6 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) if (aborting()) /* autocmds may abort script processing */ return; - /* Autocommands may have opened or closed windows for this buffer. - * Decrement the count for the close we do here. */ - if (buf->b_nwindows > 0) - --buf->b_nwindows; - /* * It's possible that autocommands change curbuf to the one being deleted. * This might cause the previous curbuf to be deleted unexpectedly. But @@ -434,6 +426,16 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) if (buf == curbuf && !is_curbuf) return; + if (win_valid_any_tab(win) && win->w_buffer == buf) { + win->w_buffer = NULL; // make sure we don't use the buffer now + } + + // Autocommands may have opened or closed windows for this buffer. + // Decrement the count for the close we do here. + if (buf->b_nwindows > 0) { + buf->b_nwindows--; + } + /* Change directories when the 'acd' option is set. */ do_autochdir(); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6f95ced147..e734cde905 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -5373,6 +5373,8 @@ static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ */ static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; #define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) +// use get_deleted_augroup() to get this +static char_u *deleted_augroup = NULL; /* * The ID of the current group. Group 0 is the default one. @@ -5387,6 +5389,14 @@ static event_T last_event; static int last_group; static int autocmd_blocked = 0; /* block all autocmds */ +static char_u *get_deleted_augroup(void) +{ + if (deleted_augroup == NULL) { + deleted_augroup = (char_u *)_("--Deleted--"); + } + return deleted_augroup; +} + /* * Show the autocommands for one AutoPat. */ @@ -5406,10 +5416,11 @@ static void show_autocmd(AutoPat *ap, event_T event) return; if (event != last_event || ap->group != last_group) { if (ap->group != AUGROUP_DEFAULT) { - if (AUGROUP_NAME(ap->group) == NULL) - msg_puts_attr((char_u *)_("--Deleted--"), hl_attr(HLF_E)); - else + if (AUGROUP_NAME(ap->group) == NULL) { + msg_puts_attr(get_deleted_augroup(), hl_attr(HLF_E)); + } else { msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T)); + } msg_puts((char_u *)" "); } msg_puts_attr(event_nr2name(event), hl_attr(HLF_T)); @@ -5575,11 +5586,33 @@ static void au_del_group(char_u *name) int i; i = au_find_group(name); - if (i == AUGROUP_ERROR) /* the group doesn't exist */ + if (i == AUGROUP_ERROR) { // the group doesn't exist EMSG2(_("E367: No such group: \"%s\""), name); - else { + } else if (i == current_augroup) { + EMSG(_("E936: Cannot delete the current group")); + } else { + event_T event; + AutoPat *ap; + int in_use = false; + + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) { + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + if (ap->group == i && ap->pat != NULL) { + give_warning((char_u *) + _("W19: Deleting augroup that is still in use"), true); + in_use = true; + event = NUM_EVENTS; + break; + } + } + } xfree(AUGROUP_NAME(i)); - AUGROUP_NAME(i) = NULL; + if (in_use) { + AUGROUP_NAME(i) = get_deleted_augroup(); + } else { + AUGROUP_NAME(i) = NULL; + } } } @@ -5591,8 +5624,9 @@ static void au_del_group(char_u *name) static int au_find_group(const char_u *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (int i = 0; i < augroups.ga_len; ++i) { - if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0) { + for (int i = 0; i < augroups.ga_len; i++) { + if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() + && STRCMP(AUGROUP_NAME(i), name) == 0) { return i; } } @@ -5640,10 +5674,21 @@ void do_augroup(char_u *arg, int del_group) #if defined(EXITFREE) void free_all_autocmds(void) { + int i; + char_u *s; + for (current_augroup = -1; current_augroup < augroups.ga_len; - ++current_augroup) - do_autocmd((char_u *)"", TRUE); - ga_clear_strings(&augroups); + current_augroup++) { + do_autocmd((char_u *)"", true); + } + + for (i = 0; i < augroups.ga_len; i++) { + s = ((char_u **)(augroups.ga_data))[i]; + if (s != get_deleted_augroup()) { + xfree(s); + } + } + ga_clear(&augroups); } #endif @@ -7105,9 +7150,11 @@ char_u *get_augroup_name(expand_T *xp, int idx) return (char_u *)"END"; if (idx >= augroups.ga_len) /* end of list */ return NULL; - if (AUGROUP_NAME(idx) == NULL) /* skip deleted entries */ + if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { + // skip deleted entries return (char_u *)""; - return AUGROUP_NAME(idx); /* return a name */ + } + return AUGROUP_NAME(idx); // return a name } static int include_groups = FALSE; @@ -7164,10 +7211,12 @@ set_context_in_autocmd ( */ char_u *get_event_name(expand_T *xp, int idx) { - if (idx < augroups.ga_len) { /* First list group names, if wanted */ - if (!include_groups || AUGROUP_NAME(idx) == NULL) - return (char_u *)""; /* skip deleted entries */ - return AUGROUP_NAME(idx); /* return a name */ + if (idx < augroups.ga_len) { // First list group names, if wanted + if (!include_groups || AUGROUP_NAME(idx) == NULL + || AUGROUP_NAME(idx) == get_deleted_augroup()) { + return (char_u *)""; // skip deleted entries + } + return AUGROUP_NAME(idx); // return a name } return (char_u *)event_names[idx - augroups.ga_len].name; } diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5675697dc4..f05a55f1aa 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -64,6 +64,66 @@ function Test_bufunload() augroup! test_bufunload_group endfunc +" SEGV occurs in older versions. (At least 7.4.2005 or older) +function Test_autocmd_bufunload_with_tabnext() + tabedit + tabfirst + + augroup test_autocmd_bufunload_with_tabnext_group + autocmd! + autocmd BufUnload tabnext + augroup END + + quit + call assert_equal(2, tabpagenr('$')) + + augroup! test_autocmd_bufunload_with_tabnext_group + tablast + quit +endfunc + +func Test_win_tab_autocmd() + let g:record = [] + + augroup testing + au WinNew * call add(g:record, 'WinNew') + au WinEnter * call add(g:record, 'WinEnter') + au WinLeave * call add(g:record, 'WinLeave') + au TabNew * call add(g:record, 'TabNew') + au TabClosed * call add(g:record, 'TabClosed') + au TabEnter * call add(g:record, 'TabEnter') + au TabLeave * call add(g:record, 'TabLeave') + augroup END + + split + tabnew + close + close + + call assert_equal([ + \ 'WinLeave', 'WinNew', 'WinEnter', + \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', + \ 'WinLeave', 'TabLeave', 'TabClosed', 'WinEnter', 'TabEnter', + \ 'WinLeave', 'WinEnter' + \ ], g:record) + + let g:record = [] + tabnew somefile + tabnext + bwipe somefile + + call assert_equal([ + \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', + \ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', + \ 'TabClosed' + \ ], g:record) + + augroup testing + au! + augroup END + unlet g:record +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' @@ -91,3 +151,48 @@ func Test_early_bar() au! vimBarTest|echo 'hello' call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) endfunc + +func RemoveGroup() + autocmd! StartOK + augroup! StartOK +endfunc + +func Test_augroup_warning() + augroup TheWarning + au VimEnter * echo 'entering' + augroup END + call assert_true(match(execute('au VimEnter'), "TheWarning.*VimEnter") >= 0) + redir => res + augroup! TheWarning + redir END + call assert_true(match(res, "W19:") >= 0) + call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + + " check "Another" does not take the pace of the deleted entry + augroup Another + augroup END + call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + + " no warning for postpone aucmd delete + augroup StartOK + au VimEnter * call RemoveGroup() + augroup END + call assert_true(match(execute('au VimEnter'), "StartOK.*VimEnter") >= 0) + redir => res + doautocmd VimEnter + redir END + call assert_true(match(res, "W19:") < 0) + au! VimEnter +endfunc + +func Test_augroup_deleted() + " This caused a crash before E936 was introduced + augroup x + call assert_fails('augroup! x', 'E936:') + au VimEnter * echo + augroup end + augroup! x + call assert_true(match(execute('au VimEnter'), "-Deleted-.*VimEnter") >= 0) + au! VimEnter +endfunc + diff --git a/src/nvim/version.c b/src/nvim/version.c index 04b42ed39f..acaad38ca2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -126,8 +126,8 @@ static int included_patches[] = { // 2317, // 2316 NA // 2315, - // 2314, - // 2313, + 2314, + 2313, 2312, // 2311 NA // 2310 NA @@ -140,7 +140,7 @@ static int included_patches[] = { // 2303, // 2302 NA // 2301 NA - // 2300, + 2300, // 2299, // 2298 NA // 2297 NA @@ -323,7 +323,7 @@ static int included_patches[] = { // 2120, // 2119, // 2118 NA - // 2117, + 2117, // 2116 NA // 2115 NA // 2114 NA @@ -363,9 +363,9 @@ static int included_patches[] = { // 2080, // 2079 NA // 2078 NA - // 2077, + 2077, // 2076, - // 2075, + 2075, // 2074, // 2073 NA // 2072, @@ -434,7 +434,7 @@ static int included_patches[] = { 2009, 2008, 2007, - // 2006, + 2006, 2005, // 2004 NA // 2003 NA diff --git a/src/nvim/window.c b/src/nvim/window.c index 00229ccca9..89228e1b0f 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -973,11 +973,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) /* * make the new window the current window */ - win_enter(wp, false); - if (flags & WSP_VERT) + win_enter_ext(wp, false, false, true, true, true); + if (flags & WSP_VERT) { p_wiw = i; - else + } else { p_wh = i; + } return OK; } @@ -1718,6 +1719,7 @@ close_windows ( { tabpage_T *tp, *nexttp; int h = tabline_height(); + int count = tabpage_index(NULL); ++RedrawingDisabled; @@ -1754,9 +1756,14 @@ close_windows ( --RedrawingDisabled; - redraw_tabline = TRUE; - if (h != tabline_height()) + if (count != tabpage_index(NULL)) { + apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf); + } + + redraw_tabline = true; + if (h != tabline_height()) { shell_new_rows(); + } } /// Check that current window is the last one. @@ -2014,10 +2021,11 @@ int win_close(win_T *win, int free_buf) } if (close_curwin) { - win_enter_ext(wp, false, TRUE, TRUE, TRUE); - if (other_buffer) - /* careful: after this wp and win may be invalid! */ - apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + win_enter_ext(wp, false, true, false, true, true); + if (other_buffer) { + // careful: after this wp and win may be invalid! + apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); + } } /* @@ -3045,8 +3053,9 @@ int win_new_tabpage(int after, char_u *filename) redraw_all_later(CLEAR); - apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); return OK; @@ -3204,8 +3213,8 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au /* We would like doing the TabEnter event first, but we don't have a * valid current window yet, which may break some commands. * This triggers autocommands, thus may make "tp" invalid. */ - win_enter_ext(tp->tp_curwin, false, TRUE, - trigger_enter_autocmds, trigger_leave_autocmds); + win_enter_ext(tp->tp_curwin, false, true, false, + trigger_enter_autocmds, trigger_leave_autocmds); prevwin = next_prevwin; last_status(FALSE); /* status line may appear or disappear */ @@ -3546,7 +3555,7 @@ end: */ void win_enter(win_T *wp, bool undo_sync) { - win_enter_ext(wp, undo_sync, FALSE, TRUE, TRUE); + win_enter_ext(wp, undo_sync, false, false, true, true); } /* @@ -3554,7 +3563,9 @@ void win_enter(win_T *wp, bool undo_sync) * Can be called with "curwin_invalid" TRUE, which means that curwin has just * been closed and isn't valid. */ -static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int trigger_enter_autocmds, int trigger_leave_autocmds) +static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, + int trigger_new_autocmds, int trigger_enter_autocmds, + int trigger_leave_autocmds) { int other_buffer = FALSE; @@ -3630,6 +3641,9 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, int tri shorten_fnames(TRUE); } + if (trigger_new_autocmds) { + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + } if (trigger_enter_autocmds) { apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE, curbuf); if (other_buffer) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 162e112047..c38bd75c69 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -12,8 +12,8 @@ describe('autocmds:', function() local expected = { 'WinLeave', 'TabLeave', - 'TabNew', 'WinEnter', + 'TabNew', 'TabEnter', 'BufLeave', 'BufEnter'