summaryrefslogtreecommitdiffstats
path: root/src/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer.c')
-rw-r--r--src/buffer.c5071
1 files changed, 5071 insertions, 0 deletions
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 0000000000..83b570f612
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,5071 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * 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 "vim.h"
+
+#if defined(FEAT_CMDL_COMPL) || defined(FEAT_LISTCMDS) || defined(FEAT_EVAL) || defined(FEAT_PERL)
+static char_u *buflist_match __ARGS((regprog_T *prog, buf_T *buf));
+# define HAVE_BUFLIST_MATCH
+static char_u *fname_match __ARGS((regprog_T *prog, char_u *name));
+#endif
+static void buflist_setfpos __ARGS((buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, int copy_options));
+static wininfo_T *find_wininfo __ARGS((buf_T *buf));
+#ifdef UNIX
+static buf_T *buflist_findname_stat __ARGS((char_u *ffname, struct stat *st));
+static int otherfile_buf __ARGS((buf_T *buf, char_u *ffname, struct stat *stp));
+static int buf_same_ino __ARGS((buf_T *buf, struct stat *stp));
+#else
+static int otherfile_buf __ARGS((buf_T *buf, char_u *ffname));
+#endif
+#ifdef FEAT_TITLE
+static int ti_change __ARGS((char_u *str, char_u **last));
+#endif
+static void free_buffer __ARGS((buf_T *));
+static void free_buffer_stuff __ARGS((buf_T *buf, int free_options));
+static void clear_wininfo __ARGS((buf_T *buf));
+
+#ifdef UNIX
+# define dev_T dev_t
+#else
+# define dev_T unsigned
+#endif
+
+#if defined(FEAT_SIGNS)
+static void insert_sign __ARGS((buf_T *buf, signlist_T *prev, signlist_T *next, int id, linenr_T lnum, int typenr));
+static void buf_delete_signs __ARGS((buf_T *buf));
+#endif
+
+/*
+ * Open current buffer, that is: open the memfile and read the file into memory
+ * return FAIL for failure, OK otherwise
+ */
+ int
+open_buffer(read_stdin, eap)
+ int read_stdin; /* read file from stdin */
+ exarg_T *eap; /* for forced 'ff' and 'fenc' or NULL */
+{
+ int retval = OK;
+#ifdef FEAT_AUTOCMD
+ buf_T *old_curbuf;
+#endif
+
+ /*
+ * 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() == 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);
+ 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 loose.
+ */
+ if (curbuf == NULL)
+ {
+ EMSG(_("E82: Cannot allocate any buffer, exiting..."));
+ getout(2);
+ }
+ EMSG(_("E83: Cannot allocate buffer, using other one..."));
+ enter_buffer(curbuf);
+ return FAIL;
+ }
+
+#ifdef FEAT_AUTOCMD
+ /* The autocommands in readfile() may change the buffer, but only AFTER
+ * reading the file. */
+ old_curbuf = curbuf;
+ modified_was_set = FALSE;
+#endif
+
+ /* mark cursor position as being invalid */
+ changed_line_abv_curs();
+
+ if (curbuf->b_ffname != NULL
+#ifdef FEAT_NETBEANS_INTG
+ && netbeansReadFile
+#endif
+ )
+ {
+#ifdef FEAT_NETBEANS_INTG
+ int oldFire = netbeansFireChanges;
+
+ netbeansFireChanges = 0;
+#endif
+ retval = readfile(curbuf->b_ffname, curbuf->b_fname,
+ (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap, READ_NEW);
+#ifdef FEAT_NETBEANS_INTG
+ netbeansFireChanges = oldFire;
+#endif
+ /* 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, 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, 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;
+#ifdef FEAT_AUTOCMD
+# ifdef FEAT_EVAL
+ apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE,
+ curbuf, &retval);
+# else
+ apply_autocmds(EVENT_STDINREADPOST, NULL, NULL, FALSE, curbuf);
+# endif
+#endif
+ }
+ }
+
+ /* if first time loading this buffer, init b_chartab[] */
+ if (curbuf->b_flags & BF_NEVERLOADED)
+ (void)buf_init_chartab(curbuf, FALSE);
+
+ /*
+ * 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 ((read_stdin && !readonlymode && !bufempty())
+#ifdef FEAT_AUTOCMD
+ || modified_was_set /* ":set modified" used in autocmd */
+# ifdef FEAT_EVAL
+ || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
+# endif
+#endif
+ || (got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL))
+ changed();
+ else if (retval != FAIL)
+ unchanged(curbuf, FALSE);
+ save_file_ff(curbuf); /* keep this fileformat */
+
+ /* require "!" to overwrite the file, because it wasn't read completely */
+#ifdef FEAT_EVAL
+ if (aborting())
+#else
+ if (got_int)
+#endif
+ curbuf->b_flags |= BF_READERR;
+
+#ifdef FEAT_AUTOCMD
+ /* need to set w_topline, unless some autocommand already did that. */
+ if (!(curwin->w_valid & VALID_TOPLINE))
+ {
+ curwin->w_topline = 1;
+# ifdef FEAT_DIFF
+ curwin->w_topfill = 0;
+# endif
+ }
+# ifdef FEAT_EVAL
+ apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval);
+# else
+ apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
+# endif
+#endif
+
+ if (retval != FAIL)
+ {
+#ifdef FEAT_AUTOCMD
+ /*
+ * 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);
+#endif
+ do_modelines();
+ curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED);
+
+#ifdef FEAT_AUTOCMD
+# ifdef FEAT_EVAL
+ apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf,
+ &retval);
+# else
+ apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
+# endif
+
+ /* restore curwin/curbuf and a few other things */
+ aucmd_restbuf(&aco);
+ }
+#endif
+ }
+
+#ifdef FEAT_FOLDING
+ /* Need to update automatic folding. */
+ foldUpdateAll(curwin);
+#endif
+
+ return retval;
+}
+
+/*
+ * Return TRUE if "buf" points to a valid buffer (in the buffer list).
+ */
+ int
+buf_valid(buf)
+ 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.
+ */
+ void
+close_buffer(win, buf, action)
+ win_T *win; /* if not NULL, set b_last_cursor */
+ buf_T *buf;
+ int action;
+{
+#ifdef FEAT_AUTOCMD
+ int is_curbuf;
+ int nwindows = buf->b_nwindows;
+#endif
+ int unload_buf = (action != 0);
+ int del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
+ int wipe_buf = (action == DOBUF_WIPE);
+
+#ifdef FEAT_QUICKFIX
+ /*
+ * 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;
+#endif
+
+ 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);
+ }
+
+#ifdef FEAT_AUTOCMD
+ /* When the buffer is no longer in a window, trigger BufWinLeave */
+ if (buf->b_nwindows == 1)
+ {
+ apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+
+ /* When the buffer becomes hidden, but is not unloaded, trigger
+ * BufHidden */
+ if (!unload_buf)
+ {
+ apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf)) /* autocmds may delete the buffer */
+ return;
+ }
+# ifdef FEAT_EVAL
+ if (aborting()) /* autocmds may abort script processing */
+ return;
+# endif
+ }
+ nwindows = buf->b_nwindows;
+#endif
+
+ /* 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)
+ {
+ if (buf == curbuf)
+ u_sync(); /* sync undo before going to another buffer */
+ 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.
+ */
+#ifdef FEAT_AUTOCMD
+ /* 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;
+#endif
+
+ buf_freeall(buf, del_buf, wipe_buf);
+
+#ifdef FEAT_AUTOCMD
+ /* Autocommands may have deleted the buffer. */
+ if (!buf_valid(buf))
+ return;
+# ifdef FEAT_EVAL
+ /* Autocommands may abort script processing. */
+ if (aborting())
+ return;
+# endif
+
+ /* 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;
+#endif
+
+#ifdef FEAT_NETBEANS_INTG
+ if (usingNetbeans)
+ netbeans_file_closed(buf);
+#endif
+#if defined(FEAT_NETBEANS_INTG) || defined(FEAT_SUN_WORKSHOP)
+ /* Change directories when the acd option is set on. */
+ if (p_acd && curbuf->b_ffname != NULL
+ && vim_chdirfile(curbuf->b_ffname) == OK)
+ shorten_fnames(TRUE);
+#endif
+
+ /*
+ * Remove the buffer from the list.
+ */
+ if (wipe_buf)
+ {
+#ifdef FEAT_SUN_WORKSHOP
+ if (usingSunWorkShop)
+ workshop_file_closed_lineno((char *)buf->b_ffname,
+ (int)buf->b_last_cursor.lnum);
+#endif
+ vim_free(buf->b_ffname);
+ vim_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)
+ buf_T *buf;
+{
+ buf->b_ml.ml_line_count = 1;
+ unchanged(buf, TRUE);
+#ifndef SHORT_FNAME
+ buf->b_shortname = FALSE;
+#endif
+ buf->b_p_eol = TRUE;
+ buf->b_start_eol = TRUE;
+#ifdef FEAT_MBYTE
+ buf->b_p_bomb = FALSE;
+#endif
+ buf->b_ml.ml_mfp = NULL;
+ buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */
+#ifdef FEAT_NETBEANS_INTG
+ netbeans_deleted_all_lines(buf);
+ netbeansOpenFile = 0; /* reset in netbeans_file_opened() */
+#endif
+}
+
+/*
+ * buf_freeall() - free all things allocated for a buffer that are related to
+ * the file.
+ */
+/*ARGSUSED*/
+ void
+buf_freeall(buf, del_buf, wipe_buf)
+ buf_T *buf;
+ int del_buf; /* buffer is going to be deleted */
+ int wipe_buf; /* buffer is going to be wiped out */
+{
+#ifdef FEAT_AUTOCMD
+ int is_curbuf = (buf == curbuf);
+
+ apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+ if (del_buf && 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 (wipe_buf)
+ {
+ apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname,
+ FALSE, buf);
+ if (!buf_valid(buf)) /* autocommands may delete the buffer */
+ return;
+ }
+# ifdef FEAT_EVAL
+ if (aborting()) /* autocmds may abort script processing */
+ return;
+# endif
+
+ /*
+ * 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;
+#endif
+#ifdef FEAT_DIFF
+ diff_buf_delete(buf); /* Can't use 'diff' for unloaded buffer. */
+#endif
+#ifdef FEAT_TCL
+ tcl_buffer_free(buf);
+#endif
+ u_blockfree(buf); /* free the memory allocated for undo */
+ ml_close(buf, TRUE); /* close and delete the memline/memfile */
+ buf->b_ml.ml_line_count = 0; /* no lines in buffer */
+ u_clearall(buf); /* reset all undo information */
+#ifdef FEAT_SYN_HL
+ syntax_clear(buf); /* reset syntax info */
+#endif
+}
+
+/*
+ * 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)
+ buf_T *buf;
+{
+ free_buffer_stuff(buf, TRUE);
+#ifdef FEAT_PERL
+ perl_buf_free(buf);
+#endif
+#ifdef FEAT_PYTHON
+ python_buffer_free(buf);
+#endif
+#ifdef FEAT_RUBY
+ ruby_buffer_free(buf);
+#endif
+ vim_free(buf);
+}
+
+/*
+ * Free stuff in the buffer for ":bdel" and when wiping out the buffer.
+ */
+ static void
+free_buffer_stuff(buf, free_options)
+ 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);
+ }
+#ifdef FEAT_EVAL
+ var_clear(&buf->b_vars); /* free all internal variables */
+#endif
+#ifdef FEAT_USR_CMDS
+ uc_clear(&buf->b_ucmds); /* clear local user commands */
+#endif
+#ifdef FEAT_SIGNS
+ buf_delete_signs(buf); /* delete any signs */
+#endif
+#ifdef FEAT_LOCALMAP
+ map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */
+ map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */
+#endif
+#ifdef FEAT_MBYTE
+ vim_free(buf->b_start_fenc);
+ buf->b_start_fenc = NULL;
+#endif
+}
+
+/*
+ * Free the b_wininfo list for buffer "buf".
+ */
+ static void
+clear_wininfo(buf)
+ 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);
+#ifdef FEAT_FOLDING
+ deleteFoldRecurse(&wip->wi_folds);
+#endif
+ }
+ vim_free(wip);
+ }
+}
+
+#if defined(FEAT_LISTCMDS) || defined(PROTO)
+/*
+ * Go to another buffer. Handles the result of the ATTENTION dialog.
+ */
+ void
+goto_buffer(eap, start, dir, count)
+ exarg_T *eap;
+ int start;
+ int dir;
+ int count;
+{
+# if defined(FEAT_WINDOWS) \
+ && (defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG))
+ 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(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG))
+ if (swap_exists_action == SEA_QUIT && *eap->cmd == 's')
+ {
+ /* Quitting means closing the split window, nothing else. */
+ win_close(curwin, TRUE);
+ swap_exists_action = SEA_NONE;
+ }
+ else
+ handle_swap_exists(old_curbuf);
+# endif
+}
+#endif
+
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) || 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(old_curbuf)
+ buf_T *old_curbuf;
+{
+ if (swap_exists_action == SEA_QUIT)
+ {
+ /* 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 */
+ close_buffer(curwin, curbuf, DOBUF_UNLOAD);
+ if (!buf_valid(old_curbuf) || old_curbuf == curbuf)
+ old_curbuf = buflist_new(NULL, NULL, 1L,
+ BLN_CURBUF | BLN_LISTED | BLN_FORCE);
+ if (old_curbuf != NULL)
+ enter_buffer(old_curbuf);
+ /* If "old_curbuf" is NULL we are in big trouble here... */
+ }
+ else if (swap_exists_action == SEA_RECOVER)
+ {
+ /* 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();
+ }
+ swap_exists_action = SEA_NONE;
+}
+#endif
+
+#if defined(FEAT_LISTCMDS) || defined(PROTO)
+/*
+ * 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(command, arg, addr_count, start_bnr, end_bnr, forceit)
+ 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;
+
+#ifdef FEAT_NETBEANS_INTG
+ netbeansCloseFile = 1;
+#endif
+ 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);
+ 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)
+ sprintf((char *)IObuff, _("E515: No buffers were unloaded"));
+ else if (command == DOBUF_DEL)
+ sprintf((char *)IObuff, _("E516: No buffers were deleted"));
+ else
+ sprintf((char *)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);
+ }
+ }
+ }
+
+#ifdef FEAT_NETBEANS_INTG
+ netbeansCloseFile = 0;
+#endif
+
+ return errormsg;
+}
+
+/*
+ * 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(action, start, dir, count, forceit)
+ 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 %ld 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;
+ }
+
+#ifdef FEAT_GUI
+ need_mouse_correct = TRUE;
+#endif
+
+#ifdef FEAT_LISTCMDS
+ /*
+ * delete buffer buf from memory and/or the list
+ */
+ if (unload)
+ {
+ int forward;
+ int retval;
+
+ /* 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 defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+ if ((p_confirm || cmdmod.confirm) && p_write)
+ {
+ dialog_changed(buf, FALSE);
+# ifdef FEAT_AUTOCMD
+ if (!buf_valid(buf))
+ /* Autocommand deleted buffer, oops! It's not changed
+ * now. */
+ return FAIL;
+# endif
+ }
+ if (bufIsChanged(buf))
+#endif
+ {
+ EMSGN(_("E89: No write since last change for buffer %ld (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)
+ {
+ if (action == DOBUF_UNLOAD)
+ {
+ EMSG(_("E90: Cannot unload last buffer"));
+ return FAIL;
+ }
+
+ /* Close any other windows on this buffer, then make it empty. */
+#ifdef FEAT_WINDOWS
+ {
+ win_T *wp, *nextwp;
+
+ for (wp = firstwin; wp != NULL; wp = nextwp)
+ {
+ nextwp = wp->w_next;
+ if (wp != curwin && wp->w_buffer == buf)
+ {
+ /* Start all over, autocommands may change the window
+ * layout. */
+ nextwp = firstwin;
+ win_close(wp, FALSE);
+ }
+ }
+ }
+#endif
+ setpcmark();
+ retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE,
+ forceit ? ECMD_FORCEIT : 0);
+
+ /*
+ * 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);
+ return retval;
+ }
+
+#ifdef FEAT_WINDOWS
+ /*
+ * If the deleted buffer is the current one, close the current window
+ * (unless it's the only window).
+ */
+ while (buf == curbuf && firstwin != lastwin)
+ win_close(curwin, FALSE);
+#endif
+
+ /*
+ * If the buffer to be deleted is not the current one, delete it here.
+ */
+ if (buf != curbuf)
+ {
+#ifdef FEAT_WINDOWS
+ close_windows(buf);
+#endif
+ if (buf != curbuf && buf_valid(buf) && buf->b_nwindows <= 0)
+ close_buffer(NULL, buf, action);
+ return OK;
+ }
+
+ /*
+ * Deleting the current buffer: Need to find another buffer to go to.
+ * There must be another, otherwise it would have been handled above.
+ * 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 */
+#ifdef FEAT_AUTOCMD
+ if (au_new_curbuf != NULL && buf_valid(au_new_curbuf))
+ buf = au_new_curbuf;
+# ifdef FEAT_JUMPLIST
+ else
+# endif
+#endif
+#ifdef FEAT_JUMPLIST
+ 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;
+ }
+ }
+#endif
+
+ 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_next;
+ else
+ buf = curbuf->b_prev;
+ }
+ }
+
+ /*
+ * make buf current buffer
+ */
+ if (action == DOBUF_SPLIT) /* split window first */
+ {
+# ifdef FEAT_WINDOWS
+ /* jump to first window containing buf if one exists ("useopen") */
+ if (vim_strchr(p_swb, 'u') && buf_jump_open_win(buf))
+ return OK;
+ if (win_split(0, 0) == FAIL)
+# endif
+ return FAIL;
+ }
+#endif
+
+ /* go to current buffer - nothing to do */
+ if (buf == curbuf)
+ return OK;
+
+ /*
+ * Check if the current buffer may be abandoned.
+ */
+ if (action == DOBUF_GOTO && !can_abandon(curbuf, forceit))
+ {
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+ if ((p_confirm || cmdmod.confirm) && p_write)
+ {
+ dialog_changed(curbuf, FALSE);
+# ifdef FEAT_AUTOCMD
+ if (