changeset 3365:9ccdc4a69d8f v7.3.449

updated for version 7.3.449 Problem: Crash when a BufWinLeave autocommand closes the only other window. (Daniel Hunt) Solution: Abort closing a buffer when it becomes the only one.
author Bram Moolenaar <bram@vim.org>
date Wed, 22 Feb 2012 14:58:37 +0100
parents e8d5db3a6d4f
children e9445f083625
files src/buffer.c src/ex_cmds.c src/ex_getln.c src/misc2.c src/proto/buffer.pro src/proto/window.pro src/quickfix.c src/version.c src/window.c
diffstat 9 files changed, 41 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -64,6 +64,9 @@ static void buf_delete_signs __ARGS((buf
 static char *msg_loclist = N_("[Location List]");
 static char *msg_qflist = N_("[Quickfix List]");
 #endif
+#ifdef FEAT_AUTOCMD
+static char *e_auabort = N_("E855: Autocommands caused command to abort");
+#endif
 
 /*
  * Open current buffer, that is: open the memfile and read the file into
@@ -96,7 +99,7 @@ open_buffer(read_stdin, eap, flags)
 	 * There MUST be a memfile, otherwise we can't do anything
 	 * If we can't create one for the current buffer, take another buffer
 	 */
-	close_buffer(NULL, curbuf, 0);
+	close_buffer(NULL, curbuf, 0, FALSE);
 	for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next)
 	    if (curbuf->b_ml.ml_mfp != NULL)
 		break;
@@ -316,12 +319,17 @@ buf_valid(buf)
  * get a new buffer very soon!
  *
  * The 'bufhidden' option can force freeing and deleting.
+ *
+ * When "abort_if_last" is TRUE then do not close the buffer if autocommands
+ * cause there to be only one window with this buffer.  e.g. when ":quit" is
+ * supposed to close the window but autocommands close all other windows.
  */
     void
-close_buffer(win, buf, action)
+close_buffer(win, buf, action, abort_if_last)
     win_T	*win;		/* if not NULL, set b_last_cursor */
     buf_T	*buf;
     int		action;
+    int		abort_if_last;
 {
 #ifdef FEAT_AUTOCMD
     int		is_curbuf;
@@ -371,8 +379,12 @@ close_buffer(win, buf, action)
     {
 	apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
 								  FALSE, buf);
-	if (!buf_valid(buf))	    /* autocommands may delete the buffer */
+	/* Return if autocommands deleted the buffer or made it the only one. */
+	if (!buf_valid(buf) || (abort_if_last && one_window()))
+	{
+	    EMSG(_(e_auabort));
 	    return;
+	}
 
 	/* When the buffer becomes hidden, but is not unloaded, trigger
 	 * BufHidden */
@@ -380,8 +392,13 @@ close_buffer(win, buf, action)
 	{
 	    apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
 								  FALSE, buf);
-	    if (!buf_valid(buf))	/* autocmds may delete the buffer */
+	    /* Return if autocommands deleted the buffer or made it the only
+	     * one. */
+	    if (!buf_valid(buf) || (abort_if_last && one_window()))
+	    {
+		EMSG(_(e_auabort));
 		return;
+	    }
 	}
 # ifdef FEAT_EVAL
 	if (aborting())	    /* autocmds may abort script processing */
@@ -775,7 +792,7 @@ handle_swap_exists(old_curbuf)
 	 * open a new, empty buffer. */
 	swap_exists_action = SEA_NONE;	/* don't want it again */
 	swap_exists_did_quit = TRUE;
-	close_buffer(curwin, curbuf, DOBUF_UNLOAD);
+	close_buffer(curwin, curbuf, DOBUF_UNLOAD, FALSE);
 	if (!buf_valid(old_curbuf) || old_curbuf == curbuf)
 	    old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
 	if (old_curbuf != NULL)
@@ -1122,7 +1139,7 @@ do_buffer(action, start, dir, count, for
 	     * if the buffer still exists.
 	     */
 	    if (buf != curbuf && buf_valid(buf) && buf->b_nwindows == 0)
-		close_buffer(NULL, buf, action);
+		close_buffer(NULL, buf, action, FALSE);
 	    return retval;
 	}
 
@@ -1146,7 +1163,7 @@ do_buffer(action, start, dir, count, for
 	    close_windows(buf, FALSE);
 #endif
 	    if (buf != curbuf && buf_valid(buf) && buf->b_nwindows <= 0)
-		close_buffer(NULL, buf, action);
+		close_buffer(NULL, buf, action, FALSE);
 	    return OK;
 	}
 
@@ -1378,7 +1395,7 @@ set_curbuf(buf, action)
 	    close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf,
 		    unload ? action : (action == DOBUF_GOTO
 			&& !P_HID(prevbuf)
-			&& !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0);
+			&& !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, FALSE);
 	}
     }
 #ifdef FEAT_AUTOCMD
@@ -2708,7 +2725,8 @@ setfname(buf, ffname, sfname, message)
 		vim_free(ffname);
 		return FAIL;
 	    }
-	    close_buffer(NULL, obuf, DOBUF_WIPE); /* delete from the list */
+	    /* delete from the list */
+	    close_buffer(NULL, obuf, DOBUF_WIPE, FALSE);
 	}
 	sfname = vim_strsave(sfname);
 	if (ffname == NULL || sfname == NULL)
@@ -5638,7 +5656,7 @@ wipe_buffer(buf, aucmd)
     if (!aucmd)		    /* Don't trigger BufDelete autocommands here. */
 	block_autocmds();
 #endif
