📄 buffer.c
字号:
/* vi:set ts=8 sts=4 sw=4:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
*/
/*
* buffer.c: functions for dealing with the buffer structure
*/
/*
* The buffer list is a double linked list of all buffers.
* Each buffer can be in one of these states:
* never loaded: BF_NEVERLOADED is set, only the file name is valid
* not loaded: b_ml.ml_mfp == NULL, no memfile allocated
* hidden: b_nwindows == 0, loaded but not displayed in a window
* normal: loaded and displayed in a window
*
* Instead of storing file names all over the place, each file name is
* stored in the buffer list. It can be referenced by a number.
*
* The current implementation remembers all file names ever used.
*/
#include "vim.h"
static void enter_buffer __ARGS((BUF *));
static char_u *buflist_match __ARGS((vim_regexp *prog, BUF *buf));
static char_u *buflist_match_try __ARGS((vim_regexp *prog, char_u *name));
static void buflist_setfpos __ARGS((BUF *, linenr_t, colnr_t));
static FPOS *buflist_findfpos __ARGS((BUF *buf));
static int append_arg_number __ARGS((char_u *, int, int));
static void free_buffer __ARGS((BUF *));
/*
* Open current buffer, that is: open the memfile and read the file into memory
* return FAIL for failure, OK otherwise
*/
int
open_buffer(read_stdin)
int read_stdin; /* read file from stdin */
{
int retval = OK;
#ifdef AUTOCMD
BUF *old_curbuf;
BUF *new_curbuf;
#endif
/*
* The 'readonly' flag is only set when BF_NEVERLOADED is being reset.
* When re-entering the same buffer, it should not change, because the
* user may have reset the flag by hand.
*/
if (readonlymode && curbuf->b_ffname != NULL
&& (curbuf->b_flags & BF_NEVERLOADED))
curbuf->b_p_ro = TRUE;
if (ml_open() == FAIL)
{
/*
* There MUST be a memfile, otherwise we can't do anything
* If we can't create one for the current buffer, take another buffer
*/
close_buffer(NULL, curbuf, FALSE, FALSE);
for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next)
if (curbuf->b_ml.ml_mfp != NULL)
break;
/*
* if there is no memfile at all, exit
* This is OK, since there are no changes to loose.
*/
if (curbuf == NULL)
{
EMSG("Cannot allocate buffer, exiting...");
getout(2);
}
EMSG("Cannot allocate buffer, using other one...");
enter_buffer(curbuf);
return FAIL;
}
#ifdef AUTOCMD
/* The autocommands in readfile() may change the buffer, but only AFTER
* reading the file. */
old_curbuf = curbuf;
modified_was_set = FALSE;
#endif
if (curbuf->b_ffname != NULL)
retval = readfile(curbuf->b_ffname, curbuf->b_fname,
(linenr_t)0, (linenr_t)0, (linenr_t)MAXLNUM, READ_NEW);
else if (read_stdin)
retval = readfile(NULL, NULL, (linenr_t)0,
(linenr_t)0, (linenr_t)MAXLNUM, READ_NEW + READ_STDIN);
else
{
MSG("Empty Buffer");
msg_col = 0;
msg_didout = FALSE; /* overwrite this message whenever you like */
}
/* if first time loading this buffer, init chartab */
if (curbuf->b_flags & BF_NEVERLOADED)
init_chartab();
/*
* Set/reset the Changed flag first, autocmds may change the buffer.
* Apply the automatic commands, before processing the modelines.
* So the modelines have priority over auto commands.
*/
/* When reading stdin, the buffer contents always needs writing, so set
* the changed flag. Unless in readonly mode: "ls | gview -". */
if ((read_stdin && !readonlymode)
#ifdef AUTOCMD
|| modified_was_set /* ":set modified" used in autocmd */
#endif
)
changed();
else if (retval != FAIL)
unchanged(curbuf, FALSE);
curbuf->b_start_ffc = *curbuf->b_p_ff; /* keep this fileformat */
/* require "!" to overwrite the file, because it wasn't read completely */
if (got_int)
curbuf->b_notedited = TRUE;
#ifdef AUTOCMD
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
#endif
if (retval != FAIL)
{
#ifdef AUTOCMD
/*
* The autocommands may have changed the current buffer. Apply the
* modelines to the correct buffer, if it still exists.
*/
if (buf_valid(old_curbuf))
{
new_curbuf = curbuf;
curbuf = old_curbuf;
curwin->w_buffer = old_curbuf;
#endif
do_modelines();
curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED);
#ifdef AUTOCMD
curbuf = new_curbuf;
curwin->w_buffer = new_curbuf;
}
#endif
}
return retval;
}
/*
* Return TRUE if "buf" points to a valid buffer (in the buffer list).
*/
int
buf_valid(buf)
BUF *buf;
{
BUF *bp;
for (bp = firstbuf; bp != NULL; bp = bp->b_next)
if (bp == buf)
return TRUE;
return FALSE;
}
/*
* Close the link to a buffer. If "free_buf" is TRUE free the buffer if it
* becomes unreferenced. The caller should get a new buffer very soon!
* if 'del_buf' is TRUE, remove the buffer from the buffer list.
*/
void
close_buffer(win, buf, free_buf, del_buf)
WIN *win; /* if not NULL, set b_last_cursor */
BUF *buf;
int free_buf;
int del_buf;
{
if (buf->b_nwindows > 0)
--buf->b_nwindows;
if (buf->b_nwindows == 0 && win != NULL)
{
set_last_cursor(win); /* may set b_last_cursor */
if (win == curwin && curwin->w_cursor.lnum != 1)
/* and remember last cursor position */
buflist_setfpos(buf, curwin->w_cursor.lnum, curwin->w_cursor.col);
}
if (buf->b_nwindows > 0 || !free_buf)
{
if (buf == curbuf)
u_sync(); /* sync undo before going to another buffer */
return;
}
/* Always remove the buffer when there is no file name. */
if (buf->b_ffname == NULL)
del_buf = TRUE;
/*
* Free all things allocated for this buffer.
* Also calls the "BufDelete" autocommands when del_buf is TRUE.
*/
buf_freeall(buf, del_buf);
if (!buf_valid(buf)) /* autocommands may delete the buffer */
return;
/*
* Remove the buffer from the list.
*/
if (del_buf)
{
vim_free(buf->b_ffname);
vim_free(buf->b_sfname);
if (buf->b_prev == NULL)
firstbuf = buf->b_next;
else
buf->b_prev->b_next = buf->b_next;
if (buf->b_next == NULL)
lastbuf = buf->b_prev;
else
buf->b_next->b_prev = buf->b_prev;
free_buffer(buf);
}
else
buf_clear(buf);
}
/*
* buf_clear() - make buffer empty
*/
void
buf_clear(buf)
BUF *buf;
{
buf->b_ml.ml_line_count = 1;
unchanged(buf, TRUE);
#ifndef SHORT_FNAME
buf->b_shortname = FALSE;
#endif
buf->b_p_eol = TRUE;
buf->b_ml.ml_mfp = NULL;
buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */
}
/*
* buf_freeall() - free all things allocated for the buffer
*/
void
buf_freeall(buf, del_buf)
BUF *buf;
int del_buf; /* buffer is going to be deleted */
{
#ifdef AUTOCMD
apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf);
if (!buf_valid(buf)) /* autocommands may delete the buffer */
return;
if (del_buf)
{
apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, FALSE, buf);
if (!buf_valid(buf)) /* autocommands may delete the buffer */
return;
}
#endif
#ifdef HAVE_TCL
tcl_buffer_free(buf);
#endif
u_blockfree(buf); /* free the memory allocated for undo */
ml_close(buf, TRUE); /* close and delete the memline/memfile */
buf->b_ml.ml_line_count = 0; /* no lines in buffer */
u_clearall(buf); /* reset all undo information */
#ifdef SYNTAX_HL
syntax_clear(buf); /* reset syntax info */
#endif
#ifdef WANT_EVAL
var_clear(&buf->b_vars); /* free all internal variables */
#endif
}
/*
* Free a buffer structure and some of the things it contains.
*/
static void
free_buffer(buf)
BUF *buf;
{
WINFPOS *wlp;
/* Free the b_winfpos list for buffer "buf". */
while (buf->b_winfpos != NULL)
{
wlp = buf->b_winfpos;
buf->b_winfpos = wlp->wl_next;
vim_free(wlp);
}
#ifdef HAVE_PERL_INTERP
perl_buf_free(buf);
#endif
#ifdef HAVE_PYTHON
python_buffer_free(buf);
#endif
free_buf_options(buf, TRUE);
vim_free(buf);
}
/*
* 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") or DOBUF_DEL (":bdel")
*
* Returns error message or NULL
*/
char_u *
do_bufdel(command, arg, addr_count, start_bnr, end_bnr, forceit)
int command;
char_u *arg; /* pointer to extra arguments */
int addr_count;
int start_bnr; /* first buffer number in a range */
int end_bnr; /* buffer number or last buffer number 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 e_trailing;
bnr = start_bnr;
}
else /* addr_count == 1 */
bnr = end_bnr;
for ( ;!got_int; ui_breakcheck())
{
/*
* delete the current buffer last, otherwise when the
* current buffer is deleted, the next buffer becomes
* the current one and will be loaded, which may then
* also be deleted, etc.
*/
if (bnr == curbuf->b_fnum)
do_current = bnr;
else if (do_buffer(command, DOBUF_FIRST, FORWARD, (int)bnr,
forceit) == OK)
++deleted;
/*
* find next buffer number to delete/unload
*/
if (addr_count == 2)
{
if (++bnr > end_bnr)
break;
}
else /* addr_count == 1 */
{
arg = skipwhite(arg);
if (*arg == NUL)
break;
if (!isdigit(*arg))
{
p = skiptowhite_esc(arg);
bnr = buflist_findpat(arg, p);
if (bnr < 0) /* failed */
break;
arg = p;
}
else
bnr = getdigits(&arg);
}
}
if (!got_int && do_current && do_buffer(command, DOBUF_FIRST,
FORWARD, do_current, forceit) == OK)
++deleted;
if (deleted == 0)
{
sprintf((char *)IObuff, "No buffers were %s",
command == DOBUF_UNLOAD ? "unloaded" : "deleted");
errormsg = IObuff;
}
else
smsg((char_u *)"%d buffer%s %s", deleted,
plural((long)deleted),
command == DOBUF_UNLOAD ? "unloaded" : "deleted");
}
return errormsg;
}
/*
* Implementation of the command 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)
*
* start == DOBUF_CURRENT go to "count" buffer from current buffer
* start == DOBUF_FIRST go to "count" buffer from first buffer
* start == DOBUF_LAST go to "count" buffer from last buffer
* start == DOBUF_MOD go to "count" modified buffer from current buffer
*
* Return FAIL or OK.
*/
int
do_buffer(action, start, dir, count, forceit)
int action;
int start;
int dir; /* FORWARD or BACKWARD */
int count; /* buffer number or number of buffers */
int forceit; /* TRUE for :...! */
{
BUF *buf;
BUF *delbuf;
int retval;
int forward;
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 && !buf_changed(buf));
}
if (!buf_changed(buf))
{
EMSG("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
{
while (count-- > 0)
{
if (dir == FORWARD)
{
buf = buf->b_next;
if (buf == NULL)
buf = firstbuf;
}
else
{
buf = buf->b_prev;
if (buf == NULL)
buf = lastbuf;
}
/* in non-help buffer, skip help buffers, and vv */
if ((buf->b_help) !=
(start == DOBUF_LAST ? lastbuf : curbuf)->b_help)
count++;
}
}
if (buf == NULL) /* could not find it */
{
if (start == DOBUF_FIRST)
{
/* don't warn when deleting */
if (action != DOBUF_UNLOAD && action != DOBUF_DEL)
EMSGN("Cannot go to buffer %ld", count);
}
else if (dir == FORWARD)
EMSG("Cannot go beyond last buffer");
else
EMSG("Cannot go before first buffer");
return FAIL;
}
#ifdef USE_GUI
need_mouse_correct = TRUE;
#endif
/*
* delete buffer buf from memory and/or the list
*/
if (action == DOBUF_UNLOAD || action == DOBUF_DEL)
{
if (!forceit && buf_changed(buf))
{
EMSGN("No write since last change for buffer %ld (use ! to override)",
buf->b_fnum);
return FAIL;
}
/*
* If deleting last buffer, make it empty.
* The last buffer cannot be unloaded.
*/
if (firstbuf->b_next == NULL)
{
if (action == DOBUF_UNLOAD)
{
EMSG("Cannot unload last buffer");
return FAIL;
}
/* Close any other windows on this buffer, then make it empty. */
close_others(FALSE, TRUE);
buf = curbuf;
setpcmark();
retval = do_ecmd(0, NULL, NULL, NULL, (linenr_t)1,
forceit ? ECMD_FORCEIT : 0);
/*
* do_ecmd() may create a new buffer, then we have to delete
* the old one. But do_ecmd() may have done that already, check
* if the buffer still exists.
*/
if (buf != curbuf && buf_valid(buf))
close_buffer(NULL, buf, TRUE, TRUE);
return retval;
}
/*
* If the deleted buffer is the current one, close the current window
* (unless it's the only window).
*/
while (buf == curbuf && firstwin != lastwin)
close_window(curwin, FALSE);
/*
* If the buffer to be deleted is not current one, delete it here.
*/
if (buf != curbuf)
{
close_windows(buf);
if (buf_valid(buf))
close_buffer(NULL, buf, TRUE, action == DOBUF_DEL);
return OK;
}
/*
* Deleting the current buffer: Need to find another buffer to go to.
* There must be another, otherwise it would have been handled above.
* First try to find one that is loaded, after the current buffer,
* then before the current buffer.
*/
forward = TRUE;
buf = curbuf->b_next;
for (;;)
{
if (buf == NULL)
{
if (!forward) /* tried both directions */
break;
buf = curbuf->b_prev;
forward = FALSE;
continue;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -