patch 9.0.0965: using one window for executing autocommands is insufficient

Problem:    Using one window for executing autocommands is insufficient.
Solution:   Use up to five windows for executing autocommands.
diff --git a/src/autocmd.c b/src/autocmd.c
index 11dc707..3a08128 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -629,28 +629,52 @@
     }
 }
 
+    void
+autocmd_init(void)
+{
+    CLEAR_FIELD(aucmd_win);
+}
+
 #if defined(EXITFREE) || defined(PROTO)
     void
 free_all_autocmds(void)
 {
-    int		i;
     char_u	*s;
 
     for (current_augroup = -1; current_augroup < augroups.ga_len;
 							    ++current_augroup)
 	do_autocmd(NULL, (char_u *)"", TRUE);
 
-    for (i = 0; i < augroups.ga_len; ++i)
+    for (int i = 0; i < augroups.ga_len; ++i)
     {
 	s = ((char_u **)(augroups.ga_data))[i];
 	if (s != get_deleted_augroup())
 	    vim_free(s);
     }
     ga_clear(&augroups);
+
+    for (int i = 0; i < AUCMD_WIN_COUNT; ++i)
+	if (aucmd_win[i].auc_win_used)
+	{
+	    aucmd_win[i].auc_win_used = FALSE;
+	    win_remove(aucmd_win[i].auc_win, NULL);
+	}
 }
 #endif
 
 /*
+ * Return TRUE if "win" is an active entry in aucmd_win[].
+ */
+    int
+is_aucmd_win(win_T *win)
+{
+    for (int i = 0; i < AUCMD_WIN_COUNT; ++i)
+	if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win)
+	    return TRUE;
+    return FALSE;
+}
+
+/*
  * Return the event number for event name "start".
  * Return NUM_EVENTS if the event name was not found.
  * Return a pointer to the next event name in "end".
@@ -1438,8 +1462,16 @@
 	if (buf->b_ml.ml_mfp == NULL || buf == curbuf)
 	    continue;
 
-	// find a window for this buffer and save some values
+	// Find a window for this buffer and save some values.
 	aucmd_prepbuf(&aco, buf);
+	if (curbuf != buf)
+	{
+	    // Failed to find a window for this buffer.  Better not execute
+	    // autocommands then.
+	    retval = FAIL;
+	    break;
+	}
+
 	set_bufref(&bufref, buf);
 
 	// execute the autocommands for this buffer
@@ -1449,7 +1481,7 @@
 	    // Execute the modeline settings, but don't set window-local
 	    // options if we are using the current window for another
 	    // buffer.
-	    do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0);
+	    do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0);
 
 	// restore the current window
 	aucmd_restbuf(&aco);
@@ -1490,8 +1522,9 @@
 /*
  * Prepare for executing autocommands for (hidden) buffer "buf".
  * Search for a visible window containing the current buffer.  If there isn't
- * one then use "aucmd_win".
+ * one then use an entry in "aucmd_win[]".
  * Set "curbuf" and "curwin" to match "buf".
+ * When this fails "curbuf" is not equal "buf".
  */
     void
 aucmd_prepbuf(
@@ -1512,18 +1545,29 @@
 	    if (win->w_buffer == buf)
 		break;
 
-    // Allocate "aucmd_win" when needed.  If this fails (out of memory) fall
-    // back to using the current window.
-    if (win == NULL && aucmd_win == NULL)
+    // Allocate a window when needed.
+    win_T *auc_win = NULL;
+    int auc_idx = AUCMD_WIN_COUNT;
+    if (win == NULL)
     {
-	aucmd_win = win_alloc_popup_win();
-	if (aucmd_win == NULL)
-	    win = curwin;
+	for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; ++auc_idx)
+	    if (!aucmd_win[auc_idx].auc_win_used)
+	    {
+		auc_win = win_alloc_popup_win();
+		if (auc_win != NULL)
+		{
+		    aucmd_win[auc_idx].auc_win = auc_win;
+		    aucmd_win[auc_idx].auc_win_used = TRUE;
+		}
+		break;
+	    }
+
+	// If this fails (out of memory or using all AUCMD_WIN_COUNT
+	// entries) then we can't reliable execute the autocmd, return with
+	// "curbuf" unequal "buf".
+	if (auc_win == NULL)
+	    return;
     }
-    if (win == NULL && aucmd_win_used)
-	// Strange recursive autocommand, fall back to using the current
-	// window.  Expect a few side effects...
-	win = curwin;
 
     aco->save_curwin_id = curwin->w_id;
     aco->save_curbuf = curbuf;