-    close_buffer(NULL, buf, DOBUF_WIPE);
+    close_buffer(NULL, buf, DOBUF_WIPE, FALSE);
 #ifdef FEAT_AUTOCMD
     if (!aucmd)
 	unblock_autocmds();
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3387,7 +3387,7 @@ do_ecmd(fnum, ffname, sfname, eap, newln
 		/* close the link to the current buffer */
 		u_sync(FALSE);
 		close_buffer(oldwin, curbuf,
-				      (flags & ECMD_HIDE) ? 0 : DOBUF_UNLOAD);
+			       (flags & ECMD_HIDE) ? 0 : DOBUF_UNLOAD, FALSE);
 
 #ifdef FEAT_AUTOCMD
 		/* Autocommands may open a new window and leave oldwin open
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -6443,7 +6443,7 @@ ex_window()
 	/* win_close() may have already wiped the buffer when 'bh' is
 	 * set to 'wipe' */
 	if (buf_valid(bp))
-	    close_buffer(NULL, bp, DOBUF_WIPE);
+	    close_buffer(NULL, bp, DOBUF_WIPE, FALSE);
 
 	/* Restore window sizes. */
 	win_size_restore(&winsizes);
--- a/src/misc2.c
+++ b/src/misc2.c
@@ -1173,7 +1173,7 @@ free_all_mem()
     for (buf = firstbuf; buf != NULL; )
     {
 	nextbuf = buf->b_next;
-	close_buffer(NULL, buf, DOBUF_WIPE);
+	close_buffer(NULL, buf, DOBUF_WIPE, FALSE);
 	if (buf_valid(buf))
 	    buf = nextbuf;	/* didn't work, try next one */
 	else
--- a/src/proto/buffer.pro
+++ b/src/proto/buffer.pro
@@ -1,7 +1,7 @@
 /* buffer.c */
 int open_buffer __ARGS((int read_stdin, exarg_T *eap, int flags));
 int buf_valid __ARGS((buf_T *buf));
-void close_buffer __ARGS((win_T *win, buf_T *buf, int action));
+void close_buffer __ARGS((win_T *win, buf_T *buf, int action, int abort_if_last));
 void buf_clear_file __ARGS((buf_T *buf));
 void buf_freeall __ARGS((buf_T *buf, int flags));
 void goto_buffer __ARGS((exarg_T *eap, int start, int dir, int count));
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -1,13 +1,14 @@
 /* window.c */
 void do_window __ARGS((int nchar, long Prenum, int xchar));
 int win_split __ARGS((int size, int flags));
-int win_split_ins __ARGS((int size, int flags, win_T *newwin, int dir));
+int win_split_ins __ARGS((int size, int flags, win_T *new_wp, int dir));
 int win_valid __ARGS((win_T *win));
 int win_count __ARGS((void));
 int make_windows __ARGS((int count, int vertical));
 void win_move_after __ARGS((win_T *win1, win_T *win2));
 void win_equal __ARGS((win_T *next_curwin, int current, int dir));
 void close_windows __ARGS((buf_T *buf, int keep_curwin));
+int one_window __ARGS((void));
 void win_close __ARGS((win_T *win, int free_buf));
 void win_close_othertab __ARGS((win_T *win, int free_buf, tabpage_T *tp));
 void win_free_all __ARGS((void));
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -3565,7 +3565,7 @@ unload_dummy_buffer(buf)
     buf_T	*buf;
 {
     if (curbuf != buf)		/* safety check */
-	close_buffer(NULL, buf, DOBUF_UNLOAD);
+	close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE);
 }
 
 #if defined(FEAT_EVAL) || defined(PROTO)
--- a/src/version.c
+++ b/src/version.c
@@ -715,6 +715,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    449,
+/**/
     448,
 /**/
     447,
--- a/src/window.c
+++ b/src/window.c
@@ -23,7 +23,6 @@ static void win_rotate __ARGS((int, int)
 static void win_totop __ARGS((int size, int flags));
 static void win_equal_rec __ARGS((win_T *next_curwin, int current, frame_T *topfr, int dir, int col, int row, int width, int height));
 static int last_window __ARGS((void));
-static int one_window __ARGS((void));
 static win_T *win_free_mem __ARGS((win_T *win, int *dirp, tabpage_T *tp));
 static frame_T *win_altframe __ARGS((win_T *win, tabpage_T *tp));
 static tabpage_T *alt_tabpage __ARGS((void));
@@ -2083,7 +2082,7 @@ last_window()
  * Return TRUE if there is only one window other than "aucmd_win" in the
  * current tab page.
  */
-    static int
+    int
 one_window()
 {
 #ifdef FEAT_AUTOCMD
@@ -2109,7 +2108,7 @@ one_window()
  * Close window "win".  Only works for the current tab page.
  * If "free_buf" is TRUE related buffer may be unloaded.
  *
- * called by :quit, :close, :xit, :wq and findtag()
+ * Called by :quit, :close, :xit, :wq and findtag().
  */
     void
 win_close(win, free_buf)
@@ -2222,7 +2221,7 @@ win_close(win, free_buf)
      * Close the link to the buffer.
      */
     if (win->w_buffer != NULL)
-	close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0);
+	close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, TRUE);
 
     /* Autocommands may have closed the window already, or closed the only
      * other window or moved to another tab page. */
@@ -2328,7 +2327,7 @@ win_close_othertab(win, free_buf, tp)
     int		free_tp = FALSE;
 
     /* Close the link to the buffer. */
-    close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0);
+    close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, FALSE);
 
     /* Careful: Autocommands may have closed the tab page or made it the
      * current tab page.  */