diff options
Diffstat (limited to 'src/buffer.c')
-rw-r--r-- | src/buffer.c | 5071 |
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 ( |