@@ -1533,24 +1577,23 @@
 	// There is a window for "buf" in the current tab page, make it the
 	// curwin.  This is preferred, it has the least side effects (esp. if
 	// "buf" is curbuf).
-	aco->use_aucmd_win = FALSE;
+	aco->use_aucmd_win_idx = -1;
 	curwin = win;
     }
     else
     {
-	// There is no window for "buf", use "aucmd_win".  To minimize the side
+	// There is no window for "buf", use "auc_win".  To minimize the side
 	// effects, insert it in the current tab page.
 	// Anything related to a window (e.g., setting folds) may have
 	// unexpected results.
-	aco->use_aucmd_win = TRUE;
-	aucmd_win_used = TRUE;
+	aco->use_aucmd_win_idx = auc_idx;
 
-	win_init_popup_win(aucmd_win, buf);
+	win_init_popup_win(auc_win, buf);
 
 	aco->globaldir = globaldir;
 	globaldir = NULL;
 
-	// Split the current window, put the aucmd_win in the upper half.
+	// Split the current window, put the auc_win in the upper half.
 	// We don't want the BufEnter or WinEnter autocommands.
 	block_autocmds();
 	make_snapshot(SNAP_AUCMD_IDX);
@@ -1565,7 +1608,7 @@
 
 	// no redrawing and don't set the window title
 	++RedrawingDisabled;
-	(void)win_split_ins(0, WSP_TOP, aucmd_win, 0);
+	(void)win_split_ins(0, WSP_TOP, auc_win, 0);
 	--RedrawingDisabled;
 	(void)win_comp_pos();   // recompute window positions
 	p_ea = save_ea;
@@ -1573,7 +1616,7 @@
 	p_acd = save_acd;
 #endif
 	unblock_autocmds();
-	curwin = aucmd_win;
+	curwin = auc_win;
     }
     curbuf = buf;
     aco->new_curwin_id = curwin->w_id;
@@ -1595,24 +1638,26 @@
     int	    dummy;
     win_T   *save_curwin;
 
-    if (aco->use_aucmd_win)
+    if (aco->use_aucmd_win_idx >= 0)
     {
+	win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win;
+
 	--curbuf->b_nwindows;
-	// Find "aucmd_win", it can't be closed, but it may be in another tab
+	// Find "awp", it can't be closed, but it may be in another tab
 	// page. Do not trigger autocommands here.
 	block_autocmds();
-	if (curwin != aucmd_win)
+	if (curwin != awp)
 	{
 	    tabpage_T	*tp;
 	    win_T	*wp;
 
 	    FOR_ALL_TAB_WINDOWS(tp, wp)
 	    {
-		if (wp == aucmd_win)
+		if (wp == awp)
 		{
 		    if (tp != curtab)
 			goto_tabpage_tp(tp, TRUE, TRUE);
-		    win_goto(aucmd_win);
+		    win_goto(awp);
 		    goto win_found;
 		}
 	    }
@@ -1622,7 +1667,7 @@
 	// Remove the window and frame from the tree of frames.
 	(void)winframe_remove(curwin, &dummy, NULL);
 	win_remove(curwin, NULL);
-	aucmd_win_used = FALSE;
+	aucmd_win[aco->use_aucmd_win_idx].auc_win_used = FALSE;
 	last_status(FALSE);	    // may need to remove last status line
 
 	if (!valid_tabpage_win(curtab))
@@ -1646,8 +1691,8 @@
 #endif
 	prevwin = win_find_by_id(aco->save_prevwin_id);
 #ifdef FEAT_EVAL
-	vars_clear(&aucmd_win->w_vars->dv_hashtab);  // free all w: variables
-	hash_init(&aucmd_win->w_vars->dv_hashtab);   // re-use the hashtab
+	vars_clear(&awp->w_vars->dv_hashtab);  // free all w: variables
+	hash_init(&awp->w_vars->dv_hashtab);   // re-use the hashtab
 #endif
 	vim_free(globaldir);
 	globaldir = aco->globaldir;
@@ -1664,11 +1709,9 @@
 #if defined(FEAT_GUI)
 	if (gui.in_use)
 	{
-	    // Hide the scrollbars from the aucmd_win and update.
-	    gui_mch_enable_scrollbar(
-				   &aucmd_win->w_scrollbars[SBAR_LEFT], FALSE);
-	    gui_mch_enable_scrollbar(
-				  &aucmd_win->w_scrollbars[SBAR_RIGHT], FALSE);
+	    // Hide the scrollbars from the "awp" and update.
+	    gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_LEFT], FALSE);
+	    gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_RIGHT], FALSE);
 	    gui_may_update_scrollbars();
 	}
 #endif