From da51dc9cf202772f60bd2da975dbef257bd9237c Mon Sep 17 00:00:00 2001 From: Eliseo Martínez Date: Mon, 12 May 2014 02:25:17 +0200 Subject: Introduce nvim namespace: Move files. Move files from src/ to src/nvim/. - src/nvim/ becomes the new root dir for nvim executable sources. - src/libnvim/ is planned to become root dir of the neovim library. --- src/nvim/buffer.c | 4657 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4657 insertions(+) create mode 100644 src/nvim/buffer.c (limited to 'src/nvim/buffer.c') 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 + +#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_next; + else + buf = curbuf->b_prev; + } + } + + if (buf == NULL) { + /* Autocommands must have wiped out all other buffers. Only option + * now is to make the current buffer empty. */ + return empty_curbuf(FALSE, forceit, action); + } + + /* + * make buf current buffer + */ + if (action == DOBUF_SPLIT) { /* split window first */ + /* If 'switchbuf' contains "useopen": jump to first window containing + * "buf" if one exists */ + if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) + return OK; + /* If 'switchbuf' contains "usetab": jump to first window in any tab + * page containing "buf" if one exists */ + if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) + return OK; + if (win_split(0, 0) == FAIL) + return FAIL; + } + + /* 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 ((p_confirm || cmdmod.confirm) && p_write) { + dialog_changed(curbuf, FALSE); + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! */ + return FAIL; + } + if (bufIsChanged(curbuf)) { + EMSG(_(e_nowrtmsg)); + return FAIL; + } + } + + /* Go to the other buffer. */ + set_curbuf(buf, action); + +#if defined(FEAT_LISTCMDS) \ + && (defined(FEAT_SCROLLBIND) || defined(FEAT_CURSORBIND)) + if (action == DOBUF_SPLIT) { + RESET_BINDING(curwin); /* reset 'scrollbind' and 'cursorbind' */ + } +#endif + + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + + return OK; +} + + +/* + * Set current buffer to "buf". Executes autocommands and closes current + * buffer. "action" tells how to close the current buffer: + * DOBUF_GOTO free or hide it + * DOBUF_SPLIT nothing + * DOBUF_UNLOAD unload it + * DOBUF_DEL delete it + * DOBUF_WIPE wipe it out + */ +void set_curbuf(buf_T *buf, int action) +{ + buf_T *prevbuf; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE); + long old_tw = curbuf->b_p_tw; + + setpcmark(); + if (!cmdmod.keepalt) + curwin->w_alt_fnum = curbuf->b_fnum; /* remember alternate file */ + buflist_altfpos(curwin); /* remember curpos */ + + /* Don't restart Select mode after switching to another buffer. */ + VIsual_reselect = FALSE; + + /* close_windows() or apply_autocmds() may change curbuf */ + prevbuf = curbuf; + + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); + if (buf_valid(prevbuf) && !aborting()) { + if (prevbuf == curwin->w_buffer) + reset_synblock(curwin); + if (unload) + close_windows(prevbuf, FALSE); + if (buf_valid(prevbuf) && !aborting()) { + win_T *previouswin = curwin; + if (prevbuf == curbuf) + u_sync(FALSE); + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, + unload ? action : (action == DOBUF_GOTO + && !P_HID(prevbuf) + && !bufIsChanged( + prevbuf)) ? DOBUF_UNLOAD : 0, FALSE); + if (curwin != previouswin && win_valid(previouswin)) + /* autocommands changed curwin, Grr! */ + curwin = previouswin; + } + } + /* An autocommand may have deleted "buf", already entered it (e.g., when + * it did ":bunload") or aborted the script processing! + * If curwin->w_buffer is null, enter_buffer() will make it valid again */ + if ((buf_valid(buf) && buf != curbuf + && !aborting() + ) || curwin->w_buffer == NULL + ) { + enter_buffer(buf); + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); + } +} + +/* + * Enter a new current buffer. + * Old curbuf must have been abandoned already! This also means "curbuf" may + * be pointing to freed memory. + */ +void enter_buffer(buf_T *buf) +{ + /* Copy buffer and window local option values. Not for a help buffer. */ + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + if (!buf->b_help) + get_winopts(buf); + else + /* Remove all folds in the window. */ + clearFolding(curwin); + foldUpdateAll(curwin); /* update folds (later). */ + + /* Get the buffer in the current window. */ + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + + if (curwin->w_p_diff) + diff_buf_add(curbuf); + + curwin->w_s = &(buf->b_s); + + /* Cursor on first line by default. */ + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + curwin->w_topline_was_set = FALSE; + + /* mark cursor position as being invalid */ + curwin->w_valid = 0; + + /* Make sure the buffer is loaded. */ + if (curbuf->b_ml.ml_mfp == NULL) { /* need to load the file */ + /* If there is no filetype, allow for detecting one. Esp. useful for + * ":ball" used in a autocommand. If there already is a filetype we + * might prefer to keep it. */ + if (*curbuf->b_p_ft == NUL) + did_filetype = FALSE; + + open_buffer(FALSE, NULL, 0); + } else { + if (!msg_silent) + need_fileinfo = TRUE; /* display file info after redraw */ + (void)buf_check_timestamp(curbuf, FALSE); /* check if file changed */ + curwin->w_topline = 1; + curwin->w_topfill = 0; + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf); + } + + /* If autocommands did not change the cursor position, restore cursor lnum + * and possibly cursor col. */ + if (curwin->w_cursor.lnum == 1 && inindent(0)) + buflist_getfpos(); + + check_arg_idx(curwin); /* check for valid arg_idx */ + maketitle(); + /* when autocmds didn't change it */ + if (curwin->w_topline == 1 && !curwin->w_topline_was_set) + scroll_cursor_halfway(FALSE); /* redisplay at correct position */ + + + /* Change directories when the 'acd' option is set. */ + do_autochdir(); + + if (curbuf->b_kmap_state & KEYMAP_INIT) + (void)keymap_init(); + /* May need to set the spell language. Can only do this after the buffer + * has been properly setup. */ + if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + (void)did_set_spelllang(curwin); + + redraw_later(NOT_VALID); +} + +/* + * Change to the directory of the current buffer. + */ +void do_autochdir(void) +{ + if (p_acd) { + if (curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname) == OK) { + shorten_fnames(TRUE); + } + } +} + +/* + * functions for dealing with the buffer list + */ + +/* + * Add a file name to the buffer list. Return a pointer to the buffer. + * If the same file name already exists return a pointer to that buffer. + * If it does not exist, or if fname == NULL, a new entry is created. + * If (flags & BLN_CURBUF) is TRUE, may use current buffer. + * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. + * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. + * This is the ONLY way to create a new buffer. + */ +static int top_file_num = 1; /* highest file number */ + +buf_T * +buflist_new ( + char_u *ffname, /* full path of fname or relative */ + char_u *sfname, /* short fname or NULL */ + linenr_T lnum, /* preferred cursor line */ + int flags /* BLN_ defines */ +) +{ + buf_T *buf; + + fname_expand(curbuf, &ffname, &sfname); /* will allocate ffname */ + + /* + * If file name already exists in the list, update the entry. + */ + /* We can use inode numbers when the file exists. Works better + * for hard links. */ + FileInfo file_info; + if (sfname == NULL || !os_get_file_info((char *)sfname, &file_info)) { + file_info.stat.st_dev = INVALID_DEVICE_ID; + } + if (ffname != NULL && !(flags & BLN_DUMMY) + && (buf = buflist_findname_file_info(ffname, &file_info)) != NULL) { + free(ffname); + if (lnum != 0) + buflist_setfpos(buf, curwin, lnum, (colnr_T)0, FALSE); + /* copy the options now, if 'cpo' doesn't have 's' and not done + * already */ + buf_copy_options(buf, 0); + if ((flags & BLN_LISTED) && !buf->b_p_bl) { + buf->b_p_bl = TRUE; + if (!(flags & BLN_DUMMY)) { + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); + if (!buf_valid(buf)) { + return NULL; + } + } + } + return buf; + } + + /* + * If the current buffer has no name and no contents, use the current + * buffer. Otherwise: Need to allocate a new buffer structure. + * + * This is the ONLY place where a new buffer structure is allocated! + * (A spell file buffer is allocated in spell.c, but that's not a normal + * buffer.) + */ + buf = NULL; + if ((flags & BLN_CURBUF) + && curbuf != NULL + && curbuf->b_ffname == NULL + && curbuf->b_nwindows <= 1 + && (curbuf->b_ml.ml_mfp == NULL || bufempty())) { + buf = curbuf; + /* It's like this buffer is deleted. Watch out for autocommands that + * change curbuf! If that happens, allocate a new buffer anyway. */ + if (curbuf->b_p_bl) + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); + if (buf == curbuf) + apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, FALSE, curbuf); + if (aborting()) /* autocmds may abort script processing */ + return NULL; + if (buf == curbuf) { + /* Make sure 'bufhidden' and 'buftype' are empty */ + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } + } + if (buf != curbuf || curbuf == NULL) { + buf = xcalloc(1, sizeof(buf_T)); + /* init b: variables */ + buf->b_vars = dict_alloc(); + init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); + } + + if (ffname != NULL) { + buf->b_ffname = ffname; + buf->b_sfname = vim_strsave(sfname); + } + + clear_wininfo(buf); + buf->b_wininfo = xcalloc(1, sizeof(wininfo_T)); + + if (ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL)) { + free(buf->b_ffname); + buf->b_ffname = NULL; + free(buf->b_sfname); + buf->b_sfname = NULL; + if (buf != curbuf) + free_buffer(buf); + return NULL; + } + + if (buf == curbuf) { + /* free all things allocated for this buffer */ + buf_freeall(buf, 0); + if (buf != curbuf) /* autocommands deleted the buffer! */ + return NULL; + if (aborting()) /* autocmds may abort script processing */ + return NULL; + /* buf->b_nwindows = 0; why was this here? */ + free_buffer_stuff(buf, FALSE); /* delete local variables et al. */ + + /* Init the options. */ + buf->b_p_initialized = FALSE; + buf_copy_options(buf, BCO_ENTER); + + /* need to reload lmaps and set b:keymap_name */ + curbuf->b_kmap_state |= KEYMAP_INIT; + } else { + /* + * put new buffer at the end of the buffer list + */ + buf->b_next = NULL; + if (firstbuf == NULL) { /* buffer list is empty */ + buf->b_prev = NULL; + firstbuf = buf; + } else { /* append new buffer at end of list */ + lastbuf->b_next = buf; + buf->b_prev = lastbuf; + } + lastbuf = buf; + + buf->b_fnum = top_file_num++; + if (top_file_num < 0) { /* wrap around (may cause duplicates) */ + EMSG(_("W14: Warning: List of file names overflow")); + if (emsg_silent == 0) { + out_flush(); + ui_delay(3000L, TRUE); /* make sure it is noticed */ + } + top_file_num = 1; + } + + /* + * Always copy the options from the current buffer. + */ + buf_copy_options(buf, BCO_ALWAYS); + } + + buf->b_wininfo->wi_fpos.lnum = lnum; + buf->b_wininfo->wi_win = curwin; + + hash_init(&buf->b_s.b_keywtab); + hash_init(&buf->b_s.b_keywtab_ic); + + buf->b_fname = buf->b_sfname; + if (file_info.stat.st_dev == INVALID_DEVICE_ID) + buf->b_dev_valid = FALSE; + else { + buf->b_dev_valid = TRUE; + buf->b_dev = file_info.stat.st_dev; + buf->b_ino = file_info.stat.st_ino; + } + buf->b_u_synced = TRUE; + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + if (flags & BLN_DUMMY) + buf->b_flags |= BF_DUMMY; + buf_clear_file(buf); + clrallmarks(buf); /* clear marks */ + fmarks_check_names(buf); /* check file marks for this file */ + buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; /* init 'buflisted' */ + if (!(flags & BLN_DUMMY)) { + apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf); + if (!buf_valid(buf)) { + return NULL; + } + if (flags & BLN_LISTED) { + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); + if (!buf_valid(buf)) { + return NULL; + } + } + if (aborting()) /* autocmds may abort script processing */ + return NULL; + } + + return buf; +} + +/* + * Free the memory for the options of a buffer. + * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and + * 'fileencoding'. + */ +void free_buf_options(buf_T *buf, int free_p_ff) +{ + if (free_p_ff) { + clear_string_option(&buf->b_p_fenc); + clear_string_option(&buf->b_p_ff); + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } + clear_string_option(&buf->b_p_def); + clear_string_option(&buf->b_p_inc); + clear_string_option(&buf->b_p_inex); + clear_string_option(&buf->b_p_inde); + clear_string_option(&buf->b_p_indk); + clear_string_option(&buf->b_p_cm); + clear_string_option(&buf->b_p_fex); + clear_string_option(&buf->b_p_key); + clear_string_option(&buf->b_p_kp); + clear_string_option(&buf->b_p_mps); + clear_string_option(&buf->b_p_fo); + clear_string_option(&buf->b_p_flp); + clear_string_option(&buf->b_p_isk); + clear_string_option(&buf->b_p_keymap); + ga_clear(&buf->b_kmap_ga); + clear_string_option(&buf->b_p_com); + clear_string_option(&buf->b_p_cms); + clear_string_option(&buf->b_p_nf); + clear_string_option(&buf->b_p_syn); + clear_string_option(&buf->b_s.b_p_spc); + clear_string_option(&buf->b_s.b_p_spf); + vim_regfree(buf->b_s.b_cap_prog); + buf->b_s.b_cap_prog = NULL; + clear_string_option(&buf->b_s.b_p_spl); + clear_string_option(&buf->b_p_sua); + clear_string_option(&buf->b_p_ft); + clear_string_option(&buf->b_p_cink); + clear_string_option(&buf->b_p_cino); + clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cpt); + clear_string_option(&buf->b_p_cfu); + clear_string_option(&buf->b_p_ofu); + clear_string_option(&buf->b_p_gp); + clear_string_option(&buf->b_p_mp); + clear_string_option(&buf->b_p_efm); + clear_string_option(&buf->b_p_ep); + clear_string_option(&buf->b_p_path); + clear_string_option(&buf->b_p_tags); + clear_string_option(&buf->b_p_dict); + clear_string_option(&buf->b_p_tsr); + clear_string_option(&buf->b_p_qe); + buf->b_p_ar = -1; + buf->b_p_ul = NO_LOCAL_UNDOLEVEL; +} + +/* + * get alternate file n + * set linenr to lnum or altfpos.lnum if lnum == 0 + * also set cursor column to altfpos.col if 'startofline' is not set. + * if (options & GETF_SETMARK) call setpcmark() + * if (options & GETF_ALT) we are jumping to an alternate file. + * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping + * + * return FAIL for failure, OK for success + */ +int buflist_getfile(int n, linenr_T lnum, int options, int forceit) +{ + buf_T *buf; + win_T *wp = NULL; + pos_T *fpos; + colnr_T col; + + buf = buflist_findnr(n); + if (buf == NULL) { + if ((options & GETF_ALT) && n == 0) + EMSG(_(e_noalt)); + else + EMSGN(_("E92: Buffer %" PRId64 " not found"), n); + return FAIL; + } + + /* if alternate file is the current buffer, nothing to do */ + if (buf == curbuf) + return OK; + + if (text_locked()) { + text_locked_msg(); + return FAIL; + } + if (curbuf_locked()) + return FAIL; + + /* altfpos may be changed by getfile(), get it now */ + if (lnum == 0) { + fpos = buflist_findfpos(buf); + lnum = fpos->lnum; + col = fpos->col; + } else + col = 0; + + if (options & GETF_SWITCH) { + /* If 'switchbuf' contains "useopen": jump to first window containing + * "buf" if one exists */ + if (swb_flags & SWB_USEOPEN) + wp = buf_jump_open_win(buf); + /* If 'switchbuf' contains "usetab": jump to first window in any tab + * page containing "buf" if one exists */ + if (wp == NULL && (swb_flags & SWB_USETAB)) + wp = buf_jump_open_tab(buf); + /* If 'switchbuf' contains "split" or "newtab" and the current buffer + * isn't empty: open new window */ + if (wp == NULL && (swb_flags & (SWB_SPLIT | SWB_NEWTAB)) && !bufempty()) { + if (swb_flags & SWB_NEWTAB) /* Open in a new tab */ + tabpage_new(); + else if (win_split(0, 0) == FAIL) /* Open in a new window */ + return FAIL; + RESET_BINDING(curwin); + } + } + + ++RedrawingDisabled; + if (getfile(buf->b_fnum, NULL, NULL, (options & GETF_SETMARK), + lnum, forceit) <= 0) { + --RedrawingDisabled; + + /* cursor is at to BOL and w_cursor.lnum is checked due to getfile() */ + if (!p_sol && col != 0) { + curwin->w_cursor.col = col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } + return OK; + } + --RedrawingDisabled; + return FAIL; +} + +/* + * go to the last know line number for the current buffer + */ +void buflist_getfpos(void) +{ + pos_T *fpos; + + fpos = buflist_findfpos(curbuf); + + curwin->w_cursor.lnum = fpos->lnum; + check_cursor_lnum(); + + if (p_sol) + curwin->w_cursor.col = 0; + else { + curwin->w_cursor.col = fpos->col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * Returns NULL if not found. + */ +buf_T *buflist_findname_exp(char_u *fname) +{ + char_u *ffname; + buf_T *buf = NULL; + + /* First make the name into a full path name */ + ffname = FullName_save(fname, +#ifdef UNIX + TRUE /* force expansion, get rid of symbolic links */ +#else + FALSE +#endif + ); + if (ffname != NULL) { + buf = buflist_findname(ffname); + free(ffname); + } + return buf; +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * "ffname" must have a full path. + * Skips dummy buffers. + * Returns NULL if not found. + */ +buf_T *buflist_findname(char_u *ffname) +{ + FileInfo file_info; + if (!os_get_file_info((char *)ffname, &file_info)) { + file_info.stat.st_dev = INVALID_DEVICE_ID; + } + return buflist_findname_file_info(ffname, &file_info); +} + +/* + * Same as buflist_findname(), but pass the FileInfo structure to avoid + * getting it twice for the same file. + * Returns NULL if not found. + */ +static buf_T *buflist_findname_file_info(char_u *ffname, FileInfo *file_info) +{ + buf_T *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if ((buf->b_flags & BF_DUMMY) == 0 + && !otherfile_buf(buf, ffname, file_info)) { + return buf; + } + } + return NULL; +} + +/* + * Find file in buffer list by a regexp pattern. + * Return fnum of the found buffer. + * Return < 0 for error. + */ +int +buflist_findpat ( + char_u *pattern, + char_u *pattern_end, /* pointer to first char after pattern */ + int unlisted, /* find unlisted buffers */ + int diffmode, /* find diff-mode buffers only */ + int curtab_only /* find buffers in current tab only */ +) +{ + buf_T *buf; + regprog_T *prog; + int match = -1; + int find_listed; + char_u *pat; + char_u *patend; + int attempt; + char_u *p; + int toggledollar; + + if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) { + if (*pattern == '%') + match = curbuf->b_fnum; + else + match = curwin->w_alt_fnum; + if (diffmode && !diff_mode_buf(buflist_findnr(match))) + match = -1; + } + /* + * Try four ways of matching a listed buffer: + * attempt == 0: without '^' or '$' (at any position) + * attempt == 1: with '^' at start (only at position 0) + * attempt == 2: with '$' at end (only match at end) + * attempt == 3: with '^' at start and '$' at end (only full match) + * Repeat this for finding an unlisted buffer if there was no matching + * listed buffer. + */ + else { + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE); + if (pat == NULL) + return -1; + patend = pat + STRLEN(pat) - 1; + toggledollar = (patend > pat && *patend == '$'); + + /* First try finding a listed buffer. If not found and "unlisted" + * is TRUE, try finding an unlisted buffer. */ + find_listed = TRUE; + for (;; ) { + for (attempt = 0; attempt <= 3; ++attempt) { + /* may add '^' and '$' */ + if (toggledollar) + *patend = (attempt < 2) ? NUL : '$'; /* add/remove '$' */ + p = pat; + if (*p == '^' && !(attempt & 1)) /* add/remove '^' */ + ++p; + prog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (prog == NULL) { + free(pat); + return -1; + } + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_p_bl == find_listed + && (!diffmode || diff_mode_buf(buf)) + && buflist_match(prog, buf) != NULL) { + if (curtab_only) { + /* Ignore the match if the buffer is not open in + * the current tab. */ + win_T *wp; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf) + break; + if (wp == NULL) + continue; + } + if (match >= 0) { /* already found a match */ + match = -2; + break; + } + match = buf->b_fnum; /* remember first match */ + } + + vim_regfree(prog); + if (match >= 0) /* found one match */ + break; + } + + /* Only search for unlisted buffers if there was no match with + * a listed buffer. */ + if (!unlisted || !find_listed || match != -1) + break; + find_listed = FALSE; + } + + free(pat); + } + + if (match == -2) + EMSG2(_("E93: More than one match for %s"), pattern); + else if (match < 0) + EMSG2(_("E94: No matching buffer for %s"), pattern); + return match; +} + + +/* + * Find all buffer names that match. + * For command line expansion of ":buf" and ":sbuf". + * Return OK if matches found, FAIL otherwise. + */ +int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) +{ + int count = 0; + buf_T *buf; + int round; + char_u *p; + int attempt; + regprog_T *prog; + char_u *patc; + + *num_file = 0; /* return values in case of FAIL */ + *file = NULL; + + /* Make a copy of "pat" and change "^" to "\(^\|[\/]\)". */ + if (*pat == '^') { + patc = xmalloc(STRLEN(pat) + 11); + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } else + patc = pat; + + /* + * attempt == 0: try match with '\<', match at start of word + * attempt == 1: try match without '\<', match anywhere + */ + for (attempt = 0; attempt <= 1; ++attempt) { + if (attempt > 0 && patc == pat) + break; /* there was no anchor, no need to try again */ + prog = vim_regcomp(patc + attempt * 11, RE_MAGIC); + if (prog == NULL) { + if (patc != pat) + free(patc); + return FAIL; + } + + /* + * round == 1: Count the matches. + * round == 2: Build the array to keep the matches. + */ + for (round = 1; round <= 2; ++round) { + count = 0; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (!buf->b_p_bl) /* skip unlisted buffers */ + continue; + p = buflist_match(prog, buf); + if (p != NULL) { + if (round == 1) + ++count; + else { + if (options & WILD_HOME_REPLACE) + p = home_replace_save(buf, p); + else + p = vim_strsave(p); + (*file)[count++] = p; + } + } + } + if (count == 0) /* no match found, break here */ + break; + if (round == 1) { + *file = xmalloc(count * sizeof(**file)); + } + } + vim_regfree(prog); + if (count) /* match(es) found, break here */ + break; + } + + if (patc != pat) + free(patc); + + *num_file = count; + return count == 0 ? FAIL : OK; +} + + +#ifdef HAVE_BUFLIST_MATCH +/* + * Check for a match on the file name for buffer "buf" with regprog "prog". + */ +static char_u *buflist_match(regprog_T *prog, buf_T *buf) +{ + char_u *match; + + /* First try the short file name, then the long file name. */ + match = fname_match(prog, buf->b_sfname); + if (match == NULL) + match = fname_match(prog, buf->b_ffname); + + return match; +} + +/* + * Try matching the regexp in "prog" with file name "name". + * Return "name" when there is a match, NULL when not. + */ +static char_u *fname_match(regprog_T *prog, char_u *name) +{ + char_u *match = NULL; + char_u *p; + regmatch_T regmatch; + + if (name != NULL) { + regmatch.regprog = prog; + regmatch.rm_ic = p_fic; /* ignore case when 'fileignorecase' is set */ + if (vim_regexec(®match, name, (colnr_T)0)) + match = name; + else { + /* Replace $(HOME) with '~' and try matching again. */ + p = home_replace_save(NULL, name); + if (p != NULL && vim_regexec(®match, p, (colnr_T)0)) + match = name; + free(p); + } + } + + return match; +} +#endif + +/* + * find file in buffer list by number + */ +buf_T *buflist_findnr(int nr) +{ + buf_T *buf; + + if (nr == 0) + nr = curwin->w_alt_fnum; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_fnum == nr) + return buf; + return NULL; +} + +/* + * Get name of file 'n' in the buffer list. + * When the file has no name an empty string is returned. + * home_replace() is used to shorten the file name (used for marks). + * Returns a pointer to allocated memory, of NULL when failed. + */ +char_u * +buflist_nr2name ( + int n, + int fullname, + int helptail /* for help buffers return tail only */ +) +{ + buf_T *buf; + + buf = buflist_findnr(n); + if (buf == NULL) + return NULL; + return home_replace_save(helptail ? buf : NULL, + fullname ? buf->b_ffname : buf->b_fname); +} + +/* + * Set the "lnum" and "col" for the buffer "buf" and the current window. + * When "copy_options" is TRUE save the local window option values. + * When "lnum" is 0 only do the options. + */ +static void buflist_setfpos(buf_T *buf, win_T *win, linenr_T lnum, colnr_T col, int copy_options) +{ + wininfo_T *wip; + + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + if (wip->wi_win == win) + break; + if (wip == NULL) { + /* allocate a new entry */ + wip = xcalloc(1, sizeof(wininfo_T)); + wip->wi_win = win; + if (lnum == 0) /* set lnum even when it's 0 */ + lnum = 1; + } else { + /* remove the entry from the list */ + if (wip->wi_prev) + wip->wi_prev->wi_next = wip->wi_next; + else + buf->b_wininfo = wip->wi_next; + if (wip->wi_next) + wip->wi_next->wi_prev = wip->wi_prev; + if (copy_options && wip->wi_optset) { + clear_winopt(&wip->wi_opt); + deleteFoldRecurse(&wip->wi_folds); + } + } + if (lnum != 0) { + wip->wi_fpos.lnum = lnum; + wip->wi_fpos.col = col; + } + if (copy_options) { + /* Save the window-specific option values. */ + copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); + wip->wi_fold_manual = win->w_fold_manual; + cloneFoldGrowArray(&win->w_folds, &wip->wi_folds); + wip->wi_optset = TRUE; + } + + /* insert the entry in front of the list */ + wip->wi_next = buf->b_wininfo; + buf->b_wininfo = wip; + wip->wi_prev = NULL; + if (wip->wi_next) + wip->wi_next->wi_prev = wip; + + return; +} + +static int wininfo_other_tab_diff(wininfo_T *wip); + +/* + * Return TRUE when "wip" has 'diff' set and the diff is only for another tab + * page. That's because a diff is local to a tab page. + */ +static int wininfo_other_tab_diff(wininfo_T *wip) +{ + win_T *wp; + + if (wip->wi_opt.wo_diff) { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + /* return FALSE when it's a window in the current tab page, thus + * the buffer was in diff mode here */ + if (wip->wi_win == wp) + return FALSE; + return TRUE; + } + return FALSE; +} + +/* + * Find info for the current window in buffer "buf". + * If not found, return the info for the most recently used window. + * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in + * another tab page. + * Returns NULL when there isn't any info. + */ +static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) +{ + wininfo_T *wip; + + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + if (wip->wi_win == curwin + && (