⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 buffer.c

📁 VIM文本编辑器
💻 C
📖 第 1 页 / 共 4 页
字号:
/* 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 + -