diff src/buffer.c @ 32670:695b50472e85

Fix line endings issue
author Christian Brabandt <cb@256bit.org>
date Mon, 26 Jun 2023 13:13:12 +0200
parents 448aef880252
children 5dfaba11d7c3
line wrap: on
line diff
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1,6064 +1,6064 @@
-/* vi:set ts=8 sts=4 sw=4 noet:
- *
- * VIM - Vi IMproved	by Bram Moolenaar
- *
- * Do ":help uganda"  in Vim to read copying and usage conditions.
- * Do ":help credits" in Vim to see a list of people who contributed.
- * See README.txt for an overview of the Vim source code.
- */
-
-/*
- * buffer.c: functions for dealing with the buffer structure
- */
-
-/*
- * The buffer list is a double linked list of all buffers.
- * Each buffer can be in one of these states:
- * never loaded: BF_NEVERLOADED is set, only the file name is valid
- *   not loaded: b_ml.ml_mfp == NULL, no memfile allocated
- *	 hidden: b_nwindows == 0, loaded but not displayed in a window
- *	 normal: loaded and displayed in a window
- *
- * Instead of storing file names all over the place, each file name is
- * stored in the buffer list. It can be referenced by a number.
- *
- * The current implementation remembers all file names ever used.
- */
-
-#include "vim.h"
-
-
-#ifdef FEAT_EVAL
-// Determines how deeply nested %{} blocks will be evaluated in statusline.
-# define MAX_STL_EVAL_DEPTH 100
-#endif
-
-static void	enter_buffer(buf_T *buf);
-static void	buflist_getfpos(void);
-static char_u	*buflist_match(regmatch_T *rmp, buf_T *buf, int ignore_case);
-static char_u	*fname_match(regmatch_T *rmp, char_u *name, int ignore_case);
-#ifdef UNIX
-static buf_T	*buflist_findname_stat(char_u *ffname, stat_T *st);
-static int	otherfile_buf(buf_T *buf, char_u *ffname, stat_T *stp);
-static int	buf_same_ino(buf_T *buf, stat_T *stp);
-#else
-static int	otherfile_buf(buf_T *buf, char_u *ffname);
-#endif
-static int	value_changed(char_u *str, char_u **last);
-static int	append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file);
-static void	free_buffer(buf_T *);
-static void	free_buffer_stuff(buf_T *buf, int free_options);
-static int	bt_nofileread(buf_T *buf);
-static void	no_write_message_buf(buf_T *buf);
-
-#ifdef UNIX
-# define dev_T dev_t
-#else
-# define dev_T unsigned
-#endif
-
-#define FOR_ALL_BUFS_FROM_LAST(buf) \
-    for ((buf) = lastbuf; (buf) != NULL; (buf) = (buf)->b_prev)
-
-#if defined(FEAT_QUICKFIX)
-static char *msg_loclist = N_("[Location List]");
-static char *msg_qflist = N_("[Quickfix List]");
-#endif
-
-// Number of times free_buffer() was called.
-static int	buf_free_count = 0;
-
-static int	top_file_num = 1;	// highest file number
-static garray_T buf_reuse = GA_EMPTY;	// file numbers to recycle
-
-/*
- * Return the highest possible buffer number.
- */
-    int
-get_highest_fnum(void)
-{
-    return top_file_num - 1;
-}
-
-/*
- * 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,
-	    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);
-    }
-    else
-    {
-	// Delete the converted lines.
-	while (curbuf->b_ml.ml_line_count > line_count)
-	    ml_delete(line_count);
-    }
-    // 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 == OK)
-	    unchanged(curbuf, FALSE, TRUE);
-
-	if (retval == OK)
-	{
-#ifdef FEAT_EVAL
-	    apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE,
-							      curbuf, &retval);
-#else
-	    apply_autocmds(EVENT_STDINREADPOST, NULL, NULL, FALSE, curbuf);
-#endif
-	}
-    }
-    return retval;
-}
-
-#if defined(FEAT_EVAL) || defined(PROTO)
-/*
- * Ensure buffer "buf" is loaded.  Does not trigger the swap-exists action.
- */
-    void
-buffer_ensure_loaded(buf_T *buf)
-{
-    if (buf->b_ml.ml_mfp != NULL)
-	return;
-
-    aco_save_T	aco;
-
-    // Make sure the buffer is in a window.  If not then skip it.
-    aucmd_prepbuf(&aco, buf);
-    if (curbuf == buf)
-    {
-	if (swap_exists_action != SEA_READONLY)
-	    swap_exists_action = SEA_NONE;
-	open_buffer(FALSE, NULL, 0);
-	aucmd_restbuf(&aco);
-    }
-}
-#endif
-
-/*
- * 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_arg)	    // extra flags for readfile()
-{
-    int		flags = flags_arg;
-    int		retval = OK;
-    bufref_T	old_curbuf;
-#ifdef FEAT_SYN_HL
-    long	old_tw = curbuf->b_p_tw;
-#endif
-    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, FALSE);
-	FOR_ALL_BUFFERS(curbuf)
-	    if (curbuf->b_ml.ml_mfp != NULL)
-		break;
-	// If there is no memfile at all, exit.
-	// This is OK, since there are no changes to lose.
-	if (curbuf == NULL)
-	{
-	    emsg(_(e_cannot_allocate_any_buffer_exiting));
-
-	    // Don't try to do any saving, with "curbuf" NULL almost nothing
-	    // will work.
-	    v_dying = 2;
-	    getout(2);
-	}
-
-	emsg(_(e_cannot_allocate_buffer_using_other_one));
-	enter_buffer(curbuf);
-#ifdef FEAT_SYN_HL
-	if (old_tw != curbuf->b_p_tw)
-	    check_colorcolumn(curwin);
-#endif
-	return FAIL;
-    }
-
-    // Do not sync this buffer yet, may first want to read the file.
-    if (curbuf->b_ml.ml_mfp != NULL)
-	curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
-
-    // 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;
-
-    // A buffer without an actual file should not use the buffer name to read a
-    // file.
-    if (bt_nofileread(curbuf))
-	flags |= READ_NOFILE;
-
-    // Read the file if there is one.
-    if (curbuf->b_ffname != NULL
-#ifdef FEAT_NETBEANS_INTG
-	    && netbeansReadFile
-#endif
-       )
-    {
-	int old_msg_silent = msg_silent;
-#ifdef UNIX
-	int save_bin = curbuf->b_p_bin;
-	int perm;
-#endif
-#ifdef FEAT_NETBEANS_INTG
-	int oldFire = netbeansFireChanges;
-
-	netbeansFireChanges = 0;
-#endif
-#ifdef UNIX
-	perm = mch_getperm(curbuf->b_ffname);
-	if (perm >= 0 && (S_ISFIFO(perm)
-		      || S_ISSOCK(perm)
-# 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;
-#ifdef FEAT_NETBEANS_INTG
-	netbeansFireChanges = oldFire;
-#endif
-	// 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);
-    }
-
-    // Can now sync this buffer in ml_sync_all().
-    if (curbuf->b_ml.ml_mfp != NULL
-	    && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC)
-	curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
-
-    // 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 autocommands.
-    //
-    // When reading stdin, the buffer contents always needs writing, so set
-    // the changed flag.  Unless in readonly mode: "ls | gview -".
-    // When interrupted and 'cpoptions' contains 'i' set changed flag.
-    if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
-		|| modified_was_set	// ":set modified" used in autocmd
-#ifdef FEAT_EVAL
-		|| (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
-#endif
-       )
-	changed();
-    else if (retval == OK && !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 = CHANGEDTICK(curbuf);
-    curbuf->b_last_changedtick_i = CHANGEDTICK(curbuf);
-    curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf);
-
-    // require "!" to overwrite the file, because it wasn't read completely
-#ifdef FEAT_EVAL
-    if (aborting())
-#else
-    if (got_int)
-#endif
-	curbuf->b_flags |= BF_READERR;
-
-#ifdef FEAT_FOLDING
-    // Need to update automatic folding.  Do this before the autocommands,
-    // they may use the fold info.
-    foldUpdateAll(curwin);
-#endif
-
-    // need to set w_topline, unless some autocommand already did that.
-    if (!(curwin->w_valid & VALID_TOPLINE))
-    {
-	curwin->w_topline = 1;
-#ifdef FEAT_DIFF
-	curwin->w_topfill = 0;
-#endif
-    }
-#ifdef FEAT_EVAL
-    apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval);
-#else
-    apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
-#endif
-
-    if (retval != OK)
-	return retval;
-
-    // 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, make sure it is in a window.
-	// If not then skip it.
-	aucmd_prepbuf(&aco, old_curbuf.br_buf);
-	if (curbuf == old_curbuf.br_buf)
-	{
-	    do_modelines(0);
-	    curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED);
-
-	    if ((flags & READ_NOWINENTER) == 0)
-#ifdef FEAT_EVAL
-		apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL,
-			FALSE, curbuf, &retval);
-#else
-	    apply_autocmds(EVENT_BUFWINENTER, NULL, NULL,
-		    FALSE, curbuf);
-#endif
-
-	    // restore curwin/curbuf and a few other things
-	    aucmd_restbuf(&aco);
-	}
-    }
-
-    return retval;
-}
-
-/*
- * Store "buf" in "bufref" and set the free count.
- */
-    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.
- */
-    int
-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;
-}
-
-/*
- * Return TRUE if "buf" points to a valid buffer (in the buffer list).
- * This can be slow if there are many buffers, prefer using bufref_valid().
- */
-    int
-buf_valid(buf_T *buf)
-{
-    buf_T	*bp;
-
-    // Assume that we more often have a recent buffer, start with the last
-    // one.
-    FOR_ALL_BUFS_FROM_LAST(bp)
-	if (bp == buf)
-	    return TRUE;
-    return FALSE;
-}
-
-/*
- * A hash table used to quickly lookup a buffer by its number.
- */
-static hashtab_T buf_hashtab;
-
-    static void
-buf_hashtab_add(buf_T *buf)
-{
-    sprintf((char *)buf->b_key, "%x", buf->b_fnum);
-    if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL)
-	emsg(_(e_buffer_cannot_be_registered));
-}
-
-    static void
-buf_hashtab_remove(buf_T *buf)
-{
-    hashitem_T *hi = hash_find(&buf_hashtab, buf->b_key);
-
-    if (!HASHITEM_EMPTY(hi))
-	hash_remove(&buf_hashtab, hi, "close buffer");
-}
-
-/*
- * Return TRUE when buffer "buf" can be unloaded.
- * Give an error message and return FALSE when the buffer is locked or the
- * screen is being redrawn and the buffer is in a window.
- */
-    static int
-can_unload_buffer(buf_T *buf)
-{
-    int	    can_unload = !buf->b_locked;
-
-    if (can_unload && updating_screen)
-    {
-	win_T	*wp;
-
-	FOR_ALL_WINDOWS(wp)
-	    if (wp->w_buffer == buf)
-	    {
-		can_unload = FALSE;
-		break;
-	    }
-    }
-    if (!can_unload)
-    {
-	char_u *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname;
-
-	semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str),
-				fname != NULL ? fname : (char_u *)"[No Name]");
-    }
-    return can_unload;
-}
-
-/*
- * Close the link to a buffer.
- * "action" is used when there is no longer a window for the buffer.
- * It can be:
- * 0			buffer becomes hidden
- * DOBUF_UNLOAD		buffer is unloaded
- * DOBUF_DELETE		buffer is unloaded and removed from buffer list
- * DOBUF_WIPE		buffer is unloaded and really deleted
- * DOBUF_WIPE_REUSE	idem, and add to buf_reuse list
- * When doing all but the first one on the current buffer, the caller should
- * get a new buffer very soon!
- *
- * The 'bufhidden' option can force freeing and deleting.
- *
- * When "abort_if_last" is TRUE then do not close the buffer if autocommands
- * cause there to be only one window with this buffer.  e.g. when ":quit" is
- * supposed to close the window but autocommands close all other windows.
- *
- * When "ignore_abort" is TRUE don't abort even when aborting() returns TRUE.
- *
- * Return TRUE when we got to the end and b_nwindows was decremented.
- */
-    int
-close_buffer(
-    win_T	*win,		// if not NULL, set b_last_cursor
-    buf_T	*buf,
-    int		action,
-    int		abort_if_last,
-    int		ignore_abort)
-{
-    int		is_curbuf;
-    int		nwindows;
-    bufref_T	bufref;
-    int		is_curwin = (curwin != NULL && curwin->w_buffer == buf);
-    win_T	*the_curwin = curwin;
-    tabpage_T	*the_curtab = curtab;
-    int		unload_buf = (action != 0);
-    int		wipe_buf = (action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
-    int		del_buf = (action == DOBUF_DEL || wipe_buf);
-
-    CHECK_CURBUF;
-
-    // Force unloading or deleting when 'bufhidden' says so.
-    // The caller must take care of NOT deleting/freeing when 'bufhidden' is
-    // "hide" (otherwise we could never free or delete a buffer).
-    if (buf->b_p_bh[0] == 'd')		// 'bufhidden' == "delete"
-    {
-	del_buf = TRUE;
-	unload_buf = TRUE;
-    }
-    else if (buf->b_p_bh[0] == 'w')	// 'bufhidden' == "wipe"
-    {
-	del_buf = TRUE;
-	unload_buf = TRUE;
-	wipe_buf = TRUE;
-    }
-    else if (buf->b_p_bh[0] == 'u')	// 'bufhidden' == "unload"
-	unload_buf = TRUE;
-
-#ifdef FEAT_TERMINAL
-    // depending on how we get here b_nwindows may already be zero
-    if (bt_terminal(buf) && (buf->b_nwindows <= 1 || del_buf))
-    {
-	CHECK_CURBUF;
-	if (term_job_running(buf->b_term))
-	{
-	    if (wipe_buf || unload_buf)
-	    {
-		if (!can_unload_buffer(buf))
-		    return FALSE;
-
-		// Wiping out or unloading a terminal buffer kills the job.
-		free_terminal(buf);
-
-		// A terminal buffer is wiped out when job has finished.
-		del_buf = TRUE;
-		unload_buf = TRUE;
-		wipe_buf = TRUE;
-	    }
-	    else
-	    {
-		// The job keeps running, hide the buffer.
-		del_buf = FALSE;
-		unload_buf = FALSE;
-	    }
-	}
-	else if (buf->b_p_bh[0] == 'h' && !del_buf)
-	{
-	    // Hide a terminal buffer.
-	    unload_buf = FALSE;
-	}
-	else
-	{
-	    if (del_buf || unload_buf)
-	    {
-		// A terminal buffer is wiped out if the job has finished.
-		// We only do this when there's an intention to unload the
-		// buffer. This way, :hide and other similar commands won't
-		// wipe the buffer.
-		del_buf = TRUE;
-		unload_buf = TRUE;
-		wipe_buf = TRUE;
-	    }
-	}
-	CHECK_CURBUF;
-    }
-#endif
-
-    // Disallow deleting the buffer when it is locked (already being closed or
-    // halfway a command that relies on it). Unloading is allowed.
-    if ((del_buf || wipe_buf) && !can_unload_buffer(buf))
-	return FALSE;
-
-    // check no autocommands closed the window
-    if (win != NULL && 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);
-    }
-
-    set_bufref(&bufref, buf);
-
-    // When the buffer is no longer in a window, trigger BufWinLeave
-    if (buf->b_nwindows == 1)
-    {
-	++buf->b_locked;
-	++buf->b_locked_split;
-	if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
-								  FALSE, buf)
-		&& !bufref_valid(&bufref))
-	{
-	    // Autocommands deleted the buffer.
-aucmd_abort:
-	    emsg(_(e_autocommands_caused_command_to_abort));
-	    return FALSE;
-	}
-	--buf->b_locked;
-	--buf->b_locked_split;
-	if (abort_if_last && one_window())
-	    // Autocommands made this the only window.
-	    goto aucmd_abort;
-
-	// When the buffer becomes hidden, but is not unloaded, trigger
-	// BufHidden
-	if (!unload_buf)
-	{
-	    ++buf->b_locked;
-	    ++buf->b_locked_split;
-	    if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
-								  FALSE, buf)
-		    && !bufref_valid(&bufref))
-		// Autocommands deleted the buffer.
-		goto aucmd_abort;
-	    --buf->b_locked;
-	    --buf->b_locked_split;
-	    if (abort_if_last && one_window())
-		// Autocommands made this the only window.
-		goto aucmd_abort;
-	}
-#ifdef FEAT_EVAL
-	// autocmds may abort script processing
-	if (!ignore_abort && aborting())
-	    return FALSE;
-#endif
-    }
-
-    // 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();
-    }
-
-    nwindows = buf->b_nwindows;
-
-    // decrease the link count from windows (unless not in any window)
-    if (buf->b_nwindows > 0)
-	--buf->b_nwindows;
-
-#ifdef FEAT_DIFF
-    if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0)
-	diff_buf_delete(buf);	// Clear 'diff' for hidden buffer.
-#endif
-
-    // Return when a window is displaying the buffer or when it's not
-    // unloaded.
-    if (buf->b_nwindows > 0 || !unload_buf)
-	return FALSE;
-
-    // Always remove the buffer when there is no file name.
-    if (buf->b_ffname == NULL)
-	del_buf = TRUE;
-
-    // When closing the current buffer stop Visual mode before freeing
-    // anything.
-    if (buf == curbuf && VIsual_active
-#if defined(EXITFREE)
-	    && !entered_free_all_mem
-#endif
-	    )
-	end_visual_mode();
-
-    // Free all things allocated for this buffer.
-    // Also calls the "BufDelete" autocommands when del_buf is TRUE.
-    //
-    // Remember if we are closing the current buffer.  Restore the number of
-    // windows, so that autocommands in buf_freeall() don't get confused.
-    is_curbuf = (buf == curbuf);
-    buf->b_nwindows = nwindows;
-
-    buf_freeall(buf, (del_buf ? BFA_DEL : 0)
-		   + (wipe_buf ? BFA_WIPE : 0)
-		   + (ignore_abort ? BFA_IGNORE_ABORT : 0));
-
-    // Autocommands may have deleted the buffer.
-    if (!bufref_valid(&bufref))
-	return FALSE;
-#ifdef FEAT_EVAL
-    // autocmds may abort script processing
-    if (!ignore_abort && aborting())
-	return FALSE;
-#endif
-
-    // 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 FALSE;
-
-    if (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;
-
-    /*
-     * Remove the buffer from the list.
-     */
-    if (wipe_buf)
-    {
-	// Do not wipe out the buffer if it is used in a window.
-	if (buf->b_nwindows > 0)
-	    return FALSE;
-
-	if (action == DOBUF_WIPE_REUSE)
-	{
-	    // we can re-use this buffer number, store it
-	    if (buf_reuse.ga_itemsize == 0)
-		ga_init2(&buf_reuse, sizeof(int), 50);
-	    if (ga_grow(&buf_reuse, 1) == OK)
-		((int *)buf_reuse.ga_data)[buf_reuse.ga_len++] = buf->b_fnum;
-	}
-	if (buf->b_sfname != buf->b_ffname)
-	    VIM_CLEAR(buf->b_sfname);
-	else
-	    buf->b_sfname = NULL;
-	VIM_CLEAR(buf->b_ffname);
-	if (buf->b_prev == NULL)
-	    firstbuf = buf->b_next;
-	else
-	    buf->b_prev->b_next = buf->b_next;
-	if (buf->b_next == NULL)
-	    lastbuf = buf->b_prev;
-	else
-	    buf->b_next->b_prev = buf->b_prev;
-	free_buffer(buf);
-    }
-    else
-    {
-	if (del_buf)
-	{
-	    // Free all internal variables and reset option values, to make
-	    // ":bdel" compatible with Vim 5.7.
-	    free_buffer_stuff(buf, TRUE);
-
-	    // Make it look like a new buffer.
-	    buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED;
-
-	    // Init the options when loaded again.
-	    buf->b_p_initialized = FALSE;
-	}
-	buf_clear_file(buf);
-	if (del_buf)
-	    buf->b_p_bl = FALSE;
-    }
-    // NOTE: at this point "curbuf" may be invalid!
-    return TRUE;
-}
-
-/*
- * 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_shortname = FALSE;
-    buf->b_p_eof = FALSE;
-    buf->b_start_eof = FALSE;
-    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
-#ifdef FEAT_NETBEANS_INTG
-    netbeans_deleted_all_lines(buf);
-#endif
-}
-
-/*
- * buf_freeall() - free all things allocated for a buffer that are related to
- * the file.  Careful: get here with "curwin" NULL when exiting.
- * 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
- * BFA_IGNORE_ABORT  don't abort even when aborting() returns TRUE
- */
-    void
-buf_freeall(buf_T *buf, int flags)
-{
-    int		is_curbuf = (buf == curbuf);
-    bufref_T	bufref;
-    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;
-    ++buf->b_locked_split;
-    set_bufref(&bufref, buf);
-    if (buf->b_ml.ml_mfp != NULL)
-    {
-	if (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)
-    {
-	if (apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname,
-								   FALSE, buf)
-		&& !bufref_valid(&bufref))
-	    // autocommands deleted the buffer
-	    return;
-    }
-    if (flags & BFA_WIPE)
-    {
-	if (apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname,
-								  FALSE, buf)
-		&& !bufref_valid(&bufref))
-	    // autocommands deleted the buffer
-	    return;
-    }
-    --buf->b_locked;
-    --buf->b_locked_split;
-
-    // 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();
-    }
-
-#ifdef FEAT_EVAL
-    // autocmds may abort script processing
-    if ((flags & BFA_IGNORE_ABORT) == 0 && aborting())
-	return;
-#endif
-
-    // 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;
-#ifdef FEAT_DIFF
-    diff_buf_delete(buf);	    // Can't use 'diff' for unloaded buffer.
-#endif
-#ifdef FEAT_SYN_HL
-    // Remove any ownsyntax, unless exiting.
-    if (curwin != NULL && curwin->w_buffer == buf)
-	reset_synblock(curwin);
-#endif
-
-#ifdef FEAT_FOLDING
-    // No folds in an empty buffer.
-    {
-	win_T		*win;
-	tabpage_T	*tp;
-
-	FOR_ALL_TAB_WINDOWS(tp, win)
-	    if (win->w_buffer == buf)
-		clearFolding(win);
-    }
-#endif
-
-#ifdef FEAT_TCL
-    tcl_buffer_free(buf);
-#endif
-    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
-    }
-#ifdef FEAT_SYN_HL
-    syntax_clear(&buf->b_s);	    // reset syntax info
-#endif
-#ifdef FEAT_PROP_POPUP
-    clear_buf_prop_types(buf);
-#endif
-    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)
-{
-    ++buf_free_count;
-    free_buffer_stuff(buf, TRUE);
-#ifdef FEAT_EVAL
-    // b:changedtick uses an item in buf_T, remove it now
-    dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di, "free buffer");
-    unref_var_dict(buf->b_vars);
-    remove_listeners(buf);
-#endif
-#ifdef FEAT_LUA
-    lua_buffer_free(buf);
-#endif
-#ifdef FEAT_MZSCHEME
-    mzscheme_buffer_free(buf);
-#endif
-#ifdef FEAT_PERL
-    perl_buf_free(buf);
-#endif
-#ifdef FEAT_PYTHON
-    python_buffer_free(buf);
-#endif
-#ifdef FEAT_PYTHON3
-    python3_buffer_free(buf);
-#endif
-#ifdef FEAT_RUBY
-    ruby_buffer_free(buf);
-#endif
-#ifdef FEAT_JOB_CHANNEL
-    channel_buffer_free(buf);
-#endif
-#ifdef FEAT_TERMINAL
-    free_terminal(buf);
-#endif
-#ifdef FEAT_JOB_CHANNEL
-    vim_free(buf->b_prompt_text);
-    free_callback(&buf->b_prompt_callback);
-    free_callback(&buf->b_prompt_interrupt);
-#endif
-
-    buf_hashtab_remove(buf);
-
-    aubuflocal_remove(buf);
-
-    if (autocmd_busy)
-    {
-	// Do not free the buffer structure while autocommands are executing,
-	// it's still needed. Free it when autocmd_busy is reset.
-	buf->b_next = au_pending_free_buf;
-	au_pending_free_buf = buf;
-    }
-    else
-    {
-	vim_free(buf);
-	if (curbuf == buf)
-	    curbuf = NULL;  // make clear it's not to be used
-    }
-}
-
-/*
- * Initializes b:changedtick.
- */
-    static void
-init_changedtick(buf_T *buf)
-{
-    dictitem_T *di = (dictitem_T *)&buf->b_ct_di;
-
-    di->di_flags = DI_FLAGS_FIX | DI_FLAGS_RO;
-    di->di_tv.v_type = VAR_NUMBER;
-    di->di_tv.v_lock = VAR_FIXED;
-    di->di_tv.vval.v_number = 0;
-
-#ifdef FEAT_EVAL
-    STRCPY(buf->b_ct_di.di_key, "changedtick");
-    (void)dict_add(buf->b_vars, di);
-#endif
-}
-
-/*
- * 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;
-	free_wininfo(wip);
-    }
-}
-
-/*
- * Free stuff in the buffer for ":bdel" and when wiping out the buffer.
- */
-    static void
-free_buffer_stuff(
-    buf_T	*buf,
-    int		free_options)		// free options as well
-{
-    if (free_options)
-    {
-	clear_wininfo(buf);		// including window-local options
-	free_buf_options(buf, TRUE);
-#ifdef FEAT_SPELL
-	ga_clear(&buf->b_s.b_langp);
-#endif
-    }
-#ifdef FEAT_EVAL
-    {
-	varnumber_T tick = CHANGEDTICK(buf);
-
-	vars_clear(&buf->b_vars->dv_hashtab); // free all buffer variables
-	hash_init(&buf->b_vars->dv_hashtab);
-	init_changedtick(buf);
-	CHANGEDTICK(buf) = tick;
-	remove_listeners(buf);
-    }
-#endif
-    uc_clear(&buf->b_ucmds);		// clear local user commands
-#ifdef FEAT_SIGNS
-    buf_delete_signs(buf, (char_u *)"*");	// delete any signs
-#endif
-#ifdef FEAT_NETBEANS_INTG
-    netbeans_file_killed(buf);
-#endif
-#ifdef FEAT_PROP_POPUP
-    ga_clear_strings(&buf->b_textprop_text);
-#endif
-    map_clear_mode(buf, MAP_ALL_MODES, TRUE, FALSE);  // clear local mappings
-    map_clear_mode(buf, MAP_ALL_MODES, TRUE, TRUE);   // clear local abbrevs
-    VIM_CLEAR(buf->b_start_fenc);
-}
-
-/*
- * Free one wininfo_T.
- */
-    void
-free_wininfo(wininfo_T *wip)
-{
-    if (wip->wi_optset)
-    {
-	clear_winopt(&wip->wi_opt);
-#ifdef FEAT_FOLDING
-	deleteFoldRecurse(&wip->wi_folds);
-#endif
-    }
-    vim_free(wip);
-}
-
-/*
- * Go to another buffer.  Handles the result of the ATTENTION dialog.
- */
-    void
-goto_buffer(
-    exarg_T	*eap,
-    int		start,
-    int		dir,
-    int		count)
-{
-    bufref_T	old_curbuf;
-    int		save_sea = swap_exists_action;
-
-    set_bufref(&old_curbuf, curbuf);
-
-    if (swap_exists_action == SEA_NONE)
-	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')
-    {
-#if defined(FEAT_EVAL)
-	cleanup_T   cs;
-
-	// Reset the error/interrupt/exception state here so that
-	// aborting() returns FALSE when closing a window.
-	enter_cleanup(&cs);
-#endif
-
-	// Quitting means closing the split window, nothing else.
-	win_close(curwin, TRUE);
-	swap_exists_action = save_sea;
-	swap_exists_did_quit = TRUE;
-
-#if defined(FEAT_EVAL)
-	// Restore the error/interrupt/exception state if not discarded by a
-	// new aborting error, interrupt, or uncaught exception.
-	leave_cleanup(&cs);
-#endif
-    }
-    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.
- */
-    void
-handle_swap_exists(bufref_T *old_curbuf)
-{
-#if defined(FEAT_EVAL)
-    cleanup_T	cs;
-#endif
-#ifdef FEAT_SYN_HL
-    long	old_tw = curbuf->b_p_tw;
-#endif
-    buf_T	*buf;
-
-    if (swap_exists_action == SEA_QUIT)
-    {
-#if defined(FEAT_EVAL)
-	// Reset the error/interrupt/exception state here so that
-	// aborting() returns FALSE when closing a buffer.
-	enter_cleanup(&cs);
-#endif
-
-	// 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, FALSE);
-	if (old_curbuf == NULL || !bufref_valid(old_curbuf)
-					      || old_curbuf->br_buf == curbuf)
-	{
-	    // Block autocommands here because curwin->w_buffer is NULL.
-	    block_autocmds();
-	    buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
-	    unblock_autocmds();
-	}
-	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;
-
-#ifdef FEAT_SYN_HL
-	    if (old_tw != curbuf->b_p_tw)
-		check_colorcolumn(curwin);
-#endif
-	}
-	// If "old_curbuf" is NULL we are in big trouble here...
-
-#if defined(FEAT_EVAL)
-	// Restore the error/interrupt/exception state if not discarded by a
-	// new aborting error, interrupt, or uncaught exception.
-	leave_cleanup(&cs);
-#endif
-    }
-    else if (swap_exists_action == SEA_RECOVER)
-    {
-#if defined(FEAT_EVAL)
-	// Reset the error/interrupt/exception state here so that
-	// aborting() returns FALSE when closing a buffer.
-	enter_cleanup(&cs);
-#endif
-
-	// 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);
-
-#if defined(FEAT_EVAL)
-	// Restore the error/interrupt/exception state if not discarded by a
-	// new aborting error, interrupt, or uncaught exception.
-	leave_cleanup(&cs);
-#endif
-    }
-    swap_exists_action = SEA_NONE;
-}
-
-/*
- * 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;
-    bufref_T bufref;
-
-    if (action == DOBUF_UNLOAD)
-    {
-	emsg(_(e_cannot_unload_last_buffer));
-	return FAIL;
-    }
-
-    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, 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
- * action == DOBUF_WIPE_REUSE idem, and add number to "buf_reuse"
- *
- * 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.
- */
-    static int
-do_buffer_ext(
-    int		action,
-    int		start,
-    int		dir,		// FORWARD or BACKWARD
-    int		count,		// buffer number or number of buffers
-    int		flags)		// DOBUF_FORCEIT etc.
-{
-    buf_T	*buf;
-    buf_T	*bp;
-    int		unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
-			|| action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
-
-    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(_(e_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(_(e_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)
-		semsg(_(e_buffer_nr_does_not_exist), count);
-	}
-	else if (dir == FORWARD)
-	    emsg(_(e_cannot_go_beyond_last_buffer));
-	else
-	    emsg(_(e_cannot_go_before_first_buffer));
-	return FAIL;
-    }
-#ifdef FEAT_PROP_POPUP
-    if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf))
-	return OK;
-#endif
-    if ((action == DOBUF_GOTO || action == DOBUF_SPLIT)
-						  && (buf->b_flags & BF_DUMMY))
-    {
-	// disallow navigating to the dummy buffer
-	semsg(_(e_buffer_nr_does_not_exist), count);
-	return FAIL;
-    }
-
-#ifdef FEAT_GUI
-    need_mouse_correct = TRUE;
-#endif
-
-    /*
-     * delete buffer "buf" from memory and/or the list
-     */
-    if (unload)
-    {
-	int	forward;
-	bufref_T bufref;
-
-	if (!can_unload_buffer(buf))
-	    return FAIL;
-
-	set_bufref(&bufref, buf);
-
-	// When unloading or deleting a buffer that's already unloaded and
-	// unlisted: fail silently.
-	if (action != DOBUF_WIPE && action != DOBUF_WIPE_REUSE
-				   && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
-	    return FAIL;
-
-	if ((flags & DOBUF_FORCEIT) == 0 && bufIsChanged(buf))
-	{
-#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
-	    if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
-	    {
-# ifdef FEAT_TERMINAL
-		if (term_job_running(buf->b_term))
-		{
-		    if (term_confirm_stop(buf) == FAIL)
-			return FAIL;
-		}
-		else
-# endif
-		{
-		    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
-#endif
-	    {
-		no_write_message_buf(buf);
-		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.
-	FOR_ALL_BUFFERS(bp)
-	    if (bp->b_p_bl && bp != buf)
-		break;
-	if (bp == NULL && buf == curbuf)
-	    return empty_curbuf(TRUE, (flags & DOBUF_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, 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)
-		{
-		    // Skip current and unlisted bufs.  Also skip a quickfix
-		    // buffer, it might be deleted soon.
-		    if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf))
-			buf = NULL;
-		    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
-			    && !bt_quickfix(buf))
-		{
-		    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(buf)
-		if (buf->b_p_bl && buf != curbuf && !bt_quickfix(buf))
-		    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 (bt_quickfix(buf))
-		buf = NULL;
-	}
-    }
-
-    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, (flags & DOBUF_FORCEIT), action);
-    }
-
-    /*
-     * make "buf" the current buffer
-     */
-    if (action == DOBUF_SPLIT)	    // split window first
-    {
-	// If 'switchbuf' is set jump to the window containing "buf".
-	if (swbuf_goto_win_with_buf(buf) != NULL)
-	    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, (flags & DOBUF_FORCEIT)))
-    {
-#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
-	if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
-	{
-# ifdef FEAT_TERMINAL
-	    if (term_job_running(curbuf->b_term))
-	    {
-		if (term_confirm_stop(curbuf) == FAIL)
-		    return FAIL;
-		// Manually kill the terminal here because this command will
-		// hide it otherwise.
-		free_terminal(curbuf);
-	    }
-	    else
-# endif
-	    {
-		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;
-		}
-	    }
-	}
-	else
-#endif
-	{
-	    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 defined(FEAT_EVAL)
-    if (aborting())	    // autocmds may abort script processing
-	return FAIL;
-#endif
-
-    return OK;
-}
-
-    int
-do_buffer(
-    int		action,
-    int		start,
-    int		dir,		// FORWARD or BACKWARD
-    int		count,		// buffer number or number of buffers
-    int		forceit)	// TRUE when using !
-{
-    return do_buffer_ext(action, start, dir, count,
-						  forceit ? DOBUF_FORCEIT : 0);
-}
-
-/*
- * 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 *
-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	*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 ex_errmsg(e_trailing_characters_str, arg);
-	    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_ext(command, DOBUF_FIRST, FORWARD, bnr,
-			  DOBUF_NOPOPUP | (forceit ? DOBUF_FORCEIT : 0)) == OK)
-		++deleted;
-
-	    // find next buffer number to delete/unload
-	    if (addr_count == 2)
-	    {
-		if (++bnr > end_bnr)
-		    break;
-	    }
-	    else    // addr_count == 1
-	    {
-		arg = skipwhite(arg);
-		if (*arg == NUL)
-		    break;
-		if (!VIM_ISDIGIT(*arg))
-		{
-		    p = skiptowhite_esc(arg);
-		    bnr = buflist_findpat(arg, p,
-			  command == DOBUF_WIPE || command == DOBUF_WIPE_REUSE,
-								FALSE, FALSE);
-		    if (bnr < 0)	    // failed
-			break;
-		    arg = p;
-		}
-		else
-		    bnr = getdigits(&arg);
-	    }
-	}
-	if (!got_int && do_current && do_buffer(command, DOBUF_FIRST,
-					  FORWARD, do_current, forceit) == OK)
-	    ++deleted;
-
-	if (deleted == 0)
-	{
-	    if (command == DOBUF_UNLOAD)
-		STRCPY(IObuff, _(e_no_buffers_were_unloaded));
-	    else if (command == DOBUF_DEL)
-		STRCPY(IObuff, _(e_no_buffers_were_deleted));
-	    else
-		STRCPY(IObuff, _(e_no_buffers_were_wiped_out));
-	    errormsg = (char *)IObuff;
-	}
-	else if (deleted >= p_report)
-	{
-	    if (command == DOBUF_UNLOAD)
-		smsg(NGETTEXT("%d buffer unloaded",
-			    "%d buffers unloaded", deleted), deleted);
-	    else if (command == DOBUF_DEL)
-		smsg(NGETTEXT("%d buffer deleted",
-			    "%d buffers deleted", deleted), deleted);
-	    else
-		smsg(NGETTEXT("%d buffer wiped out",
-			    "%d buffers wiped out", deleted), deleted);
-	}
-    }
-
-    return errormsg;
-}
-
-/*
- * 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
- * DOBUF_WIPE_REUSE wipe it out and add to "buf_reuse"
- */
-    void
-set_curbuf(buf_T *buf, int action)
-{
-    buf_T	*prevbuf;
-    int		unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
-			|| action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
-#ifdef FEAT_SYN_HL
-    long	old_tw = curbuf->b_p_tw;
-#endif
-    bufref_T	newbufref;
-    bufref_T	prevbufref;
-    int		valid;
-
-    setpcmark();
-    if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
-	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;
-    set_bufref(&prevbufref, prevbuf);
-    set_bufref(&newbufref, buf);
-
-    // Autocommands may delete the current buffer and/or the buffer we want 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)
-#ifdef FEAT_EVAL
-		&& !aborting()
-#endif
-	       ))
-    {
-#ifdef FEAT_SYN_HL
-	if (prevbuf == curwin->w_buffer)
-	    reset_synblock(curwin);
-#endif
-	if (unload)
-	    close_windows(prevbuf, FALSE);
-#if defined(FEAT_EVAL)
-	if (bufref_valid(&prevbufref) && !aborting())
-#else
-	if (bufref_valid(&prevbufref))
-#endif
-	{
-	    win_T  *previouswin = curwin;
-
-	    // Do not sync when in Insert mode and the buffer is open in
-	    // another window, might be a timer doing something in another
-	    // window.
-	    if (prevbuf == curbuf
-		    && ((State & MODE_INSERT) == 0 || curbuf->b_nwindows <= 1))
-		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, 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
-    valid = buf_valid(buf);
-    if ((valid && buf != curbuf
-#ifdef FEAT_EVAL
-		&& !aborting()
-#endif
-	) || curwin->w_buffer == NULL)
-    {
-	// If the buffer is not valid but curwin->w_buffer is NULL we must
-	// enter some buffer.  Using the last one is hopefully OK.
-	if (!valid)
-	    enter_buffer(lastbuf);
-	else
-	    enter_buffer(buf);
-#ifdef FEAT_SYN_HL
-	if (old_tw != curbuf->b_p_tw)
-	    check_colorcolumn(curwin);
-#endif
-    }
-}
-
-/*
- * Enter a new current buffer.
- * Old curbuf must have been abandoned already!  This also means "curbuf" may
- * be pointing to freed memory.
- */
-    static void
-enter_buffer(buf_T *buf)
-{
-    // when closing the current buffer stop Visual mode
-    if (VIsual_active
-#if defined(EXITFREE)
-	    && !entered_free_all_mem
-#endif
-	    )
-	end_visual_mode();
-
-    // Get the buffer in the current window.
-    curwin->w_buffer = buf;
-    curbuf = buf;
-    ++curbuf->b_nwindows;
-
-    // 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);
-#ifdef FEAT_FOLDING
-    else
-	// Remove all folds in the window.
-	clearFolding(curwin);
-    foldUpdateAll(curwin);	// update folds (later).
-#endif
-
-#ifdef FEAT_DIFF
-    if (curwin->w_p_diff)
-	diff_buf_add(curbuf);
-#endif
-
-#ifdef FEAT_SYN_HL
-    curwin->w_s = &(curbuf->b_s);
-#endif
-
-    // 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;
-
-    buflist_setfpos(curbuf, curwin, curbuf->b_last_cursor.lnum,
-					      curbuf->b_last_cursor.col, TRUE);
-
-    // 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;
-#ifdef FEAT_DIFF
-	curwin->w_topfill = 0;
-#endif
-	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, FALSE);	// redisplay at correct position
-
-#ifdef FEAT_NETBEANS_INTG
-    // Send fileOpened event because we've changed buffers.
-    netbeans_file_activated(curbuf);
-#endif
-
-    // Change directories when the 'acd' option is set.
-    DO_AUTOCHDIR;
-
-#ifdef FEAT_KEYMAP
-    if (curbuf->b_kmap_state & KEYMAP_INIT)
-	(void)keymap_init();
-#endif
-#ifdef FEAT_SPELL
-    // 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)parse_spelllang(curwin);
-#endif
-#ifdef FEAT_VIMINFO
-    curbuf->b_last_used = vim_time();
-#endif
-
-    redraw_later(UPD_NOT_VALID);
-}
-
-#if defined(FEAT_AUTOCHDIR) || defined(PROTO)
-/*
- * Change to the directory of the current buffer.
- * Don't do this while still starting up.
- */
-    void
-do_autochdir(void)
-{
-    if ((starting == 0 || test_autochdir)
-	    && curbuf->b_ffname != NULL
-	    && vim_chdirfile(curbuf->b_ffname, "auto") == OK)
-    {
-	shorten_fnames(TRUE);
-	last_chdir_reason = "autochdir";
-    }
-}
-#endif
-
-    static void
-no_write_message_buf(buf_T *buf UNUSED)
-{
-#ifdef FEAT_TERMINAL
-    if (term_job_running(buf->b_term))
-	emsg(_(e_job_still_running_add_bang_to_end_the_job));
-    else
-#endif
-	semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override),
-		buf->b_fnum);
-}
-
-    void
-no_write_message(void)
-{
-#ifdef FEAT_TERMINAL
-    if (term_job_running(curbuf->b_term))
-	emsg(_(e_job_still_running_add_bang_to_end_the_job));
-    else
-#endif
-	emsg(_(e_no_write_since_last_change_add_bang_to_override));
-}
-
-    void
-no_write_message_nobang(buf_T *buf UNUSED)
-{
-#ifdef FEAT_TERMINAL
-    if (term_job_running(buf->b_term))
-	emsg(_(e_job_still_running));
-    else
-#endif
-	emsg(_(e_no_write_since_last_change));
-}
-
-/*
- * functions for dealing with the buffer list
- */
-
-/*
- * Return TRUE if the current buffer is empty, unnamed, unmodified and used in
- * only one window.  That means it can be re-used.
- */
-    int
-curbuf_reusable(void)
-{
-    return (curbuf != NULL
-	&& curbuf->b_ffname == NULL
-	&& curbuf->b_nwindows <= 1
-	&& (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())
-	&& !bt_quickfix(curbuf)
-	&& !curbufIsChanged());
-}
-
-/*
- * Add a file name to the buffer list.  Return a pointer to the buffer.
- * If the same file name already exists return a pointer to that buffer.
- * If it does not exist, or if fname == NULL, a new entry is created.
- * If (flags & BLN_CURBUF) is TRUE, may use current buffer.
- * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list.
- * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer.
- * If (flags & BLN_NEW) is TRUE, don't use an existing buffer.
- * If (flags & BLN_NOOPT) is TRUE, don't copy options from the current buffer
- *				    if the buffer already exists.
- * If (flags & BLN_REUSE) is TRUE, may use buffer number from "buf_reuse".
- * This is the ONLY way to create a new buffer.
- */
-    buf_T *
-buflist_new(
-    char_u	*ffname_arg,	// full path of fname or relative
-    char_u	*sfname_arg,	// short fname or NULL
-    linenr_T	lnum,		// preferred cursor line
-    int		flags)		// BLN_ defines
-{
-    char_u	*ffname = ffname_arg;
-    char_u	*sfname = sfname_arg;
-    buf_T	*buf;
-#ifdef UNIX
-    stat_T	st;
-#endif
-
-    if (top_file_num == 1)
-	hash_init(&buf_hashtab);
-
-    fname_expand(curbuf, &ffname, &sfname);	// will allocate ffname
-
-    /*
-     * If the file name already exists in the list, update the entry.
-     */
-#ifdef UNIX
-    // On Unix we can use inode numbers when the file exists.  Works better
-    // for hard links.
-    if (sfname == NULL || mch_stat((char *)sfname, &st) < 0)
-	st.st_dev = (dev_T)-1;
-#endif
-    if (ffname != NULL && !(flags & (BLN_DUMMY | BLN_NEW)) && (buf =
-#ifdef UNIX
-		buflist_findname_stat(ffname, &st)
-#else
-		buflist_findname(ffname)
-#endif
-		) != NULL)
-    {
-	vim_free(ffname);
-	if (lnum != 0)
-	    buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin,
-						      lnum, (colnr_T)0, FALSE);
-
-	if ((flags & BLN_NOOPT) == 0)
-	    // copy the options now, if 'cpo' doesn't have 's' and not done
-	    // already
-	    buf_copy_options(buf, 0);
-
-	if ((flags & BLN_LISTED) && !buf->b_p_bl)
-	{
-	    bufref_T bufref;
-
-	    buf->b_p_bl = TRUE;
-	    set_bufref(&bufref, buf);
-	    if (!(flags & BLN_DUMMY))
-	    {
-		if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf)
-			&& !bufref_valid(&bufref))
-		    return NULL;
-	    }
-	}
-	return buf;
-    }
-
-    /*
-     * If the current buffer has no name and no contents, use the current
-     * buffer.	Otherwise: Need to allocate a new buffer structure.
-     *
-     * This is the ONLY place where a new buffer structure is allocated!
-     * (A spell file buffer is allocated in spell.c, but that's not a normal
-     * buffer.)
-     */
-    buf = NULL;
-    if ((flags & BLN_CURBUF) && curbuf_reusable())
-    {
-	buf = curbuf;
-	// It's like this buffer is deleted.  Watch out for autocommands that
-	// change curbuf!  If that happens, allocate a new buffer anyway.
-	buf_freeall(buf, BFA_WIPE | BFA_DEL);
-	if (buf != curbuf)   // autocommands deleted the buffer!
-	    return NULL;
-#ifdef FEAT_EVAL
-	if (aborting())		// autocmds may abort script processing
-	{
-	    vim_free(ffname);
-	    return NULL;
-	}
-#endif
-    }
-    if (buf != curbuf || curbuf == NULL)
-    {
-	buf = ALLOC_CLEAR_ONE(buf_T);
-	if (buf == NULL)
-	{
-	    vim_free(ffname);
-	    return NULL;
-	}
-#ifdef FEAT_EVAL
-	// init b: variables
-	buf->b_vars = dict_alloc_id(aid_newbuf_bvars);
-	if (buf->b_vars == NULL)
-	{
-	    vim_free(ffname);
-	    vim_free(buf);
-	    return NULL;
-	}
-	init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
-#endif
-	init_changedtick(buf);
-    }
-
-    if (ffname != NULL)
-    {
-	buf->b_ffname = ffname;
-	buf->b_sfname = vim_strsave(sfname);
-    }
-
-    clear_wininfo(buf);
-    buf->b_wininfo = ALLOC_CLEAR_ONE(wininfo_T);
-
-    if ((ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL))
-	    || buf->b_wininfo == NULL)
-    {
-	if (buf->b_sfname != buf->b_ffname)
-	    VIM_CLEAR(buf->b_sfname);
-	else
-	    buf->b_sfname = NULL;
-	VIM_CLEAR(buf->b_ffname);
-	if (buf != curbuf)
-	    free_buffer(buf);
-	return NULL;
-    }
-
-    if (buf == curbuf)
-    {
-	free_buffer_stuff(buf, FALSE);	// delete local variables et al.
-
-	// Init the options.
-	buf->b_p_initialized = FALSE;
-	buf_copy_options(buf, BCO_ENTER);
-
-#ifdef FEAT_KEYMAP
-	// need to reload lmaps and set b:keymap_name
-	curbuf->b_kmap_state |= KEYMAP_INIT;
-#endif
-    }
-    else
-    {
-	// put the new buffer at the end of the buffer list
-	buf->b_next = NULL;
-	if (firstbuf == NULL)		// buffer list is empty
-	{
-	    buf->b_prev = NULL;
-	    firstbuf = buf;
-	}
-	else				// append new buffer at end of list
-	{
-	    lastbuf->b_next = buf;
-	    buf->b_prev = lastbuf;
-	}
-	lastbuf = buf;
-
-	if ((flags & BLN_REUSE) && buf_reuse.ga_len > 0)
-	{
-	    // Recycle a previously used buffer number.  Used for buffers which
-	    // are normally hidden, e.g. in a popup window.  Avoids that the
-	    // buffer number grows rapidly.
-	    --buf_reuse.ga_len;
-	    buf->b_fnum = ((int *)buf_reuse.ga_data)[buf_reuse.ga_len];
-
-	    // Move buffer to the right place in the buffer list.
-	    while (buf->b_prev != NULL && buf->b_fnum < buf->b_prev->b_fnum)
-	    {
-		buf_T	*prev = buf->b_prev;
-
-		prev->b_next = buf->b_next;
-		if (prev->b_next != NULL)
-		    prev->b_next->b_prev = prev;
-		buf->b_next = prev;
-		buf->b_prev = prev->b_prev;
-		if (buf->b_prev != NULL)
-		    buf->b_prev->b_next = buf;
-		prev->b_prev = buf;
-		if (lastbuf == buf)
-		    lastbuf = prev;
-		if (firstbuf == prev)
-		    firstbuf = buf;
-	    }
-	}
-	else
-	    buf->b_fnum = top_file_num++;
-	if (top_file_num < 0)		// wrap around (may cause duplicates)
-	{
-	    emsg(_("W14: Warning: List of file names overflow"));
-	    if (emsg_silent == 0 && !in_assert_fails)
-	    {
-		out_flush();
-		ui_delay(3001L, TRUE);	// make sure it is noticed
-	    }
-	    top_file_num = 1;
-	}
-	buf_hashtab_add(buf);
-
-	// Always copy the options from the current buffer.
-	buf_copy_options(buf, BCO_ALWAYS);
-    }
-
-    buf->b_wininfo->wi_fpos.lnum = lnum;
-    buf->b_wininfo->wi_win = curwin;
-
-#ifdef FEAT_SYN_HL
-    hash_init(&buf->b_s.b_keywtab);
-    hash_init(&buf->b_s.b_keywtab_ic);
-#endif
-
-    buf->b_fname = buf->b_sfname;
-#ifdef UNIX
-    if (st.st_dev == (dev_T)-1)
-	buf->b_dev_valid = FALSE;
-    else
-    {
-	buf->b_dev_valid = TRUE;
-	buf->b_dev = st.st_dev;
-	buf->b_ino = st.st_ino;
-    }
-#endif
-    buf->b_u_synced = TRUE;
-    buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED;
-    if (flags & BLN_DUMMY)
-	buf->b_flags |= BF_DUMMY;
-    buf_clear_file(buf);
-    clrallmarks(buf);			// clear marks
-    fmarks_check_names(buf);		// check file marks for this file
-    buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE;	// init 'buflisted'
-    if (!(flags & BLN_DUMMY))
-    {
-	bufref_T bufref;
-
-	// Tricky: these autocommands may change the buffer list.  They could
-	// also split the window with re-using the one empty buffer. This may
-	// result in unexpectedly losing the empty buffer.
-	set_bufref(&bufref, buf);
-	if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf)
-		&& !bufref_valid(&bufref))
-	    return NULL;
-	if (flags & BLN_LISTED)
-	{
-	    if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf)
-		    && !bufref_valid(&bufref))
-		return NULL;
-	}
-#ifdef FEAT_EVAL
-	if (aborting())		// autocmds may abort script processing
-	    return NULL;
-#endif
-    }
-
-    return buf;
-}
-
-/*
- * Free the memory for the options of a buffer.
- * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and
- * 'fileencoding'.
- */
-    void
-free_buf_options(
-    buf_T	*buf,
-    int		free_p_ff)
-{
-    if (free_p_ff)
-    {
-	clear_string_option(&buf->b_p_fenc);
-	clear_string_option(&buf->b_p_ff);
-	clear_string_option(&buf->b_p_bh);
-	clear_string_option(&buf->b_p_bt);
-    }
-#ifdef FEAT_FIND_ID
-    clear_string_option(&buf->b_p_def);
-    clear_string_option(&buf->b_p_inc);
-# ifdef FEAT_EVAL
-    clear_string_option(&buf->b_p_inex);
-# endif
-#endif
-#if defined(FEAT_EVAL)
-    clear_string_option(&buf->b_p_inde);
-    clear_string_option(&buf->b_p_indk);
-#endif
-#if defined(FEAT_BEVAL) && defined(FEAT_EVAL)
-    clear_string_option(&buf->b_p_bexpr);
-#endif
-#if defined(FEAT_CRYPT)
-    clear_string_option(&buf->b_p_cm);
-#endif
-    clear_string_option(&buf->b_p_fp);
-#if defined(FEAT_EVAL)
-    clear_string_option(&buf->b_p_fex);
-#endif
-#ifdef FEAT_CRYPT
-# ifdef FEAT_SODIUM
-    if (buf->b_p_key != NULL && *buf->b_p_key != NUL
-			   && crypt_method_is_sodium(crypt_get_method_nr(buf)))
-	crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
-# endif
-    clear_string_option(&buf->b_p_key);
-#endif
-    clear_string_option(&buf->b_p_kp);
-    clear_string_option(&buf->b_p_mps);
-    clear_string_option(&buf->b_p_fo);
-    clear_string_option(&buf->b_p_flp);
-    clear_string_option(&buf->b_p_isk);
-#ifdef FEAT_VARTABS
-    clear_string_option(&buf->b_p_vsts);
-    VIM_CLEAR(buf->b_p_vsts_nopaste);
-    VIM_CLEAR(buf->b_p_vsts_array);
-    clear_string_option(&buf->b_p_vts);
-    VIM_CLEAR(buf->b_p_vts_array);
-#endif
-#ifdef FEAT_KEYMAP
-    clear_string_option(&buf->b_p_keymap);
-    keymap_clear(&buf->b_kmap_ga);
-    ga_clear(&buf->b_kmap_ga);
-#endif
-    clear_string_option(&buf->b_p_com);
-#ifdef FEAT_FOLDING
-    clear_string_option(&buf->b_p_cms);
-#endif
-    clear_string_option(&buf->b_p_nf);
-#ifdef FEAT_SYN_HL
-    clear_string_option(&buf->b_p_syn);
-    clear_string_option(&buf->b_s.b_syn_isk);
-#endif
-#ifdef FEAT_SPELL
-    clear_string_option(&buf->b_s.b_p_spc);
-    clear_string_option(&buf->b_s.b_p_spf);
-    vim_regfree(buf->b_s.b_cap_prog);
-    buf->b_s.b_cap_prog = NULL;
-    clear_string_option(&buf->b_s.b_p_spl);
-    clear_string_option(&buf->b_s.b_p_spo);
-#endif
-    clear_string_option(&buf->b_p_sua);
-    clear_string_option(&buf->b_p_ft);
-    clear_string_option(&buf->b_p_cink);
-    clear_string_option(&buf->b_p_cino);
-    clear_string_option(&buf->b_p_lop);
-    clear_string_option(&buf->b_p_cinsd);
-    clear_string_option(&buf->b_p_cinw);
-    clear_string_option(&buf->b_p_cpt);
-#ifdef FEAT_COMPL_FUNC
-    clear_string_option(&buf->b_p_cfu);
-    free_callback(&buf->b_cfu_cb);
-    clear_string_option(&buf->b_p_ofu);
-    free_callback(&buf->b_ofu_cb);
-    clear_string_option(&buf->b_p_tsrfu);
-    free_callback(&buf->b_tsrfu_cb);
-#endif
-#ifdef FEAT_QUICKFIX
-    clear_string_option(&buf->b_p_gp);
-    clear_string_option(&buf->b_p_mp);
-    clear_string_option(&buf->b_p_efm);
-#endif
-    clear_string_option(&buf->b_p_ep);
-    clear_string_option(&buf->b_p_path);
-    clear_string_option(&buf->b_p_tags);
-    clear_string_option(&buf->b_p_tc);
-#ifdef FEAT_EVAL
-    clear_string_option(&buf->b_p_tfu);
-    free_callback(&buf->b_tfu_cb);
-#endif
-    clear_string_option(&buf->b_p_dict);
-    clear_string_option(&buf->b_p_tsr);
-    clear_string_option(&buf->b_p_qe);
-    buf->b_p_ar = -1;
-    buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
-    clear_string_option(&buf->b_p_lw);
-    clear_string_option(&buf->b_p_bkc);
-    clear_string_option(&buf->b_p_menc);
-}
-
-/*
- * Get alternate file "n".
- * Set linenr to "lnum" or altfpos.lnum if "lnum" == 0.
- *	Also set cursor column to altfpos.col if 'startofline' is not set.
- * if (options & GETF_SETMARK) call setpcmark()
- * if (options & GETF_ALT) we are jumping to an alternate file.
- * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping
- *
- * Return FAIL for failure, OK for success.
- */
-    int
-buflist_getfile(
-    int		n,
-    linenr_T	lnum,
-    int		options,
-    int		forceit)
-{
-    buf_T	*buf;
-    win_T	*wp = NULL;
-    pos_T	*fpos;
-    colnr_T	col;
-
-    buf = buflist_findnr(n);
-    if (buf == NULL)
-    {
-	if ((options & GETF_ALT) && n == 0)
-	    emsg(_(e_no_alternate_file));
-	else
-	    semsg(_(e_buffer_nr_not_found), n);
-	return FAIL;
-    }
-
-    // if alternate file is the current buffer, nothing to do
-    if (buf == curbuf)
-	return OK;
-
-    if (text_or_buf_locked())
-	return FAIL;
-
-    // altfpos may be changed by getfile(), get it now
-    if (lnum == 0)
-    {
-	fpos = buflist_findfpos(buf);
-	lnum = fpos->lnum;
-	col = fpos->col;
-    }
-    else
-	col = 0;
-
-    if (options & GETF_SWITCH)
-    {
-	// If 'switchbuf' is set jump to the window containing "buf".
-	wp = swbuf_goto_win_with_buf(buf);
-
-	// If 'switchbuf' contains "split", "vsplit" or "newtab" and the
-	// current buffer isn't empty: open new tab or window
-	if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB))
-							       && !BUFEMPTY())
-	{
-	    if (swb_flags & SWB_NEWTAB)
-		tabpage_new();
-	    else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0)
-								      == FAIL)
-		return FAIL;
-	    RESET_BINDING(curwin);
-	}
-    }
-
-    ++RedrawingDisabled;
-    int retval = FAIL;
-    if (GETFILE_SUCCESS(getfile(buf->b_fnum, NULL, NULL,
-				     (options & GETF_SETMARK), lnum, forceit)))
-    {
-	// cursor is at to BOL and w_cursor.lnum is checked due to getfile()
-	if (!p_sol && col != 0)
-	{
-	    curwin->w_cursor.col = col;
-	    check_cursor_col();
-	    curwin->w_cursor.coladd = 0;
-	    curwin->w_set_curswant = TRUE;
-	}
-	retval = OK;
-    }
-
-    if (RedrawingDisabled > 0)
-	--RedrawingDisabled;
-    return retval;
-}
-
-/*
- * go to the last know line number for the current buffer
- */
-    static void
-buflist_getfpos(void)
-{
-    pos_T	*fpos;
-
-    fpos = buflist_findfpos(curbuf);
-
-    curwin->w_cursor.lnum = fpos->lnum;
-    check_cursor_lnum();
-
-    if (p_sol)
-	curwin->w_cursor.col = 0;
-    else
-    {
-	curwin->w_cursor.col = fpos->col;
-	check_cursor_col();
-	curwin->w_cursor.coladd = 0;
-	curwin->w_set_curswant = TRUE;
-    }
-}
-
-/*
- * Find file in buffer list by name (it has to be for the current window).
- * Returns NULL if not found.
- */
-    buf_T *
-buflist_findname_exp(char_u *fname)
-{
-    char_u	*ffname;
-    buf_T	*buf = NULL;
-
-    // First make the name into a full path name
-    ffname = FullName_save(fname,
-#ifdef UNIX
-	    TRUE	    // force expansion, get rid of symbolic links
-#else
-	    FALSE
-#endif
-	    );
-    if (ffname != NULL)
-    {
-	buf = buflist_findname(ffname);
-	vim_free(ffname);
-    }
-    return buf;
-}
-
-/*
- * Find file in buffer list by name (it has to be for the current window).
- * "ffname" must have a full path.
- * Skips dummy buffers.
- * Returns NULL if not found.
- */
-    buf_T *
-buflist_findname(char_u *ffname)
-{
-#ifdef UNIX
-    stat_T	st;
-
-    if (mch_stat((char *)ffname, &st) < 0)
-	st.st_dev = (dev_T)-1;
-    return buflist_findname_stat(ffname, &st);
-}
-
-/*
- * Same as buflist_findname(), but pass the stat structure to avoid getting it
- * twice for the same file.
- * Returns NULL if not found.
- */
-    static buf_T *
-buflist_findname_stat(
-    char_u	*ffname,
-    stat_T	*stp)
-{
-#endif
-    buf_T	*buf;
-
-    // Start at the last buffer, expect to find a match sooner.
-    FOR_ALL_BUFS_FROM_LAST(buf)
-	if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname
-#ifdef UNIX
-		    , stp
-#endif
-		    ))
-	    return buf;
-    return NULL;
-}
-
-/*
- * Find file in buffer list by a regexp pattern.
- * Return fnum of the found buffer.
- * Return < 0 for error.
- */
-    int
-buflist_findpat(
-    char_u	*pattern,
-    char_u	*pattern_end,	// pointer to first char after pattern
-    int		unlisted,	// find unlisted buffers
-    int		diffmode UNUSED, // find diff-mode buffers only
-    int		curtab_only)	// find buffers in current tab only
-{
-    buf_T	*buf;
-    int		match = -1;
-    int		find_listed;
-    char_u	*pat;
-    char_u	*patend;
-    int		attempt;
-    char_u	*p;
-    int		toggledollar;
-
-    // "%" is current file, "%%" or "#" is alternate file
-    if ((pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#'))
-	    || (in_vim9script() && pattern_end == pattern + 2
-				    && pattern[0] == '%' && pattern[1] == '%'))
-    {
-	if (*pattern == '#' || pattern_end == pattern + 2)
-	    match = curwin->w_alt_fnum;
-	else
-	    match = curbuf->b_fnum;
-#ifdef FEAT_DIFF
-	if (diffmode && !diff_mode_buf(buflist_findnr(match)))
-	    match = -1;
-#endif
-    }
-
-    /*
-     * Try four ways of matching a listed buffer:
-     * attempt == 0: without '^' or '$' (at any position)
-     * attempt == 1: with '^' at start (only at position 0)
-     * attempt == 2: with '$' at end (only match at end)
-     * attempt == 3: with '^' at start and '$' at end (only full match)
-     * Repeat this for finding an unlisted buffer if there was no matching
-     * listed buffer.
-     */
-    else
-    {
-	pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE);
-	if (pat == NULL)
-	    return -1;
-	patend = pat + STRLEN(pat) - 1;
-	toggledollar = (patend > pat && *patend == '$');
-
-	// First try finding a listed buffer.  If not found and "unlisted"
-	// is TRUE, try finding an unlisted buffer.
-	find_listed = TRUE;
-	for (;;)
-	{
-	    for (attempt = 0; attempt <= 3; ++attempt)
-	    {
-		regmatch_T	regmatch;
-
-		// may add '^' and '$'
-		if (toggledollar)
-		    *patend = (attempt < 2) ? NUL : '$'; // add/remove '$'
-		p = pat;
-		if (*p == '^' && !(attempt & 1))	 // add/remove '^'
-		    ++p;
-		regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0);
-
-		FOR_ALL_BUFS_FROM_LAST(buf)
-		{
-		    if (regmatch.regprog == NULL)
-		    {
-			// invalid pattern, possibly after switching engine
-			vim_free(pat);
-			return -1;
-		    }
-		    if (buf->b_p_bl == find_listed
-#ifdef FEAT_DIFF
-			    && (!diffmode || diff_mode_buf(buf))
-#endif
-			    && buflist_match(&regmatch, buf, FALSE) != NULL)
-		    {
-			if (curtab_only)
-			{
-			    // Ignore the match if the buffer is not open in
-			    // the current tab.
-			    win_T	*wp;
-
-			    FOR_ALL_WINDOWS(wp)
-				if (wp->w_buffer == buf)
-				    break;
-			    if (wp == NULL)
-				continue;
-			}
-			if (match >= 0)		// already found a match
-			{
-			    match = -2;
-			    break;
-			}
-			match = buf->b_fnum;	// remember first match
-		    }
-		}
-
-		vim_regfree(regmatch.regprog);
-		if (match >= 0)			// found one match
-		    break;
-	    }
-
-	    // Only search for unlisted buffers if there was no match with
-	    // a listed buffer.
-	    if (!unlisted || !find_listed || match != -1)
-		break;
-	    find_listed = FALSE;
-	}
-
-	vim_free(pat);
-    }
-
-    if (match == -2)
-	semsg(_(e_more_than_one_match_for_str), pattern);
-    else if (match < 0)
-	semsg(_(e_no_matching_buffer_for_str), pattern);
-    return match;
-}
-
-#ifdef FEAT_VIMINFO
-typedef struct {
-    buf_T   *buf;
-    char_u  *match;
-} bufmatch_T;
-#endif
-
-/*
- * Find all buffer names that match.
- * For command line expansion of ":buf" and ":sbuf".
- * Return OK if matches found, FAIL otherwise.
- */
-    int
-ExpandBufnames(
-    char_u	*pat,
-    int		*num_file,
-    char_u	***file,
-    int		options)
-{
-    int		count = 0;
-    buf_T	*buf;
-    int		round;
-    char_u	*p;
-    int		attempt;
-    char_u	*patc = NULL;
-#ifdef FEAT_VIMINFO
-    bufmatch_T	*matches = NULL;
-#endif
-    int		fuzzy;
-    fuzmatch_str_T  *fuzmatch = NULL;
-
-    *num_file = 0;		    // return values in case of FAIL
-    *file = NULL;
-
-#ifdef FEAT_DIFF
-    if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff)
-	return FAIL;
-#endif
-
-    fuzzy = cmdline_fuzzy_complete(pat);
-
-    // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular
-    // expression matching)
-    if (!fuzzy)
-    {
-	if (*pat == '^')
-	{
-	    patc = alloc(STRLEN(pat) + 11);
-	    if (patc == NULL)
-		return FAIL;
-	    STRCPY(patc, "\\(^\\|[\\/]\\)");
-	    STRCPY(patc + 11, pat + 1);
-	}
-	else
-	    patc = pat;
-    }
-
-    // attempt == 0: try match with    '\<', match at start of word
-    // attempt == 1: try match without '\<', match anywhere
-    for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt)
-    {
-	regmatch_T	regmatch;
-	int		score = 0;
-
-	if (!fuzzy)
-	{
-	    if (attempt > 0 && patc == pat)
-		break;	// there was no anchor, no need to try again
-	    regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC);
-	}
-
-	// round == 1: Count the matches.
-	// round == 2: Build the array to keep the matches.
-	for (round = 1; round <= 2; ++round)
-	{
-	    count = 0;
-	    FOR_ALL_BUFFERS(buf)
-	    {
-		if (!buf->b_p_bl)	// skip unlisted buffers
-		    continue;
-#ifdef FEAT_DIFF
-		if (options & BUF_DIFF_FILTER)
-		    // Skip buffers not suitable for
-		    // :diffget or :diffput completion.
-		    if (buf == curbuf || !diff_mode_buf(buf))
-			continue;
-#endif
-
-		if (!fuzzy)
-		{
-		    if (regmatch.regprog == NULL)
-		    {
-			// invalid pattern, possibly after recompiling
-			if (patc != pat)
-			    vim_free(patc);
-			return FAIL;
-		    }
-		    p = buflist_match(&regmatch, buf, p_wic);
-		}
-		else
-		{
-		    p = NULL;
-		    // first try matching with the short file name
-		    if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0)
-			p = buf->b_sfname;
-		    if (p == NULL)
-		    {
-			// next try matching with the full path file name
-			if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0)
-			    p = buf->b_ffname;
-		    }
-		}
-
-		if (p == NULL)
-		    continue;
-
-		if (round == 1)
-		{
-		    ++count;
-		    continue;
-		}
-
-		if (options & WILD_HOME_REPLACE)
-		    p = home_replace_save(buf, p);
-		else
-		    p = vim_strsave(p);
-
-		if (!fuzzy)
-		{
-#ifdef FEAT_VIMINFO
-		    if (matches != NULL)
-		    {
-			matches[count].buf = buf;
-			matches[count].match = p;
-			count++;
-		    }
-		    else
-#endif
-			(*file)[count++] = p;
-		}
-		else
-		{
-		    fuzmatch[count].idx = count;
-		    fuzmatch[count].str = p;
-		    fuzmatch[count].score = score;
-		    count++;
-		}
-	    }
-	    if (count == 0)	// no match found, break here
-		break;
-	    if (round == 1)
-	    {
-		if (!fuzzy)
-		{
-		    *file = ALLOC_MULT(char_u *, count);
-		    if (*file == NULL)
-		    {
-			vim_regfree(regmatch.regprog);
-			if (patc != pat)
-			    vim_free(patc);
-			return FAIL;
-		    }
-#ifdef FEAT_VIMINFO
-		    if (options & WILD_BUFLASTUSED)
-			matches = ALLOC_MULT(bufmatch_T, count);
-#endif
-		}
-		else
-		{
-		    fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
-		    if (fuzmatch == NULL)
-		    {
-			*num_file = 0;
-			*file = NULL;
-			return FAIL;
-		    }
-		}
-	    }
-	}
-
-	if (!fuzzy)
-	{
-	    vim_regfree(regmatch.regprog);
-	    if (count)		// match(es) found, break here
-		break;
-	}
-    }
-
-    if (!fuzzy && patc != pat)
-	vim_free(patc);
-
-#ifdef FEAT_VIMINFO
-    if (!fuzzy)
-    {
-	if (matches != NULL)
-	{
-	    int i;
-	    if (count > 1)
-		qsort(matches, count, sizeof(bufmatch_T), buf_compare);
-	    // if the current buffer is first in the list, place it at the end
-	    if (matches[0].buf == curbuf)
-	    {
-		for (i = 1; i < count; i++)
-		    (*file)[i-1] = matches[i].match;
-		(*file)[count-1] = matches[0].match;
-	    }
-	    else
-	    {
-		for (i = 0; i < count; i++)
-		    (*file)[i] = matches[i].match;
-	    }
-	    vim_free(matches);
-	}
-    }
-    else
-    {
-	if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL)
-	    return FAIL;
-    }
-#endif
-
-    *num_file = count;
-    return (count == 0 ? FAIL : OK);
-}
-
-/*
- * Check for a match on the file name for buffer "buf" with regprog "prog".
- * Note that rmp->regprog may become NULL when switching regexp engine.
- */
-    static char_u *
-buflist_match(
-    regmatch_T	*rmp,
-    buf_T	*buf,
-    int		ignore_case)  // when TRUE ignore case, when FALSE use 'fic'
-{
-    char_u	*match;
-
-    // First try the short file name, then the long file name.
-    match = fname_match(rmp, buf->b_sfname, ignore_case);
-    if (match == NULL && rmp->regprog != NULL)
-	match = fname_match(rmp, buf->b_ffname, ignore_case);
-
-    return match;
-}
-
-/*
- * Try matching the regexp in "rmp->regprog" with file name "name".
- * Note that rmp->regprog may become NULL when switching regexp engine.
- * Return "name" when there is a match, NULL when not.
- */
-    static char_u *
-fname_match(
-    regmatch_T	*rmp,
-    char_u	*name,
-    int		ignore_case)  // when TRUE ignore case, when FALSE use 'fic'
-{
-    char_u	*match = NULL;
-    char_u	*p;
-
-    // extra check for valid arguments
-    if (name == NULL || rmp->regprog == NULL)
-	return NULL;
-
-    // Ignore case when 'fileignorecase' or the argument is set.
-    rmp->rm_ic = p_fic || ignore_case;
-    if (vim_regexec(rmp, name, (colnr_T)0))
-	match = name;
-    else if (rmp->regprog != NULL)
-    {
-	// Replace $(HOME) with '~' and try matching again.
-	p = home_replace_save(NULL, name);
-	if (p != NULL && vim_regexec(rmp, p, (colnr_T)0))
-	    match = name;
-	vim_free(p);
-    }
-
-    return match;
-}
-
-/*
- * Find a file in the buffer list by buffer number.
- */
-    buf_T *
-buflist_findnr(int nr)
-{
-    char_u	key[VIM_SIZEOF_INT * 2 + 1];
-    hashitem_T	*hi;
-
-    if (nr == 0)
-	nr = curwin->w_alt_fnum;
-    sprintf((char *)key, "%x", nr);
-    hi = hash_find(&buf_hashtab, key);
-
-    if (!HASHITEM_EMPTY(hi))
-	return (buf_T *)(hi->hi_key
-			     - ((unsigned)(curbuf->b_key - (char_u *)curbuf)));
-    return NULL;
-}
-
-/*
- * Get name of file 'n' in the buffer list.
- * When the file has no name an empty string is returned.
- * home_replace() is used to shorten the file name (used for marks).
- * Returns a pointer to allocated memory, of NULL when failed.
- */
-    char_u *
-buflist_nr2name(
-    int		n,
-    int		fullname,
-    int		helptail)	// for help buffers return tail only
-{
-    buf_T	*buf;
-
-    buf = buflist_findnr(n);
-    if (buf == NULL)
-	return NULL;
-    return home_replace_save(helptail ? buf : NULL,
-				     fullname ? buf->b_ffname : buf->b_fname);
-}
-
-/*
- * Set the "lnum" and "col" for the buffer "buf" and the current window.
- * When "copy_options" is TRUE save the local window option values.
- * When "lnum" is 0 only do the options.
- */
-    void
-buflist_setfpos(
-    buf_T	*buf,
-    win_T	*win,		// may be NULL when using :badd
-    linenr_T	lnum,
-    colnr_T	col,
-    int		copy_options)
-{
-    wininfo_T	*wip;
-
-    FOR_ALL_BUF_WININFO(buf, wip)
-	if (wip->wi_win == win)
-	    break;
-    if (wip == NULL)
-    {
-	// allocate a new entry
-	wip = ALLOC_CLEAR_ONE(wininfo_T);
-	if (wip == NULL)
-	    return;
-	wip->wi_win = win;
-	if (lnum == 0)		// set lnum even when it's 0
-	    lnum = 1;
-    }
-    else
-    {
-	// remove the entry from the list
-	if (wip->wi_prev)
-	    wip->wi_prev->wi_next = wip->wi_next;
-	else
-	    buf->b_wininfo = wip->wi_next;
-	if (wip->wi_next)
-	    wip->wi_next->wi_prev = wip->wi_prev;
-	if (copy_options && wip->wi_optset)
-	{
-	    clear_winopt(&wip->wi_opt);
-#ifdef FEAT_FOLDING
-	    deleteFoldRecurse(&wip->wi_folds);
-#endif
-	}
-    }
-    if (lnum != 0)
-    {
-	wip->wi_fpos.lnum = lnum;
-	wip->wi_fpos.col = col;
-    }
-    if (win != NULL)
-	wip->wi_changelistidx = win->w_changelistidx;
-    if (copy_options && win != NULL)
-    {
-	// Save the window-specific option values.
-	copy_winopt(&win->w_onebuf_opt, &wip->wi_opt);
-#ifdef FEAT_FOLDING
-	wip->wi_fold_manual = win->w_fold_manual;
-	cloneFoldGrowArray(&win->w_folds, &wip->wi_folds);
-#endif
-	wip->wi_optset = TRUE;
-    }
-
-    // insert the entry in front of the list
-    wip->wi_next = buf->b_wininfo;
-    buf->b_wininfo = wip;
-    wip->wi_prev = NULL;
-    if (wip->wi_next)
-	wip->wi_next->wi_prev = wip;
-}
-
-#ifdef FEAT_DIFF
-/*
- * Return TRUE when "wip" has 'diff' set and the diff is only for another tab
- * page.  That's because a diff is local to a tab page.
- */
-    static int
-wininfo_other_tab_diff(wininfo_T *wip)
-{
-    win_T	*wp;
-
-    if (!wip->wi_opt.wo_diff)
-	return FALSE;
-
-    FOR_ALL_WINDOWS(wp)
-	// return FALSE when it's a window in the current tab page, thus
-	// the buffer was in diff mode here
-	if (wip->wi_win == wp)
-	    return FALSE;
-    return TRUE;
-}
-#endif
-
-/*
- * Find info for the current window in buffer "buf".
- * If not found, return the info for the most recently used window.
- * When "need_options" is TRUE skip entries where wi_optset is FALSE.
- * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in
- * another tab page.
- * Returns NULL when there isn't any info.
- */
-    static wininfo_T *
-find_wininfo(
-    buf_T	*buf,
-    int		need_options,
-    int		skip_diff_buffer UNUSED)
-{
-    wininfo_T	*wip;
-
-    FOR_ALL_BUF_WININFO(buf, wip)
-	if (wip->wi_win == curwin
-#ifdef FEAT_DIFF
-		&& (!skip_diff_buffer || !wininfo_other_tab_diff(wip))
-#endif
-
-		&& (!need_options || wip->wi_optset))
-	    break;
-
-    if (wip != NULL)
-	return wip;
-
-    // If no wininfo for curwin, use the first in the list (that doesn't have
-    // 'diff' set and is in another tab page).
-    // If "need_options" is TRUE skip entries that don't have options set,
-    // unless the window is editing "buf", so we can copy from the window
-    // itself.
-#ifdef FEAT_DIFF
-    if (skip_diff_buffer)
-    {
-	FOR_ALL_BUF_WININFO(buf, wip)
-	    if (!wininfo_other_tab_diff(wip)
-		    && (!need_options || wip->wi_optset
-			|| (wip->wi_win != NULL
-			    && wip->wi_win->w_buffer == buf)))
-		break;
-    }
-    else
-#endif
-	wip = buf->b_wininfo;
-    return wip;
-}
-
-/*
- * Reset the local window options to the values last used in this window.
- * If the buffer wasn't used in this window before, use the values from
- * the most recently used window.  If the values were never set, use the
- * global values for the window.
- */
-    void
-get_winopts(buf_T *buf)
-{
-    wininfo_T	*wip;
-
-    clear_winopt(&curwin->w_onebuf_opt);
-#ifdef FEAT_FOLDING
-    clearFolding(curwin);
-#endif
-
-    wip = find_wininfo(buf, TRUE, TRUE);
-    if (wip != NULL && wip->wi_win != NULL
-	    && wip->wi_win != curwin && wip->wi_win->w_buffer == buf)
-    {
-	// The buffer is currently displayed in the window: use the actual
-	// option values instead of the saved (possibly outdated) values.
-	win_T *wp = wip->wi_win;
-
-	copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt);
-#ifdef FEAT_FOLDING
-	curwin->w_fold_manual = wp->w_fold_manual;
-	curwin->w_foldinvalid = TRUE;
-	cloneFoldGrowArray(&wp->w_folds, &curwin->w_folds);
-#endif
-    }
-    else if (wip != NULL && wip->wi_optset)
-    {
-	// the buffer was displayed in the current window earlier
-	copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt);
-#ifdef FEAT_FOLDING
-	curwin->w_fold_manual = wip->wi_fold_manual;
-	curwin->w_foldinvalid = TRUE;
-	cloneFoldGrowArray(&wip->wi_folds, &curwin->w_folds);
-#endif
-    }
-    else
-	copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt);
-    if (wip != NULL)
-	curwin->w_changelistidx = wip->wi_changelistidx;
-
-#ifdef FEAT_FOLDING
-    // Set 'foldlevel' to 'foldlevelstart' if it's not negative.
-    if (p_fdls >= 0)
-	curwin->w_p_fdl = p_fdls;
-#endif
-    after_copy_winopt(curwin);
-}
-
-/*
- * Find the position (lnum and col) for the buffer 'buf' for the current
- * window.
- * Returns a pointer to no_position if no position is found.
- */
-    pos_T *
-buflist_findfpos(buf_T *buf)
-{
-    wininfo_T	*wip;
-    static pos_T no_position = {1, 0, 0};
-
-    wip = find_wininfo(buf, FALSE, FALSE);
-    if (wip != NULL)
-	return &(wip->wi_fpos);
-    else
-	return &no_position;
-}
-
-/*
- * Find the lnum for the buffer 'buf' for the current window.
- */
-    linenr_T
-buflist_findlnum(buf_T *buf)
-{
-    return buflist_findfpos(buf)->lnum;
-}
-
-/*
- * List all known file names (for :files and :buffers command).
- */
-    void
-buflist_list(exarg_T *eap)
-{
-    buf_T	*buf = firstbuf;
-    int		len;
-    int		i;
-    int		ro_char;
-    int		changed_char;
-#ifdef FEAT_TERMINAL
-    int		job_running;
-    int		job_none_open;
-#endif
-
-#ifdef FEAT_VIMINFO
-    garray_T	buflist;
-    buf_T	**buflist_data = NULL, **p;
-
-    if (vim_strchr(eap->arg, 't'))
-    {
-	ga_init2(&buflist, sizeof(buf_T *), 50);
-	FOR_ALL_BUFFERS(buf)
-	{
-	    if (ga_grow(&buflist, 1) == OK)
-		((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
-	}
-
-	qsort(buflist.ga_data, (size_t)buflist.ga_len,
-		sizeof(buf_T *), buf_compare);
-
-	buflist_data = (buf_T **)buflist.ga_data;
-	buf = *buflist_data;
-    }
-    p = buflist_data;
-
-    for (; buf != NULL && !got_int; buf = buflist_data != NULL
-	    ? (++p < buflist_data + buflist.ga_len ? *p : NULL)
-	    : buf->b_next)
-#else
-    for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next)
-#endif
-    {
-#ifdef FEAT_TERMINAL
-	job_running = term_job_running(buf->b_term);
-	job_none_open = term_none_open(buf->b_term);
-#endif
-	// skip unlisted buffers, unless ! was used
-	if ((!buf->b_p_bl && !eap->forceit && !vim_strchr(eap->arg, 'u'))
-		|| (vim_strchr(eap->arg, 'u') && buf->b_p_bl)
-		|| (vim_strchr(eap->arg, '+')
-			&& ((buf->b_flags & BF_READERR) || !bufIsChanged(buf)))
-		|| (vim_strchr(eap->arg, 'a')
-			&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
-		|| (vim_strchr(eap->arg, 'h')
-			&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
-#ifdef FEAT_TERMINAL
-		|| (vim_strchr(eap->arg, 'R')
-			&& (!job_running || (job_running && job_none_open)))
-		|| (vim_strchr(eap->arg, '?')
-			&& (!job_running || (job_running && !job_none_open)))
-		|| (vim_strchr(eap->arg, 'F')
-			&& (job_running || buf->b_term == NULL))
-#endif
-		|| (vim_strchr(eap->arg, '-') && buf->b_p_ma)
-		|| (vim_strchr(eap->arg, '=') && !buf->b_p_ro)
-		|| (vim_strchr(eap->arg, 'x') && !(buf->b_flags & BF_READERR))
-		|| (vim_strchr(eap->arg, '%') && buf != curbuf)
-		|| (vim_strchr(eap->arg, '#')
-		      && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum)))
-	    continue;
-	if (buf_spname(buf) != NULL)
-	    vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1);
-	else
-	    home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE);
-	if (message_filtered(NameBuff))
-	    continue;
-
-	changed_char = (buf->b_flags & BF_READERR) ? 'x'
-					     : (bufIsChanged(buf) ? '+' : ' ');
-#ifdef FEAT_TERMINAL
-	if (job_running)
-	{
-	    if (job_none_open)
-		ro_char = '?';
-	    else
-		ro_char = 'R';
-	    changed_char = ' ';  // bufIsChanged() returns TRUE to avoid
-				 // closing, but it's not actually changed.
-	}
-	else if (buf->b_term != NULL)
-	    ro_char = 'F';
-	else
-#endif
-	    ro_char = !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' ');
-
-	msg_putchar('\n');
-	len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
-		buf->b_fnum,
-		buf->b_p_bl ? ' ' : 'u',
-		buf == curbuf ? '%' :
-			(curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '),
-		buf->b_ml.ml_mfp == NULL ? ' ' :
-			(buf->b_nwindows == 0 ? 'h' : 'a'),
-		ro_char,
-		changed_char,
-		NameBuff);
-	if (len > IOSIZE - 20)
-	    len = IOSIZE - 20;
-
-	// put "line 999" in column 40 or after the file name
-	i = 40 - vim_strsize(IObuff);
-	do
-	    IObuff[len++] = ' ';
-	while (--i > 0 && len < IOSIZE - 18);
-#ifdef FEAT_VIMINFO
-	if (vim_strchr(eap->arg, 't') && buf->b_last_used)
-	    add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
-	else
-#endif
-	    vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
-		    _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum
-					       : (long)buflist_findlnum(buf));
-	msg_outtrans(IObuff);
-	out_flush();	    // output one line at a time
-	ui_breakcheck();
-    }
-
-#ifdef FEAT_VIMINFO
-    if (buflist_data)
-	ga_clear(&buflist);
-#endif
-}
-
-/*
- * Get file name and line number for file 'fnum'.
- * Used by DoOneCmd() for translating '%' and '#'.
- * Used by insert_reg() and cmdline_paste() for '#' register.
- * Return FAIL if not found, OK for success.
- */
-    int
-buflist_name_nr(
-    int		fnum,
-    char_u	**fname,
-    linenr_T	*lnum)
-{
-    buf_T	*buf;
-
-    buf = buflist_findnr(fnum);
-    if (buf == NULL || buf->b_fname == NULL)
-	return FAIL;
-
-    *fname = buf->b_fname;
-    *lnum = buflist_findlnum(buf);
-
-    return OK;
-}
-
-/*
- * Set the file name for "buf"' to "ffname_arg", short file name to
- * "sfname_arg".
- * The file name with the full path is also remembered, for when :cd is used.
- * Returns FAIL for failure (file name already in use by other buffer)
- *	OK otherwise.
- */
-    int
-setfname(
-    buf_T	*buf,
-    char_u	*ffname_arg,
-    char_u	*sfname_arg,
-    int		message)	// give message when buffer already exists
-{
-    char_u	*ffname = ffname_arg;
-    char_u	*sfname = sfname_arg;
-    buf_T	*obuf = NULL;
-#ifdef UNIX
-    stat_T	st;
-#endif
-
-    if (ffname == NULL || *ffname == NUL)
-    {
-	// Removing the name.
-	if (buf->b_sfname != buf->b_ffname)
-	    VIM_CLEAR(buf->b_sfname);
-	else
-	    buf->b_sfname = NULL;
-	VIM_CLEAR(buf->b_ffname);
-#ifdef UNIX
-	st.st_dev = (dev_T)-1;
-#endif
-    }
-    else
-    {
-	fname_expand(buf, &ffname, &sfname); // will allocate ffname
-	if (ffname == NULL)		    // out of memory
-	    return FAIL;
-
-	/*
-	 * If the file name is already used in another buffer:
-	 * - if the buffer is loaded, fail
-	 * - if the buffer is not loaded, delete it from the list
-	 */
-#ifdef UNIX
-	if (mch_stat((char *)ffname, &st) < 0)
-	    st.st_dev = (dev_T)-1;
-#endif
-	if (!(buf->b_flags & BF_DUMMY))
-#ifdef UNIX
-	    obuf = buflist_findname_stat(ffname, &st);
-#else
-	    obuf = buflist_findname(ffname);
-#endif
-	if (obuf != NULL && obuf != buf)
-	{
-	    win_T	*win;
-	    tabpage_T   *tab;
-	    int		in_use = FALSE;
-
-	    // during startup a window may use a buffer that is not loaded yet
-	    FOR_ALL_TAB_WINDOWS(tab, win)
-		if (win->w_buffer == obuf)
-		    in_use = TRUE;
-
-	    // it's loaded or used in a window, fail
-	    if (obuf->b_ml.ml_mfp != NULL || in_use)
-	    {
-		if (message)
-		    emsg(_(e_buffer_with_this_name_already_exists));
-		vim_free(ffname);
-		return FAIL;
-	    }
-	    // delete from the list
-	    close_buffer(NULL, obuf, DOBUF_WIPE, FALSE, FALSE);
-	}
-	sfname = vim_strsave(sfname);
-	if (ffname == NULL || sfname == NULL)
-	{
-	    vim_free(sfname);
-	    vim_free(ffname);
-	    return FAIL;
-	}
-#ifdef USE_FNAME_CASE
-	fname_case(sfname, 0);    // set correct case for short file name
-#endif
-	if (buf->b_sfname != buf->b_ffname)
-	    vim_free(buf->b_sfname);
-	vim_free(buf->b_ffname);
-	buf->b_ffname = ffname;
-	buf->b_sfname = sfname;
-    }
-    buf->b_fname = buf->b_sfname;
-#ifdef UNIX
-    if (st.st_dev == (dev_T)-1)
-	buf->b_dev_valid = FALSE;
-    else
-    {
-	buf->b_dev_valid = TRUE;
-	buf->b_dev = st.st_dev;
-	buf->b_ino = st.st_ino;
-    }
-#endif
-
-    buf->b_shortname = FALSE;
-
-    buf_name_changed(buf);
-    return OK;
-}
-
-/*
- * Crude way of changing the name of a buffer.  Use with care!
- * The name should be relative to the current directory.
- */
-    void
-buf_set_name(int fnum, char_u *name)
-{
-    buf_T	*buf;
-
-    buf = buflist_findnr(fnum);
-    if (buf == NULL)
-	return;
-
-    if (buf->b_sfname != buf->b_ffname)
-	vim_free(buf->b_sfname);
-    vim_free(buf->b_ffname);
-    buf->b_ffname = vim_strsave(name);
-    buf->b_sfname = NULL;
-    // Allocate ffname and expand into full path.  Also resolves .lnk
-    // files on Win32.
-    fname_expand(buf, &buf->b_ffname, &buf->b_sfname);
-    buf->b_fname = buf->b_sfname;
-}
-
-/*
- * Take care of what needs to be done when the name of buffer "buf" has
- * changed.
- */
-    void
-buf_name_changed(buf_T *buf)
-{
-    /*
-     * If the file name changed, also change the name of the swapfile
-     */
-    if (buf->b_ml.ml_mfp != NULL)
-	ml_setname(buf);
-
-#ifdef FEAT_TERMINAL
-    if (buf->b_term != NULL)
-	term_clear_status_text(buf->b_term);
-#endif
-
-    if (curwin->w_buffer == buf)
-	check_arg_idx(curwin);	// check file name for arg list
-    maketitle();		// set window title
-    status_redraw_all();	// status lines need to be redrawn
-    fmarks_check_names(buf);	// check named file marks
-    ml_timestamp(buf);		// reset timestamp
-}
-
-/*
- * set alternate file name for current window
- *
- * Used by do_one_cmd(), do_write() and do_ecmd().
- * Return the buffer.
- */
-    buf_T *
-setaltfname(
-    char_u	*ffname,
-    char_u	*sfname,
-    linenr_T	lnum)
-{
-    buf_T	*buf;
-
-    // Create a buffer.  'buflisted' is not set if it's a new buffer
-    buf = buflist_new(ffname, sfname, lnum, 0);
-    if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
-	curwin->w_alt_fnum = buf->b_fnum;
-    return buf;
-}
-
-/*
- * Get alternate file name for current window.
- * Return NULL if there isn't any, and give error message if requested.
- */
-    char_u  *
-getaltfname(
-    int		errmsg)		// give error message
-{
-    char_u	*fname;
-    linenr_T	dummy;
-
-    if (buflist_name_nr(0, &fname, &dummy) == FAIL)
-    {
-	if (errmsg)
-	    emsg(_(e_no_alternate_file));
-	return NULL;
-    }
-    return fname;
-}
-
-/*
- * Add a file name to the buflist and return its number.
- * Uses same flags as buflist_new(), except BLN_DUMMY.
- *
- * used by qf_init(), main() and doarglist()
- */
-    int
-buflist_add(char_u *fname, int flags)
-{
-    buf_T	*buf;
-
-    buf = buflist_new(fname, NULL, (linenr_T)0, flags);
-    if (buf != NULL)
-	return buf->b_fnum;
-    return 0;
-}
-
-#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO)
-/*
- * Adjust slashes in file names.  Called after 'shellslash' was set.
- */
-    void
-buflist_slash_adjust(void)
-{
-    buf_T	*bp;
-
-    FOR_ALL_BUFFERS(bp)
-    {
-	if (bp->b_ffname != NULL)
-	    slash_adjust(bp->b_ffname);
-	if (bp->b_sfname != NULL)
-	    slash_adjust(bp->b_sfname);
-    }
-}
-#endif
-
-/*
- * Set alternate cursor position for the current buffer and window "win".
- * Also save the local window option values.
- */
-    void
-buflist_altfpos(win_T *win)
-{
-    buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE);
-}
-
-/*
- * Return TRUE if 'ffname' is not the same file as current file.
- * Fname must have a full path (expanded by mch_FullName()).
- */
-    int
-otherfile(char_u *ffname)
-{
-    return otherfile_buf(curbuf, ffname
-#ifdef UNIX
-	    , NULL
-#endif
-	    );
-}
-
-    static int
-otherfile_buf(
-    buf_T		*buf,
-    char_u		*ffname
-#ifdef UNIX
-    , stat_T		*stp
-#endif
-    )
-{
-    // no name is different
-    if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL)
-	return TRUE;
-    if (fnamecmp(ffname, buf->b_ffname) == 0)
-	return FALSE;
-#ifdef UNIX
-    {
-	stat_T	    st;
-
-	// If no stat_T given, get it now
-	if (stp == NULL)
-	{
-	    if (!buf->b_dev_valid || mch_stat((char *)ffname, &st) < 0)
-		st.st_dev = (dev_T)-1;
-	    stp = &st;
-	}
-	// Use dev/ino to check if the files are the same, even when the names
-	// are different (possible with links).  Still need to compare the
-	// name above, for when the file doesn't exist yet.
-	// Problem: The dev/ino changes when a file is deleted (and created
-	// again) and remains the same when renamed/moved.  We don't want to
-	// mch_stat() each buffer each time, that would be too slow.  Get the
-	// dev/ino again when they appear to match, but not when they appear
-	// to be different: Could skip a buffer when it's actually the same
-	// file.
-	if (buf_same_ino(buf, stp))
-	{
-	    buf_setino(buf);
-	    if (buf_same_ino(buf, stp))
-		return FALSE;
-	}
-    }
-#endif
-    return TRUE;
-}
-
-#if defined(UNIX) || defined(PROTO)
-/*
- * Set inode and device number for a buffer.
- * Must always be called when b_fname is changed!.
- */
-    void
-buf_setino(buf_T *buf)
-{
-    stat_T	st;
-
-    if (buf->b_fname != NULL && mch_stat((char *)buf->b_fname, &st) >= 0)
-    {
-	buf->b_dev_valid = TRUE;
-	buf->b_dev = st.st_dev;
-	buf->b_ino = st.st_ino;
-    }
-    else
-	buf->b_dev_valid = FALSE;
-}
-
-/*
- * Return TRUE if dev/ino in buffer "buf" matches with "stp".
- */
-    static int
-buf_same_ino(
-    buf_T	*buf,
-    stat_T	*stp)
-{
-    return (buf->b_dev_valid
-	    && stp->st_dev == buf->b_dev
-	    && stp->st_ino == buf->b_ino);
-}
-#endif
-
-/*
- * Print info about the current buffer.
- */
-    void
-fileinfo(
-    int fullname,	    // when non-zero print full path
-    int shorthelp,
-    int	dont_truncate)
-{
-    char_u	*name;
-    int		n;
-    char	*p;
-    char	*buffer;
-    size_t	len;
-
-    buffer = alloc(IOSIZE);
-    if (buffer == NULL)
-	return;
-
-    if (fullname > 1)	    // 2 CTRL-G: include buffer number
-    {
-	vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
-	p = buffer + STRLEN(buffer);
-    }
-    else
-	p = buffer;
-
-    *p++ = '"';
-    if (buf_spname(curbuf) != NULL)
-	vim_strncpy((char_u *)p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1);
-    else
-    {
-	if (!fullname && curbuf->b_fname != NULL)
-	    name = curbuf->b_fname;
-	else
-	    name = curbuf->b_ffname;
-	home_replace(shorthelp ? curbuf : NULL, name, (char_u *)p,
-					  (int)(IOSIZE - (p - buffer)), TRUE);
-    }
-
-    vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s",
-	    curbufIsChanged() ? (shortmess(SHM_MOD)
-					  ?  " [+]" : _(" [Modified]")) : " ",
-	    (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
-					? _("[Not edited]") : "",
-	    (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
-					   ? new_file_message() : "",
-	    (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "",
-	    curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]")
-						      : _("[readonly]")) : "",
-	    (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK)
-							  || curbuf->b_p_ro) ?
-								    " " : "");
-    // With 32 bit longs and more than 21,474,836 lines multiplying by 100
-    // causes an overflow, thus for large numbers divide instead.
-    if (curwin->w_cursor.lnum > 1000000L)
-	n = (int)(((long)curwin->w_cursor.lnum) /
-				   ((long)curbuf->b_ml.ml_line_count / 100L));
-    else
-	n = (int)(((long)curwin->w_cursor.lnum * 100L) /
-					    (long)curbuf->b_ml.ml_line_count);
-    if (curbuf->b_ml.ml_flags & ML_EMPTY)
-	vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg));
-    else if (p_ru)
-	// Current line and column are already on the screen -- webb
-	vim_snprintf_add(buffer, IOSIZE,
-		NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--",
-						   curbuf->b_ml.ml_line_count),
-		(long)curbuf->b_ml.ml_line_count, n);
-    else
-    {
-	vim_snprintf_add(buffer, IOSIZE,
-		_("line %ld of %ld --%d%%-- col "),
-		(long)curwin->w_cursor.lnum,
-		(long)curbuf->b_ml.ml_line_count,
-		n);
-	validate_virtcol();
-	len = STRLEN(buffer);
-	col_print((char_u *)buffer + len, IOSIZE - len,
-		   (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1);
-    }
-
-    (void)append_arg_number(curwin, (char_u *)buffer, IOSIZE,
-							 !shortmess(SHM_FILE));
-
-    if (dont_truncate)
-    {
-	// Temporarily set msg_scroll to avoid the message being truncated.
-	// First call msg_start() to get the message in the right place.
-	msg_start();
-	n = msg_scroll;
-	msg_scroll = TRUE;
-	msg(buffer);
-	msg_scroll = n;
-    }
-    else
-    {
-	p = msg_trunc_attr(buffer, FALSE, 0);
-	if (restart_edit != 0 || (msg_scrolled && !need_wait_return))
-	    // Need to repeat the message after redrawing when:
-	    // - When restart_edit is set (otherwise there will be a delay
-	    //   before redrawing).
-	    // - When the screen was scrolled but there is no wait-return
-	    //   prompt.
-	    set_keep_msg((char_u *)p, 0);
-    }
-
-    vim_free(buffer);
-}
-
-    void
-col_print(
-    char_u  *buf,
-    size_t  buflen,
-    int	    col,
-    int	    vcol)
-{
-    if (col == vcol)
-	vim_snprintf((char *)buf, buflen, "%d", col);
-    else
-	vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol);
-}
-
-static char_u *lasttitle = NULL;
-static char_u *lasticon = NULL;
-
-/*
- * Put the file name in the title bar and icon of the window.
- */
-    void
-maketitle(void)
-{
-    char_u	*p;
-    char_u	*title_str = NULL;
-    char_u	*icon_str = NULL;
-    int		maxlen = 0;
-    int		len;
-    int		mustset;
-    char_u	buf[IOSIZE];
-    int		off;
-
-    if (!redrawing())
-    {
-	// Postpone updating the title when 'lazyredraw' is set.
-	need_maketitle = TRUE;
-	return;
-    }
-
-    need_maketitle = FALSE;
-    if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL)
-	return;  // nothing to do
-
-    if (p_title)
-    {
-	if (p_titlelen > 0)
-	{
-	    maxlen = p_titlelen * Columns / 100;
-	    if (maxlen < 10)
-		maxlen = 10;
-	}
-
-	title_str = buf;
-	if (*p_titlestring != NUL)
-	{
-#ifdef FEAT_STL_OPT
-	    if (stl_syntax & STL_IN_TITLE)
-		build_stl_str_hl(curwin, title_str, sizeof(buf), p_titlestring,
-				    (char_u *)"titlestring", 0,
-				    0, maxlen, NULL, NULL);
-	    else
-#endif
-		title_str = p_titlestring;
-	}
-	else
-	{
-	    // format: "fname + (path) (1 of 2) - VIM"
-
-#define SPACE_FOR_FNAME (IOSIZE - 100)
-#define SPACE_FOR_DIR   (IOSIZE - 20)
-#define SPACE_FOR_ARGNR (IOSIZE - 10)  // at least room for " - VIM"
-	    if (curbuf->b_fname == NULL)
-		vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME);
-#ifdef FEAT_TERMINAL
-	    else if (curbuf->b_term != NULL)
-	    {
-		vim_strncpy(buf, term_get_status_text(curbuf->b_term),
-							      SPACE_FOR_FNAME);
-	    }
-#endif
-	    else
-	    {
-		p = transstr(gettail(curbuf->b_fname));
-		vim_strncpy(buf, p, SPACE_FOR_FNAME);
-		vim_free(p);
-	    }
-
-#ifdef FEAT_TERMINAL
-	    if (curbuf->b_term == NULL)
-#endif
-		switch (bufIsChanged(curbuf)
-			+ (curbuf->b_p_ro * 2)
-			+ (!curbuf->b_p_ma * 4))
-		{
-		    case 1: STRCAT(buf, " +"); break;
-		    case 2: STRCAT(buf, " ="); break;
-		    case 3: STRCAT(buf, " =+"); break;
-		    case 4:
-		    case 6: STRCAT(buf, " -"); break;
-		    case 5:
-		    case 7: STRCAT(buf, " -+"); break;
-		}
-
-	    if (curbuf->b_fname != NULL
-#ifdef FEAT_TERMINAL
-		    && curbuf->b_term == NULL
-#endif
-		    )
-	    {
-		// Get path of file, replace home dir with ~
-		off = (int)STRLEN(buf);
-		buf[off++] = ' ';
-		buf[off++] = '(';
-		home_replace(curbuf, curbuf->b_ffname,
-					buf + off, SPACE_FOR_DIR - off, TRUE);
-#ifdef BACKSLASH_IN_FILENAME
-		// avoid "c:/name" to be reduced to "c"
-		if (isalpha(buf[off]) && buf[off + 1] == ':')
-		    off += 2;
-#endif
-		// remove the file name
-		p = gettail_sep(buf + off);
-		if (p == buf + off)
-		{
-		    // must be a help buffer
-		    vim_strncpy(buf + off, (char_u *)_("help"),
-					   (size_t)(SPACE_FOR_DIR - off - 1));
-		}
-		else
-		    *p = NUL;
-
-		// Translate unprintable chars and concatenate.  Keep some
-		// room for the server name.  When there is no room (very long
-		// file name) use (...).
-		if (off < SPACE_FOR_DIR)
-		{
-		    p = transstr(buf + off);
-		    vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off));
-		    vim_free(p);
-		}
-		else
-		{
-		    vim_strncpy(buf + off, (char_u *)"...",
-					     (size_t)(SPACE_FOR_ARGNR - off));
-		}
-		STRCAT(buf, ")");
-	    }
-
-	    append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE);
-
-#if defined(FEAT_CLIENTSERVER)
-	    if (serverName != NULL)
-	    {
-		STRCAT(buf, " - ");
-		vim_strcat(buf, serverName, IOSIZE);
-	    }
-	    else
-#endif
-		STRCAT(buf, " - VIM");
-
-	    if (maxlen > 0)
-	    {
-		// make it shorter by removing a bit in the middle
-		if (vim_strsize(buf) > maxlen)
-		    trunc_string(buf, buf, maxlen, IOSIZE);
-	    }
-	}
-    }
-    mustset = value_changed(title_str, &lasttitle);
-
-    if (p_icon)
-    {
-	icon_str = buf;
-	if (*p_iconstring != NUL)
-	{
-#ifdef FEAT_STL_OPT
-	    if (stl_syntax & STL_IN_ICON)
-		build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring,
-				 (char_u *)"iconstring", 0, 0, 0, NULL, NULL);
-	    else
-#endif
-		icon_str = p_iconstring;
-	}
-	else
-	{
-	    if (buf_spname(curbuf) != NULL)
-		p = buf_spname(curbuf);
-	    else		    // use file name only in icon
-		p = gettail(curbuf->b_ffname);
-	    *icon_str = NUL;
-	    // Truncate name at 100 bytes.
-	    len = (int)STRLEN(p);
-	    if (len > 100)
-	    {
-		len -= 100;
-		if (has_mbyte)
-		    len += (*mb_tail_off)(p, p + len) + 1;
-		p += len;
-	    }
-	    STRCPY(icon_str, p);
-	    trans_characters(icon_str, IOSIZE);
-	}
-    }
-
-    mustset |= value_changed(icon_str, &lasticon);
-
-    if (mustset)
-	resettitle();
-}
-
-/*
- * Used for title and icon: Check if "str" differs from "*last".  Set "*last"
- * from "str" if it does.
- * Return TRUE if resettitle() is to be called.
- */
-    static int
-value_changed(char_u *str, char_u **last)
-{
-    if ((str == NULL) != (*last == NULL)
-	    || (str != NULL && *last != NULL && STRCMP(str, *last) != 0))
-    {
-	vim_free(*last);
-	if (str == NULL)
-	{
-	    *last = NULL;
-	    mch_restore_title(
-		  last == &lasttitle ? SAVE_RESTORE_TITLE : SAVE_RESTORE_ICON);
-	}
-	else
-	{
-	    *last = vim_strsave(str);
-	    return TRUE;
-	}
-    }
-    return FALSE;
-}
-
-/*
- * Put current window title back (used after calling a shell)
- */
-    void
-resettitle(void)
-{
-    mch_settitle(lasttitle, lasticon);
-}
-
-# if defined(EXITFREE) || defined(PROTO)
-    void
-free_titles(void)
-{
-    vim_free(lasttitle);
-    vim_free(lasticon);
-}
-# endif
-
-
-#if defined(FEAT_STL_OPT) || defined(FEAT_GUI_TABLINE) || defined(PROTO)
-
-/*
- * Used for building in the status line.
- */
-typedef struct
-{
-    char_u	*stl_start;
-    int		stl_minwid;
-    int		stl_maxwid;
-    enum {
-	Normal,
-	Empty,
-	Group,
-	Separate,
-	Highlight,
-	TabPage,
-	Trunc
-    }		stl_type;
-} stl_item_T;
-
-static size_t		stl_items_len = 20; // Initial value, grows as needed.
-static stl_item_T      *stl_items = NULL;
-static int	       *stl_groupitem = NULL;
-static stl_hlrec_T     *stl_hltab = NULL;
-static stl_hlrec_T     *stl_tabtab = NULL;
-static int		*stl_separator_locations = NULL;
-
-/*
- * Build a string from the status line items in "fmt".
- * Return length of string in screen cells.
- *
- * Normally works for window "wp", except when working for 'tabline' then it
- * is "curwin".
- *
- * Items are drawn interspersed with the text that surrounds it
- * Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation
- * Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
- *
- * If maxwidth is not zero, the string will be filled at any middle marker
- * or truncated if too long, fillchar is used for all whitespace.
- */
-    int
-build_stl_str_hl(
-    win_T	*wp,
-    char_u	*out,		// buffer to write into != NameBuff
-    size_t	outlen,		// length of out[]
-    char_u	*fmt,
-    char_u	*opt_name,      // option name corresponding to "fmt"
-    int		opt_scope,	// scope for "opt_name"
-    int		fillchar,
-    int		maxwidth,
-    stl_hlrec_T **hltab,	// return: HL attributes (can be NULL)
-    stl_hlrec_T **tabtab)	// return: tab page nrs (can be NULL)
-{
-    linenr_T	lnum;
-    size_t	len;
-    char_u	*p;
-    char_u	*s;
-    char_u	*t;
-    int		byteval;
-#ifdef FEAT_EVAL
-    int		use_sandbox;
-    win_T	*save_curwin;
-    buf_T	*save_curbuf;
-    int		save_VIsual_active;
-#endif
-    int		empty_line;
-    colnr_T	virtcol;
-    long	l;
-    long	n;
-    int		prevchar_isflag;
-    int		prevchar_isitem;
-    int		itemisflag;
-    int		fillable;
-    char_u	*str;
-    long	num;
-    int		width;
-    int		itemcnt;
-    int		curitem;
-    int		group_end_userhl;
-    int		group_start_userhl;
-    int		groupdepth;
-#ifdef FEAT_EVAL
-    int		evaldepth;
-#endif
-    int		minwid;
-    int		maxwid;
-    int		zeropad;
-    char_u	base;
-    char_u	opt;
-#define TMPLEN 70
-    char_u	buf_tmp[TMPLEN];
-    char_u	win_tmp[TMPLEN];
-    char_u	*usefmt = fmt;
-    stl_hlrec_T *sp;
-    int		save_redraw_not_allowed = redraw_not_allowed;
-    int		save_KeyTyped = KeyTyped;
-    // TODO: find out why using called_emsg_before makes tests fail, does it
-    // matter?
-    // int	called_emsg_before = called_emsg;
-    int		did_emsg_before = did_emsg;
-
-    // When inside update_screen() we do not want redrawing a statusline,
-    // ruler, title, etc. to trigger another redraw, it may cause an endless
-    // loop.
-    if (updating_screen)
-	redraw_not_allowed = TRUE;
-
-    if (stl_items == NULL)
-    {
-	stl_items = ALLOC_MULT(stl_item_T, stl_items_len);
-	stl_groupitem = ALLOC_MULT(int, stl_items_len);
-
-	// Allocate one more, because the last element is used to indicate the
-	// end of the list.
-	stl_hltab  = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
-	stl_tabtab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
-
-	stl_separator_locations = ALLOC_MULT(int, stl_items_len);
-    }
-
-#ifdef FEAT_EVAL
-    // if "fmt" was set insecurely it needs to be evaluated in the sandbox
-    use_sandbox = was_set_insecurely(opt_name, opt_scope);
-
-    // When the format starts with "%!" then evaluate it as an expression and
-    // use the result as the actual format string.
-    if (fmt[0] == '%' && fmt[1] == '!')
-    {
-	typval_T	tv;
-
-	tv.v_type = VAR_NUMBER;
-	tv.vval.v_number = wp->w_id;
-	set_var((char_u *)"g:statusline_winid", &tv, FALSE);
-
-	usefmt = eval_to_string_safe(fmt + 2, use_sandbox, FALSE, FALSE);
-	if (usefmt == NULL)
-	    usefmt = fmt;
-
-	do_unlet((char_u *)"g:statusline_winid", TRUE);
-    }
-#endif
-
-    if (fillchar == 0)
-	fillchar = ' ';
-
-    // The cursor in windows other than the current one isn't always
-    // up-to-date, esp. because of autocommands and timers.
-    lnum = wp->w_cursor.lnum;
-    if (lnum > wp->w_buffer->b_ml.ml_line_count)
-    {
-	lnum = wp->w_buffer->b_ml.ml_line_count;
-	wp->w_cursor.lnum = lnum;
-    }
-
-    // Get line & check if empty (cursorpos will show "0-1").  Note that
-    // p will become invalid when getting another buffer line.
-    p = ml_get_buf(wp->w_buffer, lnum, FALSE);
-    empty_line = (*p == NUL);
-
-    // Get the byte value now, in case we need it below. This is more efficient
-    // than making a copy of the line.
-    len = STRLEN(p);
-    if (wp->w_cursor.col > (colnr_T)len)
-    {
-	// Line may have changed since checking the cursor column, or the lnum
-	// was adjusted above.
-	wp->w_cursor.col = (colnr_T)len;
-	wp->w_cursor.coladd = 0;
-	byteval = 0;
-    }
-    else
-	byteval = (*mb_ptr2char)(p + wp->w_cursor.col);
-
-    groupdepth = 0;
-#ifdef FEAT_EVAL
-    evaldepth = 0;
-#endif
-    p = out;
-    curitem = 0;
-    prevchar_isflag = TRUE;
-    prevchar_isitem = FALSE;
-    for (s = usefmt; *s != NUL; )
-    {
-	if (curitem == (int)stl_items_len)
-	{
-	    size_t	new_len = stl_items_len * 3 / 2;
-
-	    stl_item_T *new_items =
-			  vim_realloc(stl_items, sizeof(stl_item_T) * new_len);
-	    if (new_items == NULL)
-		break;
-	    stl_items = new_items;
-
-	    int *new_groupitem =
-			     vim_realloc(stl_groupitem, sizeof(int) * new_len);
-	    if (new_groupitem == NULL)
-		break;
-	    stl_groupitem = new_groupitem;
-
-	    stl_hlrec_T	*new_hlrec = vim_realloc(stl_hltab,
-					  sizeof(stl_hlrec_T) * (new_len + 1));
-	    if (new_hlrec == NULL)
-		break;
-	    stl_hltab = new_hlrec;
-	    new_hlrec = vim_realloc(stl_tabtab,
-					  sizeof(stl_hlrec_T) * (new_len + 1));
-	    if (new_hlrec == NULL)
-		break;
-	    stl_tabtab = new_hlrec;
-
-	    int *new_separator_locs = vim_realloc(stl_separator_locations,
-					    sizeof(int) * new_len);
-	    if (new_separator_locs == NULL)
-		break;
-	    stl_separator_locations = new_separator_locs;;
-
-	    stl_items_len = new_len;
-	}
-
-	if (*s != '%')
-	    prevchar_isflag = prevchar_isitem = FALSE;
-
-	/*
-	 * Handle up to the next '%' or the end.
-	 */
-	while (*s != NUL && *s != '%' && p + 1 < out + outlen)
-	    *p++ = *s++;
-	if (*s == NUL || p + 1 >= out + outlen)
-	    break;
-
-	/*
-	 * Handle one '%' item.
-	 */
-	s++;
-	if (*s == NUL)  // ignore trailing %
-	    break;
-	if (*s == '%')
-	{
-	    if (p + 1 >= out + outlen)
-		break;
-	    *p++ = *s++;
-	    prevchar_isflag = prevchar_isitem = FALSE;
-	    continue;
-	}
-	// STL_SEPARATE: Separation between items, filled with white space.
-	if (*s == STL_SEPARATE)
-	{
-	    s++;
-	    if (groupdepth > 0)
-		continue;
-	    stl_items[curitem].stl_type = Separate;
-	    stl_items[curitem++].stl_start = p;
-	    continue;
-	}
-	if (*s == STL_TRUNCMARK)
-	{
-	    s++;
-	    stl_items[curitem].stl_type = Trunc;
-	    stl_items[curitem++].stl_start = p;
-	    continue;
-	}
-	if (*s == ')')
-	{
-	    s++;
-	    if (groupdepth < 1)
-		continue;
-	    groupdepth--;
-
-	    t = stl_items[stl_groupitem[groupdepth]].stl_start;
-	    *p = NUL;
-	    l = vim_strsize(t);
-	    if (curitem > stl_groupitem[groupdepth] + 1
-		    && stl_items[stl_groupitem[groupdepth]].stl_minwid == 0)
-	    {
-		// remove group if all items are empty and highlight group
-		// doesn't change
-		group_start_userhl = group_end_userhl = 0;
-		for (n = stl_groupitem[groupdepth] - 1; n >= 0; n--)
-		{
-		    if (stl_items[n].stl_type == Highlight)
-		    {
-			group_start_userhl = group_end_userhl =
-						       stl_items[n].stl_minwid;
-			break;
-		    }
-		}
-		for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
-		{
-		    if (stl_items[n].stl_type == Normal)
-			break;
-		    if (stl_items[n].stl_type == Highlight)
-			group_end_userhl = stl_items[n].stl_minwid;
-		}
-		if (n == curitem && group_start_userhl == group_end_userhl)
-		{
-		    // empty group
-		    p = t;
-		    l = 0;
-		    for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
-		    {
-			// do not use the highlighting from the removed group
-			if (stl_items[n].stl_type == Highlight)
-			    stl_items[n].stl_type = Empty;
-			// adjust the start position of TabPage to the next
-			// item position
-			if (stl_items[n].stl_type == TabPage)
-			    stl_items[n].stl_start = p;
-		    }
-		}
-	    }
-	    if (l > stl_items[stl_groupitem[groupdepth]].stl_maxwid)
-	    {
-		// truncate, remove n bytes of text at the start
-		if (has_mbyte)
-		{
-		    // Find the first character that should be included.
-		    n = 0;
-		    while (l >= stl_items[stl_groupitem[groupdepth]].stl_maxwid)
-		    {
-			l -= ptr2cells(t + n);
-			n += (*mb_ptr2len)(t + n);
-		    }
-		}
-		else
-		    n = (long)(p - t) - stl_items[stl_groupitem[groupdepth]]
-							       .stl_maxwid + 1;
-
-		*t = '<';
-		mch_memmove(t + 1, t + n, (size_t)(p - (t + n)));
-		p = p - n + 1;
-
-		// Fill up space left over by half a double-wide char.
-		while (++l < stl_items[stl_groupitem[groupdepth]].stl_minwid)
-		    MB_CHAR2BYTES(fillchar, p);
-
-		// correct the start of the items for the truncation
-		for (l = stl_groupitem[groupdepth] + 1; l < curitem; l++)
-		{
-		    // Minus one for the leading '<' added above.
-		    stl_items[l].stl_start -= n - 1;
-		    if (stl_items[l].stl_start < t)
-			stl_items[l].stl_start = t;
-		}
-	    }
-	    else if (abs(stl_items[stl_groupitem[groupdepth]].stl_minwid) > l)
-	    {
-		// fill
-		n = stl_items[stl_groupitem[groupdepth]].stl_minwid;
-		if (n < 0)
-		{
-		    // fill by appending characters
-		    n = 0 - n;
-		    while (l++ < n && p + 1 < out + outlen)
-			MB_CHAR2BYTES(fillchar, p);
-		}
-		else
-		{
-		    // fill by inserting characters
-		    l = (n - l) * MB_CHAR2LEN(fillchar);
-		    mch_memmove(t + l, t, (size_t)(p - t));
-		    if (p + l >= out + outlen)
-			l = (long)((out + outlen) - p - 1);
-		    p += l;
-		    for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
-			stl_items[n].stl_start += l;
-		    for ( ; l > 0; l--)
-			MB_CHAR2BYTES(fillchar, t);
-		}
-	    }
-	    continue;
-	}
-	minwid = 0;
-	maxwid = 9999;
-	zeropad = FALSE;
-	l = 1;
-	if (*s == '0')
-	{
-	    s++;
-	    zeropad = TRUE;
-	}
-	if (*s == '-')
-	{
-	    s++;
-	    l = -1;
-	}
-	if (VIM_ISDIGIT(*s))
-	{
-	    minwid = (int)getdigits(&s);
-	    if (minwid < 0)	// overflow
-		minwid = 0;
-	}
-	if (*s == STL_USER_HL)
-	{
-	    stl_items[curitem].stl_type = Highlight;
-	    stl_items[curitem].stl_start = p;
-	    stl_items[curitem].stl_minwid = minwid > 9 ? 1 : minwid;
-	    s++;
-	    curitem++;
-	    continue;
-	}
-	if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR)
-	{
-	    if (*s == STL_TABCLOSENR)
-	    {
-		if (minwid == 0)
-		{
-		    // %X ends the close label, go back to the previously
-		    // define tab label nr.
-		    for (n = curitem - 1; n >= 0; --n)
-			if (stl_items[n].stl_type == TabPage
-					       && stl_items[n].stl_minwid >= 0)
-			{
-			    minwid = stl_items[n].stl_minwid;
-			    break;
-			}
-		}
-		else
-		    // close nrs are stored as negative values
-		    minwid = - minwid;
-	    }
-	    stl_items[curitem].stl_type = TabPage;
-	    stl_items[curitem].stl_start = p;
-	    stl_items[curitem].stl_minwid = minwid;
-	    s++;
-	    curitem++;
-	    continue;
-	}
-	if (*s == '.')
-	{
-	    s++;
-	    if (VIM_ISDIGIT(*s))
-	    {
-		maxwid = (int)getdigits(&s);
-		if (maxwid <= 0)	// overflow
-		    maxwid = 50;
-	    }
-	}
-	minwid = (minwid > 50 ? 50 : minwid) * l;
-	if (*s == '(')
-	{
-	    stl_groupitem[groupdepth++] = curitem;
-	    stl_items[curitem].stl_type = Group;
-	    stl_items[curitem].stl_start = p;
-	    stl_items[curitem].stl_minwid = minwid;
-	    stl_items[curitem].stl_maxwid = maxwid;
-	    s++;
-	    curitem++;
-	    continue;
-	}
-#ifdef FEAT_EVAL
-	// Denotes end of expanded %{} block
-	if (*s == '}' && evaldepth > 0)
-	{
-	    s++;
-	    evaldepth--;
-	    continue;
-	}
-#endif
-	if (vim_strchr(STL_ALL, *s) == NULL)
-	{
-	    if (*s == NUL)  // can happen with "%0"
-		break;
-	    s++;
-	    continue;
-	}
-	opt = *s++;
-
-	// OK - now for the real work
-	base = 'D';
-	itemisflag = FALSE;
-	fillable = TRUE;
-	num = -1;
-	str = NULL;
-	switch (opt)
-	{
-	case STL_FILEPATH:
-	case STL_FULLPATH:
-	case STL_FILENAME:
-	    fillable = FALSE;	// don't change ' ' to fillchar
-	    if (buf_spname(wp->w_buffer) != NULL)
-		vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1);
-	    else
-	    {
-		t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
-					  : wp->w_buffer->b_fname;
-		home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE);
-	    }
-	    trans_characters(NameBuff, MAXPATHL);
-	    if (opt != STL_FILENAME)
-		str = NameBuff;
-	    else
-		str = gettail(NameBuff);
-	    break;
-
-	case STL_VIM_EXPR: // '{'
-	{
-#ifdef FEAT_EVAL
-	    char_u *block_start = s - 1;
-#endif
-	    int reevaluate = (*s == '%');
-
-	    if (reevaluate)
-		s++;
-	    itemisflag = TRUE;
-	    t = p;
-	    while ((*s != '}' || (reevaluate && s[-1] != '%'))
-					  && *s != NUL && p + 1 < out + outlen)
-		*p++ = *s++;
-	    if (*s != '}')	// missing '}' or out of space
-		break;
-	    s++;
-	    if (reevaluate)
-		p[-1] = 0; // remove the % at the end of %{% expr %}
-	    else
-		*p = 0;
-	    p = t;
-#ifdef FEAT_EVAL
-	    vim_snprintf((char *)buf_tmp, sizeof(buf_tmp),
-							 "%d", curbuf->b_fnum);
-	    set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp);
-	    vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->w_id);
-	    set_internal_string_var((char_u *)"g:actual_curwin", win_tmp);
-
-	    save_curbuf = curbuf;
-	    save_curwin = curwin;
-	    save_VIsual_active = VIsual_active;
-	    curwin = wp;
-	    curbuf = wp->w_buffer;
-	    // Visual mode is only valid in the current window.
-	    if (curwin != save_curwin)
-		VIsual_active = FALSE;
-
-	    str = eval_to_string_safe(p, use_sandbox, FALSE, FALSE);
-
-	    curwin = save_curwin;
-	    curbuf = save_curbuf;
-	    VIsual_active = save_VIsual_active;
-	    do_unlet((char_u *)"g:actual_curbuf", TRUE);
-	    do_unlet((char_u *)"g:actual_curwin", TRUE);
-
-	    if (str != NULL && *str != 0)
-	    {
-		if (*skipdigits(str) == NUL)
-		{
-		    num = atoi((char *)str);
-		    VIM_CLEAR(str);
-		    itemisflag = FALSE;
-		}
-	    }
-
-	    // If the output of the expression needs to be evaluated
-	    // replace the %{} block with the result of evaluation
-	    if (reevaluate && str != NULL && *str != 0
-		    && strchr((const char *)str, '%') != NULL
-		    && evaldepth < MAX_STL_EVAL_DEPTH)
-	    {
-		size_t parsed_usefmt = (size_t)(block_start - usefmt);
-		size_t str_length = strlen((const char *)str);
-		size_t fmt_length = strlen((const char *)s);
-		size_t new_fmt_len = parsed_usefmt
-						 + str_length + fmt_length + 3;
-		char_u *new_fmt = (char_u *)alloc(new_fmt_len * sizeof(char_u));
-		char_u *new_fmt_p = new_fmt;
-
-		new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt)
-							       + parsed_usefmt;
-		new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length)
-								  + str_length;
-		new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2;
-		new_fmt_p = (char_u *)memcpy(new_fmt_p , s, fmt_length)
-								  + fmt_length;
-		*new_fmt_p = 0;
-		new_fmt_p = NULL;
-
-		if (usefmt != fmt)
-		    vim_free(usefmt);
-		VIM_CLEAR(str);
-		usefmt = new_fmt;
-		s = usefmt + parsed_usefmt;
-		evaldepth++;
-		continue;
-	    }
-#endif
-	    break;
-	}
-	case STL_LINE:
-	    num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
-		  ? 0L : (long)(wp->w_cursor.lnum);
-	    break;
-
-	case STL_NUMLINES:
-	    num = wp->w_buffer->b_ml.ml_line_count;
-	    break;
-
-	case STL_COLUMN:
-	    num = (State & MODE_INSERT) == 0 && empty_line
-					       ? 0 : (int)wp->w_cursor.col + 1;
-	    break;
-
-	case STL_VIRTCOL:
-	case STL_VIRTCOL_ALT:
-	    virtcol = wp->w_virtcol + 1;
-	    // Don't display %V if it's the same as %c.
-	    if (opt == STL_VIRTCOL_ALT
-		    && (virtcol == (colnr_T)((State & MODE_INSERT) == 0
-			       && empty_line ? 0 : (int)wp->w_cursor.col + 1)))
-		break;
-	    num = (long)virtcol;
-	    break;
-
-	case STL_PERCENTAGE:
-	    num = (int)(((long)wp->w_cursor.lnum * 100L) /
-			(long)wp->w_buffer->b_ml.ml_line_count);
-	    break;
-
-	case STL_ALTPERCENT:
-	    str = buf_tmp;
-	    get_rel_pos(wp, str, TMPLEN);
-	    break;
-
-	case STL_SHOWCMD:
-	    if (p_sc && STRCMP(opt_name, p_sloc) == 0)
-		str = showcmd_buf;
-	    break;
-
-	case STL_ARGLISTSTAT:
-	    fillable = FALSE;
-	    buf_tmp[0] = 0;
-	    if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), FALSE))
-		str = buf_tmp;
-	    break;
-
-	case STL_KEYMAP:
-	    fillable = FALSE;
-	    if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN))
-		str = buf_tmp;
-	    break;
-	case STL_PAGENUM:
-#if defined(FEAT_PRINTER) || defined(FEAT_GUI_TABLINE)
-	    num = printer_page_num;
-#else
-	    num = 0;
-#endif
-	    break;
-
-	case STL_BUFNO:
-	    num = wp->w_buffer->b_fnum;
-	    break;
-
-	case STL_OFFSET_X:
-	    base = 'X';
-	    // FALLTHROUGH
-	case STL_OFFSET:
-#ifdef FEAT_BYTEOFF
-	    l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
-	    num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0
-		       ? 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line
-				? 0 : (int)wp->w_cursor.col);
-#endif
-	    break;
-
-	case STL_BYTEVAL_X:
-	    base = 'X';
-	    // FALLTHROUGH
-	case STL_BYTEVAL:
-	    num = byteval;
-	    if (num == NL)
-		num = 0;
-	    else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC)
-		num = NL;
-	    break;
-
-	case STL_ROFLAG:
-	case STL_ROFLAG_ALT:
-	    itemisflag = TRUE;
-	    if (wp->w_buffer->b_p_ro)
-		str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"));
-	    break;
-
-	case STL_HELPFLAG:
-	case STL_HELPFLAG_ALT:
-	    itemisflag = TRUE;
-	    if (wp->w_buffer->b_help)
-		str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP"
-							       : _("[Help]"));
-	    break;
-
-	case STL_FILETYPE:
-	    if (*wp->w_buffer->b_p_ft != NUL
-		    && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3)
-	    {
-		vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]",
-							wp->w_buffer->b_p_ft);
-		str = buf_tmp;
-	    }
-	    break;
-
-	case STL_FILETYPE_ALT:
-	    itemisflag = TRUE;
-	    if (*wp->w_buffer->b_p_ft != NUL
-		    && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2)
-	    {
-		vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s",
-							wp->w_buffer->b_p_ft);
-		for (t = buf_tmp; *t != 0; t++)
-		    *t = TOUPPER_LOC(*t);
-		str = buf_tmp;
-	    }
-	    break;
-
-#if defined(FEAT_QUICKFIX)
-	case STL_PREVIEWFLAG:
-	case STL_PREVIEWFLAG_ALT:
-	    itemisflag = TRUE;
-	    if (wp->w_p_pvw)
-		str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV"
-							    : _("[Preview]"));
-	    break;
-
-	case STL_QUICKFIX:
-	    if (bt_quickfix(wp->w_buffer))
-		str = (char_u *)(wp->w_llist_ref
-			    ? _(msg_loclist)
-			    : _(msg_qflist));
-	    break;
-#endif
-
-	case STL_MODIFIED:
-	case STL_MODIFIED_ALT:
-	    itemisflag = TRUE;
-	    switch ((opt == STL_MODIFIED_ALT)
-		    + bufIsChanged(wp->w_buffer) * 2
-		    + (!wp->w_buffer->b_p_ma) * 4)
-	    {
-		case 2: str = (char_u *)"[+]"; break;
-		case 3: str = (char_u *)",+"; break;
-		case 4: str = (char_u *)"[-]"; break;
-		case 5: str = (char_u *)",-"; break;
-		case 6: str = (char_u *)"[+-]"; break;
-		case 7: str = (char_u *)",+-"; break;
-	    }
-	    break;
-
-	case STL_HIGHLIGHT:
-	    t = s;
-	    while (*s != '#' && *s != NUL)
-		++s;
-	    if (*s == '#')
-	    {
-		stl_items[curitem].stl_type = Highlight;
-		stl_items[curitem].stl_start = p;
-		stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t));
-		curitem++;
-	    }
-	    if (*s != NUL)
-		++s;
-	    continue;
-	}
-
-	stl_items[curitem].stl_start = p;
-	stl_items[curitem].stl_type = Normal;
-	if (str != NULL && *str)
-	{
-	    t = str;
-	    if (itemisflag)
-	    {
-		if ((t[0] && t[1])
-			&& ((!prevchar_isitem && *t == ',')
-			      || (prevchar_isflag && *t == ' ')))
-		    t++;
-		prevchar_isflag = TRUE;
-	    }
-	    l = vim_strsize(t);
-	    if (l > 0)
-		prevchar_isitem = TRUE;
-	    if (l > maxwid)
-	    {
-		while (l >= maxwid)
-		    if (has_mbyte)
-		    {
-			l -= ptr2cells(t);
-			t += (*mb_ptr2len)(t);
-		    }
-		    else
-			l -= byte2cells(*t++);
-		if (p + 1 >= out + outlen)
-		    break;
-		*p++ = '<';
-	    }
-	    if (minwid > 0)
-	    {
-		for (; l < minwid && p + 1 < out + outlen; l++)
-		{
-		    // Don't put a "-" in front of a digit.
-		    if (l + 1 == minwid && fillchar == '-' && VIM_ISDIGIT(*t))
-			*p++ = ' ';
-		    else
-			MB_CHAR2BYTES(fillchar, p);
-		}
-		minwid = 0;
-	    }
-	    else
-		minwid *= -1;
-	    for (; *t && p + 1 < out + outlen; t++)
-	    {
-		// Change a space by fillchar, unless fillchar is '-' and a
-		// digit follows.
-		if (fillable && *t == ' '
-				&& (!VIM_ISDIGIT(*(t + 1)) || fillchar != '-'))
-		    MB_CHAR2BYTES(fillchar, p);
-		else
-		    *p++ = *t;
-	    }
-	    for (; l < minwid && p + 1 < out + outlen; l++)
-		MB_CHAR2BYTES(fillchar, p);
-	}
-	else if (num >= 0)
-	{
-	    int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
-	    char_u nstr[20];
-
-	    if (p + 20 >= out + outlen)
-		break;		// not sufficient space
-	    prevchar_isitem = TRUE;
-	    t = nstr;
-	    if (opt == STL_VIRTCOL_ALT)
-	    {
-		*t++ = '-';
-		minwid--;
-	    }
-	    *t++ = '%';
-	    if (zeropad)
-		*t++ = '0';
-	    *t++ = '*';
-	    *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd');
-	    *t = 0;
-
-	    for (n = num, l = 1; n >= nbase; n /= nbase)
-		l++;
-	    if (opt == STL_VIRTCOL_ALT)
-		l++;
-	    if (l > maxwid)
-	    {
-		l += 2;
-		n = l - maxwid;
-		while (l-- > maxwid)
-		    num /= nbase;
-		*t++ = '>';
-		*t++ = '%';
-		*t = t[-3];
-		*++t = 0;
-		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
-								   0, num, n);
-	    }
-	    else
-		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
-								 minwid, num);
-	    p += STRLEN(p);
-	}
-	else
-	    stl_items[curitem].stl_type = Empty;
-
-	if (num >= 0 || (!itemisflag && str != NULL && *str != NUL))
-	    prevchar_isflag = FALSE;	    // Item not NULL, but not a flag
-					    //
-	if (opt == STL_VIM_EXPR)
-	    vim_free(str);
-	curitem++;
-    }
-    *p = NUL;
-    itemcnt = curitem;
-
-#ifdef FEAT_EVAL
-    if (usefmt != fmt)
-	vim_free(usefmt);
-#endif
-
-    width = vim_strsize(out);
-    if (maxwidth > 0 && width > maxwidth)
-    {
-	// Result is too long, must truncate somewhere.
-	l = 0;
-	if (itemcnt == 0)
-	    s = out;
-	else
-	{
-	    for ( ; l < itemcnt; l++)
-		if (stl_items[l].stl_type == Trunc)
-		{
-		    // Truncate at %< item.
-		    s = stl_items[l].stl_start;
-		    break;
-		}
-	    if (l == itemcnt)
-	    {
-		// No %< item, truncate first item.
-		s = stl_items[0].stl_start;
-		l = 0;
-	    }
-	}
-
-	if (width - vim_strsize(s) >= maxwidth)
-	{
-	    // Truncation mark is beyond max length
-	    if (has_mbyte)
-	    {
-		s = out;
-		width = 0;
-		for (;;)
-		{
-		    width += ptr2cells(s);
-		    if (width >= maxwidth)
-			break;
-		    s += (*mb_ptr2len)(s);
-		}
-		// Fill up for half a double-wide character.
-		while (++width < maxwidth)
-		    MB_CHAR2BYTES(fillchar, s);
-	    }
-	    else
-		s = out + maxwidth - 1;
-	    for (l = 0; l < itemcnt; l++)
-		if (stl_items[l].stl_start > s)
-		    break;
-	    itemcnt = l;
-	    *s++ = '>';
-	    *s = 0;
-	}
-	else
-	{
-	    if (has_mbyte)
-	    {
-		n = 0;
-		while (width >= maxwidth)
-		{
-		    width -= ptr2cells(s + n);
-		    n += (*mb_ptr2len)(s + n);
-		}
-	    }
-	    else
-		n = width - maxwidth + 1;
-	    p = s + n;
-	    STRMOVE(s + 1, p);
-	    *s = '<';
-
-	    // Fill up for half a double-wide character.
-	    while (++width < maxwidth)
-	    {
-		s = s + STRLEN(s);
-		MB_CHAR2BYTES(fillchar, s);
-		*s = NUL;
-	    }
-
-	    --n;	// count the '<'
-	    for (; l < itemcnt; l++)
-	    {
-		if (stl_items[l].stl_start - n >= s)
-		    stl_items[l].stl_start -= n;
-		else
-		    stl_items[l].stl_start = s;
-	    }
-	}
-	width = maxwidth;
-    }
-    else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < outlen)
-    {
-	// Find how many separators there are, which we will use when
-	// figuring out how many groups there are.
-	int num_separators = 0;
-
-	for (l = 0; l < itemcnt; l++)
-	{
-	    if (stl_items[l].stl_type == Separate)
-	    {
-		// Create an array of the start location for each separator
-		// mark.
-		stl_separator_locations[num_separators] = l;
-		num_separators++;
-	    }
-	}
-
-	// If we have separated groups, then we deal with it now
-	if (num_separators)
-	{
-	    int standard_spaces;
-	    int final_spaces;
-
-	    standard_spaces = (maxwidth - width) / num_separators;
-	    final_spaces = (maxwidth - width) -
-					standard_spaces * (num_separators - 1);
-	    for (l = 0; l < num_separators; l++)
-	    {
-		int dislocation = (l == (num_separators - 1)) ?
-					final_spaces : standard_spaces;
-		dislocation *= MB_CHAR2LEN(fillchar);
-		char_u *start = stl_items[stl_separator_locations[l]].stl_start;
-		char_u *seploc = start + dislocation;
-		STRMOVE(seploc, start);
-		for (s = start; s < seploc;)
-		    MB_CHAR2BYTES(fillchar, s);
-
-		for (int i = stl_separator_locations[l] + 1; i < itemcnt; i++)
-		    stl_items[i].stl_start += dislocation;
-	    }
-
-	    width = maxwidth;
-	}
-    }
-
-    // Store the info about highlighting.
-    if (hltab != NULL)
-    {
-	*hltab = stl_hltab;
-	sp = stl_hltab;
-	for (l = 0; l < itemcnt; l++)
-	{
-	    if (stl_items[l].stl_type == Highlight)
-	    {
-		sp->start = stl_items[l].stl_start;
-		sp->userhl = stl_items[l].stl_minwid;
-		sp++;
-	    }
-	}
-	sp->start = NULL;
-	sp->userhl = 0;
-    }
-
-    // Store the info about tab pages labels.
-    if (tabtab != NULL)
-    {
-	*tabtab = stl_tabtab;
-	sp = stl_tabtab;
-	for (l = 0; l < itemcnt; l++)
-	{
-	    if (stl_items[l].stl_type == TabPage)
-	    {
-		sp->start = stl_items[l].stl_start;
-		sp->userhl = stl_items[l].stl_minwid;
-		sp++;
-	    }
-	}
-	sp->start = NULL;
-	sp->userhl = 0;
-    }
-
-    redraw_not_allowed = save_redraw_not_allowed;
-
-    // A user function may reset KeyTyped, restore it.
-    KeyTyped = save_KeyTyped;
-
-    // Check for an error.  If there is one the display will be messed up and
-    // might loop redrawing.  Avoid that by making the corresponding option
-    // empty.
-    // TODO: find out why using called_emsg_before makes tests fail, does it
-    // matter?
-    // if (called_emsg > called_emsg_before)
-    if (did_emsg > did_emsg_before)
-	set_string_option_direct(opt_name, -1, (char_u *)"",
-					      OPT_FREE | opt_scope, SID_ERROR);
-
-    return width;
-}
-#endif // FEAT_STL_OPT
-
-/*
- * Get relative cursor position in window into "buf[buflen]", in the localized
- * percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate.
- */
-    void
-get_rel_pos(
-    win_T	*wp,
-    char_u	*buf,
-    int		buflen)
-{
-    long	above; // number of lines above window
-    long	below; // number of lines below window
-
-    if (buflen < 3) // need at least 3 chars for writing
-	return;
-    above = wp->w_topline - 1;
-#ifdef FEAT_DIFF
-    above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill;
-    if (wp->w_topline == 1 && wp->w_topfill >= 1)
-	above = 0;  // All buffer lines are displayed and there is an
-		    // indication of filler lines, that can be considered
-		    // seeing all lines.
-#endif
-    below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1;
-    if (below <= 0)
-	vim_strncpy(buf, (char_u *)(above == 0 ? _("All") : _("Bot")),
-		    (size_t)(buflen - 1));
-    else if (above <= 0)
-	vim_strncpy(buf, (char_u *)_("Top"), (size_t)(buflen - 1));
-    else
-    {
-	int perc = (above > 1000000L)
-			?  (int)(above / ((above + below) / 100L))
-			:  (int)(above * 100L / (above + below));
-
-	char *p = (char *)buf;
-	size_t l = buflen;
-	if (perc < 10)
-	{
-	    // prepend one space
-	    buf[0] = ' ';
-	    ++p;
-	    --l;
-	}
-	// localized percentage value
-	vim_snprintf(p, l, _("%d%%"), perc);
-    }
-}
-
-/*
- * Append (file 2 of 8) to "buf[buflen]", if editing more than one file.
- * Return TRUE if it was appended.
- */
-    static int
-append_arg_number(
-    win_T	*wp,
-    char_u	*buf,
-    int		buflen,
-    int		add_file)	// Add "file" before the arg number
-{
-    if (ARGCOUNT <= 1)		// nothing to do
-	return FALSE;
-
-    char *msg;
-    switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0))
-    {
-	case 0: msg = _(" (%d of %d)"); break;
-	case 1: msg = _(" ((%d) of %d)"); break;
-	case 2: msg = _(" (file %d of %d)"); break;
-	case 3: msg = _(" (file (%d) of %d)"); break;
-    }
-
-    char_u *p = buf + STRLEN(buf);	// go to the end of the buffer
-    vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), msg,
-						  wp->w_arg_idx + 1, ARGCOUNT);
-    return TRUE;
-}
-
-/*
- * If fname is not a full path, make it a full path.
- * Returns pointer to allocated memory (NULL for failure).
- */
-    char_u  *
-fix_fname(char_u  *fname)
-{
-    /*
-     * Force expanding the path always for Unix, because symbolic links may
-     * mess up the full path name, even though it starts with a '/'.
-     * Also expand when there is ".." in the file name, try to remove it,
-     * because "c:/src/../README" is equal to "c:/README".
-     * Similarly "c:/src//file" is equal to "c:/src/file".
-     * For MS-Windows also expand names like "longna~1" to "longname".
-     */
-#ifdef UNIX
-    return FullName_save(fname, TRUE);
-#else
-    if (!vim_isAbsName(fname)
-	    || strstr((char *)fname, "..") != NULL
-	    || strstr((char *)fname, "//") != NULL
-# ifdef BACKSLASH_IN_FILENAME
-	    || strstr((char *)fname, "\\\\") != NULL
-# endif
-# if defined(MSWIN)
-	    || vim_strchr(fname, '~') != NULL
-# endif
-	    )
-	return FullName_save(fname, FALSE);
-
-    fname = vim_strsave(fname);
-
-# ifdef USE_FNAME_CASE
-    if (fname != NULL)
-	fname_case(fname, 0);	// set correct case for file name
-# endif
-
-    return fname;
-#endif
-}
-
-/*
- * Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL.
- * "*ffname" becomes a pointer to allocated memory (or NULL).
- * When resolving a link both "*sfname" and "*ffname" will point to the same
- * allocated memory.
- * The "*ffname" and "*sfname" pointer values on call will not be freed.
- * Note that the resulting "*ffname" pointer should be considered not allocated.
- */
-    void
-fname_expand(
-    buf_T	*buf UNUSED,
-    char_u	**ffname,
-    char_u	**sfname)
-{
-    if (*ffname == NULL)	    // no file name given, nothing to do
-	return;
-    if (*sfname == NULL)	    // no short file name given, use ffname
-	*sfname = *ffname;
-    *ffname = fix_fname(*ffname);   // expand to full path
-
-#ifdef FEAT_SHORTCUT
-    if (!buf->b_p_bin)
-    {
-	char_u  *rfname;
-
-	// If the file name is a shortcut file, use the file it links to.
-	rfname = mch_resolve_path(*ffname, FALSE);
-	if (rfname != NULL)
-	{
-	    vim_free(*ffname);
-	    *ffname = rfname;
-	    *sfname = rfname;
-	}
-    }
-#endif
-}
-
-/*
- * Open a window for a number of buffers.
- */
-    void
-ex_buffer_all(exarg_T *eap)
-{
-    buf_T	*buf;
-    win_T	*wp, *wpnext;
-    int		split_ret = OK;
-    int		p_ea_save;
-    int		open_wins = 0;
-    int		r;
-    int		count;		// Maximum number of windows to open.
-    int		all;		// When TRUE also load inactive buffers.
-    int		had_tab = cmdmod.cmod_tab;
-    tabpage_T	*tpnext;
-
-    if (eap->addr_count == 0)	// make as many windows as possible
-	count = 9999;
-    else
-	count = eap->line2;	// make as many windows as specified
-    if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide)
-	all = FALSE;
-    else
-	all = TRUE;
-
-    // Stop Visual mode, the cursor and "VIsual" may very well be invalid after
-    // switching to another buffer.
-    reset_VIsual_and_resel();
-
-    setpcmark();
-
-#ifdef FEAT_GUI
-    need_mouse_correct = TRUE;
-#endif
-
-    /*
-     * Close superfluous windows (two windows for the same buffer).
-     * Also close windows that are not full-width.
-     */
-    if (had_tab > 0)
-	goto_tabpage_tp(first_tabpage, TRUE, TRUE);
-    for (;;)
-    {
-	tpnext = curtab->tp_next;
-	for (wp = firstwin; wp != NULL; wp = wpnext)
-	{
-	    wpnext = wp->w_next;
-	    if ((wp->w_buffer->b_nwindows > 1
-			|| ((cmdmod.cmod_split & WSP_VERT)
-			    ? wp->w_height + wp->w_status_height < Rows - p_ch
-							     - tabline_height()
-			    : wp->w_width != Columns)
-			|| (had_tab > 0 && wp != firstwin))
-		    && !ONE_WINDOW
-		    && !(wp->w_closing || wp->w_buffer->b_locked > 0)
-		    && !win_unlisted(wp))
-	    {
-		if (win_close(wp, FALSE) == FAIL)
-		    break;
-		// Just in case an autocommand does something strange with
-		// windows: start all over...
-		wpnext = firstwin;
-		tpnext = first_tabpage;
-		open_wins = 0;
-	    }
-	    else
-		++open_wins;
-	}
-
-	// Without the ":tab" modifier only do the current tab page.
-	if (had_tab == 0 || tpnext == NULL)
-	    break;
-	goto_tabpage_tp(tpnext, TRUE, TRUE);
-    }
-
-    /*
-     * Go through the buffer list.  When a buffer doesn't have a window yet,
-     * open one.  Otherwise move the window to the right position.
-     * Watch out for autocommands that delete buffers or windows!
-     */
-    // Don't execute Win/Buf Enter/Leave autocommands here.
-    ++autocmd_no_enter;
-    win_enter(lastwin, FALSE);
-    ++autocmd_no_leave;
-    for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next)
-    {
-	// Check if this buffer needs a window
-	if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl)
-	    continue;
-
-	if (had_tab != 0)
-	{
-	    // With the ":tab" modifier don't move the window.
-	    if (buf->b_nwindows > 0)
-		wp = lastwin;	    // buffer has a window, skip it
-	    else
-		wp = NULL;
-	}
-	else
-	{
-	    // Check if this buffer already has a window
-	    FOR_ALL_WINDOWS(wp)
-		if (wp->w_buffer == buf)
-		    break;
-	    // If the buffer already has a window, move it
-	    if (wp != NULL)
-		win_move_after(wp, curwin);
-	}
-
-	if (wp == NULL && split_ret == OK)
-	{
-	    bufref_T	bufref;
-
-	    set_bufref(&bufref, buf);
-
-	    // Split the window and put the buffer in it
-	    p_ea_save = p_ea;
-	    p_ea = TRUE;		// use space from all windows
-	    split_ret = win_split(0, WSP_ROOM | WSP_BELOW);
-	    ++open_wins;
-	    p_ea = p_ea_save;
-	    if (split_ret == FAIL)
-		continue;
-
-	    // Open the buffer in this window.
-	    swap_exists_action = SEA_DIALOG;
-	    set_curbuf(buf, DOBUF_GOTO);
-	    if (!bufref_valid(&bufref))
-	    {
-		// autocommands deleted the buffer!!!
-		swap_exists_action = SEA_NONE;
-		break;
-	    }
-	    if (swap_exists_action == SEA_QUIT)
-	    {
-#if defined(FEAT_EVAL)
-		cleanup_T   cs;
-
-		// Reset the error/interrupt/exception state here so that
-		// aborting() returns FALSE when closing a window.
-		enter_cleanup(&cs);
-#endif
-
-		// User selected Quit at ATTENTION prompt; close this window.
-		win_close(curwin, TRUE);
-		--open_wins;
-		swap_exists_action = SEA_NONE;
-		swap_exists_did_quit = TRUE;
-
-#if defined(FEAT_EVAL)
-		// Restore the error/interrupt/exception state if not
-		// discarded by a new aborting error, interrupt, or uncaught
-		// exception.
-		leave_cleanup(&cs);
-#endif
-	    }
-	    else
-		handle_swap_exists(NULL);
-	}
-
-	ui_breakcheck();
-	if (got_int)
-	{
-	    (void)vgetc();	// only break the file loading, not the rest
-	    break;
-	}
-#ifdef FEAT_EVAL
-	// Autocommands deleted the buffer or aborted script processing!!!
-	if (aborting())
-	    break;
-#endif
-	// When ":tab" was used open a new tab for a new window repeatedly.
-	if (had_tab > 0 && tabpage_index(NULL) <= p_tpm)
-	    cmdmod.cmod_tab = 9999;
-    }
-    --autocmd_no_enter;
-    win_enter(firstwin, FALSE);		// back to first window
-    --autocmd_no_leave;
-
-    /*
-     * Close superfluous windows.
-     */
-    for (wp = lastwin; open_wins > count; )
-    {
-	r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer)
-				     || autowrite(wp->w_buffer, FALSE) == OK);
-	if (!win_valid(wp))
-	{
-	    // BufWrite Autocommands made the window invalid, start over
-	    wp = lastwin;
-	}
-	else if (r)
-	{
-	    win_close(wp, !buf_hide(wp->w_buffer));
-	    --open_wins;
-	    wp = lastwin;
-	}
-	else
-	{
-	    wp = wp->w_prev;
-	    if (wp == NULL)
-		break;
-	}
-    }
-}
-
-
-static int  chk_modeline(linenr_T, int);
-
-/*
- * do_modelines() - process mode lines for the current file
- *
- * "flags" can be:
- * OPT_WINONLY	    only set options local to window
- * OPT_NOWIN	    don't set options local to window
- *
- * Returns immediately if the "ml" option isn't set.
- */
-    void
-do_modelines(int flags)
-{
-    linenr_T	lnum;
-    int		nmlines;
-    static int	entered = 0;
-
-    if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0)
-	return;
-
-    // Disallow recursive entry here.  Can happen when executing a modeline
-    // triggers an autocommand, which reloads modelines with a ":do".
-    if (entered)
-	return;
-
-    ++entered;
-    for (lnum = 1; curbuf->b_p_ml && lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines;
-								       ++lnum)
-	if (chk_modeline(lnum, flags) == FAIL)
-	    nmlines = 0;
-
-    for (lnum = curbuf->b_ml.ml_line_count; curbuf->b_p_ml && lnum > 0 && lnum > nmlines
-		       && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum)
-	if (chk_modeline(lnum, flags) == FAIL)
-	    nmlines = 0;
-    --entered;
-}
-
-#include "version.h"		// for version number
-
-/*
- * chk_modeline() - check a single line for a mode string
- * Return FAIL if an error encountered.
- */
-    static int
-chk_modeline(
-    linenr_T	lnum,
-    int		flags)		// Same as for do_modelines().
-{
-    char_u	*s;
-    char_u	*e;
-    char_u	*linecopy;		// local copy of any modeline found
-    int		prev;
-    int		vers;
-    int		end;
-    int		retval = OK;
-    sctx_T	save_current_sctx;
-    ESTACK_CHECK_DECLARATION;
-
-    prev = -1;
-    for (s = ml_get(lnum); *s != NUL; ++s)
-    {
-	if (prev == -1 || vim_isspace(prev))
-	{
-	    if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0)
-		    || STRNCMP(s, "vi:", (size_t)3) == 0)
-		break;
-	    // Accept both "vim" and "Vim".
-	    if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm')
-	    {
-		if (s[3] == '<' || s[3] == '=' || s[3] == '>')
-		    e = s + 4;
-		else
-		    e = s + 3;
-		vers = getdigits(&e);
-		if (*e == ':'
-			&& (s[0] != 'V'
-				  || STRNCMP(skipwhite(e + 1), "set", 3) == 0)
-			&& (s[3] == ':'
-			    || (VIM_VERSION_100 >= vers && isdigit(s[3]))
-			    || (VIM_VERSION_100 < vers && s[3] == '<')
-			    || (VIM_VERSION_100 > vers && s[3] == '>')
-			    || (VIM_VERSION_100 == vers && s[3] == '=')))
-		    break;
-	    }
-	}
-	prev = *s;
-    }
-
-    if (*s)
-    {
-	do				// skip over "ex:", "vi:" or "vim:"
-	    ++s;
-	while (s[-1] != ':');
-
-	s = linecopy = vim_strsave(s);	// copy the line, it will change
-	if (linecopy == NULL)
-	    return FAIL;
-
-	// prepare for emsg()
-	estack_push(ETYPE_MODELINE, (char_u *)"modelines", lnum);
-	ESTACK_CHECK_SETUP;
-
-	end = FALSE;
-	while (end == FALSE)
-	{
-	    s = skipwhite(s);
-	    if (*s == NUL)
-		break;
-
-	    /*
-	     * Find end of set command: ':' or end of line.
-	     * Skip over "\:", replacing it with ":".
-	     */
-	    for (e = s; *e != ':' && *e != NUL; ++e)
-		if (e[0] == '\\' && e[1] == ':')
-		    STRMOVE(e, e + 1);
-	    if (*e == NUL)
-		end = TRUE;
-
-	    /*
-	     * If there is a "set" command, require a terminating ':' and
-	     * ignore the stuff after the ':'.
-	     * "vi:set opt opt opt: foo" -- foo not interpreted
-	     * "vi:opt opt opt: foo" -- foo interpreted
-	     * Accept "se" for compatibility with Elvis.
-	     */
-	    if (STRNCMP(s, "set ", (size_t)4) == 0
-		    || STRNCMP(s, "se ", (size_t)3) == 0)
-	    {
-		if (*e != ':')		// no terminating ':'?
-		    break;
-		end = TRUE;
-		s = vim_strchr(s, ' ') + 1;
-	    }
-	    *e = NUL;			// truncate the set command
-
-	    if (*s != NUL)		// skip over an empty "::"
-	    {
-		int secure_save = secure;
-
-		save_current_sctx = current_sctx;
-		current_sctx.sc_version = 1;
-#ifdef FEAT_EVAL
-		current_sctx.sc_sid = SID_MODELINE;
-		current_sctx.sc_seq = 0;
-		current_sctx.sc_lnum = lnum;
-#endif
-
-		// Make sure no risky things are executed as a side effect.
-		secure = 1;
-
-		retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags);
-
-		secure = secure_save;
-		current_sctx = save_current_sctx;
-		if (retval == FAIL)		// stop if error found
-		    break;
-	    }
-	    s = e + 1;			// advance to next part
-	}
-
-	ESTACK_CHECK_NOW;
-	estack_pop();
-	vim_free(linecopy);
-    }
-    return retval;
-}
-
-/*
- * Return TRUE if "buf" is a normal buffer, 'buftype' is empty.
- */
-    int
-bt_normal(buf_T *buf)
-{
-    return buf != NULL && buf->b_p_bt[0] == NUL;
-}
-
-/*
- * Return TRUE if "buf" is the quickfix buffer.
- */
-    int
-bt_quickfix(buf_T *buf UNUSED)
-{
-#ifdef FEAT_QUICKFIX
-    return buf != NULL && buf->b_p_bt[0] == 'q';
-#else
-    return FALSE;
-#endif
-}
-
-/*
- * Return TRUE if "buf" is a terminal buffer.
- */
-    int
-bt_terminal(buf_T *buf UNUSED)
-{
-#if defined(FEAT_TERMINAL)
-    return buf != NULL && buf->b_p_bt[0] == 't';
-#else
-    return FALSE;
-#endif
-}
-
-/*
- * Return TRUE if "buf" is a help buffer.
- */
-    int
-bt_help(buf_T *buf)
-{
-    return buf != NULL && buf->b_help;
-}
-
-/*
- * Return TRUE if "buf" is a prompt buffer.
- */
-    int
-bt_prompt(buf_T *buf)
-{
-    return buf != NULL && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'r';
-}
-
-#if defined(FEAT_PROP_POPUP) || defined(PROTO)
-/*
- * Return TRUE if "buf" is a buffer for a popup window.
- */
-    int
-bt_popup(buf_T *buf)
-{
-    return buf != NULL && buf->b_p_bt != NULL
-	&& buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'o';
-}
-#endif
-
-/*
- * Return TRUE if "buf" is a "nofile", "acwrite", "terminal" or "prompt"
- * buffer.  This means the buffer name may not be a file name, at least not for
- * writing the buffer.
- */
-    int
-bt_nofilename(buf_T *buf)
-{
-    return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
-	    || buf->b_p_bt[0] == 'a'
-	    || buf->b_p_bt[0] == 't'
-	    || buf->b_p_bt[0] == 'p');
-}
-
-/*
- * Return TRUE if "buf" is a "nofile", "quickfix", "terminal" or "prompt"
- * buffer.  This means the buffer is not to be read from a file.
- */
-    static int
-bt_nofileread(buf_T *buf)
-{
-    return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
-	    || buf->b_p_bt[0] == 't'
-	    || buf->b_p_bt[0] == 'q'
-	    || buf->b_p_bt[0] == 'p');
-}
-
-#if defined(FEAT_QUICKFIX) || defined(PROTO)
-/*
- * Return TRUE if "buf" has 'buftype' set to "nofile".
- */
-    int
-bt_nofile(buf_T *buf)
-{
-    return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f';
-}
-#endif
-
-/*
- * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or
- * "popup" buffer.
- */
-    int
-bt_dontwrite(buf_T *buf)
-{
-    return buf != NULL && (buf->b_p_bt[0] == 'n'
-		 || buf->b_p_bt[0] == 't'
-		 || buf->b_p_bt[0] == 'p');
-}
-
-    int
-bt_dontwrite_msg(buf_T *buf)
-{
-    if (bt_dontwrite(buf))
-    {
-	emsg(_(e_cannot_write_buftype_option_is_set));
-	return TRUE;
-    }
-    return FALSE;
-}
-
-/*
- * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide"
- * and 'bufhidden'.
- */
-    int
-buf_hide(buf_T *buf)
-{
-    // 'bufhidden' overrules 'hidden' and ":hide", check it first
-    switch (buf->b_p_bh[0])
-    {
-	case 'u':		    // "unload"
-	case 'w':		    // "wipe"
-	case 'd': return FALSE;	    // "delete"
-	case 'h': return TRUE;	    // "hide"
-    }
-    return (p_hid || (cmdmod.cmod_flags & CMOD_HIDE));
-}
-
-/*
- * Return special buffer name.
- * Returns NULL when the buffer has a normal file name.
- */
-    char_u *
-buf_spname(buf_T *buf)
-{
-#if defined(FEAT_QUICKFIX)
-    if (bt_quickfix(buf))
-    {
-	/*
-	 * Differentiate between the quickfix and location list buffers using
-	 * the buffer number stored in the global quickfix stack.
-	 */
-	if (buf->b_fnum == qf_stack_get_bufnr())
-	    return (char_u *)_(msg_qflist);
-	else
-	    return (char_u *)_(msg_loclist);
-    }
-#endif
-
-    // There is no _file_ when 'buftype' is "nofile", b_sfname
-    // contains the name as specified by the user.
-    if (bt_nofilename(buf))
-    {
-#ifdef FEAT_TERMINAL
-	if (buf->b_term != NULL)
-	    return term_get_status_text(buf->b_term);
-#endif
-	if (buf->b_fname != NULL)
-	    return buf->b_fname;
-#ifdef FEAT_JOB_CHANNEL
-	if (bt_prompt(buf))
-	    return (char_u *)_("[Prompt]");
-#endif
-#ifdef FEAT_PROP_POPUP
-	if (bt_popup(buf))
-	    return (char_u *)_("[Popup]");
-#endif
-	return (char_u *)_("[Scratch]");
-    }
-
-    if (buf->b_fname == NULL)
-	return buf_get_fname(buf);
-    return NULL;
-}
-
-/*
- * Get "buf->b_fname", use "[No Name]" if it is NULL.
- */
-    char_u *
-buf_get_fname(buf_T *buf)
-{
-    if (buf->b_fname == NULL)
-	return (char_u *)_("[No Name]");
-    return buf->b_fname;
-}
-
-/*
- * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
- */
-    void
-set_buflisted(int on)
-{
-    if (on == curbuf->b_p_bl)
-	return;
-
-    curbuf->b_p_bl = on;
-    if (on)
-	apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf);
-    else
-	apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf);
-}
-
-/*
- * Read the file for "buf" again and check if the contents changed.
- * Return TRUE if it changed or this could not be checked.
- */
-    int
-buf_contents_changed(buf_T *buf)
-{
-    buf_T	*newbuf;
-    int		differ = TRUE;
-    linenr_T	lnum;
-    aco_save_T	aco;
-    exarg_T	ea;
-
-    // Allocate a buffer without putting it in the buffer list.
-    newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
-    if (newbuf == NULL)
-	return TRUE;
-
-    // Force the 'fileencoding' and 'fileformat' to be equal.
-    if (prep_exarg(&ea, buf) == FAIL)
-    {
-	wipe_buffer(newbuf, FALSE);
-	return TRUE;
-    }
-
-    // Set curwin/curbuf to buf and save a few things.
-    aucmd_prepbuf(&aco, newbuf);
-    if (curbuf != newbuf)
-    {
-	// Failed to find a window for "newbuf".
-	wipe_buffer(newbuf, FALSE);
-	return TRUE;
-    }
-
-    if (ml_open(curbuf) == OK
-	    && readfile(buf->b_ffname, buf->b_fname,
-				  (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM,
-					    &ea, READ_NEW | READ_DUMMY) == OK)
-    {
-	// compare the two files line by line
-	if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count)
-	{
-	    differ = FALSE;
-	    for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum)
-		if (STRCMP(ml_get_buf(buf, lnum, FALSE), ml_get(lnum)) != 0)
-		{
-		    differ = TRUE;
-		    break;
-		}
-	}
-    }
-    vim_free(ea.cmd);
-
-    // restore curwin/curbuf and a few other things
-    aucmd_restbuf(&aco);
-
-    if (curbuf != newbuf)	// safety check
-	wipe_buffer(newbuf, FALSE);
-
-    return differ;
-}
-
-/*
- * Wipe out a buffer and decrement the last buffer number if it was used for
- * this buffer.  Call this to wipe out a temp buffer that does not contain any
- * marks.
- */
-    void
-wipe_buffer(
-    buf_T	*buf,
-    int		aucmd)	    // When TRUE trigger autocommands.
-{
-    if (buf->b_fnum == top_file_num - 1)
-	--top_file_num;
-
-    if (!aucmd)		    // Don't trigger BufDelete autocommands here.
-	block_autocmds();
-
-    close_buffer(NULL, buf, DOBUF_WIPE, FALSE, TRUE);
-
-    if (!aucmd)
-	unblock_autocmds();
-}
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved	by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * buffer.c: functions for dealing with the buffer structure
+ */
+
+/*
+ * The buffer list is a double linked list of all buffers.
+ * Each buffer can be in one of these states:
+ * never loaded: BF_NEVERLOADED is set, only the file name is valid
+ *   not loaded: b_ml.ml_mfp == NULL, no memfile allocated
+ *	 hidden: b_nwindows == 0, loaded but not displayed in a window
+ *	 normal: loaded and displayed in a window
+ *
+ * Instead of storing file names all over the place, each file name is
+ * stored in the buffer list. It can be referenced by a number.
+ *
+ * The current implementation remembers all file names ever used.
+ */
+
+#include "vim.h"
+
+
+#ifdef FEAT_EVAL
+// Determines how deeply nested %{} blocks will be evaluated in statusline.
+# define MAX_STL_EVAL_DEPTH 100
+#endif
+
+static void	enter_buffer(buf_T *buf);
+static void	buflist_getfpos(void);
+static char_u	*buflist_match(regmatch_T *rmp, buf_T *buf, int ignore_case);
+static char_u	*fname_match(regmatch_T *rmp, char_u *name, int ignore_case);
+#ifdef UNIX
+static buf_T	*buflist_findname_stat(char_u *ffname, stat_T *st);
+static int	otherfile_buf(buf_T *buf, char_u *ffname, stat_T *stp);
+static int	buf_same_ino(buf_T *buf, stat_T *stp);
+#else
+static int	otherfile_buf(buf_T *buf, char_u *ffname);
+#endif
+static int	value_changed(char_u *str, char_u **last);
+static int	append_arg_number(win_T *wp, char_u *buf, int buflen, int add_file);
+static void	free_buffer(buf_T *);
+static void	free_buffer_stuff(buf_T *buf, int free_options);
+static int	bt_nofileread(buf_T *buf);
+static void	no_write_message_buf(buf_T *buf);
+
+#ifdef UNIX
+# define dev_T dev_t
+#else
+# define dev_T unsigned
+#endif
+
+#define FOR_ALL_BUFS_FROM_LAST(buf) \
+    for ((buf) = lastbuf; (buf) != NULL; (buf) = (buf)->b_prev)
+
+#if defined(FEAT_QUICKFIX)
+static char *msg_loclist = N_("[Location List]");
+static char *msg_qflist = N_("[Quickfix List]");
+#endif
+
+// Number of times free_buffer() was called.
+static int	buf_free_count = 0;
+
+static int	top_file_num = 1;	// highest file number
+static garray_T buf_reuse = GA_EMPTY;	// file numbers to recycle
+
+/*
+ * Return the highest possible buffer number.
+ */
+    int
+get_highest_fnum(void)
+{
+    return top_file_num - 1;
+}
+
+/*
+ * 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,
+	    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);
+    }
+    else
+    {
+	// Delete the converted lines.
+	while (curbuf->b_ml.ml_line_count > line_count)
+	    ml_delete(line_count);
+    }
+    // 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 == OK)
+	    unchanged(curbuf, FALSE, TRUE);
+
+	if (retval == OK)
+	{
+#ifdef FEAT_EVAL
+	    apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE,
+							      curbuf, &retval);
+#else
+	    apply_autocmds(EVENT_STDINREADPOST, NULL, NULL, FALSE, curbuf);
+#endif
+	}
+    }
+    return retval;
+}
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+/*
+ * Ensure buffer "buf" is loaded.  Does not trigger the swap-exists action.
+ */
+    void
+buffer_ensure_loaded(buf_T *buf)
+{
+    if (buf->b_ml.ml_mfp != NULL)
+	return;
+
+    aco_save_T	aco;
+
+    // Make sure the buffer is in a window.  If not then skip it.
+    aucmd_prepbuf(&aco, buf);
+    if (curbuf == buf)
+    {
+	if (swap_exists_action != SEA_READONLY)
+	    swap_exists_action = SEA_NONE;
+	open_buffer(FALSE, NULL, 0);
+	aucmd_restbuf(&aco);
+    }
+}
+#endif
+
+/*
+ * 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_arg)	    // extra flags for readfile()
+{
+    int		flags = flags_arg;
+    int		retval = OK;
+    bufref_T	old_curbuf;
+#ifdef FEAT_SYN_HL
+    long	old_tw = curbuf->b_p_tw;
+#endif
+    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, FALSE);
+	FOR_ALL_BUFFERS(curbuf)
+	    if (curbuf->b_ml.ml_mfp != NULL)
+		break;
+	// If there is no memfile at all, exit.
+	// This is OK, since there are no changes to lose.
+	if (curbuf == NULL)
+	{
+	    emsg(_(e_cannot_allocate_any_buffer_exiting));
+
+	    // Don't try to do any saving, with "curbuf" NULL almost nothing
+	    // will work.
+	    v_dying = 2;
+	    getout(2);
+	}
+
+	emsg(_(e_cannot_allocate_buffer_using_other_one));
+	enter_buffer(curbuf);
+#ifdef FEAT_SYN_HL
+	if (old_tw != curbuf->b_p_tw)
+	    check_colorcolumn(curwin);
+#endif
+	return FAIL;
+    }
+
+    // Do not sync this buffer yet, may first want to read the file.
+    if (curbuf->b_ml.ml_mfp != NULL)
+	curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
+
+    // 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;
+
+    // A buffer without an actual file should not use the buffer name to read a
+    // file.
+    if (bt_nofileread(curbuf))
+	flags |= READ_NOFILE;
+
+    // Read the file if there is one.
+    if (curbuf->b_ffname != NULL
+#ifdef FEAT_NETBEANS_INTG
+	    && netbeansReadFile
+#endif
+       )
+    {
+	int old_msg_silent = msg_silent;
+#ifdef UNIX
+	int save_bin = curbuf->b_p_bin;
+	int perm;
+#endif
+#ifdef FEAT_NETBEANS_INTG
+	int oldFire = netbeansFireChanges;
+
+	netbeansFireChanges = 0;
+#endif
+#ifdef UNIX
+	perm = mch_getperm(curbuf->b_ffname);
+	if (perm >= 0 && (S_ISFIFO(perm)
+		      || S_ISSOCK(perm)
+# 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;
+#ifdef FEAT_NETBEANS_INTG
+	netbeansFireChanges = oldFire;
+#endif
+	// 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);
+    }
+
+    // Can now sync this buffer in ml_sync_all().
+    if (curbuf->b_ml.ml_mfp != NULL
+	    && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC)
+	curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
+
+    // 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 autocommands.
+    //
+    // When reading stdin, the buffer contents always needs writing, so set
+    // the changed flag.  Unless in readonly mode: "ls | gview -".
+    // When interrupted and 'cpoptions' contains 'i' set changed flag.
+    if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
+		|| modified_was_set	// ":set modified" used in autocmd
+#ifdef FEAT_EVAL
+		|| (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
+#endif
+       )
+	changed();
+    else if (retval == OK && !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 = CHANGEDTICK(curbuf);
+    curbuf->b_last_changedtick_i = CHANGEDTICK(curbuf);
+    curbuf->b_last_changedtick_pum = CHANGEDTICK(curbuf);
+
+    // require "!" to overwrite the file, because it wasn't read completely
+#ifdef FEAT_EVAL
+    if (aborting())
+#else
+    if (got_int)
+#endif
+	curbuf->b_flags |= BF_READERR;
+
+#ifdef FEAT_FOLDING
+    // Need to update automatic folding.  Do this before the autocommands,
+    // they may use the fold info.
+    foldUpdateAll(curwin);
+#endif
+
+    // need to set w_topline, unless some autocommand already did that.
+    if (!(curwin->w_valid & VALID_TOPLINE))
+    {
+	curwin->w_topline = 1;
+#ifdef FEAT_DIFF
+	curwin->w_topfill = 0;
+#endif
+    }
+#ifdef FEAT_EVAL
+    apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval);
+#else
+    apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
+#endif
+
+    if (retval != OK)
+	return retval;
+
+    // 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, make sure it is in a window.
+	// If not then skip it.
+	aucmd_prepbuf(&aco, old_curbuf.br_buf);
+	if (curbuf == old_curbuf.br_buf)
+	{
+	    do_modelines(0);
+	    curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED);
+
+	    if ((flags & READ_NOWINENTER) == 0)
+#ifdef FEAT_EVAL
+		apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL,
+			FALSE, curbuf, &retval);
+#else
+	    apply_autocmds(EVENT_BUFWINENTER, NULL, NULL,
+		    FALSE, curbuf);
+#endif
+
+	    // restore curwin/curbuf and a few other things
+	    aucmd_restbuf(&aco);
+	}
+    }
+
+    return retval;
+}
+
+/*
+ * Store "buf" in "bufref" and set the free count.
+ */
+    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.
+ */
+    int
+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;
+}
+
+/*
+ * Return TRUE if "buf" points to a valid buffer (in the buffer list).
+ * This can be slow if there are many buffers, prefer using bufref_valid().
+ */
+    int
+buf_valid(buf_T *buf)
+{
+    buf_T	*bp;
+
+    // Assume that we more often have a recent buffer, start with the last
+    // one.
+    FOR_ALL_BUFS_FROM_LAST(bp)
+	if (bp == buf)
+	    return TRUE;
+    return FALSE;
+}
+
+/*
+ * A hash table used to quickly lookup a buffer by its number.
+ */
+static hashtab_T buf_hashtab;
+
+    static void
+buf_hashtab_add(buf_T *buf)
+{
+    sprintf((char *)buf->b_key, "%x", buf->b_fnum);
+    if (hash_add(&buf_hashtab, buf->b_key, "create buffer") == FAIL)
+	emsg(_(e_buffer_cannot_be_registered));
+}
+
+    static void
+buf_hashtab_remove(buf_T *buf)
+{
+    hashitem_T *hi = hash_find(&buf_hashtab, buf->b_key);
+
+    if (!HASHITEM_EMPTY(hi))
+	hash_remove(&buf_hashtab, hi, "close buffer");
+}
+
+/*
+ * Return TRUE when buffer "buf" can be unloaded.
+ * Give an error message and return FALSE when the buffer is locked or the
+ * screen is being redrawn and the buffer is in a window.
+ */
+    static int
+can_unload_buffer(buf_T *buf)
+{
+    int	    can_unload = !buf->b_locked;
+
+    if (can_unload && updating_screen)
+    {
+	win_T	*wp;
+
+	FOR_ALL_WINDOWS(wp)
+	    if (wp->w_buffer == buf)
+	    {
+		can_unload = FALSE;
+		break;
+	    }
+    }
+    if (!can_unload)
+    {
+	char_u *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname;
+
+	semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str),
+				fname != NULL ? fname : (char_u *)"[No Name]");
+    }
+    return can_unload;
+}
+
+/*
+ * Close the link to a buffer.
+ * "action" is used when there is no longer a window for the buffer.
+ * It can be:
+ * 0			buffer becomes hidden
+ * DOBUF_UNLOAD		buffer is unloaded
+ * DOBUF_DELETE		buffer is unloaded and removed from buffer list
+ * DOBUF_WIPE		buffer is unloaded and really deleted
+ * DOBUF_WIPE_REUSE	idem, and add to buf_reuse list
+ * When doing all but the first one on the current buffer, the caller should
+ * get a new buffer very soon!
+ *
+ * The 'bufhidden' option can force freeing and deleting.
+ *
+ * When "abort_if_last" is TRUE then do not close the buffer if autocommands
+ * cause there to be only one window with this buffer.  e.g. when ":quit" is
+ * supposed to close the window but autocommands close all other windows.
+ *
+ * When "ignore_abort" is TRUE don't abort even when aborting() returns TRUE.
+ *
+ * Return TRUE when we got to the end and b_nwindows was decremented.
+ */
+    int
+close_buffer(
+    win_T	*win,		// if not NULL, set b_last_cursor
+    buf_T	*buf,
+    int		action,
+    int		abort_if_last,
+    int		ignore_abort)
+{
+    int		is_curbuf;
+    int		nwindows;
+    bufref_T	bufref;
+    int		is_curwin = (curwin != NULL && curwin->w_buffer == buf);
+    win_T	*the_curwin = curwin;
+    tabpage_T	*the_curtab = curtab;
+    int		unload_buf = (action != 0);
+    int		wipe_buf = (action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
+    int		del_buf = (action == DOBUF_DEL || wipe_buf);
+
+    CHECK_CURBUF;
+
+    // Force unloading or deleting when 'bufhidden' says so.
+    // The caller must take care of NOT deleting/freeing when 'bufhidden' is
+    // "hide" (otherwise we could never free or delete a buffer).
+    if (buf->b_p_bh[0] == 'd')		// 'bufhidden' == "delete"
+    {
+	del_buf = TRUE;
+	unload_buf = TRUE;
+    }
+    else if (buf->b_p_bh[0] == 'w')	// 'bufhidden' == "wipe"
+    {
+	del_buf = TRUE;
+	unload_buf = TRUE;
+	wipe_buf = TRUE;
+    }
+    else if (buf->b_p_bh[0] == 'u')	// 'bufhidden' == "unload"
+	unload_buf = TRUE;
+
+#ifdef FEAT_TERMINAL
+    // depending on how we get here b_nwindows may already be zero
+    if (bt_terminal(buf) && (buf->b_nwindows <= 1 || del_buf))
+    {
+	CHECK_CURBUF;
+	if (term_job_running(buf->b_term))
+	{
+	    if (wipe_buf || unload_buf)
+	    {
+		if (!can_unload_buffer(buf))
+		    return FALSE;
+
+		// Wiping out or unloading a terminal buffer kills the job.
+		free_terminal(buf);
+
+		// A terminal buffer is wiped out when job has finished.
+		del_buf = TRUE;
+		unload_buf = TRUE;
+		wipe_buf = TRUE;
+	    }
+	    else
+	    {
+		// The job keeps running, hide the buffer.
+		del_buf = FALSE;
+		unload_buf = FALSE;
+	    }
+	}
+	else if (buf->b_p_bh[0] == 'h' && !del_buf)
+	{
+	    // Hide a terminal buffer.
+	    unload_buf = FALSE;
+	}
+	else
+	{
+	    if (del_buf || unload_buf)
+	    {
+		// A terminal buffer is wiped out if the job has finished.
+		// We only do this when there's an intention to unload the
+		// buffer. This way, :hide and other similar commands won't
+		// wipe the buffer.
+		del_buf = TRUE;
+		unload_buf = TRUE;
+		wipe_buf = TRUE;
+	    }
+	}
+	CHECK_CURBUF;
+    }
+#endif
+
+    // Disallow deleting the buffer when it is locked (already being closed or
+    // halfway a command that relies on it). Unloading is allowed.
+    if ((del_buf || wipe_buf) && !can_unload_buffer(buf))
+	return FALSE;
+
+    // check no autocommands closed the window
+    if (win != NULL && 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);
+    }
+
+    set_bufref(&bufref, buf);
+
+    // When the buffer is no longer in a window, trigger BufWinLeave
+    if (buf->b_nwindows == 1)
+    {
+	++buf->b_locked;
+	++buf->b_locked_split;
+	if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
+								  FALSE, buf)
+		&& !bufref_valid(&bufref))
+	{
+	    // Autocommands deleted the buffer.
+aucmd_abort:
+	    emsg(_(e_autocommands_caused_command_to_abort));
+	    return FALSE;
+	}
+	--buf->b_locked;
+	--buf->b_locked_split;
+	if (abort_if_last && one_window())
+	    // Autocommands made this the only window.
+	    goto aucmd_abort;
+
+	// When the buffer becomes hidden, but is not unloaded, trigger
+	// BufHidden
+	if (!unload_buf)
+	{
+	    ++buf->b_locked;
+	    ++buf->b_locked_split;
+	    if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
+								  FALSE, buf)
+		    && !bufref_valid(&bufref))
+		// Autocommands deleted the buffer.
+		goto aucmd_abort;
+	    --buf->b_locked;
+	    --buf->b_locked_split;
+	    if (abort_if_last && one_window())
+		// Autocommands made this the only window.
+		goto aucmd_abort;
+	}
+#ifdef FEAT_EVAL
+	// autocmds may abort script processing
+	if (!ignore_abort && aborting())
+	    return FALSE;
+#endif
+    }
+
+    // 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();
+    }
+
+    nwindows = buf->b_nwindows;
+
+    // decrease the link count from windows (unless not in any window)
+    if (buf->b_nwindows > 0)
+	--buf->b_nwindows;
+
+#ifdef FEAT_DIFF
+    if (diffopt_hiddenoff() && !unload_buf && buf->b_nwindows == 0)
+	diff_buf_delete(buf);	// Clear 'diff' for hidden buffer.
+#endif
+
+    // Return when a window is displaying the buffer or when it's not
+    // unloaded.
+    if (buf->b_nwindows > 0 || !unload_buf)
+	return FALSE;
+
+    // Always remove the buffer when there is no file name.
+    if (buf->b_ffname == NULL)
+	del_buf = TRUE;
+
+    // When closing the current buffer stop Visual mode before freeing
+    // anything.
+    if (buf == curbuf && VIsual_active
+#if defined(EXITFREE)
+	    && !entered_free_all_mem
+#endif
+	    )
+	end_visual_mode();
+
+    // Free all things allocated for this buffer.
+    // Also calls the "BufDelete" autocommands when del_buf is TRUE.
+    //
+    // Remember if we are closing the current buffer.  Restore the number of
+    // windows, so that autocommands in buf_freeall() don't get confused.
+    is_curbuf = (buf == curbuf);
+    buf->b_nwindows = nwindows;
+
+    buf_freeall(buf, (del_buf ? BFA_DEL : 0)
+		   + (wipe_buf ? BFA_WIPE : 0)
+		   + (ignore_abort ? BFA_IGNORE_ABORT : 0));
+
+    // Autocommands may have deleted the buffer.
+    if (!bufref_valid(&bufref))
+	return FALSE;
+#ifdef FEAT_EVAL
+    // autocmds may abort script processing
+    if (!ignore_abort && aborting())
+	return FALSE;
+#endif
+
+    // 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 FALSE;
+
+    if (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;
+
+    /*
+     * Remove the buffer from the list.
+     */
+    if (wipe_buf)
+    {
+	// Do not wipe out the buffer if it is used in a window.
+	if (buf->b_nwindows > 0)
+	    return FALSE;
+
+	if (action == DOBUF_WIPE_REUSE)
+	{
+	    // we can re-use this buffer number, store it
+	    if (buf_reuse.ga_itemsize == 0)
+		ga_init2(&buf_reuse, sizeof(int), 50);
+	    if (ga_grow(&buf_reuse, 1) == OK)
+		((int *)buf_reuse.ga_data)[buf_reuse.ga_len++] = buf->b_fnum;
+	}
+	if (buf->b_sfname != buf->b_ffname)
+	    VIM_CLEAR(buf->b_sfname);
+	else
+	    buf->b_sfname = NULL;
+	VIM_CLEAR(buf->b_ffname);
+	if (buf->b_prev == NULL)
+	    firstbuf = buf->b_next;
+	else
+	    buf->b_prev->b_next = buf->b_next;
+	if (buf->b_next == NULL)
+	    lastbuf = buf->b_prev;
+	else
+	    buf->b_next->b_prev = buf->b_prev;
+	free_buffer(buf);
+    }
+    else
+    {
+	if (del_buf)
+	{
+	    // Free all internal variables and reset option values, to make
+	    // ":bdel" compatible with Vim 5.7.
+	    free_buffer_stuff(buf, TRUE);
+
+	    // Make it look like a new buffer.
+	    buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED;
+
+	    // Init the options when loaded again.
+	    buf->b_p_initialized = FALSE;
+	}
+	buf_clear_file(buf);
+	if (del_buf)
+	    buf->b_p_bl = FALSE;
+    }
+    // NOTE: at this point "curbuf" may be invalid!
+    return TRUE;
+}
+
+/*
+ * 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_shortname = FALSE;
+    buf->b_p_eof = FALSE;
+    buf->b_start_eof = FALSE;
+    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
+#ifdef FEAT_NETBEANS_INTG
+    netbeans_deleted_all_lines(buf);
+#endif
+}
+
+/*
+ * buf_freeall() - free all things allocated for a buffer that are related to
+ * the file.  Careful: get here with "curwin" NULL when exiting.
+ * 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
+ * BFA_IGNORE_ABORT  don't abort even when aborting() returns TRUE
+ */
+    void
+buf_freeall(buf_T *buf, int flags)
+{
+    int		is_curbuf = (buf == curbuf);
+    bufref_T	bufref;
+    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;
+    ++buf->b_locked_split;
+    set_bufref(&bufref, buf);
+    if (buf->b_ml.ml_mfp != NULL)
+    {
+	if (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)
+    {
+	if (apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname,
+								   FALSE, buf)
+		&& !bufref_valid(&bufref))
+	    // autocommands deleted the buffer
+	    return;
+    }
+    if (flags & BFA_WIPE)
+    {
+	if (apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname,
+								  FALSE, buf)
+		&& !bufref_valid(&bufref))
+	    // autocommands deleted the buffer
+	    return;
+    }
+    --buf->b_locked;
+    --buf->b_locked_split;
+
+    // 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();
+    }
+
+#ifdef FEAT_EVAL
+    // autocmds may abort script processing
+    if ((flags & BFA_IGNORE_ABORT) == 0 && aborting())
+	return;
+#endif
+
+    // 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;
+#ifdef FEAT_DIFF
+    diff_buf_delete(buf);	    // Can't use 'diff' for unloaded buffer.
+#endif
+#ifdef FEAT_SYN_HL
+    // Remove any ownsyntax, unless exiting.
+    if (curwin != NULL && curwin->w_buffer == buf)
+	reset_synblock(curwin);
+#endif
+
+#ifdef FEAT_FOLDING
+    // No folds in an empty buffer.
+    {
+	win_T		*win;
+	tabpage_T	*tp;
+
+	FOR_ALL_TAB_WINDOWS(tp, win)
+	    if (win->w_buffer == buf)
+		clearFolding(win);
+    }
+#endif
+
+#ifdef FEAT_TCL
+    tcl_buffer_free(buf);
+#endif
+    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
+    }
+#ifdef FEAT_SYN_HL
+    syntax_clear(&buf->b_s);	    // reset syntax info
+#endif
+#ifdef FEAT_PROP_POPUP
+    clear_buf_prop_types(buf);
+#endif
+    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)
+{
+    ++buf_free_count;
+    free_buffer_stuff(buf, TRUE);
+#ifdef FEAT_EVAL
+    // b:changedtick uses an item in buf_T, remove it now
+    dictitem_remove(buf->b_vars, (dictitem_T *)&buf->b_ct_di, "free buffer");
+    unref_var_dict(buf->b_vars);
+    remove_listeners(buf);
+#endif
+#ifdef FEAT_LUA
+    lua_buffer_free(buf);
+#endif
+#ifdef FEAT_MZSCHEME
+    mzscheme_buffer_free(buf);
+#endif
+#ifdef FEAT_PERL
+    perl_buf_free(buf);
+#endif
+#ifdef FEAT_PYTHON
+    python_buffer_free(buf);
+#endif
+#ifdef FEAT_PYTHON3
+    python3_buffer_free(buf);
+#endif
+#ifdef FEAT_RUBY
+    ruby_buffer_free(buf);
+#endif
+#ifdef FEAT_JOB_CHANNEL
+    channel_buffer_free(buf);
+#endif
+#ifdef FEAT_TERMINAL
+    free_terminal(buf);
+#endif
+#ifdef FEAT_JOB_CHANNEL
+    vim_free(buf->b_prompt_text);
+    free_callback(&buf->b_prompt_callback);
+    free_callback(&buf->b_prompt_interrupt);
+#endif
+
+    buf_hashtab_remove(buf);
+
+    aubuflocal_remove(buf);
+
+    if (autocmd_busy)
+    {
+	// Do not free the buffer structure while autocommands are executing,
+	// it's still needed. Free it when autocmd_busy is reset.
+	buf->b_next = au_pending_free_buf;
+	au_pending_free_buf = buf;
+    }
+    else
+    {
+	vim_free(buf);
+	if (curbuf == buf)
+	    curbuf = NULL;  // make clear it's not to be used
+    }
+}
+
+/*
+ * Initializes b:changedtick.
+ */
+    static void
+init_changedtick(buf_T *buf)
+{
+    dictitem_T *di = (dictitem_T *)&buf->b_ct_di;
+
+    di->di_flags = DI_FLAGS_FIX | DI_FLAGS_RO;
+    di->di_tv.v_type = VAR_NUMBER;
+    di->di_tv.v_lock = VAR_FIXED;
+    di->di_tv.vval.v_number = 0;
+
+#ifdef FEAT_EVAL
+    STRCPY(buf->b_ct_di.di_key, "changedtick");
+    (void)dict_add(buf->b_vars, di);
+#endif
+}
+
+/*
+ * 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;
+	free_wininfo(wip);
+    }
+}
+
+/*
+ * Free stuff in the buffer for ":bdel" and when wiping out the buffer.
+ */
+    static void
+free_buffer_stuff(
+    buf_T	*buf,
+    int		free_options)		// free options as well
+{
+    if (free_options)
+    {
+	clear_wininfo(buf);		// including window-local options
+	free_buf_options(buf, TRUE);
+#ifdef FEAT_SPELL
+	ga_clear(&buf->b_s.b_langp);
+#endif
+    }
+#ifdef FEAT_EVAL
+    {
+	varnumber_T tick = CHANGEDTICK(buf);
+
+	vars_clear(&buf->b_vars->dv_hashtab); // free all buffer variables
+	hash_init(&buf->b_vars->dv_hashtab);
+	init_changedtick(buf);
+	CHANGEDTICK(buf) = tick;
+	remove_listeners(buf);
+    }
+#endif
+    uc_clear(&buf->b_ucmds);		// clear local user commands
+#ifdef FEAT_SIGNS
+    buf_delete_signs(buf, (char_u *)"*");	// delete any signs
+#endif
+#ifdef FEAT_NETBEANS_INTG
+    netbeans_file_killed(buf);
+#endif
+#ifdef FEAT_PROP_POPUP
+    ga_clear_strings(&buf->b_textprop_text);
+#endif
+    map_clear_mode(buf, MAP_ALL_MODES, TRUE, FALSE);  // clear local mappings
+    map_clear_mode(buf, MAP_ALL_MODES, TRUE, TRUE);   // clear local abbrevs
+    VIM_CLEAR(buf->b_start_fenc);
+}
+
+/*
+ * Free one wininfo_T.
+ */
+    void
+free_wininfo(wininfo_T *wip)
+{
+    if (wip->wi_optset)
+    {
+	clear_winopt(&wip->wi_opt);
+#ifdef FEAT_FOLDING
+	deleteFoldRecurse(&wip->wi_folds);
+#endif
+    }
+    vim_free(wip);
+}
+
+/*
+ * Go to another buffer.  Handles the result of the ATTENTION dialog.
+ */
+    void
+goto_buffer(
+    exarg_T	*eap,
+    int		start,
+    int		dir,
+    int		count)
+{
+    bufref_T	old_curbuf;
+    int		save_sea = swap_exists_action;
+
+    set_bufref(&old_curbuf, curbuf);
+
+    if (swap_exists_action == SEA_NONE)
+	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')
+    {
+#if defined(FEAT_EVAL)
+	cleanup_T   cs;
+
+	// Reset the error/interrupt/exception state here so that
+	// aborting() returns FALSE when closing a window.
+	enter_cleanup(&cs);
+#endif
+
+	// Quitting means closing the split window, nothing else.
+	win_close(curwin, TRUE);
+	swap_exists_action = save_sea;
+	swap_exists_did_quit = TRUE;
+
+#if defined(FEAT_EVAL)
+	// Restore the error/interrupt/exception state if not discarded by a
+	// new aborting error, interrupt, or uncaught exception.
+	leave_cleanup(&cs);
+#endif
+    }
+    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.
+ */
+    void
+handle_swap_exists(bufref_T *old_curbuf)
+{
+#if defined(FEAT_EVAL)
+    cleanup_T	cs;
+#endif
+#ifdef FEAT_SYN_HL
+    long	old_tw = curbuf->b_p_tw;
+#endif
+    buf_T	*buf;
+
+    if (swap_exists_action == SEA_QUIT)
+    {
+#if defined(FEAT_EVAL)
+	// Reset the error/interrupt/exception state here so that
+	// aborting() returns FALSE when closing a buffer.
+	enter_cleanup(&cs);
+#endif
+
+	// 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, FALSE);
+	if (old_curbuf == NULL || !bufref_valid(old_curbuf)
+					      || old_curbuf->br_buf == curbuf)
+	{
+	    // Block autocommands here because curwin->w_buffer is NULL.
+	    block_autocmds();
+	    buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
+	    unblock_autocmds();
+	}
+	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;
+
+#ifdef FEAT_SYN_HL
+	    if (old_tw != curbuf->b_p_tw)
+		check_colorcolumn(curwin);
+#endif
+	}
+	// If "old_curbuf" is NULL we are in big trouble here...
+
+#if defined(FEAT_EVAL)
+	// Restore the error/interrupt/exception state if not discarded by a
+	// new aborting error, interrupt, or uncaught exception.
+	leave_cleanup(&cs);
+#endif
+    }
+    else if (swap_exists_action == SEA_RECOVER)
+    {
+#if defined(FEAT_EVAL)
+	// Reset the error/interrupt/exception state here so that
+	// aborting() returns FALSE when closing a buffer.
+	enter_cleanup(&cs);
+#endif
+
+	// 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);
+
+#if defined(FEAT_EVAL)
+	// Restore the error/interrupt/exception state if not discarded by a
+	// new aborting error, interrupt, or uncaught exception.
+	leave_cleanup(&cs);
+#endif
+    }
+    swap_exists_action = SEA_NONE;
+}
+
+/*
+ * 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;
+    bufref_T bufref;
+
+    if (action == DOBUF_UNLOAD)
+    {
+	emsg(_(e_cannot_unload_last_buffer));
+	return FAIL;
+    }
+
+    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, 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
+ * action == DOBUF_WIPE_REUSE idem, and add number to "buf_reuse"
+ *
+ * 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.
+ */
+    static int
+do_buffer_ext(
+    int		action,
+    int		start,
+    int		dir,		// FORWARD or BACKWARD
+    int		count,		// buffer number or number of buffers
+    int		flags)		// DOBUF_FORCEIT etc.
+{
+    buf_T	*buf;
+    buf_T	*bp;
+    int		unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
+			|| action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
+
+    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(_(e_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(_(e_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)
+		semsg(_(e_buffer_nr_does_not_exist), count);
+	}
+	else if (dir == FORWARD)
+	    emsg(_(e_cannot_go_beyond_last_buffer));
+	else
+	    emsg(_(e_cannot_go_before_first_buffer));
+	return FAIL;
+    }
+#ifdef FEAT_PROP_POPUP
+    if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf))
+	return OK;
+#endif
+    if ((action == DOBUF_GOTO || action == DOBUF_SPLIT)
+						  && (buf->b_flags & BF_DUMMY))
+    {
+	// disallow navigating to the dummy buffer
+	semsg(_(e_buffer_nr_does_not_exist), count);
+	return FAIL;
+    }
+
+#ifdef FEAT_GUI
+    need_mouse_correct = TRUE;
+#endif
+
+    /*
+     * delete buffer "buf" from memory and/or the list
+     */
+    if (unload)
+    {
+	int	forward;
+	bufref_T bufref;
+
+	if (!can_unload_buffer(buf))
+	    return FAIL;
+
+	set_bufref(&bufref, buf);
+
+	// When unloading or deleting a buffer that's already unloaded and
+	// unlisted: fail silently.
+	if (action != DOBUF_WIPE && action != DOBUF_WIPE_REUSE
+				   && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
+	    return FAIL;
+
+	if ((flags & DOBUF_FORCEIT) == 0 && bufIsChanged(buf))
+	{
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+	    if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
+	    {
+# ifdef FEAT_TERMINAL
+		if (term_job_running(buf->b_term))
+		{
+		    if (term_confirm_stop(buf) == FAIL)
+			return FAIL;
+		}
+		else
+# endif
+		{
+		    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
+#endif
+	    {
+		no_write_message_buf(buf);
+		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.
+	FOR_ALL_BUFFERS(bp)
+	    if (bp->b_p_bl && bp != buf)
+		break;
+	if (bp == NULL && buf == curbuf)
+	    return empty_curbuf(TRUE, (flags & DOBUF_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, 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)
+		{
+		    // Skip current and unlisted bufs.  Also skip a quickfix
+		    // buffer, it might be deleted soon.
+		    if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf))
+			buf = NULL;
+		    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
+			    && !bt_quickfix(buf))
+		{
+		    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(buf)
+		if (buf->b_p_bl && buf != curbuf && !bt_quickfix(buf))
+		    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 (bt_quickfix(buf))
+		buf = NULL;
+	}
+    }
+
+    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, (flags & DOBUF_FORCEIT), action);
+    }
+
+    /*
+     * make "buf" the current buffer
+     */
+    if (action == DOBUF_SPLIT)	    // split window first
+    {
+	// If 'switchbuf' is set jump to the window containing "buf".
+	if (swbuf_goto_win_with_buf(buf) != NULL)
+	    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, (flags & DOBUF_FORCEIT)))
+    {
+#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
+	if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write)
+	{
+# ifdef FEAT_TERMINAL
+	    if (term_job_running(curbuf->b_term))
+	    {
+		if (term_confirm_stop(curbuf) == FAIL)
+		    return FAIL;
+		// Manually kill the terminal here because this command will
+		// hide it otherwise.
+		free_terminal(curbuf);
+	    }
+	    else
+# endif
+	    {
+		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;
+		}
+	    }
+	}
+	else
+#endif
+	{
+	    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 defined(FEAT_EVAL)
+    if (aborting())	    // autocmds may abort script processing
+	return FAIL;
+#endif
+
+    return OK;
+}
+
+    int
+do_buffer(
+    int		action,
+    int		start,
+    int		dir,		// FORWARD or BACKWARD
+    int		count,		// buffer number or number of buffers
+    int		forceit)	// TRUE when using !
+{
+    return do_buffer_ext(action, start, dir, count,
+						  forceit ? DOBUF_FORCEIT : 0);
+}
+
+/*
+ * 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 *
+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	*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 ex_errmsg(e_trailing_characters_str, arg);
+	    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_ext(command, DOBUF_FIRST, FORWARD, bnr,
+			  DOBUF_NOPOPUP | (forceit ? DOBUF_FORCEIT : 0)) == OK)
+		++deleted;
+
+	    // find next buffer number to delete/unload
+	    if (addr_count == 2)
+	    {
+		if (++bnr > end_bnr)
+		    break;
+	    }
+	    else    // addr_count == 1
+	    {
+		arg = skipwhite(arg);
+		if (*arg == NUL)
+		    break;
+		if (!VIM_ISDIGIT(*arg))
+		{
+		    p = skiptowhite_esc(arg);
+		    bnr = buflist_findpat(arg, p,
+			  command == DOBUF_WIPE || command == DOBUF_WIPE_REUSE,
+								FALSE, FALSE);
+		    if (bnr < 0)	    // failed
+			break;
+		    arg = p;
+		}
+		else
+		    bnr = getdigits(&arg);
+	    }
+	}
+	if (!got_int && do_current && do_buffer(command, DOBUF_FIRST,
+					  FORWARD, do_current, forceit) == OK)
+	    ++deleted;
+
+	if (deleted == 0)
+	{
+	    if (command == DOBUF_UNLOAD)
+		STRCPY(IObuff, _(e_no_buffers_were_unloaded));
+	    else if (command == DOBUF_DEL)
+		STRCPY(IObuff, _(e_no_buffers_were_deleted));
+	    else
+		STRCPY(IObuff, _(e_no_buffers_were_wiped_out));
+	    errormsg = (char *)IObuff;
+	}
+	else if (deleted >= p_report)
+	{
+	    if (command == DOBUF_UNLOAD)
+		smsg(NGETTEXT("%d buffer unloaded",
+			    "%d buffers unloaded", deleted), deleted);
+	    else if (command == DOBUF_DEL)
+		smsg(NGETTEXT("%d buffer deleted",
+			    "%d buffers deleted", deleted), deleted);
+	    else
+		smsg(NGETTEXT("%d buffer wiped out",
+			    "%d buffers wiped out", deleted), deleted);
+	}
+    }
+
+    return errormsg;
+}
+
+/*
+ * 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
+ * DOBUF_WIPE_REUSE wipe it out and add to "buf_reuse"
+ */
+    void
+set_curbuf(buf_T *buf, int action)
+{
+    buf_T	*prevbuf;
+    int		unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
+			|| action == DOBUF_WIPE || action == DOBUF_WIPE_REUSE);
+#ifdef FEAT_SYN_HL
+    long	old_tw = curbuf->b_p_tw;
+#endif
+    bufref_T	newbufref;
+    bufref_T	prevbufref;
+    int		valid;
+
+    setpcmark();
+    if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
+	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;
+    set_bufref(&prevbufref, prevbuf);
+    set_bufref(&newbufref, buf);
+
+    // Autocommands may delete the current buffer and/or the buffer we want 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)
+#ifdef FEAT_EVAL
+		&& !aborting()
+#endif
+	       ))
+    {
+#ifdef FEAT_SYN_HL
+	if (prevbuf == curwin->w_buffer)
+	    reset_synblock(curwin);
+#endif
+	if (unload)
+	    close_windows(prevbuf, FALSE);
+#if defined(FEAT_EVAL)
+	if (bufref_valid(&prevbufref) && !aborting())
+#else
+	if (bufref_valid(&prevbufref))
+#endif
+	{
+	    win_T  *previouswin = curwin;
+
+	    // Do not sync when in Insert mode and the buffer is open in
+	    // another window, might be a timer doing something in another
+	    // window.
+	    if (prevbuf == curbuf
+		    && ((State & MODE_INSERT) == 0 || curbuf->b_nwindows <= 1))
+		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, 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
+    valid = buf_valid(buf);
+    if ((valid && buf != curbuf
+#ifdef FEAT_EVAL
+		&& !aborting()
+#endif
+	) || curwin->w_buffer == NULL)
+    {
+	// If the buffer is not valid but curwin->w_buffer is NULL we must
+	// enter some buffer.  Using the last one is hopefully OK.
+	if (!valid)
+	    enter_buffer(lastbuf);
+	else
+	    enter_buffer(buf);
+#ifdef FEAT_SYN_HL
+	if (old_tw != curbuf->b_p_tw)
+	    check_colorcolumn(curwin);
+#endif
+    }
+}
+
+/*
+ * Enter a new current buffer.
+ * Old curbuf must have been abandoned already!  This also means "curbuf" may
+ * be pointing to freed memory.
+ */
+    static void
+enter_buffer(buf_T *buf)
+{
+    // when closing the current buffer stop Visual mode
+    if (VIsual_active
+#if defined(EXITFREE)
+	    && !entered_free_all_mem
+#endif
+	    )
+	end_visual_mode();
+
+    // Get the buffer in the current window.
+    curwin->w_buffer = buf;
+    curbuf = buf;
+    ++curbuf->b_nwindows;
+
+    // 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);
+#ifdef FEAT_FOLDING
+    else
+	// Remove all folds in the window.
+	clearFolding(curwin);
+    foldUpdateAll(curwin);	// update folds (later).
+#endif
+
+#ifdef FEAT_DIFF
+    if (curwin->w_p_diff)
+	diff_buf_add(curbuf);
+#endif
+
+#ifdef FEAT_SYN_HL
+    curwin->w_s = &(curbuf->b_s);
+#endif
+
+    // 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;
+
+    buflist_setfpos(curbuf, curwin, curbuf->b_last_cursor.lnum,
+					      curbuf->b_last_cursor.col, TRUE);
+
+    // 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;
+#ifdef FEAT_DIFF
+	curwin->w_topfill = 0;
+#endif
+	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, FALSE);	// redisplay at correct position
+
+#ifdef FEAT_NETBEANS_INTG
+    // Send fileOpened event because we've changed buffers.
+    netbeans_file_activated(curbuf);
+#endif
+
+    // Change directories when the 'acd' option is set.
+    DO_AUTOCHDIR;
+
+#ifdef FEAT_KEYMAP
+    if (curbuf->b_kmap_state & KEYMAP_INIT)
+	(void)keymap_init();
+#endif
+#ifdef FEAT_SPELL
+    // 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)parse_spelllang(curwin);
+#endif
+#ifdef FEAT_VIMINFO
+    curbuf->b_last_used = vim_time();
+#endif
+
+    redraw_later(UPD_NOT_VALID);
+}
+
+#if defined(FEAT_AUTOCHDIR) || defined(PROTO)
+/*
+ * Change to the directory of the current buffer.
+ * Don't do this while still starting up.
+ */
+    void
+do_autochdir(void)
+{
+    if ((starting == 0 || test_autochdir)
+	    && curbuf->b_ffname != NULL
+	    && vim_chdirfile(curbuf->b_ffname, "auto") == OK)
+    {
+	shorten_fnames(TRUE);
+	last_chdir_reason = "autochdir";
+    }
+}
+#endif
+
+    static void
+no_write_message_buf(buf_T *buf UNUSED)
+{
+#ifdef FEAT_TERMINAL
+    if (term_job_running(buf->b_term))
+	emsg(_(e_job_still_running_add_bang_to_end_the_job));
+    else
+#endif
+	semsg(_(e_no_write_since_last_change_for_buffer_nr_add_bang_to_override),
+		buf->b_fnum);
+}
+
+    void
+no_write_message(void)
+{
+#ifdef FEAT_TERMINAL
+    if (term_job_running(curbuf->b_term))
+	emsg(_(e_job_still_running_add_bang_to_end_the_job));
+    else
+#endif
+	emsg(_(e_no_write_since_last_change_add_bang_to_override));
+}
+
+    void
+no_write_message_nobang(buf_T *buf UNUSED)
+{
+#ifdef FEAT_TERMINAL
+    if (term_job_running(buf->b_term))
+	emsg(_(e_job_still_running));
+    else
+#endif
+	emsg(_(e_no_write_since_last_change));
+}
+
+/*
+ * functions for dealing with the buffer list
+ */
+
+/*
+ * Return TRUE if the current buffer is empty, unnamed, unmodified and used in
+ * only one window.  That means it can be re-used.
+ */
+    int
+curbuf_reusable(void)
+{
+    return (curbuf != NULL
+	&& curbuf->b_ffname == NULL
+	&& curbuf->b_nwindows <= 1
+	&& (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())
+	&& !bt_quickfix(curbuf)
+	&& !curbufIsChanged());
+}
+
+/*
+ * Add a file name to the buffer list.  Return a pointer to the buffer.
+ * If the same file name already exists return a pointer to that buffer.
+ * If it does not exist, or if fname == NULL, a new entry is created.
+ * If (flags & BLN_CURBUF) is TRUE, may use current buffer.
+ * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list.
+ * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer.
+ * If (flags & BLN_NEW) is TRUE, don't use an existing buffer.
+ * If (flags & BLN_NOOPT) is TRUE, don't copy options from the current buffer
+ *				    if the buffer already exists.
+ * If (flags & BLN_REUSE) is TRUE, may use buffer number from "buf_reuse".
+ * This is the ONLY way to create a new buffer.
+ */
+    buf_T *
+buflist_new(
+    char_u	*ffname_arg,	// full path of fname or relative
+    char_u	*sfname_arg,	// short fname or NULL
+    linenr_T	lnum,		// preferred cursor line
+    int		flags)		// BLN_ defines
+{
+    char_u	*ffname = ffname_arg;
+    char_u	*sfname = sfname_arg;
+    buf_T	*buf;
+#ifdef UNIX
+    stat_T	st;
+#endif
+
+    if (top_file_num == 1)
+	hash_init(&buf_hashtab);
+
+    fname_expand(curbuf, &ffname, &sfname);	// will allocate ffname
+
+    /*
+     * If the file name already exists in the list, update the entry.
+     */
+#ifdef UNIX
+    // On Unix we can use inode numbers when the file exists.  Works better
+    // for hard links.
+    if (sfname == NULL || mch_stat((char *)sfname, &st) < 0)
+	st.st_dev = (dev_T)-1;
+#endif
+    if (ffname != NULL && !(flags & (BLN_DUMMY | BLN_NEW)) && (buf =
+#ifdef UNIX
+		buflist_findname_stat(ffname, &st)
+#else
+		buflist_findname(ffname)
+#endif
+		) != NULL)
+    {
+	vim_free(ffname);
+	if (lnum != 0)
+	    buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin,
+						      lnum, (colnr_T)0, FALSE);
+
+	if ((flags & BLN_NOOPT) == 0)
+	    // copy the options now, if 'cpo' doesn't have 's' and not done
+	    // already
+	    buf_copy_options(buf, 0);
+
+	if ((flags & BLN_LISTED) && !buf->b_p_bl)
+	{
+	    bufref_T bufref;
+
+	    buf->b_p_bl = TRUE;
+	    set_bufref(&bufref, buf);
+	    if (!(flags & BLN_DUMMY))
+	    {
+		if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf)
+			&& !bufref_valid(&bufref))
+		    return NULL;
+	    }
+	}
+	return buf;
+    }
+
+    /*
+     * If the current buffer has no name and no contents, use the current
+     * buffer.	Otherwise: Need to allocate a new buffer structure.
+     *
+     * This is the ONLY place where a new buffer structure is allocated!
+     * (A spell file buffer is allocated in spell.c, but that's not a normal
+     * buffer.)
+     */
+    buf = NULL;
+    if ((flags & BLN_CURBUF) && curbuf_reusable())
+    {
+	buf = curbuf;
+	// It's like this buffer is deleted.  Watch out for autocommands that
+	// change curbuf!  If that happens, allocate a new buffer anyway.
+	buf_freeall(buf, BFA_WIPE | BFA_DEL);
+	if (buf != curbuf)   // autocommands deleted the buffer!
+	    return NULL;
+#ifdef FEAT_EVAL
+	if (aborting())		// autocmds may abort script processing
+	{
+	    vim_free(ffname);
+	    return NULL;
+	}
+#endif
+    }
+    if (buf != curbuf || curbuf == NULL)
+    {
+	buf = ALLOC_CLEAR_ONE(buf_T);
+	if (buf == NULL)
+	{
+	    vim_free(ffname);
+	    return NULL;
+	}
+#ifdef FEAT_EVAL
+	// init b: variables
+	buf->b_vars = dict_alloc_id(aid_newbuf_bvars);
+	if (buf->b_vars == NULL)
+	{
+	    vim_free(ffname);
+	    vim_free(buf);
+	    return NULL;
+	}
+	init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
+#endif
+	init_changedtick(buf);
+    }
+
+    if (ffname != NULL)
+    {
+	buf->b_ffname = ffname;
+	buf->b_sfname = vim_strsave(sfname);
+    }
+
+    clear_wininfo(buf);
+    buf->b_wininfo = ALLOC_CLEAR_ONE(wininfo_T);
+
+    if ((ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL))
+	    || buf->b_wininfo == NULL)
+    {
+	if (buf->b_sfname != buf->b_ffname)
+	    VIM_CLEAR(buf->b_sfname);
+	else
+	    buf->b_sfname = NULL;
+	VIM_CLEAR(buf->b_ffname);
+	if (buf != curbuf)
+	    free_buffer(buf);
+	return NULL;
+    }
+
+    if (buf == curbuf)
+    {
+	free_buffer_stuff(buf, FALSE);	// delete local variables et al.
+
+	// Init the options.
+	buf->b_p_initialized = FALSE;
+	buf_copy_options(buf, BCO_ENTER);
+
+#ifdef FEAT_KEYMAP
+	// need to reload lmaps and set b:keymap_name
+	curbuf->b_kmap_state |= KEYMAP_INIT;
+#endif
+    }
+    else
+    {
+	// put the new buffer at the end of the buffer list
+	buf->b_next = NULL;
+	if (firstbuf == NULL)		// buffer list is empty
+	{
+	    buf->b_prev = NULL;
+	    firstbuf = buf;
+	}
+	else				// append new buffer at end of list
+	{
+	    lastbuf->b_next = buf;
+	    buf->b_prev = lastbuf;
+	}
+	lastbuf = buf;
+
+	if ((flags & BLN_REUSE) && buf_reuse.ga_len > 0)
+	{
+	    // Recycle a previously used buffer number.  Used for buffers which
+	    // are normally hidden, e.g. in a popup window.  Avoids that the
+	    // buffer number grows rapidly.
+	    --buf_reuse.ga_len;
+	    buf->b_fnum = ((int *)buf_reuse.ga_data)[buf_reuse.ga_len];
+
+	    // Move buffer to the right place in the buffer list.
+	    while (buf->b_prev != NULL && buf->b_fnum < buf->b_prev->b_fnum)
+	    {
+		buf_T	*prev = buf->b_prev;
+
+		prev->b_next = buf->b_next;
+		if (prev->b_next != NULL)
+		    prev->b_next->b_prev = prev;
+		buf->b_next = prev;
+		buf->b_prev = prev->b_prev;
+		if (buf->b_prev != NULL)
+		    buf->b_prev->b_next = buf;
+		prev->b_prev = buf;
+		if (lastbuf == buf)
+		    lastbuf = prev;
+		if (firstbuf == prev)
+		    firstbuf = buf;
+	    }
+	}
+	else
+	    buf->b_fnum = top_file_num++;
+	if (top_file_num < 0)		// wrap around (may cause duplicates)
+	{
+	    emsg(_("W14: Warning: List of file names overflow"));
+	    if (emsg_silent == 0 && !in_assert_fails)
+	    {
+		out_flush();
+		ui_delay(3001L, TRUE);	// make sure it is noticed
+	    }
+	    top_file_num = 1;
+	}
+	buf_hashtab_add(buf);
+
+	// Always copy the options from the current buffer.
+	buf_copy_options(buf, BCO_ALWAYS);
+    }
+
+    buf->b_wininfo->wi_fpos.lnum = lnum;
+    buf->b_wininfo->wi_win = curwin;
+
+#ifdef FEAT_SYN_HL
+    hash_init(&buf->b_s.b_keywtab);
+    hash_init(&buf->b_s.b_keywtab_ic);
+#endif
+
+    buf->b_fname = buf->b_sfname;
+#ifdef UNIX
+    if (st.st_dev == (dev_T)-1)
+	buf->b_dev_valid = FALSE;
+    else
+    {
+	buf->b_dev_valid = TRUE;
+	buf->b_dev = st.st_dev;
+	buf->b_ino = st.st_ino;
+    }
+#endif
+    buf->b_u_synced = TRUE;
+    buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED;
+    if (flags & BLN_DUMMY)
+	buf->b_flags |= BF_DUMMY;
+    buf_clear_file(buf);
+    clrallmarks(buf);			// clear marks
+    fmarks_check_names(buf);		// check file marks for this file
+    buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE;	// init 'buflisted'
+    if (!(flags & BLN_DUMMY))
+    {
+	bufref_T bufref;
+
+	// Tricky: these autocommands may change the buffer list.  They could
+	// also split the window with re-using the one empty buffer. This may
+	// result in unexpectedly losing the empty buffer.
+	set_bufref(&bufref, buf);
+	if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf)
+		&& !bufref_valid(&bufref))
+	    return NULL;
+	if (flags & BLN_LISTED)
+	{
+	    if (apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf)
+		    && !bufref_valid(&bufref))
+		return NULL;
+	}
+#ifdef FEAT_EVAL
+	if (aborting())		// autocmds may abort script processing
+	    return NULL;
+#endif
+    }
+
+    return buf;
+}
+
+/*
+ * Free the memory for the options of a buffer.
+ * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and
+ * 'fileencoding'.
+ */
+    void
+free_buf_options(
+    buf_T	*buf,
+    int		free_p_ff)
+{
+    if (free_p_ff)
+    {
+	clear_string_option(&buf->b_p_fenc);
+	clear_string_option(&buf->b_p_ff);
+	clear_string_option(&buf->b_p_bh);
+	clear_string_option(&buf->b_p_bt);
+    }
+#ifdef FEAT_FIND_ID
+    clear_string_option(&buf->b_p_def);
+    clear_string_option(&buf->b_p_inc);
+# ifdef FEAT_EVAL
+    clear_string_option(&buf->b_p_inex);
+# endif
+#endif
+#if defined(FEAT_EVAL)
+    clear_string_option(&buf->b_p_inde);
+    clear_string_option(&buf->b_p_indk);
+#endif
+#if defined(FEAT_BEVAL) && defined(FEAT_EVAL)
+    clear_string_option(&buf->b_p_bexpr);
+#endif
+#if defined(FEAT_CRYPT)
+    clear_string_option(&buf->b_p_cm);
+#endif
+    clear_string_option(&buf->b_p_fp);
+#if defined(FEAT_EVAL)
+    clear_string_option(&buf->b_p_fex);
+#endif
+#ifdef FEAT_CRYPT
+# ifdef FEAT_SODIUM
+    if (buf->b_p_key != NULL && *buf->b_p_key != NUL
+			   && crypt_method_is_sodium(crypt_get_method_nr(buf)))
+	crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
+# endif
+    clear_string_option(&buf->b_p_key);
+#endif
+    clear_string_option(&buf->b_p_kp);
+    clear_string_option(&buf->b_p_mps);
+    clear_string_option(&buf->b_p_fo);
+    clear_string_option(&buf->b_p_flp);
+    clear_string_option(&buf->b_p_isk);
+#ifdef FEAT_VARTABS
+    clear_string_option(&buf->b_p_vsts);
+    VIM_CLEAR(buf->b_p_vsts_nopaste);
+    VIM_CLEAR(buf->b_p_vsts_array);
+    clear_string_option(&buf->b_p_vts);
+    VIM_CLEAR(buf->b_p_vts_array);
+#endif
+#ifdef FEAT_KEYMAP
+    clear_string_option(&buf->b_p_keymap);
+    keymap_clear(&buf->b_kmap_ga);
+    ga_clear(&buf->b_kmap_ga);
+#endif
+    clear_string_option(&buf->b_p_com);
+#ifdef FEAT_FOLDING
+    clear_string_option(&buf->b_p_cms);
+#endif
+    clear_string_option(&buf->b_p_nf);
+#ifdef FEAT_SYN_HL
+    clear_string_option(&buf->b_p_syn);
+    clear_string_option(&buf->b_s.b_syn_isk);
+#endif
+#ifdef FEAT_SPELL
+    clear_string_option(&buf->b_s.b_p_spc);
+    clear_string_option(&buf->b_s.b_p_spf);
+    vim_regfree(buf->b_s.b_cap_prog);
+    buf->b_s.b_cap_prog = NULL;
+    clear_string_option(&buf->b_s.b_p_spl);
+    clear_string_option(&buf->b_s.b_p_spo);
+#endif
+    clear_string_option(&buf->b_p_sua);
+    clear_string_option(&buf->b_p_ft);
+    clear_string_option(&buf->b_p_cink);
+    clear_string_option(&buf->b_p_cino);
+    clear_string_option(&buf->b_p_lop);
+    clear_string_option(&buf->b_p_cinsd);
+    clear_string_option(&buf->b_p_cinw);
+    clear_string_option(&buf->b_p_cpt);
+#ifdef FEAT_COMPL_FUNC
+    clear_string_option(&buf->b_p_cfu);
+    free_callback(&buf->b_cfu_cb);
+    clear_string_option(&buf->b_p_ofu);
+    free_callback(&buf->b_ofu_cb);
+    clear_string_option(&buf->b_p_tsrfu);
+    free_callback(&buf->b_tsrfu_cb);
+#endif
+#ifdef FEAT_QUICKFIX
+    clear_string_option(&buf->b_p_gp);
+    clear_string_option(&buf->b_p_mp);
+    clear_string_option(&buf->b_p_efm);
+#endif
+    clear_string_option(&buf->b_p_ep);
+    clear_string_option(&buf->b_p_path);
+    clear_string_option(&buf->b_p_tags);
+    clear_string_option(&buf->b_p_tc);
+#ifdef FEAT_EVAL
+    clear_string_option(&buf->b_p_tfu);
+    free_callback(&buf->b_tfu_cb);
+#endif
+    clear_string_option(&buf->b_p_dict);
+    clear_string_option(&buf->b_p_tsr);
+    clear_string_option(&buf->b_p_qe);
+    buf->b_p_ar = -1;
+    buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
+    clear_string_option(&buf->b_p_lw);
+    clear_string_option(&buf->b_p_bkc);
+    clear_string_option(&buf->b_p_menc);
+}
+
+/*
+ * Get alternate file "n".
+ * Set linenr to "lnum" or altfpos.lnum if "lnum" == 0.
+ *	Also set cursor column to altfpos.col if 'startofline' is not set.
+ * if (options & GETF_SETMARK) call setpcmark()
+ * if (options & GETF_ALT) we are jumping to an alternate file.
+ * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping
+ *
+ * Return FAIL for failure, OK for success.
+ */
+    int
+buflist_getfile(
+    int		n,
+    linenr_T	lnum,
+    int		options,
+    int		forceit)
+{
+    buf_T	*buf;
+    win_T	*wp = NULL;
+    pos_T	*fpos;
+    colnr_T	col;
+
+    buf = buflist_findnr(n);
+    if (buf == NULL)
+    {
+	if ((options & GETF_ALT) && n == 0)
+	    emsg(_(e_no_alternate_file));
+	else
+	    semsg(_(e_buffer_nr_not_found), n);
+	return FAIL;
+    }
+
+    // if alternate file is the current buffer, nothing to do
+    if (buf == curbuf)
+	return OK;
+
+    if (text_or_buf_locked())
+	return FAIL;
+
+    // altfpos may be changed by getfile(), get it now
+    if (lnum == 0)
+    {
+	fpos = buflist_findfpos(buf);
+	lnum = fpos->lnum;
+	col = fpos->col;
+    }
+    else
+	col = 0;
+
+    if (options & GETF_SWITCH)
+    {
+	// If 'switchbuf' is set jump to the window containing "buf".
+	wp = swbuf_goto_win_with_buf(buf);
+
+	// If 'switchbuf' contains "split", "vsplit" or "newtab" and the
+	// current buffer isn't empty: open new tab or window
+	if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB))
+							       && !BUFEMPTY())
+	{
+	    if (swb_flags & SWB_NEWTAB)
+		tabpage_new();
+	    else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0)
+								      == FAIL)
+		return FAIL;
+	    RESET_BINDING(curwin);
+	}
+    }
+
+    ++RedrawingDisabled;
+    int retval = FAIL;
+    if (GETFILE_SUCCESS(getfile(buf->b_fnum, NULL, NULL,
+				     (options & GETF_SETMARK), lnum, forceit)))
+    {
+	// cursor is at to BOL and w_cursor.lnum is checked due to getfile()
+	if (!p_sol && col != 0)
+	{
+	    curwin->w_cursor.col = col;
+	    check_cursor_col();
+	    curwin->w_cursor.coladd = 0;
+	    curwin->w_set_curswant = TRUE;
+	}
+	retval = OK;
+    }
+
+    if (RedrawingDisabled > 0)
+	--RedrawingDisabled;
+    return retval;
+}
+
+/*
+ * go to the last know line number for the current buffer
+ */
+    static void
+buflist_getfpos(void)
+{
+    pos_T	*fpos;
+
+    fpos = buflist_findfpos(curbuf);
+
+    curwin->w_cursor.lnum = fpos->lnum;
+    check_cursor_lnum();
+
+    if (p_sol)
+	curwin->w_cursor.col = 0;
+    else
+    {
+	curwin->w_cursor.col = fpos->col;
+	check_cursor_col();
+	curwin->w_cursor.coladd = 0;
+	curwin->w_set_curswant = TRUE;
+    }
+}
+
+/*
+ * Find file in buffer list by name (it has to be for the current window).
+ * Returns NULL if not found.
+ */
+    buf_T *
+buflist_findname_exp(char_u *fname)
+{
+    char_u	*ffname;
+    buf_T	*buf = NULL;
+
+    // First make the name into a full path name
+    ffname = FullName_save(fname,
+#ifdef UNIX
+	    TRUE	    // force expansion, get rid of symbolic links
+#else
+	    FALSE
+#endif
+	    );
+    if (ffname != NULL)
+    {
+	buf = buflist_findname(ffname);
+	vim_free(ffname);
+    }
+    return buf;
+}
+
+/*
+ * Find file in buffer list by name (it has to be for the current window).
+ * "ffname" must have a full path.
+ * Skips dummy buffers.
+ * Returns NULL if not found.
+ */
+    buf_T *
+buflist_findname(char_u *ffname)
+{
+#ifdef UNIX
+    stat_T	st;
+
+    if (mch_stat((char *)ffname, &st) < 0)
+	st.st_dev = (dev_T)-1;
+    return buflist_findname_stat(ffname, &st);
+}
+
+/*
+ * Same as buflist_findname(), but pass the stat structure to avoid getting it
+ * twice for the same file.
+ * Returns NULL if not found.
+ */
+    static buf_T *
+buflist_findname_stat(
+    char_u	*ffname,
+    stat_T	*stp)
+{
+#endif
+    buf_T	*buf;
+
+    // Start at the last buffer, expect to find a match sooner.
+    FOR_ALL_BUFS_FROM_LAST(buf)
+	if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname
+#ifdef UNIX
+		    , stp
+#endif
+		    ))
+	    return buf;
+    return NULL;
+}
+
+/*
+ * Find file in buffer list by a regexp pattern.
+ * Return fnum of the found buffer.
+ * Return < 0 for error.
+ */
+    int
+buflist_findpat(
+    char_u	*pattern,
+    char_u	*pattern_end,	// pointer to first char after pattern
+    int		unlisted,	// find unlisted buffers
+    int		diffmode UNUSED, // find diff-mode buffers only
+    int		curtab_only)	// find buffers in current tab only
+{
+    buf_T	*buf;
+    int		match = -1;
+    int		find_listed;
+    char_u	*pat;
+    char_u	*patend;
+    int		attempt;
+    char_u	*p;
+    int		toggledollar;
+
+    // "%" is current file, "%%" or "#" is alternate file
+    if ((pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#'))
+	    || (in_vim9script() && pattern_end == pattern + 2
+				    && pattern[0] == '%' && pattern[1] == '%'))
+    {
+	if (*pattern == '#' || pattern_end == pattern + 2)
+	    match = curwin->w_alt_fnum;
+	else
+	    match = curbuf->b_fnum;
+#ifdef FEAT_DIFF
+	if (diffmode && !diff_mode_buf(buflist_findnr(match)))
+	    match = -1;
+#endif
+    }
+
+    /*
+     * Try four ways of matching a listed buffer:
+     * attempt == 0: without '^' or '$' (at any position)
+     * attempt == 1: with '^' at start (only at position 0)
+     * attempt == 2: with '$' at end (only match at end)
+     * attempt == 3: with '^' at start and '$' at end (only full match)
+     * Repeat this for finding an unlisted buffer if there was no matching
+     * listed buffer.
+     */
+    else
+    {
+	pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE);
+	if (pat == NULL)
+	    return -1;
+	patend = pat + STRLEN(pat) - 1;
+	toggledollar = (patend > pat && *patend == '$');
+
+	// First try finding a listed buffer.  If not found and "unlisted"
+	// is TRUE, try finding an unlisted buffer.
+	find_listed = TRUE;
+	for (;;)
+	{
+	    for (attempt = 0; attempt <= 3; ++attempt)
+	    {
+		regmatch_T	regmatch;
+
+		// may add '^' and '$'
+		if (toggledollar)
+		    *patend = (attempt < 2) ? NUL : '$'; // add/remove '$'
+		p = pat;
+		if (*p == '^' && !(attempt & 1))	 // add/remove '^'
+		    ++p;
+		regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0);
+
+		FOR_ALL_BUFS_FROM_LAST(buf)
+		{
+		    if (regmatch.regprog == NULL)
+		    {
+			// invalid pattern, possibly after switching engine
+			vim_free(pat);
+			return -1;
+		    }
+		    if (buf->b_p_bl == find_listed
+#ifdef FEAT_DIFF
+			    && (!diffmode || diff_mode_buf(buf))
+#endif
+			    && buflist_match(&regmatch, buf, FALSE) != NULL)
+		    {
+			if (curtab_only)
+			{
+			    // Ignore the match if the buffer is not open in
+			    // the current tab.
+			    win_T	*wp;
+
+			    FOR_ALL_WINDOWS(wp)
+				if (wp->w_buffer == buf)
+				    break;
+			    if (wp == NULL)
+				continue;
+			}
+			if (match >= 0)		// already found a match
+			{
+			    match = -2;
+			    break;
+			}
+			match = buf->b_fnum;	// remember first match
+		    }
+		}
+
+		vim_regfree(regmatch.regprog);
+		if (match >= 0)			// found one match
+		    break;
+	    }
+
+	    // Only search for unlisted buffers if there was no match with
+	    // a listed buffer.
+	    if (!unlisted || !find_listed || match != -1)
+		break;
+	    find_listed = FALSE;
+	}
+
+	vim_free(pat);
+    }
+
+    if (match == -2)
+	semsg(_(e_more_than_one_match_for_str), pattern);
+    else if (match < 0)
+	semsg(_(e_no_matching_buffer_for_str), pattern);
+    return match;
+}
+
+#ifdef FEAT_VIMINFO
+typedef struct {
+    buf_T   *buf;
+    char_u  *match;
+} bufmatch_T;
+#endif
+
+/*
+ * Find all buffer names that match.
+ * For command line expansion of ":buf" and ":sbuf".
+ * Return OK if matches found, FAIL otherwise.
+ */
+    int
+ExpandBufnames(
+    char_u	*pat,
+    int		*num_file,
+    char_u	***file,
+    int		options)
+{
+    int		count = 0;
+    buf_T	*buf;
+    int		round;
+    char_u	*p;
+    int		attempt;
+    char_u	*patc = NULL;
+#ifdef FEAT_VIMINFO
+    bufmatch_T	*matches = NULL;
+#endif
+    int		fuzzy;
+    fuzmatch_str_T  *fuzmatch = NULL;
+
+    *num_file = 0;		    // return values in case of FAIL
+    *file = NULL;
+
+#ifdef FEAT_DIFF
+    if ((options & BUF_DIFF_FILTER) && !curwin->w_p_diff)
+	return FAIL;
+#endif
+
+    fuzzy = cmdline_fuzzy_complete(pat);
+
+    // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular
+    // expression matching)
+    if (!fuzzy)
+    {
+	if (*pat == '^')
+	{
+	    patc = alloc(STRLEN(pat) + 11);
+	    if (patc == NULL)
+		return FAIL;
+	    STRCPY(patc, "\\(^\\|[\\/]\\)");
+	    STRCPY(patc + 11, pat + 1);
+	}
+	else
+	    patc = pat;
+    }
+
+    // attempt == 0: try match with    '\<', match at start of word
+    // attempt == 1: try match without '\<', match anywhere
+    for (attempt = 0; attempt <= (fuzzy ? 0 : 1); ++attempt)
+    {
+	regmatch_T	regmatch;
+	int		score = 0;
+
+	if (!fuzzy)
+	{
+	    if (attempt > 0 && patc == pat)
+		break;	// there was no anchor, no need to try again
+	    regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC);
+	}
+
+	// round == 1: Count the matches.
+	// round == 2: Build the array to keep the matches.
+	for (round = 1; round <= 2; ++round)
+	{
+	    count = 0;
+	    FOR_ALL_BUFFERS(buf)
+	    {
+		if (!buf->b_p_bl)	// skip unlisted buffers
+		    continue;
+#ifdef FEAT_DIFF
+		if (options & BUF_DIFF_FILTER)
+		    // Skip buffers not suitable for
+		    // :diffget or :diffput completion.
+		    if (buf == curbuf || !diff_mode_buf(buf))
+			continue;
+#endif
+
+		if (!fuzzy)
+		{
+		    if (regmatch.regprog == NULL)
+		    {
+			// invalid pattern, possibly after recompiling
+			if (patc != pat)
+			    vim_free(patc);
+			return FAIL;
+		    }
+		    p = buflist_match(&regmatch, buf, p_wic);
+		}
+		else
+		{
+		    p = NULL;
+		    // first try matching with the short file name
+		    if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0)
+			p = buf->b_sfname;
+		    if (p == NULL)
+		    {
+			// next try matching with the full path file name
+			if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0)
+			    p = buf->b_ffname;
+		    }
+		}
+
+		if (p == NULL)
+		    continue;
+
+		if (round == 1)
+		{
+		    ++count;
+		    continue;
+		}
+
+		if (options & WILD_HOME_REPLACE)
+		    p = home_replace_save(buf, p);
+		else
+		    p = vim_strsave(p);
+
+		if (!fuzzy)
+		{
+#ifdef FEAT_VIMINFO
+		    if (matches != NULL)
+		    {
+			matches[count].buf = buf;
+			matches[count].match = p;
+			count++;
+		    }
+		    else
+#endif
+			(*file)[count++] = p;
+		}
+		else
+		{
+		    fuzmatch[count].idx = count;
+		    fuzmatch[count].str = p;
+		    fuzmatch[count].score = score;
+		    count++;
+		}
+	    }
+	    if (count == 0)	// no match found, break here
+		break;
+	    if (round == 1)
+	    {
+		if (!fuzzy)
+		{
+		    *file = ALLOC_MULT(char_u *, count);
+		    if (*file == NULL)
+		    {
+			vim_regfree(regmatch.regprog);
+			if (patc != pat)
+			    vim_free(patc);
+			return FAIL;
+		    }
+#ifdef FEAT_VIMINFO
+		    if (options & WILD_BUFLASTUSED)
+			matches = ALLOC_MULT(bufmatch_T, count);
+#endif
+		}
+		else
+		{
+		    fuzmatch = ALLOC_MULT(fuzmatch_str_T, count);
+		    if (fuzmatch == NULL)
+		    {
+			*num_file = 0;
+			*file = NULL;
+			return FAIL;
+		    }
+		}
+	    }
+	}
+
+	if (!fuzzy)
+	{
+	    vim_regfree(regmatch.regprog);
+	    if (count)		// match(es) found, break here
+		break;
+	}
+    }
+
+    if (!fuzzy && patc != pat)
+	vim_free(patc);
+
+#ifdef FEAT_VIMINFO
+    if (!fuzzy)
+    {
+	if (matches != NULL)
+	{
+	    int i;
+	    if (count > 1)
+		qsort(matches, count, sizeof(bufmatch_T), buf_compare);
+	    // if the current buffer is first in the list, place it at the end
+	    if (matches[0].buf == curbuf)
+	    {
+		for (i = 1; i < count; i++)
+		    (*file)[i-1] = matches[i].match;
+		(*file)[count-1] = matches[0].match;
+	    }
+	    else
+	    {
+		for (i = 0; i < count; i++)
+		    (*file)[i] = matches[i].match;
+	    }
+	    vim_free(matches);
+	}
+    }
+    else
+    {
+	if (fuzzymatches_to_strmatches(fuzmatch, file, count, FALSE) == FAIL)
+	    return FAIL;
+    }
+#endif
+
+    *num_file = count;
+    return (count == 0 ? FAIL : OK);
+}
+
+/*
+ * Check for a match on the file name for buffer "buf" with regprog "prog".
+ * Note that rmp->regprog may become NULL when switching regexp engine.
+ */
+    static char_u *
+buflist_match(
+    regmatch_T	*rmp,
+    buf_T	*buf,
+    int		ignore_case)  // when TRUE ignore case, when FALSE use 'fic'
+{
+    char_u	*match;
+
+    // First try the short file name, then the long file name.
+    match = fname_match(rmp, buf->b_sfname, ignore_case);
+    if (match == NULL && rmp->regprog != NULL)
+	match = fname_match(rmp, buf->b_ffname, ignore_case);
+
+    return match;
+}
+
+/*
+ * Try matching the regexp in "rmp->regprog" with file name "name".
+ * Note that rmp->regprog may become NULL when switching regexp engine.
+ * Return "name" when there is a match, NULL when not.
+ */
+    static char_u *
+fname_match(
+    regmatch_T	*rmp,
+    char_u	*name,
+    int		ignore_case)  // when TRUE ignore case, when FALSE use 'fic'
+{
+    char_u	*match = NULL;
+    char_u	*p;
+
+    // extra check for valid arguments
+    if (name == NULL || rmp->regprog == NULL)
+	return NULL;
+
+    // Ignore case when 'fileignorecase' or the argument is set.
+    rmp->rm_ic = p_fic || ignore_case;
+    if (vim_regexec(rmp, name, (colnr_T)0))
+	match = name;
+    else if (rmp->regprog != NULL)
+    {
+	// Replace $(HOME) with '~' and try matching again.
+	p = home_replace_save(NULL, name);
+	if (p != NULL && vim_regexec(rmp, p, (colnr_T)0))
+	    match = name;
+	vim_free(p);
+    }
+
+    return match;
+}
+
+/*
+ * Find a file in the buffer list by buffer number.
+ */
+    buf_T *
+buflist_findnr(int nr)
+{
+    char_u	key[VIM_SIZEOF_INT * 2 + 1];
+    hashitem_T	*hi;
+
+    if (nr == 0)
+	nr = curwin->w_alt_fnum;
+    sprintf((char *)key, "%x", nr);
+    hi = hash_find(&buf_hashtab, key);
+
+    if (!HASHITEM_EMPTY(hi))
+	return (buf_T *)(hi->hi_key
+			     - ((unsigned)(curbuf->b_key - (char_u *)curbuf)));
+    return NULL;
+}
+
+/*
+ * Get name of file 'n' in the buffer list.
+ * When the file has no name an empty string is returned.
+ * home_replace() is used to shorten the file name (used for marks).
+ * Returns a pointer to allocated memory, of NULL when failed.
+ */
+    char_u *
+buflist_nr2name(
+    int		n,
+    int		fullname,
+    int		helptail)	// for help buffers return tail only
+{
+    buf_T	*buf;
+
+    buf = buflist_findnr(n);
+    if (buf == NULL)
+	return NULL;
+    return home_replace_save(helptail ? buf : NULL,
+				     fullname ? buf->b_ffname : buf->b_fname);
+}
+
+/*
+ * Set the "lnum" and "col" for the buffer "buf" and the current window.
+ * When "copy_options" is TRUE save the local window option values.
+ * When "lnum" is 0 only do the options.
+ */
+    void
+buflist_setfpos(
+    buf_T	*buf,
+    win_T	*win,		// may be NULL when using :badd
+    linenr_T	lnum,
+    colnr_T	col,
+    int		copy_options)
+{
+    wininfo_T	*wip;
+
+    FOR_ALL_BUF_WININFO(buf, wip)
+	if (wip->wi_win == win)
+	    break;
+    if (wip == NULL)
+    {
+	// allocate a new entry
+	wip = ALLOC_CLEAR_ONE(wininfo_T);
+	if (wip == NULL)
+	    return;
+	wip->wi_win = win;
+	if (lnum == 0)		// set lnum even when it's 0
+	    lnum = 1;
+    }
+    else
+    {
+	// remove the entry from the list
+	if (wip->wi_prev)
+	    wip->wi_prev->wi_next = wip->wi_next;
+	else
+	    buf->b_wininfo = wip->wi_next;
+	if (wip->wi_next)
+	    wip->wi_next->wi_prev = wip->wi_prev;
+	if (copy_options && wip->wi_optset)
+	{
+	    clear_winopt(&wip->wi_opt);
+#ifdef FEAT_FOLDING
+	    deleteFoldRecurse(&wip->wi_folds);
+#endif
+	}
+    }
+    if (lnum != 0)
+    {
+	wip->wi_fpos.lnum = lnum;
+	wip->wi_fpos.col = col;
+    }
+    if (win != NULL)
+	wip->wi_changelistidx = win->w_changelistidx;
+    if (copy_options && win != NULL)
+    {
+	// Save the window-specific option values.
+	copy_winopt(&win->w_onebuf_opt, &wip->wi_opt);
+#ifdef FEAT_FOLDING
+	wip->wi_fold_manual = win->w_fold_manual;
+	cloneFoldGrowArray(&win->w_folds, &wip->wi_folds);
+#endif
+	wip->wi_optset = TRUE;
+    }
+
+    // insert the entry in front of the list
+    wip->wi_next = buf->b_wininfo;
+    buf->b_wininfo = wip;
+    wip->wi_prev = NULL;
+    if (wip->wi_next)
+	wip->wi_next->wi_prev = wip;
+}
+
+#ifdef FEAT_DIFF
+/*
+ * Return TRUE when "wip" has 'diff' set and the diff is only for another tab
+ * page.  That's because a diff is local to a tab page.
+ */
+    static int
+wininfo_other_tab_diff(wininfo_T *wip)
+{
+    win_T	*wp;
+
+    if (!wip->wi_opt.wo_diff)
+	return FALSE;
+
+    FOR_ALL_WINDOWS(wp)
+	// return FALSE when it's a window in the current tab page, thus
+	// the buffer was in diff mode here
+	if (wip->wi_win == wp)
+	    return FALSE;
+    return TRUE;
+}
+#endif
+
+/*
+ * Find info for the current window in buffer "buf".
+ * If not found, return the info for the most recently used window.
+ * When "need_options" is TRUE skip entries where wi_optset is FALSE.
+ * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in
+ * another tab page.
+ * Returns NULL when there isn't any info.
+ */
+    static wininfo_T *
+find_wininfo(
+    buf_T	*buf,
+    int		need_options,
+    int		skip_diff_buffer UNUSED)
+{
+    wininfo_T	*wip;
+
+    FOR_ALL_BUF_WININFO(buf, wip)
+	if (wip->wi_win == curwin
+#ifdef FEAT_DIFF
+		&& (!skip_diff_buffer || !wininfo_other_tab_diff(wip))
+#endif
+
+		&& (!need_options || wip->wi_optset))
+	    break;
+
+    if (wip != NULL)
+	return wip;
+
+    // If no wininfo for curwin, use the first in the list (that doesn't have
+    // 'diff' set and is in another tab page).
+    // If "need_options" is TRUE skip entries that don't have options set,
+    // unless the window is editing "buf", so we can copy from the window
+    // itself.
+#ifdef FEAT_DIFF
+    if (skip_diff_buffer)
+    {
+	FOR_ALL_BUF_WININFO(buf, wip)
+	    if (!wininfo_other_tab_diff(wip)
+		    && (!need_options || wip->wi_optset
+			|| (wip->wi_win != NULL
+			    && wip->wi_win->w_buffer == buf)))
+		break;
+    }
+    else
+#endif
+	wip = buf->b_wininfo;
+    return wip;
+}
+
+/*
+ * Reset the local window options to the values last used in this window.
+ * If the buffer wasn't used in this window before, use the values from
+ * the most recently used window.  If the values were never set, use the
+ * global values for the window.
+ */
+    void
+get_winopts(buf_T *buf)
+{
+    wininfo_T	*wip;
+
+    clear_winopt(&curwin->w_onebuf_opt);
+#ifdef FEAT_FOLDING
+    clearFolding(curwin);
+#endif
+
+    wip = find_wininfo(buf, TRUE, TRUE);
+    if (wip != NULL && wip->wi_win != NULL
+	    && wip->wi_win != curwin && wip->wi_win->w_buffer == buf)
+    {
+	// The buffer is currently displayed in the window: use the actual
+	// option values instead of the saved (possibly outdated) values.
+	win_T *wp = wip->wi_win;
+
+	copy_winopt(&wp->w_onebuf_opt, &curwin->w_onebuf_opt);
+#ifdef FEAT_FOLDING
+	curwin->w_fold_manual = wp->w_fold_manual;
+	curwin->w_foldinvalid = TRUE;
+	cloneFoldGrowArray(&wp->w_folds, &curwin->w_folds);
+#endif
+    }
+    else if (wip != NULL && wip->wi_optset)
+    {
+	// the buffer was displayed in the current window earlier
+	copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt);
+#ifdef FEAT_FOLDING
+	curwin->w_fold_manual = wip->wi_fold_manual;
+	curwin->w_foldinvalid = TRUE;
+	cloneFoldGrowArray(&wip->wi_folds, &curwin->w_folds);
+#endif
+    }
+    else
+	copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt);
+    if (wip != NULL)
+	curwin->w_changelistidx = wip->wi_changelistidx;
+
+#ifdef FEAT_FOLDING
+    // Set 'foldlevel' to 'foldlevelstart' if it's not negative.
+    if (p_fdls >= 0)
+	curwin->w_p_fdl = p_fdls;
+#endif
+    after_copy_winopt(curwin);
+}
+
+/*
+ * Find the position (lnum and col) for the buffer 'buf' for the current
+ * window.
+ * Returns a pointer to no_position if no position is found.
+ */
+    pos_T *
+buflist_findfpos(buf_T *buf)
+{
+    wininfo_T	*wip;
+    static pos_T no_position = {1, 0, 0};
+
+    wip = find_wininfo(buf, FALSE, FALSE);
+    if (wip != NULL)
+	return &(wip->wi_fpos);
+    else
+	return &no_position;
+}
+
+/*
+ * Find the lnum for the buffer 'buf' for the current window.
+ */
+    linenr_T
+buflist_findlnum(buf_T *buf)
+{
+    return buflist_findfpos(buf)->lnum;
+}
+
+/*
+ * List all known file names (for :files and :buffers command).
+ */
+    void
+buflist_list(exarg_T *eap)
+{
+    buf_T	*buf = firstbuf;
+    int		len;
+    int		i;
+    int		ro_char;
+    int		changed_char;
+#ifdef FEAT_TERMINAL
+    int		job_running;
+    int		job_none_open;
+#endif
+
+#ifdef FEAT_VIMINFO
+    garray_T	buflist;
+    buf_T	**buflist_data = NULL, **p;
+
+    if (vim_strchr(eap->arg, 't'))
+    {
+	ga_init2(&buflist, sizeof(buf_T *), 50);
+	FOR_ALL_BUFFERS(buf)
+	{
+	    if (ga_grow(&buflist, 1) == OK)
+		((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
+	}
+
+	qsort(buflist.ga_data, (size_t)buflist.ga_len,
+		sizeof(buf_T *), buf_compare);
+
+	buflist_data = (buf_T **)buflist.ga_data;
+	buf = *buflist_data;
+    }
+    p = buflist_data;
+
+    for (; buf != NULL && !got_int; buf = buflist_data != NULL
+	    ? (++p < buflist_data + buflist.ga_len ? *p : NULL)
+	    : buf->b_next)
+#else
+    for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next)
+#endif
+    {
+#ifdef FEAT_TERMINAL
+	job_running = term_job_running(buf->b_term);
+	job_none_open = term_none_open(buf->b_term);
+#endif
+	// skip unlisted buffers, unless ! was used
+	if ((!buf->b_p_bl && !eap->forceit && !vim_strchr(eap->arg, 'u'))
+		|| (vim_strchr(eap->arg, 'u') && buf->b_p_bl)
+		|| (vim_strchr(eap->arg, '+')
+			&& ((buf->b_flags & BF_READERR) || !bufIsChanged(buf)))
+		|| (vim_strchr(eap->arg, 'a')
+			&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
+		|| (vim_strchr(eap->arg, 'h')
+			&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+#ifdef FEAT_TERMINAL
+		|| (vim_strchr(eap->arg, 'R')
+			&& (!job_running || (job_running && job_none_open)))
+		|| (vim_strchr(eap->arg, '?')
+			&& (!job_running || (job_running && !job_none_open)))
+		|| (vim_strchr(eap->arg, 'F')
+			&& (job_running || buf->b_term == NULL))
+#endif
+		|| (vim_strchr(eap->arg, '-') && buf->b_p_ma)
+		|| (vim_strchr(eap->arg, '=') && !buf->b_p_ro)
+		|| (vim_strchr(eap->arg, 'x') && !(buf->b_flags & BF_READERR))
+		|| (vim_strchr(eap->arg, '%') && buf != curbuf)
+		|| (vim_strchr(eap->arg, '#')
+		      && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum)))
+	    continue;
+	if (buf_spname(buf) != NULL)
+	    vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1);
+	else
+	    home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE);
+	if (message_filtered(NameBuff))
+	    continue;
+
+	changed_char = (buf->b_flags & BF_READERR) ? 'x'
+					     : (bufIsChanged(buf) ? '+' : ' ');
+#ifdef FEAT_TERMINAL
+	if (job_running)
+	{
+	    if (job_none_open)
+		ro_char = '?';
+	    else
+		ro_char = 'R';
+	    changed_char = ' ';  // bufIsChanged() returns TRUE to avoid
+				 // closing, but it's not actually changed.
+	}
+	else if (buf->b_term != NULL)
+	    ro_char = 'F';
+	else
+#endif
+	    ro_char = !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' ');
+
+	msg_putchar('\n');
+	len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
+		buf->b_fnum,
+		buf->b_p_bl ? ' ' : 'u',
+		buf == curbuf ? '%' :
+			(curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '),
+		buf->b_ml.ml_mfp == NULL ? ' ' :
+			(buf->b_nwindows == 0 ? 'h' : 'a'),
+		ro_char,
+		changed_char,
+		NameBuff);
+	if (len > IOSIZE - 20)
+	    len = IOSIZE - 20;
+
+	// put "line 999" in column 40 or after the file name
+	i = 40 - vim_strsize(IObuff);
+	do
+	    IObuff[len++] = ' ';
+	while (--i > 0 && len < IOSIZE - 18);
+#ifdef FEAT_VIMINFO
+	if (vim_strchr(eap->arg, 't') && buf->b_last_used)
+	    add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
+	else
+#endif
+	    vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
+		    _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum
+					       : (long)buflist_findlnum(buf));
+	msg_outtrans(IObuff);
+	out_flush();	    // output one line at a time
+	ui_breakcheck();
+    }
+
+#ifdef FEAT_VIMINFO
+    if (buflist_data)
+	ga_clear(&buflist);
+#endif
+}
+
+/*
+ * Get file name and line number for file 'fnum'.
+ * Used by DoOneCmd() for translating '%' and '#'.
+ * Used by insert_reg() and cmdline_paste() for '#' register.
+ * Return FAIL if not found, OK for success.
+ */
+    int
+buflist_name_nr(
+    int		fnum,
+    char_u	**fname,
+    linenr_T	*lnum)
+{
+    buf_T	*buf;
+
+    buf = buflist_findnr(fnum);
+    if (buf == NULL || buf->b_fname == NULL)
+	return FAIL;
+
+    *fname = buf->b_fname;
+    *lnum = buflist_findlnum(buf);
+
+    return OK;
+}
+
+/*
+ * Set the file name for "buf"' to "ffname_arg", short file name to
+ * "sfname_arg".
+ * The file name with the full path is also remembered, for when :cd is used.
+ * Returns FAIL for failure (file name already in use by other buffer)
+ *	OK otherwise.
+ */
+    int
+setfname(
+    buf_T	*buf,
+    char_u	*ffname_arg,
+    char_u	*sfname_arg,
+    int		message)	// give message when buffer already exists
+{
+    char_u	*ffname = ffname_arg;
+    char_u	*sfname = sfname_arg;
+    buf_T	*obuf = NULL;
+#ifdef UNIX
+    stat_T	st;
+#endif
+
+    if (ffname == NULL || *ffname == NUL)
+    {
+	// Removing the name.
+	if (buf->b_sfname != buf->b_ffname)
+	    VIM_CLEAR(buf->b_sfname);
+	else
+	    buf->b_sfname = NULL;
+	VIM_CLEAR(buf->b_ffname);
+#ifdef UNIX
+	st.st_dev = (dev_T)-1;
+#endif
+    }
+    else
+    {
+	fname_expand(buf, &ffname, &sfname); // will allocate ffname
+	if (ffname == NULL)		    // out of memory
+	    return FAIL;
+
+	/*
+	 * If the file name is already used in another buffer:
+	 * - if the buffer is loaded, fail
+	 * - if the buffer is not loaded, delete it from the list
+	 */
+#ifdef UNIX
+	if (mch_stat((char *)ffname, &st) < 0)
+	    st.st_dev = (dev_T)-1;
+#endif
+	if (!(buf->b_flags & BF_DUMMY))
+#ifdef UNIX
+	    obuf = buflist_findname_stat(ffname, &st);
+#else
+	    obuf = buflist_findname(ffname);
+#endif
+	if (obuf != NULL && obuf != buf)
+	{
+	    win_T	*win;
+	    tabpage_T   *tab;
+	    int		in_use = FALSE;
+
+	    // during startup a window may use a buffer that is not loaded yet
+	    FOR_ALL_TAB_WINDOWS(tab, win)
+		if (win->w_buffer == obuf)
+		    in_use = TRUE;
+
+	    // it's loaded or used in a window, fail
+	    if (obuf->b_ml.ml_mfp != NULL || in_use)
+	    {
+		if (message)
+		    emsg(_(e_buffer_with_this_name_already_exists));
+		vim_free(ffname);
+		return FAIL;
+	    }
+	    // delete from the list
+	    close_buffer(NULL, obuf, DOBUF_WIPE, FALSE, FALSE);
+	}
+	sfname = vim_strsave(sfname);
+	if (ffname == NULL || sfname == NULL)
+	{
+	    vim_free(sfname);
+	    vim_free(ffname);
+	    return FAIL;
+	}
+#ifdef USE_FNAME_CASE
+	fname_case(sfname, 0);    // set correct case for short file name
+#endif
+	if (buf->b_sfname != buf->b_ffname)
+	    vim_free(buf->b_sfname);
+	vim_free(buf->b_ffname);
+	buf->b_ffname = ffname;
+	buf->b_sfname = sfname;
+    }
+    buf->b_fname = buf->b_sfname;
+#ifdef UNIX
+    if (st.st_dev == (dev_T)-1)
+	buf->b_dev_valid = FALSE;
+    else
+    {
+	buf->b_dev_valid = TRUE;
+	buf->b_dev = st.st_dev;
+	buf->b_ino = st.st_ino;
+    }
+#endif
+
+    buf->b_shortname = FALSE;
+
+    buf_name_changed(buf);
+    return OK;
+}
+
+/*
+ * Crude way of changing the name of a buffer.  Use with care!
+ * The name should be relative to the current directory.
+ */
+    void
+buf_set_name(int fnum, char_u *name)
+{
+    buf_T	*buf;
+
+    buf = buflist_findnr(fnum);
+    if (buf == NULL)
+	return;
+
+    if (buf->b_sfname != buf->b_ffname)
+	vim_free(buf->b_sfname);
+    vim_free(buf->b_ffname);
+    buf->b_ffname = vim_strsave(name);
+    buf->b_sfname = NULL;
+    // Allocate ffname and expand into full path.  Also resolves .lnk
+    // files on Win32.
+    fname_expand(buf, &buf->b_ffname, &buf->b_sfname);
+    buf->b_fname = buf->b_sfname;
+}
+
+/*
+ * Take care of what needs to be done when the name of buffer "buf" has
+ * changed.
+ */
+    void
+buf_name_changed(buf_T *buf)
+{
+    /*
+     * If the file name changed, also change the name of the swapfile
+     */
+    if (buf->b_ml.ml_mfp != NULL)
+	ml_setname(buf);
+
+#ifdef FEAT_TERMINAL
+    if (buf->b_term != NULL)
+	term_clear_status_text(buf->b_term);
+#endif
+
+    if (curwin->w_buffer == buf)
+	check_arg_idx(curwin);	// check file name for arg list
+    maketitle();		// set window title
+    status_redraw_all();	// status lines need to be redrawn
+    fmarks_check_names(buf);	// check named file marks
+    ml_timestamp(buf);		// reset timestamp
+}
+
+/*
+ * set alternate file name for current window
+ *
+ * Used by do_one_cmd(), do_write() and do_ecmd().
+ * Return the buffer.
+ */
+    buf_T *
+setaltfname(
+    char_u	*ffname,
+    char_u	*sfname,
+    linenr_T	lnum)
+{
+    buf_T	*buf;
+
+    // Create a buffer.  'buflisted' is not set if it's a new buffer
+    buf = buflist_new(ffname, sfname, lnum, 0);
+    if (buf != NULL && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
+	curwin->w_alt_fnum = buf->b_fnum;
+    return buf;
+}
+
+/*
+ * Get alternate file name for current window.
+ * Return NULL if there isn't any, and give error message if requested.
+ */
+    char_u  *
+getaltfname(
+    int		errmsg)		// give error message
+{
+    char_u	*fname;
+    linenr_T	dummy;
+
+    if (buflist_name_nr(0, &fname, &dummy) == FAIL)
+    {
+	if (errmsg)
+	    emsg(_(e_no_alternate_file));
+	return NULL;
+    }
+    return fname;
+}
+
+/*
+ * Add a file name to the buflist and return its number.
+ * Uses same flags as buflist_new(), except BLN_DUMMY.
+ *
+ * used by qf_init(), main() and doarglist()
+ */
+    int
+buflist_add(char_u *fname, int flags)
+{
+    buf_T	*buf;
+
+    buf = buflist_new(fname, NULL, (linenr_T)0, flags);
+    if (buf != NULL)
+	return buf->b_fnum;
+    return 0;
+}
+
+#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO)
+/*
+ * Adjust slashes in file names.  Called after 'shellslash' was set.
+ */
+    void
+buflist_slash_adjust(void)
+{
+    buf_T	*bp;
+
+    FOR_ALL_BUFFERS(bp)
+    {
+	if (bp->b_ffname != NULL)
+	    slash_adjust(bp->b_ffname);
+	if (bp->b_sfname != NULL)
+	    slash_adjust(bp->b_sfname);
+    }
+}
+#endif
+
+/*
+ * Set alternate cursor position for the current buffer and window "win".
+ * Also save the local window option values.
+ */
+    void
+buflist_altfpos(win_T *win)
+{
+    buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE);
+}
+
+/*
+ * Return TRUE if 'ffname' is not the same file as current file.
+ * Fname must have a full path (expanded by mch_FullName()).
+ */
+    int
+otherfile(char_u *ffname)
+{
+    return otherfile_buf(curbuf, ffname
+#ifdef UNIX
+	    , NULL
+#endif
+	    );
+}
+
+    static int
+otherfile_buf(
+    buf_T		*buf,
+    char_u		*ffname
+#ifdef UNIX
+    , stat_T		*stp
+#endif
+    )
+{
+    // no name is different
+    if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL)
+	return TRUE;
+    if (fnamecmp(ffname, buf->b_ffname) == 0)
+	return FALSE;
+#ifdef UNIX
+    {
+	stat_T	    st;
+
+	// If no stat_T given, get it now
+	if (stp == NULL)
+	{
+	    if (!buf->b_dev_valid || mch_stat((char *)ffname, &st) < 0)
+		st.st_dev = (dev_T)-1;
+	    stp = &st;
+	}
+	// Use dev/ino to check if the files are the same, even when the names
+	// are different (possible with links).  Still need to compare the
+	// name above, for when the file doesn't exist yet.
+	// Problem: The dev/ino changes when a file is deleted (and created
+	// again) and remains the same when renamed/moved.  We don't want to
+	// mch_stat() each buffer each time, that would be too slow.  Get the
+	// dev/ino again when they appear to match, but not when they appear
+	// to be different: Could skip a buffer when it's actually the same
+	// file.
+	if (buf_same_ino(buf, stp))
+	{
+	    buf_setino(buf);
+	    if (buf_same_ino(buf, stp))
+		return FALSE;
+	}
+    }
+#endif
+    return TRUE;
+}
+
+#if defined(UNIX) || defined(PROTO)
+/*
+ * Set inode and device number for a buffer.
+ * Must always be called when b_fname is changed!.
+ */
+    void
+buf_setino(buf_T *buf)
+{
+    stat_T	st;
+
+    if (buf->b_fname != NULL && mch_stat((char *)buf->b_fname, &st) >= 0)
+    {
+	buf->b_dev_valid = TRUE;
+	buf->b_dev = st.st_dev;
+	buf->b_ino = st.st_ino;
+    }
+    else
+	buf->b_dev_valid = FALSE;
+}
+
+/*
+ * Return TRUE if dev/ino in buffer "buf" matches with "stp".
+ */
+    static int
+buf_same_ino(
+    buf_T	*buf,
+    stat_T	*stp)
+{
+    return (buf->b_dev_valid
+	    && stp->st_dev == buf->b_dev
+	    && stp->st_ino == buf->b_ino);
+}
+#endif
+
+/*
+ * Print info about the current buffer.
+ */
+    void
+fileinfo(
+    int fullname,	    // when non-zero print full path
+    int shorthelp,
+    int	dont_truncate)
+{
+    char_u	*name;
+    int		n;
+    char	*p;
+    char	*buffer;
+    size_t	len;
+
+    buffer = alloc(IOSIZE);
+    if (buffer == NULL)
+	return;
+
+    if (fullname > 1)	    // 2 CTRL-G: include buffer number
+    {
+	vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
+	p = buffer + STRLEN(buffer);
+    }
+    else
+	p = buffer;
+
+    *p++ = '"';
+    if (buf_spname(curbuf) != NULL)
+	vim_strncpy((char_u *)p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1);
+    else
+    {
+	if (!fullname && curbuf->b_fname != NULL)
+	    name = curbuf->b_fname;
+	else
+	    name = curbuf->b_ffname;
+	home_replace(shorthelp ? curbuf : NULL, name, (char_u *)p,
+					  (int)(IOSIZE - (p - buffer)), TRUE);
+    }
+
+    vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s",
+	    curbufIsChanged() ? (shortmess(SHM_MOD)
+					  ?  " [+]" : _(" [Modified]")) : " ",
+	    (curbuf->b_flags & BF_NOTEDITED) && !bt_dontwrite(curbuf)
+					? _("[Not edited]") : "",
+	    (curbuf->b_flags & BF_NEW) && !bt_dontwrite(curbuf)
+					   ? new_file_message() : "",
+	    (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "",
+	    curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]")
+						      : _("[readonly]")) : "",
+	    (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK)
+							  || curbuf->b_p_ro) ?
+								    " " : "");
+    // With 32 bit longs and more than 21,474,836 lines multiplying by 100
+    // causes an overflow, thus for large numbers divide instead.
+    if (curwin->w_cursor.lnum > 1000000L)
+	n = (int)(((long)curwin->w_cursor.lnum) /
+				   ((long)curbuf->b_ml.ml_line_count / 100L));
+    else
+	n = (int)(((long)curwin->w_cursor.lnum * 100L) /
+					    (long)curbuf->b_ml.ml_line_count);
+    if (curbuf->b_ml.ml_flags & ML_EMPTY)
+	vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg));
+    else if (p_ru)
+	// Current line and column are already on the screen -- webb
+	vim_snprintf_add(buffer, IOSIZE,
+		NGETTEXT("%ld line --%d%%--", "%ld lines --%d%%--",
+						   curbuf->b_ml.ml_line_count),
+		(long)curbuf->b_ml.ml_line_count, n);
+    else
+    {
+	vim_snprintf_add(buffer, IOSIZE,
+		_("line %ld of %ld --%d%%-- col "),
+		(long)curwin->w_cursor.lnum,
+		(long)curbuf->b_ml.ml_line_count,
+		n);
+	validate_virtcol();
+	len = STRLEN(buffer);
+	col_print((char_u *)buffer + len, IOSIZE - len,
+		   (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1);
+    }
+
+    (void)append_arg_number(curwin, (char_u *)buffer, IOSIZE,
+							 !shortmess(SHM_FILE));
+
+    if (dont_truncate)
+    {
+	// Temporarily set msg_scroll to avoid the message being truncated.
+	// First call msg_start() to get the message in the right place.
+	msg_start();
+	n = msg_scroll;
+	msg_scroll = TRUE;
+	msg(buffer);
+	msg_scroll = n;
+    }
+    else
+    {
+	p = msg_trunc_attr(buffer, FALSE, 0);
+	if (restart_edit != 0 || (msg_scrolled && !need_wait_return))
+	    // Need to repeat the message after redrawing when:
+	    // - When restart_edit is set (otherwise there will be a delay
+	    //   before redrawing).
+	    // - When the screen was scrolled but there is no wait-return
+	    //   prompt.
+	    set_keep_msg((char_u *)p, 0);
+    }
+
+    vim_free(buffer);
+}
+
+    void
+col_print(
+    char_u  *buf,
+    size_t  buflen,
+    int	    col,
+    int	    vcol)
+{
+    if (col == vcol)
+	vim_snprintf((char *)buf, buflen, "%d", col);
+    else
+	vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol);
+}
+
+static char_u *lasttitle = NULL;
+static char_u *lasticon = NULL;
+
+/*
+ * Put the file name in the title bar and icon of the window.
+ */
+    void
+maketitle(void)
+{
+    char_u	*p;
+    char_u	*title_str = NULL;
+    char_u	*icon_str = NULL;
+    int		maxlen = 0;
+    int		len;
+    int		mustset;
+    char_u	buf[IOSIZE];
+    int		off;
+
+    if (!redrawing())
+    {
+	// Postpone updating the title when 'lazyredraw' is set.
+	need_maketitle = TRUE;
+	return;
+    }
+
+    need_maketitle = FALSE;
+    if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL)
+	return;  // nothing to do
+
+    if (p_title)
+    {
+	if (p_titlelen > 0)
+	{
+	    maxlen = p_titlelen * Columns / 100;
+	    if (maxlen < 10)
+		maxlen = 10;
+	}
+
+	title_str = buf;
+	if (*p_titlestring != NUL)
+	{
+#ifdef FEAT_STL_OPT
+	    if (stl_syntax & STL_IN_TITLE)
+		build_stl_str_hl(curwin, title_str, sizeof(buf), p_titlestring,
+				    (char_u *)"titlestring", 0,
+				    0, maxlen, NULL, NULL);
+	    else
+#endif
+		title_str = p_titlestring;
+	}
+	else
+	{
+	    // format: "fname + (path) (1 of 2) - VIM"
+
+#define SPACE_FOR_FNAME (IOSIZE - 100)
+#define SPACE_FOR_DIR   (IOSIZE - 20)
+#define SPACE_FOR_ARGNR (IOSIZE - 10)  // at least room for " - VIM"
+	    if (curbuf->b_fname == NULL)
+		vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME);
+#ifdef FEAT_TERMINAL
+	    else if (curbuf->b_term != NULL)
+	    {
+		vim_strncpy(buf, term_get_status_text(curbuf->b_term),
+							      SPACE_FOR_FNAME);
+	    }
+#endif
+	    else
+	    {
+		p = transstr(gettail(curbuf->b_fname));
+		vim_strncpy(buf, p, SPACE_FOR_FNAME);
+		vim_free(p);
+	    }
+
+#ifdef FEAT_TERMINAL
+	    if (curbuf->b_term == NULL)
+#endif
+		switch (bufIsChanged(curbuf)
+			+ (curbuf->b_p_ro * 2)
+			+ (!curbuf->b_p_ma * 4))
+		{
+		    case 1: STRCAT(buf, " +"); break;
+		    case 2: STRCAT(buf, " ="); break;
+		    case 3: STRCAT(buf, " =+"); break;
+		    case 4:
+		    case 6: STRCAT(buf, " -"); break;
+		    case 5:
+		    case 7: STRCAT(buf, " -+"); break;
+		}
+
+	    if (curbuf->b_fname != NULL
+#ifdef FEAT_TERMINAL
+		    && curbuf->b_term == NULL
+#endif
+		    )
+	    {
+		// Get path of file, replace home dir with ~
+		off = (int)STRLEN(buf);
+		buf[off++] = ' ';
+		buf[off++] = '(';
+		home_replace(curbuf, curbuf->b_ffname,
+					buf + off, SPACE_FOR_DIR - off, TRUE);
+#ifdef BACKSLASH_IN_FILENAME
+		// avoid "c:/name" to be reduced to "c"
+		if (isalpha(buf[off]) && buf[off + 1] == ':')
+		    off += 2;
+#endif
+		// remove the file name
+		p = gettail_sep(buf + off);
+		if (p == buf + off)
+		{
+		    // must be a help buffer
+		    vim_strncpy(buf + off, (char_u *)_("help"),
+					   (size_t)(SPACE_FOR_DIR - off - 1));
+		}
+		else
+		    *p = NUL;
+
+		// Translate unprintable chars and concatenate.  Keep some
+		// room for the server name.  When there is no room (very long
+		// file name) use (...).
+		if (off < SPACE_FOR_DIR)
+		{
+		    p = transstr(buf + off);
+		    vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off));
+		    vim_free(p);
+		}
+		else
+		{
+		    vim_strncpy(buf + off, (char_u *)"...",
+					     (size_t)(SPACE_FOR_ARGNR - off));
+		}
+		STRCAT(buf, ")");
+	    }
+
+	    append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE);
+
+#if defined(FEAT_CLIENTSERVER)
+	    if (serverName != NULL)
+	    {
+		STRCAT(buf, " - ");
+		vim_strcat(buf, serverName, IOSIZE);
+	    }
+	    else
+#endif
+		STRCAT(buf, " - VIM");
+
+	    if (maxlen > 0)
+	    {
+		// make it shorter by removing a bit in the middle
+		if (vim_strsize(buf) > maxlen)
+		    trunc_string(buf, buf, maxlen, IOSIZE);
+	    }
+	}
+    }
+    mustset = value_changed(title_str, &lasttitle);
+
+    if (p_icon)
+    {
+	icon_str = buf;
+	if (*p_iconstring != NUL)
+	{
+#ifdef FEAT_STL_OPT
+	    if (stl_syntax & STL_IN_ICON)
+		build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring,
+				 (char_u *)"iconstring", 0, 0, 0, NULL, NULL);
+	    else
+#endif
+		icon_str = p_iconstring;
+	}
+	else
+	{
+	    if (buf_spname(curbuf) != NULL)
+		p = buf_spname(curbuf);
+	    else		    // use file name only in icon
+		p = gettail(curbuf->b_ffname);
+	    *icon_str = NUL;
+	    // Truncate name at 100 bytes.
+	    len = (int)STRLEN(p);
+	    if (len > 100)
+	    {
+		len -= 100;
+		if (has_mbyte)
+		    len += (*mb_tail_off)(p, p + len) + 1;
+		p += len;
+	    }
+	    STRCPY(icon_str, p);
+	    trans_characters(icon_str, IOSIZE);
+	}
+    }
+
+    mustset |= value_changed(icon_str, &lasticon);
+
+    if (mustset)
+	resettitle();
+}
+
+/*
+ * Used for title and icon: Check if "str" differs from "*last".  Set "*last"
+ * from "str" if it does.
+ * Return TRUE if resettitle() is to be called.
+ */
+    static int
+value_changed(char_u *str, char_u **last)
+{
+    if ((str == NULL) != (*last == NULL)
+	    || (str != NULL && *last != NULL && STRCMP(str, *last) != 0))
+    {
+	vim_free(*last);
+	if (str == NULL)
+	{
+	    *last = NULL;
+	    mch_restore_title(
+		  last == &lasttitle ? SAVE_RESTORE_TITLE : SAVE_RESTORE_ICON);
+	}
+	else
+	{
+	    *last = vim_strsave(str);
+	    return TRUE;
+	}
+    }
+    return FALSE;
+}
+
+/*
+ * Put current window title back (used after calling a shell)
+ */
+    void
+resettitle(void)
+{
+    mch_settitle(lasttitle, lasticon);
+}
+
+# if defined(EXITFREE) || defined(PROTO)
+    void
+free_titles(void)
+{
+    vim_free(lasttitle);
+    vim_free(lasticon);
+}
+# endif
+
+
+#if defined(FEAT_STL_OPT) || defined(FEAT_GUI_TABLINE) || defined(PROTO)
+
+/*
+ * Used for building in the status line.
+ */
+typedef struct
+{
+    char_u	*stl_start;
+    int		stl_minwid;
+    int		stl_maxwid;
+    enum {
+	Normal,
+	Empty,
+	Group,
+	Separate,
+	Highlight,
+	TabPage,
+	Trunc
+    }		stl_type;
+} stl_item_T;
+
+static size_t		stl_items_len = 20; // Initial value, grows as needed.
+static stl_item_T      *stl_items = NULL;
+static int	       *stl_groupitem = NULL;
+static stl_hlrec_T     *stl_hltab = NULL;
+static stl_hlrec_T     *stl_tabtab = NULL;
+static int		*stl_separator_locations = NULL;
+
+/*
+ * Build a string from the status line items in "fmt".
+ * Return length of string in screen cells.
+ *
+ * Normally works for window "wp", except when working for 'tabline' then it
+ * is "curwin".
+ *
+ * Items are drawn interspersed with the text that surrounds it
+ * Specials: %-<wid>(xxx%) => group, %= => separation marker, %< => truncation
+ * Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
+ *
+ * If maxwidth is not zero, the string will be filled at any middle marker
+ * or truncated if too long, fillchar is used for all whitespace.
+ */
+    int
+build_stl_str_hl(
+    win_T	*wp,
+    char_u	*out,		// buffer to write into != NameBuff
+    size_t	outlen,		// length of out[]
+    char_u	*fmt,
+    char_u	*opt_name,      // option name corresponding to "fmt"
+    int		opt_scope,	// scope for "opt_name"
+    int		fillchar,
+    int		maxwidth,
+    stl_hlrec_T **hltab,	// return: HL attributes (can be NULL)
+    stl_hlrec_T **tabtab)	// return: tab page nrs (can be NULL)
+{
+    linenr_T	lnum;
+    size_t	len;
+    char_u	*p;
+    char_u	*s;
+    char_u	*t;
+    int		byteval;
+#ifdef FEAT_EVAL
+    int		use_sandbox;
+    win_T	*save_curwin;
+    buf_T	*save_curbuf;
+    int		save_VIsual_active;
+#endif
+    int		empty_line;
+    colnr_T	virtcol;
+    long	l;
+    long	n;
+    int		prevchar_isflag;
+    int		prevchar_isitem;
+    int		itemisflag;
+    int		fillable;
+    char_u	*str;
+    long	num;
+    int		width;
+    int		itemcnt;
+    int		curitem;
+    int		group_end_userhl;
+    int		group_start_userhl;
+    int		groupdepth;
+#ifdef FEAT_EVAL
+    int		evaldepth;
+#endif
+    int		minwid;
+    int		maxwid;
+    int		zeropad;
+    char_u	base;
+    char_u	opt;
+#define TMPLEN 70
+    char_u	buf_tmp[TMPLEN];
+    char_u	win_tmp[TMPLEN];
+    char_u	*usefmt = fmt;
+    stl_hlrec_T *sp;
+    int		save_redraw_not_allowed = redraw_not_allowed;
+    int		save_KeyTyped = KeyTyped;
+    // TODO: find out why using called_emsg_before makes tests fail, does it
+    // matter?
+    // int	called_emsg_before = called_emsg;
+    int		did_emsg_before = did_emsg;
+
+    // When inside update_screen() we do not want redrawing a statusline,
+    // ruler, title, etc. to trigger another redraw, it may cause an endless
+    // loop.
+    if (updating_screen)
+	redraw_not_allowed = TRUE;
+
+    if (stl_items == NULL)
+    {
+	stl_items = ALLOC_MULT(stl_item_T, stl_items_len);
+	stl_groupitem = ALLOC_MULT(int, stl_items_len);
+
+	// Allocate one more, because the last element is used to indicate the
+	// end of the list.
+	stl_hltab  = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
+	stl_tabtab = ALLOC_MULT(stl_hlrec_T, stl_items_len + 1);
+
+	stl_separator_locations = ALLOC_MULT(int, stl_items_len);
+    }
+
+#ifdef FEAT_EVAL
+    // if "fmt" was set insecurely it needs to be evaluated in the sandbox
+    use_sandbox = was_set_insecurely(opt_name, opt_scope);
+
+    // When the format starts with "%!" then evaluate it as an expression and
+    // use the result as the actual format string.
+    if (fmt[0] == '%' && fmt[1] == '!')
+    {
+	typval_T	tv;
+
+	tv.v_type = VAR_NUMBER;
+	tv.vval.v_number = wp->w_id;
+	set_var((char_u *)"g:statusline_winid", &tv, FALSE);
+
+	usefmt = eval_to_string_safe(fmt + 2, use_sandbox, FALSE, FALSE);
+	if (usefmt == NULL)
+	    usefmt = fmt;
+
+	do_unlet((char_u *)"g:statusline_winid", TRUE);
+    }
+#endif
+
+    if (fillchar == 0)
+	fillchar = ' ';
+
+    // The cursor in windows other than the current one isn't always
+    // up-to-date, esp. because of autocommands and timers.
+    lnum = wp->w_cursor.lnum;
+    if (lnum > wp->w_buffer->b_ml.ml_line_count)
+    {
+	lnum = wp->w_buffer->b_ml.ml_line_count;
+	wp->w_cursor.lnum = lnum;
+    }
+
+    // Get line & check if empty (cursorpos will show "0-1").  Note that
+    // p will become invalid when getting another buffer line.
+    p = ml_get_buf(wp->w_buffer, lnum, FALSE);
+    empty_line = (*p == NUL);
+
+    // Get the byte value now, in case we need it below. This is more efficient
+    // than making a copy of the line.
+    len = STRLEN(p);
+    if (wp->w_cursor.col > (colnr_T)len)
+    {
+	// Line may have changed since checking the cursor column, or the lnum
+	// was adjusted above.
+	wp->w_cursor.col = (colnr_T)len;
+	wp->w_cursor.coladd = 0;
+	byteval = 0;
+    }
+    else
+	byteval = (*mb_ptr2char)(p + wp->w_cursor.col);
+
+    groupdepth = 0;
+#ifdef FEAT_EVAL
+    evaldepth = 0;
+#endif
+    p = out;
+    curitem = 0;
+    prevchar_isflag = TRUE;
+    prevchar_isitem = FALSE;
+    for (s = usefmt; *s != NUL; )
+    {
+	if (curitem == (int)stl_items_len)
+	{
+	    size_t	new_len = stl_items_len * 3 / 2;
+
+	    stl_item_T *new_items =
+			  vim_realloc(stl_items, sizeof(stl_item_T) * new_len);
+	    if (new_items == NULL)
+		break;
+	    stl_items = new_items;
+
+	    int *new_groupitem =
+			     vim_realloc(stl_groupitem, sizeof(int) * new_len);
+	    if (new_groupitem == NULL)
+		break;
+	    stl_groupitem = new_groupitem;
+
+	    stl_hlrec_T	*new_hlrec = vim_realloc(stl_hltab,
+					  sizeof(stl_hlrec_T) * (new_len + 1));
+	    if (new_hlrec == NULL)
+		break;
+	    stl_hltab = new_hlrec;
+	    new_hlrec = vim_realloc(stl_tabtab,
+					  sizeof(stl_hlrec_T) * (new_len + 1));
+	    if (new_hlrec == NULL)
+		break;
+	    stl_tabtab = new_hlrec;
+
+	    int *new_separator_locs = vim_realloc(stl_separator_locations,
+					    sizeof(int) * new_len);
+	    if (new_separator_locs == NULL)
+		break;
+	    stl_separator_locations = new_separator_locs;;
+
+	    stl_items_len = new_len;
+	}
+
+	if (*s != '%')
+	    prevchar_isflag = prevchar_isitem = FALSE;
+
+	/*
+	 * Handle up to the next '%' or the end.
+	 */
+	while (*s != NUL && *s != '%' && p + 1 < out + outlen)
+	    *p++ = *s++;
+	if (*s == NUL || p + 1 >= out + outlen)
+	    break;
+
+	/*
+	 * Handle one '%' item.
+	 */
+	s++;
+	if (*s == NUL)  // ignore trailing %
+	    break;
+	if (*s == '%')
+	{
+	    if (p + 1 >= out + outlen)
+		break;
+	    *p++ = *s++;
+	    prevchar_isflag = prevchar_isitem = FALSE;
+	    continue;
+	}
+	// STL_SEPARATE: Separation between items, filled with white space.
+	if (*s == STL_SEPARATE)
+	{
+	    s++;
+	    if (groupdepth > 0)
+		continue;
+	    stl_items[curitem].stl_type = Separate;
+	    stl_items[curitem++].stl_start = p;
+	    continue;
+	}
+	if (*s == STL_TRUNCMARK)
+	{
+	    s++;
+	    stl_items[curitem].stl_type = Trunc;
+	    stl_items[curitem++].stl_start = p;
+	    continue;
+	}
+	if (*s == ')')
+	{
+	    s++;
+	    if (groupdepth < 1)
+		continue;
+	    groupdepth--;
+
+	    t = stl_items[stl_groupitem[groupdepth]].stl_start;
+	    *p = NUL;
+	    l = vim_strsize(t);
+	    if (curitem > stl_groupitem[groupdepth] + 1
+		    && stl_items[stl_groupitem[groupdepth]].stl_minwid == 0)
+	    {
+		// remove group if all items are empty and highlight group
+		// doesn't change
+		group_start_userhl = group_end_userhl = 0;
+		for (n = stl_groupitem[groupdepth] - 1; n >= 0; n--)
+		{
+		    if (stl_items[n].stl_type == Highlight)
+		    {
+			group_start_userhl = group_end_userhl =
+						       stl_items[n].stl_minwid;
+			break;
+		    }
+		}
+		for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
+		{
+		    if (stl_items[n].stl_type == Normal)
+			break;
+		    if (stl_items[n].stl_type == Highlight)
+			group_end_userhl = stl_items[n].stl_minwid;
+		}
+		if (n == curitem && group_start_userhl == group_end_userhl)
+		{
+		    // empty group
+		    p = t;
+		    l = 0;
+		    for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
+		    {
+			// do not use the highlighting from the removed group
+			if (stl_items[n].stl_type == Highlight)
+			    stl_items[n].stl_type = Empty;
+			// adjust the start position of TabPage to the next
+			// item position
+			if (stl_items[n].stl_type == TabPage)
+			    stl_items[n].stl_start = p;
+		    }
+		}
+	    }
+	    if (l > stl_items[stl_groupitem[groupdepth]].stl_maxwid)
+	    {
+		// truncate, remove n bytes of text at the start
+		if (has_mbyte)
+		{
+		    // Find the first character that should be included.
+		    n = 0;
+		    while (l >= stl_items[stl_groupitem[groupdepth]].stl_maxwid)
+		    {
+			l -= ptr2cells(t + n);
+			n += (*mb_ptr2len)(t + n);
+		    }
+		}
+		else
+		    n = (long)(p - t) - stl_items[stl_groupitem[groupdepth]]
+							       .stl_maxwid + 1;
+
+		*t = '<';
+		mch_memmove(t + 1, t + n, (size_t)(p - (t + n)));
+		p = p - n + 1;
+
+		// Fill up space left over by half a double-wide char.
+		while (++l < stl_items[stl_groupitem[groupdepth]].stl_minwid)
+		    MB_CHAR2BYTES(fillchar, p);
+
+		// correct the start of the items for the truncation
+		for (l = stl_groupitem[groupdepth] + 1; l < curitem; l++)
+		{
+		    // Minus one for the leading '<' added above.
+		    stl_items[l].stl_start -= n - 1;
+		    if (stl_items[l].stl_start < t)
+			stl_items[l].stl_start = t;
+		}
+	    }
+	    else if (abs(stl_items[stl_groupitem[groupdepth]].stl_minwid) > l)
+	    {
+		// fill
+		n = stl_items[stl_groupitem[groupdepth]].stl_minwid;
+		if (n < 0)
+		{
+		    // fill by appending characters
+		    n = 0 - n;
+		    while (l++ < n && p + 1 < out + outlen)
+			MB_CHAR2BYTES(fillchar, p);
+		}
+		else
+		{
+		    // fill by inserting characters
+		    l = (n - l) * MB_CHAR2LEN(fillchar);
+		    mch_memmove(t + l, t, (size_t)(p - t));
+		    if (p + l >= out + outlen)
+			l = (long)((out + outlen) - p - 1);
+		    p += l;
+		    for (n = stl_groupitem[groupdepth] + 1; n < curitem; n++)
+			stl_items[n].stl_start += l;
+		    for ( ; l > 0; l--)
+			MB_CHAR2BYTES(fillchar, t);
+		}
+	    }
+	    continue;
+	}
+	minwid = 0;
+	maxwid = 9999;
+	zeropad = FALSE;
+	l = 1;
+	if (*s == '0')
+	{
+	    s++;
+	    zeropad = TRUE;
+	}
+	if (*s == '-')
+	{
+	    s++;
+	    l = -1;
+	}
+	if (VIM_ISDIGIT(*s))
+	{
+	    minwid = (int)getdigits(&s);
+	    if (minwid < 0)	// overflow
+		minwid = 0;
+	}
+	if (*s == STL_USER_HL)
+	{
+	    stl_items[curitem].stl_type = Highlight;
+	    stl_items[curitem].stl_start = p;
+	    stl_items[curitem].stl_minwid = minwid > 9 ? 1 : minwid;
+	    s++;
+	    curitem++;
+	    continue;
+	}
+	if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR)
+	{
+	    if (*s == STL_TABCLOSENR)
+	    {
+		if (minwid == 0)
+		{
+		    // %X ends the close label, go back to the previously
+		    // define tab label nr.
+		    for (n = curitem - 1; n >= 0; --n)
+			if (stl_items[n].stl_type == TabPage
+					       && stl_items[n].stl_minwid >= 0)
+			{
+			    minwid = stl_items[n].stl_minwid;
+			    break;
+			}
+		}
+		else
+		    // close nrs are stored as negative values
+		    minwid = - minwid;
+	    }
+	    stl_items[curitem].stl_type = TabPage;
+	    stl_items[curitem].stl_start = p;
+	    stl_items[curitem].stl_minwid = minwid;
+	    s++;
+	    curitem++;
+	    continue;
+	}
+	if (*s == '.')
+	{
+	    s++;
+	    if (VIM_ISDIGIT(*s))
+	    {
+		maxwid = (int)getdigits(&s);
+		if (maxwid <= 0)	// overflow
+		    maxwid = 50;
+	    }
+	}
+	minwid = (minwid > 50 ? 50 : minwid) * l;
+	if (*s == '(')
+	{
+	    stl_groupitem[groupdepth++] = curitem;
+	    stl_items[curitem].stl_type = Group;
+	    stl_items[curitem].stl_start = p;
+	    stl_items[curitem].stl_minwid = minwid;
+	    stl_items[curitem].stl_maxwid = maxwid;
+	    s++;
+	    curitem++;
+	    continue;
+	}
+#ifdef FEAT_EVAL
+	// Denotes end of expanded %{} block
+	if (*s == '}' && evaldepth > 0)
+	{
+	    s++;
+	    evaldepth--;
+	    continue;
+	}
+#endif
+	if (vim_strchr(STL_ALL, *s) == NULL)
+	{
+	    if (*s == NUL)  // can happen with "%0"
+		break;
+	    s++;
+	    continue;
+	}
+	opt = *s++;
+
+	// OK - now for the real work
+	base = 'D';
+	itemisflag = FALSE;
+	fillable = TRUE;
+	num = -1;
+	str = NULL;
+	switch (opt)
+	{
+	case STL_FILEPATH:
+	case STL_FULLPATH:
+	case STL_FILENAME:
+	    fillable = FALSE;	// don't change ' ' to fillchar
+	    if (buf_spname(wp->w_buffer) != NULL)
+		vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1);
+	    else
+	    {
+		t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
+					  : wp->w_buffer->b_fname;
+		home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE);
+	    }
+	    trans_characters(NameBuff, MAXPATHL);
+	    if (opt != STL_FILENAME)
+		str = NameBuff;
+	    else
+		str = gettail(NameBuff);
+	    break;
+
+	case STL_VIM_EXPR: // '{'
+	{
+#ifdef FEAT_EVAL
+	    char_u *block_start = s - 1;
+#endif
+	    int reevaluate = (*s == '%');
+
+	    if (reevaluate)
+		s++;
+	    itemisflag = TRUE;
+	    t = p;
+	    while ((*s != '}' || (reevaluate && s[-1] != '%'))
+					  && *s != NUL && p + 1 < out + outlen)
+		*p++ = *s++;
+	    if (*s != '}')	// missing '}' or out of space
+		break;
+	    s++;
+	    if (reevaluate)
+		p[-1] = 0; // remove the % at the end of %{% expr %}
+	    else
+		*p = 0;
+	    p = t;
+#ifdef FEAT_EVAL
+	    vim_snprintf((char *)buf_tmp, sizeof(buf_tmp),
+							 "%d", curbuf->b_fnum);
+	    set_internal_string_var((char_u *)"g:actual_curbuf", buf_tmp);
+	    vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->w_id);
+	    set_internal_string_var((char_u *)"g:actual_curwin", win_tmp);
+
+	    save_curbuf = curbuf;
+	    save_curwin = curwin;
+	    save_VIsual_active = VIsual_active;
+	    curwin = wp;
+	    curbuf = wp->w_buffer;
+	    // Visual mode is only valid in the current window.
+	    if (curwin != save_curwin)
+		VIsual_active = FALSE;
+
+	    str = eval_to_string_safe(p, use_sandbox, FALSE, FALSE);
+
+	    curwin = save_curwin;
+	    curbuf = save_curbuf;
+	    VIsual_active = save_VIsual_active;
+	    do_unlet((char_u *)"g:actual_curbuf", TRUE);
+	    do_unlet((char_u *)"g:actual_curwin", TRUE);
+
+	    if (str != NULL && *str != 0)
+	    {
+		if (*skipdigits(str) == NUL)
+		{
+		    num = atoi((char *)str);
+		    VIM_CLEAR(str);
+		    itemisflag = FALSE;
+		}
+	    }
+
+	    // If the output of the expression needs to be evaluated
+	    // replace the %{} block with the result of evaluation
+	    if (reevaluate && str != NULL && *str != 0
+		    && strchr((const char *)str, '%') != NULL
+		    && evaldepth < MAX_STL_EVAL_DEPTH)
+	    {
+		size_t parsed_usefmt = (size_t)(block_start - usefmt);
+		size_t str_length = strlen((const char *)str);
+		size_t fmt_length = strlen((const char *)s);
+		size_t new_fmt_len = parsed_usefmt
+						 + str_length + fmt_length + 3;
+		char_u *new_fmt = (char_u *)alloc(new_fmt_len * sizeof(char_u));
+		char_u *new_fmt_p = new_fmt;
+
+		new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt)
+							       + parsed_usefmt;
+		new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length)
+								  + str_length;
+		new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2;
+		new_fmt_p = (char_u *)memcpy(new_fmt_p , s, fmt_length)
+								  + fmt_length;
+		*new_fmt_p = 0;
+		new_fmt_p = NULL;
+
+		if (usefmt != fmt)
+		    vim_free(usefmt);
+		VIM_CLEAR(str);
+		usefmt = new_fmt;
+		s = usefmt + parsed_usefmt;
+		evaldepth++;
+		continue;
+	    }
+#endif
+	    break;
+	}
+	case STL_LINE:
+	    num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
+		  ? 0L : (long)(wp->w_cursor.lnum);
+	    break;
+
+	case STL_NUMLINES:
+	    num = wp->w_buffer->b_ml.ml_line_count;
+	    break;
+
+	case STL_COLUMN:
+	    num = (State & MODE_INSERT) == 0 && empty_line
+					       ? 0 : (int)wp->w_cursor.col + 1;
+	    break;
+
+	case STL_VIRTCOL:
+	case STL_VIRTCOL_ALT:
+	    virtcol = wp->w_virtcol + 1;
+	    // Don't display %V if it's the same as %c.
+	    if (opt == STL_VIRTCOL_ALT
+		    && (virtcol == (colnr_T)((State & MODE_INSERT) == 0
+			       && empty_line ? 0 : (int)wp->w_cursor.col + 1)))
+		break;
+	    num = (long)virtcol;
+	    break;
+
+	case STL_PERCENTAGE:
+	    num = (int)(((long)wp->w_cursor.lnum * 100L) /
+			(long)wp->w_buffer->b_ml.ml_line_count);
+	    break;
+
+	case STL_ALTPERCENT:
+	    str = buf_tmp;
+	    get_rel_pos(wp, str, TMPLEN);
+	    break;
+
+	case STL_SHOWCMD:
+	    if (p_sc && STRCMP(opt_name, p_sloc) == 0)
+		str = showcmd_buf;
+	    break;
+
+	case STL_ARGLISTSTAT:
+	    fillable = FALSE;
+	    buf_tmp[0] = 0;
+	    if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), FALSE))
+		str = buf_tmp;
+	    break;
+
+	case STL_KEYMAP:
+	    fillable = FALSE;
+	    if (get_keymap_str(wp, (char_u *)"<%s>", buf_tmp, TMPLEN))
+		str = buf_tmp;
+	    break;
+	case STL_PAGENUM:
+#if defined(FEAT_PRINTER) || defined(FEAT_GUI_TABLINE)
+	    num = printer_page_num;
+#else
+	    num = 0;
+#endif
+	    break;
+
+	case STL_BUFNO:
+	    num = wp->w_buffer->b_fnum;
+	    break;
+
+	case STL_OFFSET_X:
+	    base = 'X';
+	    // FALLTHROUGH
+	case STL_OFFSET:
+#ifdef FEAT_BYTEOFF
+	    l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
+	    num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0
+		       ? 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line
+				? 0 : (int)wp->w_cursor.col);
+#endif
+	    break;
+
+	case STL_BYTEVAL_X:
+	    base = 'X';
+	    // FALLTHROUGH
+	case STL_BYTEVAL:
+	    num = byteval;
+	    if (num == NL)
+		num = 0;
+	    else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC)
+		num = NL;
+	    break;
+
+	case STL_ROFLAG:
+	case STL_ROFLAG_ALT:
+	    itemisflag = TRUE;
+	    if (wp->w_buffer->b_p_ro)
+		str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"));
+	    break;
+
+	case STL_HELPFLAG:
+	case STL_HELPFLAG_ALT:
+	    itemisflag = TRUE;
+	    if (wp->w_buffer->b_help)
+		str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP"
+							       : _("[Help]"));
+	    break;
+
+	case STL_FILETYPE:
+	    if (*wp->w_buffer->b_p_ft != NUL
+		    && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3)
+	    {
+		vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), "[%s]",
+							wp->w_buffer->b_p_ft);
+		str = buf_tmp;
+	    }
+	    break;
+
+	case STL_FILETYPE_ALT:
+	    itemisflag = TRUE;
+	    if (*wp->w_buffer->b_p_ft != NUL
+		    && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2)
+	    {
+		vim_snprintf((char *)buf_tmp, sizeof(buf_tmp), ",%s",
+							wp->w_buffer->b_p_ft);
+		for (t = buf_tmp; *t != 0; t++)
+		    *t = TOUPPER_LOC(*t);
+		str = buf_tmp;
+	    }
+	    break;
+
+#if defined(FEAT_QUICKFIX)
+	case STL_PREVIEWFLAG:
+	case STL_PREVIEWFLAG_ALT:
+	    itemisflag = TRUE;
+	    if (wp->w_p_pvw)
+		str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV"
+							    : _("[Preview]"));
+	    break;
+
+	case STL_QUICKFIX:
+	    if (bt_quickfix(wp->w_buffer))
+		str = (char_u *)(wp->w_llist_ref
+			    ? _(msg_loclist)
+			    : _(msg_qflist));
+	    break;
+#endif
+
+	case STL_MODIFIED:
+	case STL_MODIFIED_ALT:
+	    itemisflag = TRUE;
+	    switch ((opt == STL_MODIFIED_ALT)
+		    + bufIsChanged(wp->w_buffer) * 2
+		    + (!wp->w_buffer->b_p_ma) * 4)
+	    {
+		case 2: str = (char_u *)"[+]"; break;
+		case 3: str = (char_u *)",+"; break;
+		case 4: str = (char_u *)"[-]"; break;
+		case 5: str = (char_u *)",-"; break;
+		case 6: str = (char_u *)"[+-]"; break;
+		case 7: str = (char_u *)",+-"; break;
+	    }
+	    break;
+
+	case STL_HIGHLIGHT:
+	    t = s;
+	    while (*s != '#' && *s != NUL)
+		++s;
+	    if (*s == '#')
+	    {
+		stl_items[curitem].stl_type = Highlight;
+		stl_items[curitem].stl_start = p;
+		stl_items[curitem].stl_minwid = -syn_namen2id(t, (int)(s - t));
+		curitem++;
+	    }
+	    if (*s != NUL)
+		++s;
+	    continue;
+	}
+
+	stl_items[curitem].stl_start = p;
+	stl_items[curitem].stl_type = Normal;
+	if (str != NULL && *str)
+	{
+	    t = str;
+	    if (itemisflag)
+	    {
+		if ((t[0] && t[1])
+			&& ((!prevchar_isitem && *t == ',')
+			      || (prevchar_isflag && *t == ' ')))
+		    t++;
+		prevchar_isflag = TRUE;
+	    }
+	    l = vim_strsize(t);
+	    if (l > 0)
+		prevchar_isitem = TRUE;
+	    if (l > maxwid)
+	    {
+		while (l >= maxwid)
+		    if (has_mbyte)
+		    {
+			l -= ptr2cells(t);
+			t += (*mb_ptr2len)(t);
+		    }
+		    else
+			l -= byte2cells(*t++);
+		if (p + 1 >= out + outlen)
+		    break;
+		*p++ = '<';
+	    }
+	    if (minwid > 0)
+	    {
+		for (; l < minwid && p + 1 < out + outlen; l++)
+		{
+		    // Don't put a "-" in front of a digit.
+		    if (l + 1 == minwid && fillchar == '-' && VIM_ISDIGIT(*t))
+			*p++ = ' ';
+		    else
+			MB_CHAR2BYTES(fillchar, p);
+		}
+		minwid = 0;
+	    }
+	    else
+		minwid *= -1;
+	    for (; *t && p + 1 < out + outlen; t++)
+	    {
+		// Change a space by fillchar, unless fillchar is '-' and a
+		// digit follows.
+		if (fillable && *t == ' '
+				&& (!VIM_ISDIGIT(*(t + 1)) || fillchar != '-'))
+		    MB_CHAR2BYTES(fillchar, p);
+		else
+		    *p++ = *t;
+	    }
+	    for (; l < minwid && p + 1 < out + outlen; l++)
+		MB_CHAR2BYTES(fillchar, p);
+	}
+	else if (num >= 0)
+	{
+	    int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
+	    char_u nstr[20];
+
+	    if (p + 20 >= out + outlen)
+		break;		// not sufficient space
+	    prevchar_isitem = TRUE;
+	    t = nstr;
+	    if (opt == STL_VIRTCOL_ALT)
+	    {
+		*t++ = '-';
+		minwid--;
+	    }
+	    *t++ = '%';
+	    if (zeropad)
+		*t++ = '0';
+	    *t++ = '*';
+	    *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd');
+	    *t = 0;
+
+	    for (n = num, l = 1; n >= nbase; n /= nbase)
+		l++;
+	    if (opt == STL_VIRTCOL_ALT)
+		l++;
+	    if (l > maxwid)
+	    {
+		l += 2;
+		n = l - maxwid;
+		while (l-- > maxwid)
+		    num /= nbase;
+		*t++ = '>';
+		*t++ = '%';
+		*t = t[-3];
+		*++t = 0;
+		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+								   0, num, n);
+	    }
+	    else
+		vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+								 minwid, num);
+	    p += STRLEN(p);
+	}
+	else
+	    stl_items[curitem].stl_type = Empty;
+
+	if (num >= 0 || (!itemisflag && str != NULL && *str != NUL))
+	    prevchar_isflag = FALSE;	    // Item not NULL, but not a flag
+					    //
+	if (opt == STL_VIM_EXPR)
+	    vim_free(str);
+	curitem++;
+    }
+    *p = NUL;
+    itemcnt = curitem;
+
+#ifdef FEAT_EVAL
+    if (usefmt != fmt)
+	vim_free(usefmt);
+#endif
+
+    width = vim_strsize(out);
+    if (maxwidth > 0 && width > maxwidth)
+    {
+	// Result is too long, must truncate somewhere.
+	l = 0;
+	if (itemcnt == 0)
+	    s = out;
+	else
+	{
+	    for ( ; l < itemcnt; l++)
+		if (stl_items[l].stl_type == Trunc)
+		{
+		    // Truncate at %< item.
+		    s = stl_items[l].stl_start;
+		    break;
+		}
+	    if (l == itemcnt)
+	    {
+		// No %< item, truncate first item.
+		s = stl_items[0].stl_start;
+		l = 0;
+	    }
+	}
+
+	if (width - vim_strsize(s) >= maxwidth)
+	{
+	    // Truncation mark is beyond max length
+	    if (has_mbyte)
+	    {
+		s = out;
+		width = 0;
+		for (;;)
+		{
+		    width += ptr2cells(s);
+		    if (width >= maxwidth)
+			break;
+		    s += (*mb_ptr2len)(s);
+		}
+		// Fill up for half a double-wide character.
+		while (++width < maxwidth)
+		    MB_CHAR2BYTES(fillchar, s);
+	    }
+	    else
+		s = out + maxwidth - 1;
+	    for (l = 0; l < itemcnt; l++)
+		if (stl_items[l].stl_start > s)
+		    break;
+	    itemcnt = l;
+	    *s++ = '>';
+	    *s = 0;
+	}
+	else
+	{
+	    if (has_mbyte)
+	    {
+		n = 0;
+		while (width >= maxwidth)
+		{
+		    width -= ptr2cells(s + n);
+		    n += (*mb_ptr2len)(s + n);
+		}
+	    }
+	    else
+		n = width - maxwidth + 1;
+	    p = s + n;
+	    STRMOVE(s + 1, p);
+	    *s = '<';
+
+	    // Fill up for half a double-wide character.
+	    while (++width < maxwidth)
+	    {
+		s = s + STRLEN(s);
+		MB_CHAR2BYTES(fillchar, s);
+		*s = NUL;
+	    }
+
+	    --n;	// count the '<'
+	    for (; l < itemcnt; l++)
+	    {
+		if (stl_items[l].stl_start - n >= s)
+		    stl_items[l].stl_start -= n;
+		else
+		    stl_items[l].stl_start = s;
+	    }
+	}
+	width = maxwidth;
+    }
+    else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < outlen)
+    {
+	// Find how many separators there are, which we will use when
+	// figuring out how many groups there are.
+	int num_separators = 0;
+
+	for (l = 0; l < itemcnt; l++)
+	{
+	    if (stl_items[l].stl_type == Separate)
+	    {
+		// Create an array of the start location for each separator
+		// mark.
+		stl_separator_locations[num_separators] = l;
+		num_separators++;
+	    }
+	}
+
+	// If we have separated groups, then we deal with it now
+	if (num_separators)
+	{
+	    int standard_spaces;
+	    int final_spaces;
+
+	    standard_spaces = (maxwidth - width) / num_separators;
+	    final_spaces = (maxwidth - width) -
+					standard_spaces * (num_separators - 1);
+	    for (l = 0; l < num_separators; l++)
+	    {
+		int dislocation = (l == (num_separators - 1)) ?
+					final_spaces : standard_spaces;
+		dislocation *= MB_CHAR2LEN(fillchar);
+		char_u *start = stl_items[stl_separator_locations[l]].stl_start;
+		char_u *seploc = start + dislocation;
+		STRMOVE(seploc, start);
+		for (s = start; s < seploc;)
+		    MB_CHAR2BYTES(fillchar, s);
+
+		for (int i = stl_separator_locations[l] + 1; i < itemcnt; i++)
+		    stl_items[i].stl_start += dislocation;
+	    }
+
+	    width = maxwidth;
+	}
+    }
+
+    // Store the info about highlighting.
+    if (hltab != NULL)
+    {
+	*hltab = stl_hltab;
+	sp = stl_hltab;
+	for (l = 0; l < itemcnt; l++)
+	{
+	    if (stl_items[l].stl_type == Highlight)
+	    {
+		sp->start = stl_items[l].stl_start;
+		sp->userhl = stl_items[l].stl_minwid;
+		sp++;
+	    }
+	}
+	sp->start = NULL;
+	sp->userhl = 0;
+    }
+
+    // Store the info about tab pages labels.
+    if (tabtab != NULL)
+    {
+	*tabtab = stl_tabtab;
+	sp = stl_tabtab;
+	for (l = 0; l < itemcnt; l++)
+	{
+	    if (stl_items[l].stl_type == TabPage)
+	    {
+		sp->start = stl_items[l].stl_start;
+		sp->userhl = stl_items[l].stl_minwid;
+		sp++;
+	    }
+	}
+	sp->start = NULL;
+	sp->userhl = 0;
+    }
+
+    redraw_not_allowed = save_redraw_not_allowed;
+
+    // A user function may reset KeyTyped, restore it.
+    KeyTyped = save_KeyTyped;
+
+    // Check for an error.  If there is one the display will be messed up and
+    // might loop redrawing.  Avoid that by making the corresponding option
+    // empty.
+    // TODO: find out why using called_emsg_before makes tests fail, does it
+    // matter?
+    // if (called_emsg > called_emsg_before)
+    if (did_emsg > did_emsg_before)
+	set_string_option_direct(opt_name, -1, (char_u *)"",
+					      OPT_FREE | opt_scope, SID_ERROR);
+
+    return width;
+}
+#endif // FEAT_STL_OPT
+
+/*
+ * Get relative cursor position in window into "buf[buflen]", in the localized
+ * percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate.
+ */
+    void
+get_rel_pos(
+    win_T	*wp,
+    char_u	*buf,
+    int		buflen)
+{
+    long	above; // number of lines above window
+    long	below; // number of lines below window
+
+    if (buflen < 3) // need at least 3 chars for writing
+	return;
+    above = wp->w_topline - 1;
+#ifdef FEAT_DIFF
+    above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill;
+    if (wp->w_topline == 1 && wp->w_topfill >= 1)
+	above = 0;  // All buffer lines are displayed and there is an
+		    // indication of filler lines, that can be considered
+		    // seeing all lines.
+#endif
+    below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1;
+    if (below <= 0)
+	vim_strncpy(buf, (char_u *)(above == 0 ? _("All") : _("Bot")),
+		    (size_t)(buflen - 1));
+    else if (above <= 0)
+	vim_strncpy(buf, (char_u *)_("Top"), (size_t)(buflen - 1));
+    else
+    {
+	int perc = (above > 1000000L)
+			?  (int)(above / ((above + below) / 100L))
+			:  (int)(above * 100L / (above + below));
+
+	char *p = (char *)buf;
+	size_t l = buflen;
+	if (perc < 10)
+	{
+	    // prepend one space
+	    buf[0] = ' ';
+	    ++p;
+	    --l;
+	}
+	// localized percentage value
+	vim_snprintf(p, l, _("%d%%"), perc);
+    }
+}
+
+/*
+ * Append (file 2 of 8) to "buf[buflen]", if editing more than one file.
+ * Return TRUE if it was appended.
+ */
+    static int
+append_arg_number(
+    win_T	*wp,
+    char_u	*buf,
+    int		buflen,
+    int		add_file)	// Add "file" before the arg number
+{
+    if (ARGCOUNT <= 1)		// nothing to do
+	return FALSE;
+
+    char *msg;
+    switch ((wp->w_arg_idx_invalid ? 1 : 0) + (add_file ? 2 : 0))
+    {
+	case 0: msg = _(" (%d of %d)"); break;
+	case 1: msg = _(" ((%d) of %d)"); break;
+	case 2: msg = _(" (file %d of %d)"); break;
+	case 3: msg = _(" (file (%d) of %d)"); break;
+    }
+
+    char_u *p = buf + STRLEN(buf);	// go to the end of the buffer
+    vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), msg,
+						  wp->w_arg_idx + 1, ARGCOUNT);
+    return TRUE;
+}
+
+/*
+ * If fname is not a full path, make it a full path.
+ * Returns pointer to allocated memory (NULL for failure).
+ */
+    char_u  *
+fix_fname(char_u  *fname)
+{
+    /*
+     * Force expanding the path always for Unix, because symbolic links may
+     * mess up the full path name, even though it starts with a '/'.
+     * Also expand when there is ".." in the file name, try to remove it,
+     * because "c:/src/../README" is equal to "c:/README".
+     * Similarly "c:/src//file" is equal to "c:/src/file".
+     * For MS-Windows also expand names like "longna~1" to "longname".
+     */
+#ifdef UNIX
+    return FullName_save(fname, TRUE);
+#else
+    if (!vim_isAbsName(fname)
+	    || strstr((char *)fname, "..") != NULL
+	    || strstr((char *)fname, "//") != NULL
+# ifdef BACKSLASH_IN_FILENAME
+	    || strstr((char *)fname, "\\\\") != NULL
+# endif
+# if defined(MSWIN)
+	    || vim_strchr(fname, '~') != NULL
+# endif
+	    )
+	return FullName_save(fname, FALSE);
+
+    fname = vim_strsave(fname);
+
+# ifdef USE_FNAME_CASE
+    if (fname != NULL)
+	fname_case(fname, 0);	// set correct case for file name
+# endif
+
+    return fname;
+#endif
+}
+
+/*
+ * Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL.
+ * "*ffname" becomes a pointer to allocated memory (or NULL).
+ * When resolving a link both "*sfname" and "*ffname" will point to the same
+ * allocated memory.
+ * The "*ffname" and "*sfname" pointer values on call will not be freed.
+ * Note that the resulting "*ffname" pointer should be considered not allocated.
+ */
+    void
+fname_expand(
+    buf_T	*buf UNUSED,
+    char_u	**ffname,
+    char_u	**sfname)
+{
+    if (*ffname == NULL)	    // no file name given, nothing to do
+	return;
+    if (*sfname == NULL)	    // no short file name given, use ffname
+	*sfname = *ffname;
+    *ffname = fix_fname(*ffname);   // expand to full path
+
+#ifdef FEAT_SHORTCUT
+    if (!buf->b_p_bin)
+    {
+	char_u  *rfname;
+
+	// If the file name is a shortcut file, use the file it links to.
+	rfname = mch_resolve_path(*ffname, FALSE);
+	if (rfname != NULL)
+	{
+	    vim_free(*ffname);
+	    *ffname = rfname;
+	    *sfname = rfname;
+	}
+    }
+#endif
+}
+
+/*
+ * Open a window for a number of buffers.
+ */
+    void
+ex_buffer_all(exarg_T *eap)
+{
+    buf_T	*buf;
+    win_T	*wp, *wpnext;
+    int		split_ret = OK;
+    int		p_ea_save;
+    int		open_wins = 0;
+    int		r;
+    int		count;		// Maximum number of windows to open.
+    int		all;		// When TRUE also load inactive buffers.
+    int		had_tab = cmdmod.cmod_tab;
+    tabpage_T	*tpnext;
+
+    if (eap->addr_count == 0)	// make as many windows as possible
+	count = 9999;
+    else
+	count = eap->line2;	// make as many windows as specified
+    if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide)
+	all = FALSE;
+    else
+	all = TRUE;
+
+    // Stop Visual mode, the cursor and "VIsual" may very well be invalid after
+    // switching to another buffer.
+    reset_VIsual_and_resel();
+
+    setpcmark();
+
+#ifdef FEAT_GUI
+    need_mouse_correct = TRUE;
+#endif
+
+    /*
+     * Close superfluous windows (two windows for the same buffer).
+     * Also close windows that are not full-width.
+     */
+    if (had_tab > 0)
+	goto_tabpage_tp(first_tabpage, TRUE, TRUE);
+    for (;;)
+    {
+	tpnext = curtab->tp_next;
+	for (wp = firstwin; wp != NULL; wp = wpnext)
+	{
+	    wpnext = wp->w_next;
+	    if ((wp->w_buffer->b_nwindows > 1
+			|| ((cmdmod.cmod_split & WSP_VERT)
+			    ? wp->w_height + wp->w_status_height < Rows - p_ch
+							     - tabline_height()
+			    : wp->w_width != Columns)
+			|| (had_tab > 0 && wp != firstwin))
+		    && !ONE_WINDOW
+		    && !(wp->w_closing || wp->w_buffer->b_locked > 0)
+		    && !win_unlisted(wp))
+	    {
+		if (win_close(wp, FALSE) == FAIL)
+		    break;
+		// Just in case an autocommand does something strange with
+		// windows: start all over...
+		wpnext = firstwin;
+		tpnext = first_tabpage;
+		open_wins = 0;
+	    }
+	    else
+		++open_wins;
+	}
+
+	// Without the ":tab" modifier only do the current tab page.
+	if (had_tab == 0 || tpnext == NULL)
+	    break;
+	goto_tabpage_tp(tpnext, TRUE, TRUE);
+    }
+
+    /*
+     * Go through the buffer list.  When a buffer doesn't have a window yet,
+     * open one.  Otherwise move the window to the right position.
+     * Watch out for autocommands that delete buffers or windows!
+     */
+    // Don't execute Win/Buf Enter/Leave autocommands here.
+    ++autocmd_no_enter;
+    win_enter(lastwin, FALSE);
+    ++autocmd_no_leave;
+    for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next)
+    {
+	// Check if this buffer needs a window
+	if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl)
+	    continue;
+
+	if (had_tab != 0)
+	{
+	    // With the ":tab" modifier don't move the window.
+	    if (buf->b_nwindows > 0)
+		wp = lastwin;	    // buffer has a window, skip it
+	    else
+		wp = NULL;
+	}
+	else
+	{
+	    // Check if this buffer already has a window
+	    FOR_ALL_WINDOWS(wp)
+		if (wp->w_buffer == buf)
+		    break;
+	    // If the buffer already has a window, move it
+	    if (wp != NULL)
+		win_move_after(wp, curwin);
+	}
+
+	if (wp == NULL && split_ret == OK)
+	{
+	    bufref_T	bufref;
+
+	    set_bufref(&bufref, buf);
+
+	    // Split the window and put the buffer in it
+	    p_ea_save = p_ea;
+	    p_ea = TRUE;		// use space from all windows
+	    split_ret = win_split(0, WSP_ROOM | WSP_BELOW);
+	    ++open_wins;
+	    p_ea = p_ea_save;
+	    if (split_ret == FAIL)
+		continue;
+
+	    // Open the buffer in this window.
+	    swap_exists_action = SEA_DIALOG;
+	    set_curbuf(buf, DOBUF_GOTO);
+	    if (!bufref_valid(&bufref))
+	    {
+		// autocommands deleted the buffer!!!
+		swap_exists_action = SEA_NONE;
+		break;
+	    }
+	    if (swap_exists_action == SEA_QUIT)
+	    {
+#if defined(FEAT_EVAL)
+		cleanup_T   cs;
+
+		// Reset the error/interrupt/exception state here so that
+		// aborting() returns FALSE when closing a window.
+		enter_cleanup(&cs);
+#endif
+
+		// User selected Quit at ATTENTION prompt; close this window.
+		win_close(curwin, TRUE);
+		--open_wins;
+		swap_exists_action = SEA_NONE;
+		swap_exists_did_quit = TRUE;
+
+#if defined(FEAT_EVAL)
+		// Restore the error/interrupt/exception state if not
+		// discarded by a new aborting error, interrupt, or uncaught
+		// exception.
+		leave_cleanup(&cs);
+#endif
+	    }
+	    else
+		handle_swap_exists(NULL);
+	}
+
+	ui_breakcheck();
+	if (got_int)
+	{
+	    (void)vgetc();	// only break the file loading, not the rest
+	    break;
+	}
+#ifdef FEAT_EVAL
+	// Autocommands deleted the buffer or aborted script processing!!!
+	if (aborting())
+	    break;
+#endif
+	// When ":tab" was used open a new tab for a new window repeatedly.
+	if (had_tab > 0 && tabpage_index(NULL) <= p_tpm)
+	    cmdmod.cmod_tab = 9999;
+    }
+    --autocmd_no_enter;
+    win_enter(firstwin, FALSE);		// back to first window
+    --autocmd_no_leave;
+
+    /*
+     * Close superfluous windows.
+     */
+    for (wp = lastwin; open_wins > count; )
+    {
+	r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer)
+				     || autowrite(wp->w_buffer, FALSE) == OK);
+	if (!win_valid(wp))
+	{
+	    // BufWrite Autocommands made the window invalid, start over
+	    wp = lastwin;
+	}
+	else if (r)
+	{
+	    win_close(wp, !buf_hide(wp->w_buffer));
+	    --open_wins;
+	    wp = lastwin;
+	}
+	else
+	{
+	    wp = wp->w_prev;
+	    if (wp == NULL)
+		break;
+	}
+    }
+}
+
+
+static int  chk_modeline(linenr_T, int);
+
+/*
+ * do_modelines() - process mode lines for the current file
+ *
+ * "flags" can be:
+ * OPT_WINONLY	    only set options local to window
+ * OPT_NOWIN	    don't set options local to window
+ *
+ * Returns immediately if the "ml" option isn't set.
+ */
+    void
+do_modelines(int flags)
+{
+    linenr_T	lnum;
+    int		nmlines;
+    static int	entered = 0;
+
+    if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0)
+	return;
+
+    // Disallow recursive entry here.  Can happen when executing a modeline
+    // triggers an autocommand, which reloads modelines with a ":do".
+    if (entered)
+	return;
+
+    ++entered;
+    for (lnum = 1; curbuf->b_p_ml && lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines;
+								       ++lnum)
+	if (chk_modeline(lnum, flags) == FAIL)
+	    nmlines = 0;
+
+    for (lnum = curbuf->b_ml.ml_line_count; curbuf->b_p_ml && lnum > 0 && lnum > nmlines
+		       && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum)
+	if (chk_modeline(lnum, flags) == FAIL)
+	    nmlines = 0;
+    --entered;
+}
+
+#include "version.h"		// for version number
+
+/*
+ * chk_modeline() - check a single line for a mode string
+ * Return FAIL if an error encountered.
+ */
+    static int
+chk_modeline(
+    linenr_T	lnum,
+    int		flags)		// Same as for do_modelines().
+{
+    char_u	*s;
+    char_u	*e;
+    char_u	*linecopy;		// local copy of any modeline found
+    int		prev;
+    int		vers;
+    int		end;
+    int		retval = OK;
+    sctx_T	save_current_sctx;
+    ESTACK_CHECK_DECLARATION;
+
+    prev = -1;
+    for (s = ml_get(lnum); *s != NUL; ++s)
+    {
+	if (prev == -1 || vim_isspace(prev))
+	{
+	    if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0)
+		    || STRNCMP(s, "vi:", (size_t)3) == 0)
+		break;
+	    // Accept both "vim" and "Vim".
+	    if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm')
+	    {
+		if (s[3] == '<' || s[3] == '=' || s[3] == '>')
+		    e = s + 4;
+		else
+		    e = s + 3;
+		vers = getdigits(&e);
+		if (*e == ':'
+			&& (s[0] != 'V'
+				  || STRNCMP(skipwhite(e + 1), "set", 3) == 0)
+			&& (s[3] == ':'
+			    || (VIM_VERSION_100 >= vers && isdigit(s[3]))
+			    || (VIM_VERSION_100 < vers && s[3] == '<')
+			    || (VIM_VERSION_100 > vers && s[3] == '>')
+			    || (VIM_VERSION_100 == vers && s[3] == '=')))
+		    break;
+	    }
+	}
+	prev = *s;
+    }
+
+    if (*s)
+    {
+	do				// skip over "ex:", "vi:" or "vim:"
+	    ++s;
+	while (s[-1] != ':');
+
+	s = linecopy = vim_strsave(s);	// copy the line, it will change
+	if (linecopy == NULL)
+	    return FAIL;
+
+	// prepare for emsg()
+	estack_push(ETYPE_MODELINE, (char_u *)"modelines", lnum);
+	ESTACK_CHECK_SETUP;
+
+	end = FALSE;
+	while (end == FALSE)
+	{
+	    s = skipwhite(s);
+	    if (*s == NUL)
+		break;
+
+	    /*
+	     * Find end of set command: ':' or end of line.
+	     * Skip over "\:", replacing it with ":".
+	     */
+	    for (e = s; *e != ':' && *e != NUL; ++e)
+		if (e[0] == '\\' && e[1] == ':')
+		    STRMOVE(e, e + 1);
+	    if (*e == NUL)
+		end = TRUE;
+
+	    /*
+	     * If there is a "set" command, require a terminating ':' and
+	     * ignore the stuff after the ':'.
+	     * "vi:set opt opt opt: foo" -- foo not interpreted
+	     * "vi:opt opt opt: foo" -- foo interpreted
+	     * Accept "se" for compatibility with Elvis.
+	     */
+	    if (STRNCMP(s, "set ", (size_t)4) == 0
+		    || STRNCMP(s, "se ", (size_t)3) == 0)
+	    {
+		if (*e != ':')		// no terminating ':'?
+		    break;
+		end = TRUE;
+		s = vim_strchr(s, ' ') + 1;
+	    }
+	    *e = NUL;			// truncate the set command
+
+	    if (*s != NUL)		// skip over an empty "::"
+	    {
+		int secure_save = secure;
+
+		save_current_sctx = current_sctx;
+		current_sctx.sc_version = 1;
+#ifdef FEAT_EVAL
+		current_sctx.sc_sid = SID_MODELINE;
+		current_sctx.sc_seq = 0;
+		current_sctx.sc_lnum = lnum;
+#endif
+
+		// Make sure no risky things are executed as a side effect.
+		secure = 1;
+
+		retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags);
+
+		secure = secure_save;
+		current_sctx = save_current_sctx;
+		if (retval == FAIL)		// stop if error found
+		    break;
+	    }
+	    s = e + 1;			// advance to next part
+	}
+
+	ESTACK_CHECK_NOW;
+	estack_pop();
+	vim_free(linecopy);
+    }
+    return retval;
+}
+
+/*
+ * Return TRUE if "buf" is a normal buffer, 'buftype' is empty.
+ */
+    int
+bt_normal(buf_T *buf)
+{
+    return buf != NULL && buf->b_p_bt[0] == NUL;
+}
+
+/*
+ * Return TRUE if "buf" is the quickfix buffer.
+ */
+    int
+bt_quickfix(buf_T *buf UNUSED)
+{
+#ifdef FEAT_QUICKFIX
+    return buf != NULL && buf->b_p_bt[0] == 'q';
+#else
+    return FALSE;
+#endif
+}
+
+/*
+ * Return TRUE if "buf" is a terminal buffer.
+ */
+    int
+bt_terminal(buf_T *buf UNUSED)
+{
+#if defined(FEAT_TERMINAL)
+    return buf != NULL && buf->b_p_bt[0] == 't';
+#else
+    return FALSE;
+#endif
+}
+
+/*
+ * Return TRUE if "buf" is a help buffer.
+ */
+    int
+bt_help(buf_T *buf)
+{
+    return buf != NULL && buf->b_help;
+}
+
+/*
+ * Return TRUE if "buf" is a prompt buffer.
+ */
+    int
+bt_prompt(buf_T *buf)
+{
+    return buf != NULL && buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'r';
+}
+
+#if defined(FEAT_PROP_POPUP) || defined(PROTO)
+/*
+ * Return TRUE if "buf" is a buffer for a popup window.
+ */
+    int
+bt_popup(buf_T *buf)
+{
+    return buf != NULL && buf->b_p_bt != NULL
+	&& buf->b_p_bt[0] == 'p' && buf->b_p_bt[1] == 'o';
+}
+#endif
+
+/*
+ * Return TRUE if "buf" is a "nofile", "acwrite", "terminal" or "prompt"
+ * buffer.  This means the buffer name may not be a file name, at least not for
+ * writing the buffer.
+ */
+    int
+bt_nofilename(buf_T *buf)
+{
+    return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
+	    || buf->b_p_bt[0] == 'a'
+	    || buf->b_p_bt[0] == 't'
+	    || buf->b_p_bt[0] == 'p');
+}
+
+/*
+ * Return TRUE if "buf" is a "nofile", "quickfix", "terminal" or "prompt"
+ * buffer.  This means the buffer is not to be read from a file.
+ */
+    static int
+bt_nofileread(buf_T *buf)
+{
+    return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
+	    || buf->b_p_bt[0] == 't'
+	    || buf->b_p_bt[0] == 'q'
+	    || buf->b_p_bt[0] == 'p');
+}
+
+#if defined(FEAT_QUICKFIX) || defined(PROTO)
+/*
+ * Return TRUE if "buf" has 'buftype' set to "nofile".
+ */
+    int
+bt_nofile(buf_T *buf)
+{
+    return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f';
+}
+#endif
+
+/*
+ * Return TRUE if "buf" is a "nowrite", "nofile", "terminal", "prompt", or
+ * "popup" buffer.
+ */
+    int
+bt_dontwrite(buf_T *buf)
+{
+    return buf != NULL && (buf->b_p_bt[0] == 'n'
+		 || buf->b_p_bt[0] == 't'
+		 || buf->b_p_bt[0] == 'p');
+}
+
+    int
+bt_dontwrite_msg(buf_T *buf)
+{
+    if (bt_dontwrite(buf))
+    {
+	emsg(_(e_cannot_write_buftype_option_is_set));
+	return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide"
+ * and 'bufhidden'.
+ */
+    int
+buf_hide(buf_T *buf)
+{
+    // 'bufhidden' overrules 'hidden' and ":hide", check it first
+    switch (buf->b_p_bh[0])
+    {
+	case 'u':		    // "unload"
+	case 'w':		    // "wipe"
+	case 'd': return FALSE;	    // "delete"
+	case 'h': return TRUE;	    // "hide"
+    }
+    return (p_hid || (cmdmod.cmod_flags & CMOD_HIDE));
+}
+
+/*
+ * Return special buffer name.
+ * Returns NULL when the buffer has a normal file name.
+ */
+    char_u *
+buf_spname(buf_T *buf)
+{
+#if defined(FEAT_QUICKFIX)
+    if (bt_quickfix(buf))
+    {
+	/*
+	 * Differentiate between the quickfix and location list buffers using
+	 * the buffer number stored in the global quickfix stack.
+	 */
+	if (buf->b_fnum == qf_stack_get_bufnr())
+	    return (char_u *)_(msg_qflist);
+	else
+	    return (char_u *)_(msg_loclist);
+    }
+#endif
+
+    // There is no _file_ when 'buftype' is "nofile", b_sfname
+    // contains the name as specified by the user.
+    if (bt_nofilename(buf))
+    {
+#ifdef FEAT_TERMINAL
+	if (buf->b_term != NULL)
+	    return term_get_status_text(buf->b_term);
+#endif
+	if (buf->b_fname != NULL)
+	    return buf->b_fname;
+#ifdef FEAT_JOB_CHANNEL
+	if (bt_prompt(buf))
+	    return (char_u *)_("[Prompt]");
+#endif
+#ifdef FEAT_PROP_POPUP
+	if (bt_popup(buf))
+	    return (char_u *)_("[Popup]");
+#endif
+	return (char_u *)_("[Scratch]");
+    }
+
+    if (buf->b_fname == NULL)
+	return buf_get_fname(buf);
+    return NULL;
+}
+
+/*
+ * Get "buf->b_fname", use "[No Name]" if it is NULL.
+ */
+    char_u *
+buf_get_fname(buf_T *buf)
+{
+    if (buf->b_fname == NULL)
+	return (char_u *)_("[No Name]");
+    return buf->b_fname;
+}
+
+/*
+ * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
+ */
+    void
+set_buflisted(int on)
+{
+    if (on == curbuf->b_p_bl)
+	return;
+
+    curbuf->b_p_bl = on;
+    if (on)
+	apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf);
+    else
+	apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf);
+}
+
+/*
+ * Read the file for "buf" again and check if the contents changed.
+ * Return TRUE if it changed or this could not be checked.
+ */
+    int
+buf_contents_changed(buf_T *buf)
+{
+    buf_T	*newbuf;
+    int		differ = TRUE;
+    linenr_T	lnum;
+    aco_save_T	aco;
+    exarg_T	ea;
+
+    // Allocate a buffer without putting it in the buffer list.
+    newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+    if (newbuf == NULL)
+	return TRUE;
+
+    // Force the 'fileencoding' and 'fileformat' to be equal.
+    if (prep_exarg(&ea, buf) == FAIL)
+    {
+	wipe_buffer(newbuf, FALSE);
+	return TRUE;
+    }
+
+    // Set curwin/curbuf to buf and save a few things.
+    aucmd_prepbuf(&aco, newbuf);
+    if (curbuf != newbuf)
+    {
+	// Failed to find a window for "newbuf".
+	wipe_buffer(newbuf, FALSE);
+	return TRUE;
+    }
+
+    if (ml_open(curbuf) == OK
+	    && readfile(buf->b_ffname, buf->b_fname,
+				  (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM,
+					    &ea, READ_NEW | READ_DUMMY) == OK)
+    {
+	// compare the two files line by line
+	if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count)
+	{
+	    differ = FALSE;
+	    for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum)
+		if (STRCMP(ml_get_buf(buf, lnum, FALSE), ml_get(lnum)) != 0)
+		{
+		    differ = TRUE;
+		    break;
+		}
+	}
+    }
+    vim_free(ea.cmd);
+
+    // restore curwin/curbuf and a few other things
+    aucmd_restbuf(&aco);
+
+    if (curbuf != newbuf)	// safety check
+	wipe_buffer(newbuf, FALSE);
+
+    return differ;
+}
+
+/*
+ * Wipe out a buffer and decrement the last buffer number if it was used for
+ * this buffer.  Call this to wipe out a temp buffer that does not contain any
+ * marks.
+ */
+    void
+wipe_buffer(
+    buf_T	*buf,
+    int		aucmd)	    // When TRUE trigger autocommands.
+{
+    if (buf->b_fnum == top_file_num - 1)
+	--top_file_num;
+
+    if (!aucmd)		    // Don't trigger BufDelete autocommands here.
+	block_autocmds();
+
+    close_buffer(NULL, buf, DOBUF_WIPE, FALSE, TRUE);
+
+    if (!aucmd)
+	unblock_autocmds();
+}