// 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);
}