mirror of
https://github.com/helix-editor/helix
synced 2026-08-08 15:44:15 +02:00
1673 lines
49 KiB
Plaintext
1673 lines
49 KiB
Plaintext
// This sample is from Neovim's 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 <stdbool.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
|
|
#include "nvim/api/private/handle.h"
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/api/vim.h"
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/assert.h"
|
|
#include "nvim/channel.h"
|
|
#include "nvim/vim.h"
|
|
#include "nvim/buffer.h"
|
|
#include "nvim/change.h"
|
|
#include "nvim/charset.h"
|
|
#include "nvim/cursor.h"
|
|
#include "nvim/diff.h"
|
|
#include "nvim/digraph.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/ex_cmds2.h"
|
|
#include "nvim/ex_cmds.h"
|
|
#include "nvim/ex_docmd.h"
|
|
#include "nvim/ex_eval.h"
|
|
#include "nvim/ex_getln.h"
|
|
#include "nvim/fileio.h"
|
|
#include "nvim/file_search.h"
|
|
#include "nvim/fold.h"
|
|
#include "nvim/getchar.h"
|
|
#include "nvim/hashtab.h"
|
|
#include "nvim/highlight.h"
|
|
#include "nvim/indent.h"
|
|
#include "nvim/indent_c.h"
|
|
#include "nvim/main.h"
|
|
#include "nvim/mark.h"
|
|
#include "nvim/extmark.h"
|
|
#include "nvim/mbyte.h"
|
|
#include "nvim/memline.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/misc1.h"
|
|
#include "nvim/garray.h"
|
|
#include "nvim/move.h"
|
|
#include "nvim/option.h"
|
|
#include "nvim/os_unix.h"
|
|
#include "nvim/path.h"
|
|
#include "nvim/quickfix.h"
|
|
#include "nvim/regexp.h"
|
|
#include "nvim/screen.h"
|
|
#include "nvim/sign.h"
|
|
#include "nvim/spell.h"
|
|
#include "nvim/strings.h"
|
|
#include "nvim/syntax.h"
|
|
#include "nvim/ui.h"
|
|
#include "nvim/undo.h"
|
|
#include "nvim/version.h"
|
|
#include "nvim/window.h"
|
|
#include "nvim/shada.h"
|
|
#include "nvim/os/os.h"
|
|
#include "nvim/os/time.h"
|
|
#include "nvim/os/input.h"
|
|
#include "nvim/buffer_updates.h"
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "buffer.c.generated.h"
|
|
#endif
|
|
|
|
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");
|
|
|
|
// Number of times free_buffer() was called.
|
|
static int buf_free_count = 0;
|
|
|
|
typedef enum {
|
|
kBffClearWinInfo = 1,
|
|
kBffInitChangedtick = 2,
|
|
} BufFreeFlags;
|
|
|
|
// Read data from buffer for retrying.
|
|
static int
|
|
read_buffer(
|
|
int read_stdin, // read file from stdin, otherwise fifo
|
|
exarg_T *eap, // for forced 'ff' and 'fenc' or NULL
|
|
int flags) // extra flags for readfile()
|
|
{
|
|
int retval = OK;
|
|
linenr_T line_count;
|
|
|
|
//
|
|
// Read from the buffer which the text is already filled in and append at
|
|
// the end. This makes it possible to retry when 'fileformat' or
|
|
// 'fileencoding' was guessed wrong.
|
|
//
|
|
line_count = curbuf->b_ml.ml_line_count;
|
|
retval = readfile(
|
|
read_stdin ? NULL : curbuf->b_ffname,
|
|
read_stdin ? NULL : curbuf->b_fname,
|
|
(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;
|
|
|
|
if (read_stdin) {
|
|
// 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, true);
|
|
}
|
|
|
|
apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, false,
|
|
curbuf, &retval);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
// 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;
|
|
bufref_T old_curbuf;
|
|
long old_tw = curbuf->b_p_tw;
|
|
int read_fifo = false;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
curbuf = NULL;
|
|
FOR_ALL_BUFFERS(buf) {
|
|
if (buf->b_ml.ml_mfp != NULL) {
|
|
curbuf = buf;
|
|
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.
|
|
set_bufref(&old_curbuf, curbuf);
|
|
modified_was_set = false;
|
|
|
|
// mark cursor position as being invalid
|
|
curwin->w_valid = 0;
|
|
|
|
if (curbuf->b_ffname != NULL) {
|
|
int old_msg_silent = msg_silent;
|
|
#ifdef UNIX
|
|
int save_bin = curbuf->b_p_bin;
|
|
int perm;
|
|
|
|
perm = os_getperm((const char *)curbuf->b_ffname);
|
|
if (perm >= 0 && (0
|
|
# ifdef S_ISFIFO
|
|
|| S_ISFIFO(perm)
|
|
# endif
|
|
# ifdef S_ISSOCK
|
|
|| S_ISSOCK(perm)
|
|
# endif
|
|
# ifdef OPEN_CHR_FILES
|
|
|| (S_ISCHR(perm)
|
|
&& is_dev_fd_file(curbuf->b_ffname))
|
|
# endif
|
|
)
|
|
) {
|
|
read_fifo = true;
|
|
}
|
|
if (read_fifo) {
|
|
curbuf->b_p_bin = true;
|
|
}
|
|
#endif
|
|
if (shortmess(SHM_FILEINFO)) {
|
|
msg_silent = 1;
|
|
}
|
|
|
|
retval = readfile(curbuf->b_ffname, curbuf->b_fname,
|
|
(linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap,
|
|
flags | READ_NEW | (read_fifo ? READ_FIFO : 0));
|
|
#ifdef UNIX
|
|
if (read_fifo) {
|
|
curbuf->b_p_bin = save_bin;
|
|
if (retval == OK) {
|
|
retval = read_buffer(false, eap, flags);
|
|
}
|
|
}
|
|
#endif
|
|
msg_silent = old_msg_silent;
|
|
|
|
// Help buffer is filtered.
|
|
if (bt_help(curbuf)) {
|
|
fix_help_buffer();
|
|
}
|
|
} else if (read_stdin) {
|
|
int save_bin = curbuf->b_p_bin;
|
|
|
|
/*
|
|
* 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) {
|
|
retval = read_buffer(true, eap, flags);
|
|
}
|
|
}
|
|
|
|
// 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 | nvim -R -".
|
|
// 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 && !read_fifo) {
|
|
unchanged(curbuf, false, true);
|
|
}
|
|
save_file_ff(curbuf); // keep this fileformat
|
|
|
|
// Set last_changedtick to avoid triggering a TextChanged autocommand right
|
|
// after it was added.
|
|
curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
|
|
curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
|
|
|
|
// 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) {
|
|
return FAIL;
|
|
}
|
|
|
|
/*
|
|
* The autocommands may have changed the current buffer. Apply the
|
|
* modelines to the correct buffer, if it still exists and is loaded.
|
|
*/
|
|
if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->b_ml.ml_mfp != NULL) {
|
|
aco_save_T aco;
|
|
|
|
// Go to the buffer that was opened.
|
|
aucmd_prepbuf(&aco, old_curbuf.br_buf);
|
|
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;
|
|
}
|
|
|
|
/// Store "buf" in "bufref" and set the free count.
|
|
///
|
|
/// @param bufref Reference to be used for the buffer.
|
|
/// @param buf The buffer to reference.
|
|
void set_bufref(bufref_T *bufref, buf_T *buf)
|
|
{
|
|
bufref->br_buf = buf;
|
|
bufref->br_fnum = buf == NULL ? 0 : buf->b_fnum;
|
|
bufref->br_buf_free_count = buf_free_count;
|
|
}
|
|
|
|
/// Return true if "bufref->br_buf" points to the same buffer as when
|
|
/// set_bufref() was called and it is a valid buffer.
|
|
/// Only goes through the buffer list if buf_free_count changed.
|
|
/// Also checks if b_fnum is still the same, a :bwipe followed by :new might get
|
|
/// the same allocated memory, but it's a different buffer.
|
|
///
|
|
/// @param bufref Buffer reference to check for.
|
|
bool bufref_valid(bufref_T *bufref)
|
|
{
|
|
return bufref->br_buf_free_count == buf_free_count
|
|
? true
|
|
: buf_valid(bufref->br_buf) && bufref->br_fnum == bufref->br_buf->b_fnum;
|
|
}
|
|
|
|
/// Check that "buf" points to a valid buffer in the buffer list.
|
|
///
|
|
/// Can be slow if there are many buffers, prefer using bufref_valid().
|
|
///
|
|
/// @param buf The buffer to check for.
|
|
bool buf_valid(buf_T *buf)
|
|
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
if (buf == NULL) {
|
|
return false;
|
|
}
|
|
// Assume that we more often have a recent buffer,
|
|
// start with the last one.
|
|
for (buf_T *bp = lastbuf; bp != NULL; bp = bp->b_prev) {
|
|
if (bp == buf) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Close the link to a buffer.
|
|
///
|
|
/// @param win If not NULL, set b_last_cursor.
|
|
/// @param buf
|
|
/// @param action Used when there is no longer a window for the buffer.
|
|
/// Possible values:
|
|
/// 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.
|
|
/// @param abort_if_last
|
|
/// If true, 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, buf_T *buf, int action, bool abort_if_last)
|
|
{
|
|
bool unload_buf = (action != 0);
|
|
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
|
|
bool wipe_buf = (action == DOBUF_WIPE);
|
|
|
|
bool is_curwin = (curwin != NULL && curwin->w_buffer == buf);
|
|
win_T *the_curwin = curwin;
|
|
tabpage_T *the_curtab = curtab;
|
|
|
|
// Force unloading or deleting when 'bufhidden' says so, but not for terminal
|
|
// buffers.
|
|
// The caller must take care of NOT deleting/freeing when 'bufhidden' is
|
|
// "hide" (otherwise we could never free or delete a buffer).
|
|
if (!buf->terminal) {
|
|
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 (buf->terminal && (unload_buf || del_buf || wipe_buf)) {
|
|
// terminal buffers can only be wiped
|
|
unload_buf = true;
|
|
del_buf = true;
|
|
wipe_buf = true;
|
|
}
|
|
|
|
// Disallow deleting the buffer when it is locked (already being closed or
|
|
// halfway a command that relies on it). Unloading is allowed.
|
|
if (buf->b_locked > 0 && (del_buf || wipe_buf)) {
|
|
EMSG(_("E937: Attempt to delete a buffer that is in use"));
|
|
return;
|
|
}
|
|
|
|
if (win != NULL // Avoid bogus clang warning.
|
|
&& win_valid_any_tab(win)) {
|
|
// 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);
|
|
}
|
|
|
|
bufref_T bufref;
|
|
set_bufref(&bufref, buf);
|
|
|
|
// When the buffer is no longer in a window, trigger BufWinLeave
|
|
if (buf->b_nwindows == 1) {
|
|
buf->b_locked++;
|
|
if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, false,
|
|
buf) && !bufref_valid(&bufref)) {
|
|
// Autocommands deleted the buffer.
|
|
EMSG(_(e_auabort));
|
|
return;
|
|
}
|
|
buf->b_locked--;
|
|
if (abort_if_last && last_nonfloat(win)) {
|
|
// Autocommands made this the only window.
|
|
EMSG(_(e_auabort));
|
|
return;
|
|
}
|
|
|
|
// When the buffer becomes hidden, but is not unloaded, trigger
|
|
// BufHidden
|
|
if (!unload_buf) {
|
|
buf->b_locked++;
|
|
if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, false,
|
|
buf) && !bufref_valid(&bufref)) {
|
|
// Autocommands deleted the buffer.
|
|
EMSG(_(e_auabort));
|
|
return;
|
|
}
|
|
buf->b_locked--;
|
|
if (abort_if_last && last_nonfloat(win)) {
|
|
// Autocommands made this the only window.
|
|
EMSG(_(e_auabort));
|
|
return;
|
|
}
|
|
}
|
|
if (aborting()) { // autocmds may abort script processing
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If the buffer was in curwin and the window has changed, go back to that
|
|
// window, if it still exists. This avoids that ":edit x" triggering a
|
|
// "tabnext" BufUnload autocmd leaves a window behind without a buffer.
|
|
if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) {
|
|
block_autocmds();
|
|
goto_tabpage_win(the_curtab, the_curwin);
|
|
unblock_autocmds();
|
|
}
|
|
|
|
int nwindows = buf->b_nwindows;
|
|
|
|
// decrease the link count from windows (unless not in any window)
|
|
if (buf->b_nwindows > 0) {
|
|
buf->b_nwindows--;
|
|
}
|
|
|
|
if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0) {
|
|
diff_buf_delete(buf); // Clear 'diff' for hidden buffer.
|
|
}
|
|
|
|
/* Return when a window is displaying the buffer or when it's not
|
|
* unloaded. */
|
|
if (buf->b_nwindows > 0 || !unload_buf) {
|
|
return;
|
|
}
|
|
|
|
if (buf->terminal) {
|
|
terminal_close(buf->terminal, NULL);
|
|
}
|
|
|
|
// 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. */
|
|
bool is_curbuf = (buf == curbuf);
|
|
|
|
// When closing the current buffer stop Visual mode before freeing
|
|
// anything.
|
|
if (is_curbuf && VIsual_active
|
|
#if defined(EXITFREE)
|
|
&& !entered_free_all_mem
|
|
#endif
|
|
) {
|
|
end_visual_mode();
|
|
}
|
|
|
|
buf->b_nwindows = nwindows;
|
|
|
|
buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0));
|
|
|
|
if (!bufref_valid(&bufref)) {
|
|
// Autocommands may have deleted the buffer.
|
|
return;
|
|
}
|
|
if (aborting()) {
|
|
// Autocmds may abort script processing.
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
if (win != NULL // Avoid bogus clang warning.
|
|
&& win_valid_any_tab(win)
|
|
&& win->w_buffer == buf) {
|
|
win->w_buffer = NULL; // make sure we don't use the buffer now
|
|
}
|
|
|
|
// 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--;
|
|
}
|
|
|
|
// Change directories when the 'acd' option is set.
|
|
do_autochdir();
|
|
|
|
// Disable buffer-updates for the current buffer.
|
|
// No need to check `unload_buf`: in that case the function returned above.
|
|
buf_updates_unregister_all(buf);
|
|
|
|
/*
|
|
* Remove the buffer from the list.
|
|
*/
|
|
if (wipe_buf) {
|
|
xfree(buf->b_ffname);
|
|
xfree(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, kBffClearWinInfo | kBffInitChangedtick);
|
|
|
|
// 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, 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
|
|
}
|
|
|
|
/// Clears the current buffer contents.
|
|
void buf_clear(void)
|
|
{
|
|
linenr_T line_count = curbuf->b_ml.ml_line_count;
|
|
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
|
|
ml_delete((linenr_T)1, false);
|
|
}
|
|
deleted_lines_mark(1, line_count); // prepare for display
|
|
ml_close(curbuf, true); // free memline_T
|
|
buf_clear_file(curbuf);
|
|
}
|
|
|
|
/// buf_freeall() - free all things allocated for a buffer that are related to
|
|
/// the file. Careful: get here with "curwin" NULL when exiting.
|
|
///
|
|
/// @param 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)
|
|
{
|
|
bool is_curbuf = (buf == curbuf);
|
|
int is_curwin = (curwin != NULL && curwin->w_buffer == buf);
|
|
win_T *the_curwin = curwin;
|
|
tabpage_T *the_curtab = curtab;
|
|
|
|
// Make sure the buffer isn't closed by autocommands.
|
|
buf->b_locked++;
|
|
|
|
bufref_T bufref;
|
|
set_bufref(&bufref, buf);
|
|
|
|
if ((buf->b_ml.ml_mfp != NULL)
|
|
&& apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, false, buf)
|
|
&& !bufref_valid(&bufref)) {
|
|
// Autocommands deleted the buffer.
|
|
return;
|
|
}
|
|
if ((flags & BFA_DEL)
|
|
&& buf->b_p_bl
|
|
&& apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, false, buf)
|
|
&& !bufref_valid(&bufref)) {
|
|
// Autocommands may delete the buffer.
|
|
return;
|
|
}
|
|
if ((flags & BFA_WIPE)
|
|
&& apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, false,
|
|
buf)
|
|
&& !bufref_valid(&bufref)) {
|
|
// Autocommands may delete the buffer.
|
|
return;
|
|
}
|
|
buf->b_locked--;
|
|
|
|
// If the buffer was in curwin and the window has changed, go back to that
|
|
// window, if it still exists. This avoids that ":edit x" triggering a
|
|
// "tabnext" BufUnload autocmd leaves a window behind without a buffer.
|
|
if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) {
|
|
block_autocmds();
|
|
goto_tabpage_win(the_curtab, the_curwin);
|
|
unblock_autocmds();
|
|
}
|
|
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 (curwin != NULL && curwin->w_buffer == buf) {
|
|
reset_synblock(curwin);
|
|
}
|
|
|
|
// No folds in an empty buffer.
|
|
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)
|
|
{
|
|
handle_unregister_buffer(buf);
|
|
buf_free_count++;
|
|
// b:changedtick uses an item in buf_T.
|
|
free_buffer_stuff(buf, kBffClearWinInfo);
|
|
if (buf->b_vars->dv_refcount > DO_NOT_FREE_CNT) {
|
|
tv_dict_add(buf->b_vars,
|
|
tv_dict_item_copy((dictitem_T *)(&buf->changedtick_di)));
|
|
}
|
|
unref_var_dict(buf->b_vars);
|
|
aubuflocal_remove(buf);
|
|
tv_dict_unref(buf->additional_data);
|
|
xfree(buf->b_prompt_text);
|
|
callback_free(&buf->b_prompt_callback);
|
|
callback_free(&buf->b_prompt_interrupt);
|
|
clear_fmark(&buf->b_last_cursor);
|
|
clear_fmark(&buf->b_last_insert);
|
|
clear_fmark(&buf->b_last_change);
|
|
for (size_t i = 0; i < NMARKS; i++) {
|
|
free_fmark(buf->b_namedm[i]);
|
|
}
|
|
for (int i = 0; i < buf->b_changelistlen; i++) {
|
|
free_fmark(buf->b_changelist[i]);
|
|
}
|
|
if (autocmd_busy) {
|
|
// Do not free the buffer structure while autocommands are executing,
|
|
// it's still needed. Free it when autocmd_busy is reset.
|
|
memset(&buf->b_namedm[0], 0, sizeof(buf->b_namedm));
|
|
memset(&buf->b_changelist[0], 0, sizeof(buf->b_changelist));
|
|
buf->b_next = au_pending_free_buf;
|
|
au_pending_free_buf = buf;
|
|
} else {
|
|
xfree(buf);
|
|
}
|
|
}
|
|
|
|
/// Free stuff in the buffer for ":bdel" and when wiping out the buffer.
|
|
///
|
|
/// @param buf Buffer pointer
|
|
/// @param free_flags BufFreeFlags
|
|
static void free_buffer_stuff(buf_T *buf, int free_flags)
|
|
{
|
|
if (free_flags & kBffClearWinInfo) {
|
|
clear_wininfo(buf); // including window-local options
|
|
free_buf_options(buf, true);
|
|
ga_clear(&buf->b_s.b_langp);
|
|
}
|
|
{
|
|
// Avoid losing b:changedtick when deleting buffer: clearing variables
|
|
// implies using clear_tv() on b:changedtick and that sets changedtick to
|
|
// zero.
|
|
hashitem_T *const changedtick_hi = hash_find(
|
|
&buf->b_vars->dv_hashtab, (const char_u *)"changedtick");
|
|
assert(changedtick_hi != NULL);
|
|
hash_remove(&buf->b_vars->dv_hashtab, changedtick_hi);
|
|
}
|
|
vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables
|
|
hash_init(&buf->b_vars->dv_hashtab);
|
|
if (free_flags & kBffInitChangedtick) {
|
|
buf_init_changedtick(buf);
|
|
}
|
|
uc_clear(&buf->b_ucmds); // clear local user commands
|
|
buf_delete_signs(buf, (char_u *)"*"); // delete any signs
|
|
extmark_free_all(buf); // delete any extmarks
|
|
map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings
|
|
map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
|
|
XFREE_CLEAR(buf->b_start_fenc);
|
|
|
|
buf_updates_unregister_all(buf);
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
xfree(wip);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Go to another buffer. Handles the result of the ATTENTION dialog.
|
|
*/
|
|
void goto_buffer(exarg_T *eap, int start, int dir, int count)
|
|
{
|
|
bufref_T old_curbuf;
|
|
set_bufref(&old_curbuf, curbuf);
|
|
swap_exists_action = SEA_DIALOG;
|
|
|
|
(void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
|
|
start, dir, count, eap->forceit);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// Handle the situation of swap_exists_action being set.
|
|
///
|
|
/// It is allowed for "old_curbuf" to be NULL or invalid.
|
|
///
|
|
/// @param old_curbuf The buffer to check for.
|
|
void handle_swap_exists(bufref_T *old_curbuf)
|
|
{
|
|
cleanup_T cs;
|
|
long old_tw = curbuf->b_p_tw;
|
|
buf_T *buf;
|
|
|
|
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 (old_curbuf == NULL
|
|
|| !bufref_valid(old_curbuf)
|
|
|| old_curbuf->br_buf == curbuf) {
|
|
buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
|
|
} else {
|
|
buf = old_curbuf->br_buf;
|
|
}
|
|
if (buf != NULL) {
|
|
int old_msg_silent = msg_silent;
|
|
|
|
if (shortmess(SHM_FILEINFO)) {
|
|
msg_silent = 1; // prevent fileinfo message
|
|
}
|
|
enter_buffer(buf);
|
|
// restore msg_silent, so that the command line will be shown
|
|
msg_silent = old_msg_silent;
|
|
|
|
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(false);
|
|
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; // -V519
|
|
}
|
|
|
|
/*
|
|
* 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; os_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, 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 (!ascii_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_int(&arg, false, 0);
|
|
}
|
|
}
|
|
}
|
|
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(_("%d buffers unloaded"), deleted);
|
|
}
|
|
} else if (command == DOBUF_DEL) {
|
|
if (deleted == 1) {
|
|
MSG(_("1 buffer deleted"));
|
|
} else {
|
|
smsg(_("%d buffers deleted"), deleted);
|
|
}
|
|
} else {
|
|
if (deleted == 1) {
|
|
MSG(_("1 buffer wiped out"));
|
|
} else {
|
|
smsg(_("%d buffers wiped out"), deleted);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return errormsg;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
bufref_T bufref;
|
|
set_bufref(&bufref, buf);
|
|
|
|
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 && bufref_valid(&bufref) && 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(_(e_nobufnr), 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;
|
|
bufref_T bufref;
|
|
set_bufref(&bufref, buf);
|
|
|
|
/* 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 && (buf->terminal || bufIsChanged(buf))) {
|
|
if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) {
|
|
dialog_changed(buf, false);
|
|
if (!bufref_valid(&bufref)) {
|
|
// 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 {
|
|
if (buf->terminal) {
|
|
if (p_confirm || cmdmod.confirm) {
|
|
if (!dialog_close_terminal(buf)) {
|
|
return FAIL;
|
|
}
|
|
} else {
|
|
EMSG2(_("E89: %s will be killed (add ! to override)"),
|
|
(char *)buf->b_fname);
|
|
return FAIL;
|
|
}
|
|
} else {
|
|
EMSGN(_("E89: No write since last change for buffer %" PRId64
|
|
" (add ! to override)"),
|
|
buf->b_fnum);
|
|
return FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// When closing the current buffer stop Visual mode.
|
|
if (buf == curbuf && VIsual_active) {
|
|
end_visual_mode();
|
|
}
|
|
|
|
/*
|
|
* If deleting the last (listed) buffer, make it empty.
|
|
* The last (listed) buffer cannot be unloaded.
|
|
*/
|
|
bp = NULL;
|
|
FOR_ALL_BUFFERS(bp2) {
|
|
if (bp2->b_p_bl && bp2 != buf) {
|
|
bp = bp2;
|
|
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_locked > 0)
|
|
&& (!ONE_WINDOW || 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 && bufref_valid(&bufref) && 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.br_buf, 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.br_buf != NULL && bufref_valid(&au_new_curbuf)) {
|
|
buf = au_new_curbuf.br_buf;
|
|
} 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_ALL_BUFFERS(buf2) {
|
|
if (buf2->b_p_bl && buf2 != curbuf) {
|
|
buf = buf2;
|
|
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) {
|
|
bufref_T bufref;
|
|
set_bufref(&bufref, buf);
|
|
dialog_changed(curbuf, false);
|
|
if (!bufref_valid(&bufref)) {
|
|
// Autocommand deleted buffer, oops!
|
|
return FAIL;
|
|
}
|
|
}
|
|
if (bufIsChanged(curbuf)) {
|
|
no_write_message();
|
|
return FAIL;
|
|
}
|
|
}
|
|
|
|
// Go to the other buffer.
|
|
set_curbuf(buf, action);
|
|
|
|
if (action == DOBUF_SPLIT) {
|
|
RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind'
|
|
}
|
|
|
|
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 and wipe out "buf"
|
|
prevbuf = curbuf;
|
|
bufref_T newbufref;
|
|
bufref_T prevbufref;
|
|
set_bufref(&prevbufref, prevbuf);
|
|
set_bufref(&newbufref, buf);
|
|
|
|
// Autocommands may delete the curren buffer and/or the buffer we wan to go
|
|
// to. In those cases don't close the buffer.
|
|
if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf)
|
|
|| (bufref_valid(&prevbufref) && bufref_valid(&newbufref)
|
|
&& !aborting())) {
|
|
if (prevbuf == curwin->w_buffer) {
|
|
reset_synblock(curwin);
|
|
}
|
|
if (unload) {
|
|
close_windows(prevbuf, false);
|
|
}
|
|
if (bufref_valid(&prevbufref) && !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 && !buf_hide(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);
|
|
}
|
|
}
|
|
|
|
if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) {
|
|
terminal_check_size(prevbuf->terminal);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = &(curbuf->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 an 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 && !shortmess(SHM_FILEINFO)) {
|
|
need_fileinfo = true; // display file info after redraw
|
|
}
|
|
// check if file changed
|
|
(void)buf_check_timestamp(curbuf, false);
|
|
|
|
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.
|
|
// Don't do this while still starting up.
|
|
void do_autochdir(void)
|
|
{
|
|
if (p_acd) {
|
|
if (starting == 0
|
|
&& curbuf->b_ffname != NULL
|
|
&& vim_chdirfile(curbuf->b_ffname) == OK) {
|
|
post_chdir(kCdScopeGlobal, false);
|
|
shorten_fnames(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void no_write_message(void)
|
|
{
|
|
EMSG(_("E37: No write since last change (add ! to override)"));
|
|
}
|
|
|
|
void no_write_message_nobang(void)
|
|
{
|
|
EMSG(_("E37: No write since last change"));
|
|
}
|
|
|
|
//
|
|
// functions for dealing with the buffer list
|
|
//
|
|
|
|
static int top_file_num = 1; ///< highest file number
|
|
|
|
/// Initialize b:changedtick and changedtick_val attribute
|
|
///
|
|
/// @param[out] buf Buffer to intialize for.
|
|
static inline void buf_init_changedtick(buf_T *const buf)
|
|
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
STATIC_ASSERT(sizeof("changedtick") <= sizeof(buf->changedtick_di.di_key),
|
|
"buf->changedtick_di cannot hold large enough keys");
|
|
buf->changedtick_di = (ChangedtickDictItem) {
|
|
.di_flags = DI_FLAGS_RO|DI_FLAGS_FIX, // Must not include DI_FLAGS_ALLOC.
|
|
.di_tv = (typval_T) {
|
|
.v_type = VAR_NUMBER,
|
|
.v_lock = VAR_FIXED,
|
|
.vval.v_number = buf_get_changedtick(buf),
|
|
},
|
|
.di_key = "changedtick",
|
|
};
|
|
tv_dict_add(buf->b_vars, (dictitem_T *)&buf->changedtick_di);
|
|
}
|