aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/buffer.c')
-rw-r--r--src/nvim/buffer.c4657
1 files changed, 4657 insertions, 0 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
new file mode 100644
index 0000000000..7cd773b55c
--- /dev/null
+++ b/src/nvim/buffer.c
@@ -0,0 +1,4657 @@
+/*
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * buffer.c: functions for dealing with the buffer structure
+ */
+
+/*
+ * The buffer list is a double linked list of all buffers.
+ * Each buffer can be in one of these states:
+ * never loaded: BF_NEVERLOADED is set, only the file name is valid
+ * not loaded: b_ml.ml_mfp == NULL, no memfile allocated
+ * hidden: b_nwindows == 0, loaded but not displayed in a window
+ * normal: loaded and displayed in a window
+ *
+ * Instead of storing file names all over the place, each file name is
+ * stored in the buffer list. It can be referenced by a number.
+ *
+ * The current implementation remembers all file names ever used.
+ */
+
+#include <string.h>
+
+#include "vim.h"
+#include "buffer.h"
+#include "charset.h"
+#include "diff.h"
+#include "digraph.h"
+#include "eval.h"
+#include "ex_cmds2.h"
+#include "ex_cmds.h"
+#include "ex_docmd.h"
+#include "ex_eval.h"
+#include "ex_getln.h"
+#include "fileio.h"
+#include "fold.h"
+#include "getchar.h"
+#include "hashtab.h"
+#include "indent.h"
+#include "indent_c.h"
+#include "main.h"
+#include "mark.h"
+#include "mbyte.h"
+#include "memline.h"
+#include "memory.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "garray.h"
+#include "move.h"
+#include "option.h"
+#include "os_unix.h"
+#include "path.h"
+#include "quickfix.h"
+#include "regexp.h"
+#include "screen.h"
+#include "spell.h"
+#include "syntax.h"
+#include "term.h"
+#include "ui.h"
+#include "undo.h"
+#include "window.h"
+#include "os/os.h"
+
+// Todo(stefan991): remove this macro
+#define INVALID_DEVICE_ID UINT64_MAX
+
+static char_u *buflist_match(regprog_T *prog, buf_T *buf);
+# define HAVE_BUFLIST_MATCH
+static char_u *fname_match(regprog_T *prog, char_u *name);
+static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum,
+ colnr_T col, int copy_options);
+static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer);
+static buf_T *buflist_findname_file_info(char_u *ffname, FileInfo *file_info);
+static int otherfile_buf(buf_T *buf, char_u *ffname, FileInfo *file_info);
+static int buf_same_ino(buf_T *buf, FileInfo *file_info);
+static int ti_change(char_u *str, char_u **last);
+static int append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file);
+static void free_buffer(buf_T *);
+static void free_buffer_stuff(buf_T *buf, int free_options);
+static void clear_wininfo(buf_T *buf);
+
+static void insert_sign(buf_T *buf, signlist_T *prev, signlist_T *next, int id, linenr_T lnum, int typenr);
+
+static char *msg_loclist = N_("[Location List]");
+static char *msg_qflist = N_("[Quickfix List]");
+static char *e_auabort = N_("E855: Autocommands caused command to abort");
+
+/*
+ * Open current buffer, that is: open the memfile and read the file into
+ * memory.
+ * Return FAIL for failure, OK otherwise.
+ */
+int
+open_buffer (
+ int read_stdin, /* read file from stdin */
+ exarg_T *eap, /* for forced 'ff' and 'fenc' or NULL */
+ int flags /* extra flags for readfile() */
+)
+{
+ int retval = OK;
+ buf_T *old_curbuf;
+ long old_tw = curbuf->b_p_tw;
+
+ /*
+ * The 'readonly' flag is only set when BF_NEVERLOADED is being reset.
+ * When re-entering the same buffer, it should not change, because the
+ * user may have reset the flag by hand.
+ */
+ if (readonlymode && curbuf->b_ffname != NULL
+ && (curbuf->b_flags & BF_NEVERLOADED))
+ curbuf->b_p_ro = TRUE;
+
+ if (ml_open(curbuf) == FAIL) {
+ /*
+ * 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, FALSE);
+ for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next)
+ if (curbuf->b_ml.ml_mfp != NULL)
+ break;
+ /*
+ * if there is no memfile at all, exit
+ * This is OK, since there are no changes to lose.
+ */
+ if (curbuf == NULL) {
+ EMSG(_("E82: Cannot allocate any buffer, exiting..."));
+ getout(2);
+ }
+ EMSG(_("E83: Cannot allocate buffer, using other one..."));
+ enter_buffer(curbuf);
+ if (old_tw != curbuf->b_p_tw)
+ check_colorcolumn(curwin);
+ return FAIL;
+ }
+
+ /* The autocommands in readfile() may change the buffer, but only AFTER
+ * reading the file. */
+ old_curbuf = curbuf;
+ modified_was_set = FALSE;
+
+ /* mark cursor position as being invalid */
+ curwin->w_valid = 0;
+
+ if (curbuf->b_ffname != NULL
+ ) {
+ retval = readfile(curbuf->b_ffname, curbuf->b_fname,
+ (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ flags | READ_NEW);
+ /* Help buffer is filtered. */
+ if (curbuf->b_help)
+ fix_help_buffer();
+ } else if (read_stdin) {
+ int save_bin = curbuf->b_p_bin;
+ linenr_T line_count;
+
+ /*
+ * First read the text in binary mode into the buffer.
+ * Then read from that same buffer and append at the end. This makes
+ * it possible to retry when 'fileformat' or 'fileencoding' was
+ * guessed wrong.
+ */
+ curbuf->b_p_bin = TRUE;
+ retval = readfile(NULL, NULL, (linenr_T)0,
+ (linenr_T)0, (linenr_T)MAXLNUM, NULL,
+ flags | (READ_NEW + READ_STDIN));
+ curbuf->b_p_bin = save_bin;
+ if (retval == OK) {
+ line_count = curbuf->b_ml.ml_line_count;
+ retval = readfile(NULL, NULL, (linenr_T)line_count,
+ (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ flags | READ_BUFFER);
+ if (retval == OK) {
+ /* Delete the binary lines. */
+ while (--line_count >= 0)
+ ml_delete((linenr_T)1, FALSE);
+ } else {
+ /* Delete the converted lines. */
+ while (curbuf->b_ml.ml_line_count > line_count)
+ ml_delete(line_count, FALSE);
+ }
+ /* Put the cursor on the first line. */
+ curwin->w_cursor.lnum = 1;
+ curwin->w_cursor.col = 0;
+
+ /* Set or reset 'modified' before executing autocommands, so that
+ * it can be changed there. */
+ if (!readonlymode && !bufempty())
+ changed();
+ else if (retval != FAIL)
+ unchanged(curbuf, FALSE);
+ apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE,
+ curbuf, &retval);
+ }
+ }
+
+ /* if first time loading this buffer, init b_chartab[] */
+ if (curbuf->b_flags & BF_NEVERLOADED) {
+ (void)buf_init_chartab(curbuf, FALSE);
+ parse_cino(curbuf);
+ }
+
+ /*
+ * Set/reset the Changed flag first, autocmds may change the buffer.
+ * Apply the automatic commands, before processing the modelines.
+ * So the modelines have priority over auto commands.
+ */
+ /* When reading stdin, the buffer contents always needs writing, so set
+ * the changed flag. Unless in readonly mode: "ls | gview -".
+ * When interrupted and 'cpoptions' contains 'i' set changed flag. */
+ if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
+ || modified_was_set /* ":set modified" used in autocmd */
+ || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
+ )
+ changed();
+ else if (retval != FAIL && !read_stdin)
+ unchanged(curbuf, FALSE);
+ save_file_ff(curbuf); /* keep this fileformat */
+
+ /* require "!" to overwrite the file, because it wasn't read completely */
+ if (aborting())
+ curbuf->b_flags |= BF_READERR;
+
+ /* Need to update automatic folding. Do this before the autocommands,
+ * they may use the fold info. */
+ foldUpdateAll(curwin);
+
+ /* need to set w_topline, unless some autocommand already did that. */
+ if (!(curwin->w_valid & VALID_TOPLINE)) {
+ curwin->w_topline = 1;
+ curwin->w_topfill = 0;
+ }
+ apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval);
+
+ if (retval != FAIL) {
+ /*
+ * The autocommands may have changed the current buffer. Apply the
+ * modelines to the correct buffer, if it still exists and is loaded.
+ */
+ if (buf_valid(old_curbuf) && old_curbuf->b_ml.ml_mfp != NULL) {
+ aco_save_T aco;
+
+ /* Go to the buffer that was opened. */
+ aucmd_prepbuf(&aco, old_curbuf);
+ do_modelines(0);
+ curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED);
+
+ apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf,
+ &retval);
+
+ /* restore curwin/curbuf and a few other things */
+ aucmd_restbuf(&aco);
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * Return TRUE if "buf" points to a valid buffer (in the buffer list).
+ */
+int buf_valid(buf_T *buf)
+{
+ buf_T *bp;
+
+ for (bp = firstbuf; bp != NULL; bp = bp->b_next)
+ if (bp == buf)
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * Close the link to a buffer.
+ * "action" is used when there is no longer a window for the buffer.
+ * It can be:
+ * 0 buffer becomes hidden
+ * DOBUF_UNLOAD buffer is unloaded
+ * DOBUF_DELETE buffer is unloaded and removed from buffer list
+ * DOBUF_WIPE buffer is unloaded and really deleted
+ * When doing all but the first one on the current buffer, the caller should
+ * 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_T *win, /* if not NULL, set b_last_cursor */
+ buf_T *buf,
+ int action,
+ int abort_if_last
+)
+{
+ int is_curbuf;
+ int nwindows;
+ int unload_buf = (action != 0);
+ int del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
+ int wipe_buf = (action == DOBUF_WIPE);
+
+ /*
+ * Force unloading or deleting when 'bufhidden' says so.
+ * The caller must take care of NOT deleting/freeing when 'bufhidden' is
+ * "hide" (otherwise we could never free or delete a buffer).
+ */
+ if (buf->b_p_bh[0] == 'd') { /* 'bufhidden' == "delete" */
+ del_buf = TRUE;
+ unload_buf = TRUE;
+ } else if (buf->b_p_bh[0] == 'w') { /* 'bufhidden' == "wipe" */
+ del_buf = TRUE;
+ unload_buf = TRUE;
+ wipe_buf = TRUE;
+ } else if (buf->b_p_bh[0] == 'u') /* 'bufhidden' == "unload" */
+ unload_buf = TRUE;
+
+ if (win != NULL) {
+ /* Set b_last_cursor when closing the last window for the buffer.
+ * Remember the last cursor position and window options of the buffer.
+ * This used to be only for the current window, but then options like
+ * 'foldmethod' may be lost with a ":only" command. */
+ if (buf->b_nwindows == 1)
+ set_last_cursor(win);
+ buflist_setfpos(buf, win,
+ win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum,
+ win->w_cursor.col, TRUE);
+ }
+
+ /* When the buffer is no longer in a window, trigger BufWinLeave */
+ if (buf->b_nwindows == 1) {
+ buf->b_closing = TRUE;
+ apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf)) {
+ /* Autocommands deleted the buffer. */
+aucmd_abort:
+ EMSG(_(e_auabort));
+ return;
+ }
+ buf->b_closing = FALSE;
+ if (abort_if_last && one_window())
+ /* Autocommands made this the only window. */
+ goto aucmd_abort;
+
+ /* When the buffer becomes hidden, but is not unloaded, trigger
+ * BufHidden */
+ if (!unload_buf) {
+ buf->b_closing = TRUE;
+ apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf))
+ /* Autocommands deleted the buffer. */
+ goto aucmd_abort;
+ buf->b_closing = FALSE;
+ if (abort_if_last && one_window())
+ /* Autocommands made this the only window. */
+ goto aucmd_abort;
+ }
+ if (aborting()) /* autocmds may abort script processing */
+ return;
+ }
+ nwindows = buf->b_nwindows;
+
+ /* decrease the link count from windows (unless not in any window) */
+ if (buf->b_nwindows > 0)
+ --buf->b_nwindows;
+
+ /* Return when a window is displaying the buffer or when it's not
+ * unloaded. */
+ if (buf->b_nwindows > 0 || !unload_buf)
+ return;
+
+ /* Always remove the buffer when there is no file name. */
+ if (buf->b_ffname == NULL)
+ del_buf = TRUE;
+
+ /*
+ * Free all things allocated for this buffer.
+ * Also calls the "BufDelete" autocommands when del_buf is TRUE.
+ */
+ /* Remember if we are closing the current buffer. Restore the number of
+ * windows, so that autocommands in buf_freeall() don't get confused. */
+ is_curbuf = (buf == curbuf);
+ buf->b_nwindows = nwindows;
+
+ buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0));
+ if (
+ win_valid(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))
+ return;
+ 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
+ * in some cases it's OK to delete the curbuf, because a new one is
+ * obtained anyway. Therefore only return if curbuf changed to the
+ * deleted buffer.
+ */
+ if (buf == curbuf && !is_curbuf)
+ return;
+
+ /* Change directories when the 'acd' option is set. */
+ do_autochdir();
+
+ /*
+ * Remove the buffer from the list.
+ */
+ if (wipe_buf) {
+ free(buf->b_ffname);
+ free(buf->b_sfname);
+ if (buf->b_prev == NULL)
+ firstbuf = buf->b_next;
+ else
+ buf->b_prev->b_next = buf->b_next;
+ if (buf->b_next == NULL)
+ lastbuf = buf->b_prev;
+ else
+ buf->b_next->b_prev = buf->b_prev;
+ free_buffer(buf);
+ } else {
+ if (del_buf) {
+ /* Free all internal variables and reset option values, to make
+ * ":bdel" compatible with Vim 5.7. */
+ free_buffer_stuff(buf, TRUE);
+
+ /* Make it look like a new buffer. */
+ buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED;
+
+ /* Init the options when loaded again. */
+ buf->b_p_initialized = FALSE;
+ }
+ buf_clear_file(buf);
+ if (del_buf)
+ buf->b_p_bl = FALSE;
+ }
+}
+
+/*
+ * Make buffer not contain a file.
+ */
+void buf_clear_file(buf_T *buf)
+{
+ buf->b_ml.ml_line_count = 1;
+ unchanged(buf, TRUE);
+ buf->b_p_eol = TRUE;
+ buf->b_start_eol = TRUE;
+ buf->b_p_bomb = FALSE;
+ buf->b_start_bomb = FALSE;
+ buf->b_ml.ml_mfp = NULL;
+ buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */
+}
+
+/*
+ * buf_freeall() - free all things allocated for a buffer that are related to
+ * the file. flags:
+ * BFA_DEL buffer is going to be deleted
+ * BFA_WIPE buffer is going to be wiped out
+ * BFA_KEEP_UNDO do not free undo information
+ */
+void buf_freeall(buf_T *buf, int flags)
+{
+ int is_curbuf = (buf == curbuf);
+
+ buf->b_closing = TRUE;
+ apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+ if ((flags & BFA_DEL) && buf->b_p_bl) {
+ apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+ }
+ if (flags & BFA_WIPE) {
+ apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+ }
+ buf->b_closing = FALSE;
+ if (aborting()) /* autocmds may abort script processing */
+ return;
+
+ /*
+ * It's possible that autocommands change curbuf to the one being deleted.
+ * This might cause curbuf to be deleted unexpectedly. But in some cases
+ * it's OK to delete the curbuf, because a new one is obtained anyway.
+ * Therefore only return if curbuf changed to the deleted buffer.
+ */
+ if (buf == curbuf && !is_curbuf)
+ return;
+ diff_buf_delete(buf); /* Can't use 'diff' for unloaded buffer. */
+ /* Remove any ownsyntax, unless exiting. */
+ if (firstwin != NULL && curwin->w_buffer == buf)
+ reset_synblock(curwin);
+
+ /* No folds in an empty buffer. */
+ {
+ win_T *win;
+ tabpage_T *tp;
+
+ FOR_ALL_TAB_WINDOWS(tp, win)
+ if (win->w_buffer == buf)
+ clearFolding(win);
+ }
+
+ ml_close(buf, TRUE); /* close and delete the memline/memfile */
+ buf->b_ml.ml_line_count = 0; /* no lines in buffer */
+ if ((flags & BFA_KEEP_UNDO) == 0) {
+ u_blockfree(buf); /* free the memory allocated for undo */
+ u_clearall(buf); /* reset all undo information */
+ }
+ syntax_clear(&buf->b_s); /* reset syntax info */
+ buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */
+}
+
+/*
+ * Free a buffer structure and the things it contains related to the buffer
+ * itself (not the file, that must have been done already).
+ */
+static void free_buffer(buf_T *buf)
+{
+ free_buffer_stuff(buf, TRUE);
+ unref_var_dict(buf->b_vars);
+ aubuflocal_remove(buf);
+ if (autocmd_busy) {
+ // Do not free the buffer structure while autocommands are executing,
+ // it's still needed. Free it when autocmd_busy is reset.
+ buf->b_next = au_pending_free_buf;
+ au_pending_free_buf = buf;
+ } else {
+ free(buf);
+ }
+}
+
+/*
+ * Free stuff in the buffer for ":bdel" and when wiping out the buffer.
+ */
+static void
+free_buffer_stuff (
+ buf_T *buf,
+ int free_options /* free options as well */
+)
+{
+ if (free_options) {
+ clear_wininfo(buf); /* including window-local options */
+ free_buf_options(buf, TRUE);
+ ga_clear(&buf->b_s.b_langp);
+ }
+ vars_clear(&buf->b_vars->dv_hashtab); /* free all internal variables */
+ hash_init(&buf->b_vars->dv_hashtab);
+ uc_clear(&buf->b_ucmds); /* clear local user commands */
+ buf_delete_signs(buf); /* delete any signs */
+ map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */
+ map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */
+ free(buf->b_start_fenc);
+ buf->b_start_fenc = NULL;
+}
+
+/*
+ * Free the b_wininfo list for buffer "buf".
+ */
+static void clear_wininfo(buf_T *buf)
+{
+ wininfo_T *wip;
+
+ while (buf->b_wininfo != NULL) {
+ wip = buf->b_wininfo;
+ buf->b_wininfo = wip->wi_next;
+ if (wip->wi_optset) {
+ clear_winopt(&wip->wi_opt);
+ deleteFoldRecurse(&wip->wi_folds);
+ }
+ free(wip);
+ }
+}
+
+/*
+ * Go to another buffer. Handles the result of the ATTENTION dialog.
+ */
+void goto_buffer(exarg_T *eap, int start, int dir, int count)
+{
+# if defined(FEAT_WINDOWS) && defined(HAS_SWAP_EXISTS_ACTION)
+ buf_T *old_curbuf = curbuf;
+
+ swap_exists_action = SEA_DIALOG;
+# endif
+ (void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
+ start, dir, count, eap->forceit);
+# if defined(FEAT_WINDOWS) && defined(HAS_SWAP_EXISTS_ACTION)
+ if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') {
+ cleanup_T cs;
+
+ /* Reset the error/interrupt/exception state here so that
+ * aborting() returns FALSE when closing a window. */
+ enter_cleanup(&cs);
+
+ /* Quitting means closing the split window, nothing else. */
+ win_close(curwin, TRUE);
+ swap_exists_action = SEA_NONE;
+ swap_exists_did_quit = TRUE;
+
+ /* Restore the error/interrupt/exception state if not discarded by a
+ * new aborting error, interrupt, or uncaught exception. */
+ leave_cleanup(&cs);
+ } else
+ handle_swap_exists(old_curbuf);
+# endif
+}
+
+#if defined(HAS_SWAP_EXISTS_ACTION) || defined(PROTO)
+/*
+ * Handle the situation of swap_exists_action being set.
+ * It is allowed for "old_curbuf" to be NULL or invalid.
+ */
+void handle_swap_exists(buf_T *old_curbuf)
+{
+ cleanup_T cs;
+ long old_tw = curbuf->b_p_tw;
+
+ if (swap_exists_action == SEA_QUIT) {
+ /* Reset the error/interrupt/exception state here so that
+ * aborting() returns FALSE when closing a buffer. */
+ enter_cleanup(&cs);
+
+ /* User selected Quit at ATTENTION prompt. Go back to previous
+ * buffer. If that buffer is gone or the same as the current one,
+ * 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, FALSE);
+ if (!buf_valid(old_curbuf) || old_curbuf == curbuf)
+ old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
+ if (old_curbuf != NULL) {
+ enter_buffer(old_curbuf);
+ if (old_tw != curbuf->b_p_tw)
+ check_colorcolumn(curwin);
+ }
+ /* If "old_curbuf" is NULL we are in big trouble here... */
+
+ /* Restore the error/interrupt/exception state if not discarded by a
+ * new aborting error, interrupt, or uncaught exception. */
+ leave_cleanup(&cs);
+ } else if (swap_exists_action == SEA_RECOVER) {
+ /* Reset the error/interrupt/exception state here so that
+ * aborting() returns FALSE when closing a buffer. */
+ enter_cleanup(&cs);
+
+ /* User selected Recover at ATTENTION prompt. */
+ msg_scroll = TRUE;
+ ml_recover();
+ MSG_PUTS("\n"); /* don't overwrite the last message */
+ cmdline_row = msg_row;
+ do_modelines(0);
+
+ /* Restore the error/interrupt/exception state if not discarded by a
+ * new aborting error, interrupt, or uncaught exception. */
+ leave_cleanup(&cs);
+ }
+ swap_exists_action = SEA_NONE;
+}
+#endif
+
+/*
+ * do_bufdel() - delete or unload buffer(s)
+ *
+ * addr_count == 0: ":bdel" - delete current buffer
+ * addr_count == 1: ":N bdel" or ":bdel N [N ..]" - first delete
+ * buffer "end_bnr", then any other arguments.
+ * addr_count == 2: ":N,N bdel" - delete buffers in range
+ *
+ * command can be DOBUF_UNLOAD (":bunload"), DOBUF_WIPE (":bwipeout") or
+ * DOBUF_DEL (":bdel")
+ *
+ * Returns error message or NULL
+ */
+char_u *
+do_bufdel (
+ int command,
+ char_u *arg, /* pointer to extra arguments */
+ int addr_count,
+ int start_bnr, /* first buffer number in a range */
+ int end_bnr, /* buffer nr or last buffer nr in a range */
+ int forceit
+)
+{
+ int do_current = 0; /* delete current buffer? */
+ int deleted = 0; /* number of buffers deleted */
+ char_u *errormsg = NULL; /* return value */
+ int bnr; /* buffer number */
+ char_u *p;
+
+ if (addr_count == 0) {
+ (void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit);
+ } else {
+ if (addr_count == 2) {
+ if (*arg) /* both range and argument is not allowed */
+ return (char_u *)_(e_trailing);
+ bnr = start_bnr;
+ } else /* addr_count == 1 */
+ bnr = end_bnr;
+
+ for (; !got_int; ui_breakcheck()) {
+ /*
+ * delete the current buffer last, otherwise when the
+ * current buffer is deleted, the next buffer becomes
+ * the current one and will be loaded, which may then
+ * also be deleted, etc.
+ */
+ if (bnr == curbuf->b_fnum)
+ do_current = bnr;
+ else if (do_buffer(command, DOBUF_FIRST, FORWARD, (int)bnr,
+ forceit) == OK)
+ ++deleted;
+
+ /*
+ * find next buffer number to delete/unload
+ */
+ if (addr_count == 2) {
+ if (++bnr > end_bnr)
+ break;
+ } else { /* addr_count == 1 */
+ arg = skipwhite(arg);
+ if (*arg == NUL)
+ break;
+ if (!VIM_ISDIGIT(*arg)) {
+ p = skiptowhite_esc(arg);
+ bnr = buflist_findpat(arg, p, command == DOBUF_WIPE,
+ FALSE, FALSE);
+ if (bnr < 0) /* failed */
+ break;
+ arg = p;
+ } else
+ bnr = getdigits(&arg);
+ }
+ }
+ if (!got_int && do_current && do_buffer(command, DOBUF_FIRST,
+ FORWARD, do_current, forceit) == OK)
+ ++deleted;
+
+ if (deleted == 0) {
+ if (command == DOBUF_UNLOAD)
+ STRCPY(IObuff, _("E515: No buffers were unloaded"));
+ else if (command == DOBUF_DEL)
+ STRCPY(IObuff, _("E516: No buffers were deleted"));
+ else
+ STRCPY(IObuff, _("E517: No buffers were wiped out"));
+ errormsg = IObuff;
+ } else if (deleted >= p_report) {
+ if (command == DOBUF_UNLOAD) {
+ if (deleted == 1)
+ MSG(_("1 buffer unloaded"));
+ else
+ smsg((char_u *)_("%d buffers unloaded"), deleted);
+ } else if (command == DOBUF_DEL) {
+ if (deleted == 1)
+ MSG(_("1 buffer deleted"));
+ else
+ smsg((char_u *)_("%d buffers deleted"), deleted);
+ } else {
+ if (deleted == 1)
+ MSG(_("1 buffer wiped out"));
+ else
+ smsg((char_u *)_("%d buffers wiped out"), deleted);
+ }
+ }
+ }
+
+
+ return errormsg;
+}
+
+
+static int empty_curbuf(int close_others, int forceit, int action);
+
+/*
+ * Make the current buffer empty.
+ * Used when it is wiped out and it's the last buffer.
+ */
+static int empty_curbuf(int close_others, int forceit, int action)
+{
+ int retval;
+ buf_T *buf = curbuf;
+
+ if (action == DOBUF_UNLOAD) {
+ EMSG(_("E90: Cannot unload last buffer"));
+ return FAIL;
+ }
+
+ if (close_others) {
+ /* Close any other windows on this buffer, then make it empty. */
+ close_windows(buf, TRUE);
+ }
+
+ setpcmark();
+ retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE,
+ forceit ? ECMD_FORCEIT : 0, curwin);
+
+ /*
+ * do_ecmd() may create a new buffer, then we have to delete
+ * the old one. But do_ecmd() may have done that already, check
+ * if the buffer still exists.
+ */
+ if (buf != curbuf && buf_valid(buf) && buf->b_nwindows == 0)
+ close_buffer(NULL, buf, action, FALSE);
+ if (!close_others)
+ need_fileinfo = FALSE;
+ return retval;
+}
+/*
+ * Implementation of the commands for the buffer list.
+ *
+ * action == DOBUF_GOTO go to specified buffer
+ * action == DOBUF_SPLIT split window and go to specified buffer
+ * action == DOBUF_UNLOAD unload specified buffer(s)
+ * action == DOBUF_DEL delete specified buffer(s) from buffer list
+ * action == DOBUF_WIPE delete specified buffer(s) really
+ *
+ * start == DOBUF_CURRENT go to "count" buffer from current buffer
+ * start == DOBUF_FIRST go to "count" buffer from first buffer
+ * start == DOBUF_LAST go to "count" buffer from last buffer
+ * start == DOBUF_MOD go to "count" modified buffer from current buffer
+ *
+ * Return FAIL or OK.
+ */
+int
+do_buffer (
+ int action,
+ int start,
+ int dir, /* FORWARD or BACKWARD */
+ int count, /* buffer number or number of buffers */
+ int forceit /* TRUE for :...! */
+)
+{
+ buf_T *buf;
+ buf_T *bp;
+ int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
+ || action == DOBUF_WIPE);
+
+ switch (start) {
+ case DOBUF_FIRST: buf = firstbuf; break;
+ case DOBUF_LAST: buf = lastbuf; break;
+ default: buf = curbuf; break;
+ }
+ if (start == DOBUF_MOD) { /* find next modified buffer */
+ while (count-- > 0) {
+ do {
+ buf = buf->b_next;
+ if (buf == NULL)
+ buf = firstbuf;
+ } while (buf != curbuf && !bufIsChanged(buf));
+ }
+ if (!bufIsChanged(buf)) {
+ EMSG(_("E84: No modified buffer found"));
+ return FAIL;
+ }
+ } else if (start == DOBUF_FIRST && count) { /* find specified buffer number */
+ while (buf != NULL && buf->b_fnum != count)
+ buf = buf->b_next;
+ } else {
+ bp = NULL;
+ while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) {
+ /* remember the buffer where we start, we come back there when all
+ * buffers are unlisted. */
+ if (bp == NULL)
+ bp = buf;
+ if (dir == FORWARD) {
+ buf = buf->b_next;
+ if (buf == NULL)
+ buf = firstbuf;
+ } else {
+ buf = buf->b_prev;
+ if (buf == NULL)
+ buf = lastbuf;
+ }
+ /* don't count unlisted buffers */
+ if (unload || buf->b_p_bl) {
+ --count;
+ bp = NULL; /* use this buffer as new starting point */
+ }
+ if (bp == buf) {
+ /* back where we started, didn't find anything. */
+ EMSG(_("E85: There is no listed buffer"));
+ return FAIL;
+ }
+ }
+ }
+
+ if (buf == NULL) { /* could not find it */
+ if (start == DOBUF_FIRST) {
+ /* don't warn when deleting */
+ if (!unload)
+ EMSGN(_("E86: Buffer %" PRId64 " does not exist"), count);
+ } else if (dir == FORWARD)
+ EMSG(_("E87: Cannot go beyond last buffer"));
+ else
+ EMSG(_("E88: Cannot go before first buffer"));
+ return FAIL;
+ }
+
+
+ /*
+ * delete buffer buf from memory and/or the list
+ */
+ if (unload) {
+ int forward;
+
+ /* When unloading or deleting a buffer that's already unloaded and
+ * unlisted: fail silently. */
+ if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
+ return FAIL;
+
+ if (!forceit && bufIsChanged(buf)) {
+ if ((p_confirm || cmdmod.confirm) && p_write) {
+ dialog_changed(buf, FALSE);
+ if (!buf_valid(buf))
+ /* Autocommand deleted buffer, oops! It's not changed
+ * now. */
+ return FAIL;
+ /* If it's still changed fail silently, the dialog already
+ * mentioned why it fails. */
+ if (bufIsChanged(buf))
+ return FAIL;
+ } else {
+ EMSGN(_("E89: No write since last change for buffer %" PRId64
+ " (add ! to override)"),
+ buf->b_fnum);
+ return FAIL;
+ }
+ }
+
+ /*
+ * If deleting the last (listed) buffer, make it empty.
+ * The last (listed) buffer cannot be unloaded.
+ */
+ for (bp = firstbuf; bp != NULL; bp = bp->b_next)
+ if (bp->b_p_bl && bp != buf)
+ break;
+ if (bp == NULL && buf == curbuf)
+ return empty_curbuf(TRUE, forceit, action);
+
+ /*
+ * If the deleted buffer is the current one, close the current window
+ * (unless it's the only window). Repeat this so long as we end up in
+ * a window with this buffer.
+ */
+ while (buf == curbuf
+ && !(curwin->w_closing || curwin->w_buffer->b_closing)
+ && (firstwin != lastwin || first_tabpage->tp_next != NULL)) {
+ if (win_close(curwin, FALSE) == FAIL)
+ break;
+ }
+
+ /*
+ * If the buffer to be deleted is not the current one, delete it here.
+ */
+ if (buf != curbuf) {
+ close_windows(buf, FALSE);
+ if (buf != curbuf && buf_valid(buf) && buf->b_nwindows <= 0)
+ close_buffer(NULL, buf, action, FALSE);
+ return OK;
+ }
+
+ /*
+ * Deleting the current buffer: Need to find another buffer to go to.
+ * There should be another, otherwise it would have been handled
+ * above. However, autocommands may have deleted all buffers.
+ * First use au_new_curbuf, if it is valid.
+ * Then prefer the buffer we most recently visited.
+ * Else try to find one that is loaded, after the current buffer,
+ * then before the current buffer.
+ * Finally use any buffer.
+ */
+ buf = NULL; /* selected buffer */
+ bp = NULL; /* used when no loaded buffer found */
+ if (au_new_curbuf != NULL && buf_valid(au_new_curbuf))
+ buf = au_new_curbuf;
+ else if (curwin->w_jumplistlen > 0) {
+ int jumpidx;
+
+ jumpidx = curwin->w_jumplistidx - 1;
+ if (jumpidx < 0)
+ jumpidx = curwin->w_jumplistlen - 1;
+
+ forward = jumpidx;
+ while (jumpidx != curwin->w_jumplistidx) {
+ buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum);
+ if (buf != NULL) {
+ if (buf == curbuf || !buf->b_p_bl)
+ buf = NULL; /* skip current and unlisted bufs */
+ else if (buf->b_ml.ml_mfp == NULL) {
+ /* skip unloaded buf, but may keep it for later */
+ if (bp == NULL)
+ bp = buf;
+ buf = NULL;
+ }
+ }
+ if (buf != NULL) /* found a valid buffer: stop searching */
+ break;
+ /* advance to older entry in jump list */
+ if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen)
+ break;
+ if (--jumpidx < 0)
+ jumpidx = curwin->w_jumplistlen - 1;
+ if (jumpidx == forward) /* List exhausted for sure */
+ break;
+ }
+ }
+
+ if (buf == NULL) { /* No previous buffer, Try 2'nd approach */
+ forward = TRUE;
+ buf = curbuf->b_next;
+ for (;; ) {
+ if (buf == NULL) {
+ if (!forward) /* tried both directions */
+ break;
+ buf = curbuf->b_prev;
+ forward = FALSE;
+ continue;
+ }
+ /* in non-help buffer, try to skip help buffers, and vv */
+ if (buf->b_help == curbuf->b_help && buf->b_p_bl) {
+ if (buf->b_ml.ml_mfp != NULL) /* found loaded buffer */
+ break;
+ if (bp == NULL) /* remember unloaded buf for later */
+ bp = buf;
+ }
+ if (forward)
+ buf = buf->b_next;
+ else
+ buf = buf->b_prev;
+ }
+ }
+ if (buf == NULL) /* No loaded buffer, use unloaded one */
+ buf = bp;
+ if (buf == NULL) { /* No loaded buffer, find listed one */
+ for (buf = firstbuf; buf != NULL; buf = buf->b_next)
+ if (buf->b_p_bl && buf != curbuf)
+ break;
+ }
+ if (buf == NULL) { /* Still no buffer, just take one */
+ if (curbuf->b_next != NULL)
+ buf = curbuf->b_nex