/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Read the file "credits.txt" for a list of people who contributed.
 * Read the file "uganda.txt" for copying and usage conditions.
 */

/*
 * 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: b_neverloaded == TRUE, 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"
#include "globals.h"
#include "proto.h"
#include "param.h"
#ifdef KANJI
#include "kanji.h"
#endif
#if defined(USE_EXFILE) && defined(NT)
# undef remove
#endif

static void		enter_buffer __ARGS((BUF *));
static BUF		*buflist_findname __ARGS((char_u *));
static BUF		*buflist_findnr __ARGS((int));
static void		buflist_setlnum __ARGS((BUF *, linenr_t));
static linenr_t buflist_findlnum __ARGS((BUF *));

/*
 * Open current buffer, that is: open the memfile and read the file into memory
 * return FAIL for failure, OK otherwise
 */
 	int
open_buffer()
{
	if (readonlymode && curbuf->b_filename != NULL)
		curbuf->b_p_ro = TRUE;
	if (ml_open() == FAIL)
	{
		/*
		 * There MUST be a memfile, otherwise we can't do anything
		 * If we can't create one for the current buffer, take another buffer
		 */
		close_buffer(curbuf, FALSE, FALSE);
		for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next)
			if (curbuf->b_ml.ml_mfp != NULL)
				break;
		/*
		 * if there is no memfile at all, exit
		 * This is OK, since there are no changes to loose.
		 */
		if (curbuf == NULL)
		{
			EMSG("Cannot allocate buffer, exiting...");
			getout(2);
		}
		EMSG("Cannot allocate buffer, using other one...");
		enter_buffer(curbuf);
		return FAIL;
	}
	if (curbuf->b_filename != NULL)
	{
#ifdef FEXRC
		if (p_fexrc)
		{
			char_u			tmp[MAXPATHL];
			char_u		*	dotp = NULL;
			char_u		*	p = NULL;

# ifdef NT
			NoResize = TRUE;
# endif
			p = curbuf->b_filename;
			while (*p)
			{
				if (*p == '.')
					dotp = p;
# ifdef KANJI
				else if (ISkanji(*p))
					p++;
# endif
				p++;
			}
			if (dotp == curbuf->b_filename)
				dotp = NULL;
			STRCPY(tmp, SYSVIMRC_FILE);
			if (dotp != NULL)
				STRCAT(tmp, dotp);
			if (dosource(tmp) == FAIL && dotp != NULL)
				(void)dosource((char_u *)SYSVIMRC_FILE);
			if (fullpathcmp((char_u *)SYSVIMRC_FILE, (char_u *)VIMRC_FILE))
			{
				if (p_exrc)
				{
					STRCPY(tmp, VIMRC_FILE);
					if (dotp != NULL)
						STRCAT(tmp, dotp);
# ifdef UNIX
					{
						struct stat s;
#  ifdef KANJI
						if (stat(fileconvsto(tmp), &s) || s.st_uid != getuid())
#  else
						if (stat(tmp, &s) || s.st_uid != getuid())
#  endif
							secure = p_secure;
					}
# else
					secure = p_secure;
# endif
					if (dosource(tmp) == FAIL && dotp != NULL)
						dosource((char_u *)VIMRC_FILE);
					secure = 0;
				}
			}
# ifdef NT
			NoResize = FALSE;
# endif
		}
#endif
		if (readfile(curbuf->b_filename, curbuf->b_sfilename, (linenr_t)0,
										TRUE, (linenr_t)0, MAXLNUM) == FAIL)
			return FAIL;
	}
	else
		MSG("Empty Buffer");
	UNCHANGED(curbuf);
	curbuf->b_neverloaded = FALSE;
	return OK;
}

/*
 * Close the link to a buffer. If "free_buf" is TRUE free the buffer if it
 * becomes unreferenced. The caller should get a new buffer very soon!
 * if 'remove' is TRUE, remove the buffer from the buffer list.
 */
	void
close_buffer(buf, free_buf, remove)
	BUF		*buf;
	int		free_buf;
	int		remove;
{
	if (buf->b_nwindows > 0)
		--buf->b_nwindows;
	if (buf->b_nwindows > 0 || !free_buf)
	{
		if (buf == curbuf)
			u_sync();		/* sync undo before going to another buffer */
		return;
	}

	buf_freeall(buf);		/* free all things allocated for this buffer */
	/*
	 * If there is no file name, remove the buffer from the list
	 */
	if (buf->b_filename == NULL || remove)
	{
		free(buf->b_filename);
		free(buf->b_sfilename);
		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;
#ifdef KANJI
		free(buf->b_p_jc);
#endif
#ifdef FEPCTRL
		free(buf->b_p_ji);
#endif
#ifdef TRACK
		free(buf->b_p_trs);
#endif
#if defined(KANJI) && defined(NT) && defined(SYNTAX)
		syn_clr(buf);
#endif
		free(buf);
	}
	else
		buf_clear(buf);
}

/*
 * buf_clear() - make buffer empty
 */
	void
buf_clear(buf)
	BUF		*buf;
{
	buf->b_ml.ml_line_count = 1;
	buf->b_changed = FALSE;
#if !defined(MSDOS) || defined(NT)
	buf->b_shortname = FALSE;
#endif
	buf->b_p_eol = TRUE;
	buf->b_ml.ml_mfp = NULL;
	buf->b_ml.ml_flags = ML_EMPTY;					/* empty buffer */
}

/*
 * buf_freeall() - free all things allocated for the buffer
 */
	void
buf_freeall(buf)
	BUF		*buf;
{
	u_blockfree(buf);				/* free the memory allocated for undo */
	ml_close(buf);					/* close and delete the memline/memfile */
	buf->b_ml.ml_line_count = 0;	/* no lines in buffer */
	u_clearall(buf);				/* reset all undo information */
}

/*
 * Implementation of the command for the buffer list
 */
	int
do_buffer(action, start, dir, count, forceit)
	int		action;		/* 0 = normal, 1 = split window, 2 = unload, 3 = delete */
	int		start;		/* 0 = current, 1 = first, 2 = last, 3 = modified */
	int		dir;		/* FORWARD or BACKWARD */
	int		count;		/* buffer number or number of buffers */
	int		forceit;	/* TRUE for :bdelete! */
{
	BUF		*buf;
	int		retval;

	switch (start)
	{
	case 0: buf = curbuf;
			break;
	case 1: buf = firstbuf;
			break;
	case 2: buf = curbuf;
			while (buf->b_next != NULL)
				buf = buf->b_next;
			break;
	default: buf = curbuf;
			break;
	}
	if (start == 3)			/* find next modified buffer */
	{
		while (count-- > 0)
		{
			do
			{
				buf = buf->b_next;
				if (buf == NULL)
					buf = firstbuf;
			}
			while (buf != curbuf && !buf->b_changed);
		}
		if (!buf->b_changed)
		{
			EMSG("No modified buffer found");
			return FAIL;
		}
	}
	else if (start == 1 && count)		/* find specified buffer number */
	{
		while (buf != NULL && buf->b_fnum != count)
			buf = buf->b_next;
	}
	else
	{
		while (buf != NULL && count-- > 0)
		{
			if (dir == FORWARD)
				buf = buf->b_next;
			else
				buf = buf->b_prev;
		}
	}
	if (buf == NULL)		/* could not find it */
	{
		if (start == 1)
			EMSG2("Cannot go to buffer %ld", (char_u *)count);
		else if (dir == FORWARD)
			EMSG("Cannot go beyond last buffer");
		else
			EMSG("Cannot go before first buffer");
		return FAIL;
	}
	/*
	 * delete buffer buf from memory and/or the list
	 */
	if (action == 2 || action == 3)
	{
		if (buf->b_nwindows > 1 || (buf != curbuf && buf->b_nwindows != 0))
		{
			EMSG2("Other window editing buffer %ld", (char_u *)buf->b_fnum);
			return FAIL;
		}
		if (!forceit && buf->b_changed)
		{
			EMSG2("No write since last change for buffer %ld (use ! to override)",
						(char_u *)buf->b_fnum);
			return FAIL;
		}
		/*
		 * if deleting last buffer, make it empty
		 */
		if (firstbuf->b_next == NULL)
		{
			buf = curbuf;
			retval = doecmd(NULL, NULL, NULL, FALSE, (linenr_t)1);
				/* the doecmd() may create a new buffer, then we have to
				 * delete the old one */
#if 1
			{
				BUF			*wbuf;
				int			empty = FALSE;

				for (wbuf = firstbuf; wbuf != NULL; wbuf = wbuf->b_next)
				{
					if (wbuf == buf)
					{
						empty = TRUE;
						break;
					}
				}
				if (action == 3 && buf != curbuf && empty)
					close_buffer(buf, TRUE, action == 3);
			}
#else
			if (action == 3 && buf != curbuf)
				close_buffer(buf, TRUE, action == 3);
#endif
			return retval;
		}
		/*
		 * If deleted buffer is not current one, delete it here.
		 * Otherwise find buffer to go to and delete it below.
		 */
		{
			if (buf != curbuf)
			{
				close_buffer(buf, TRUE, action == 3);
				return OK;
			}
			if (buf->b_next != NULL)
				buf = buf->b_next;
			else
				buf = buf->b_prev;
		}
	}
/*
 * make buf current buffer
 */
	if (action == 1)		/* split window first */
	{
		if (win_split(0L, FALSE) == FAIL)
			return FAIL;
	}
	buflist_altlnum();		/* remember curpos.lnum */
	close_buffer(curbuf, action == 2 || action == 3, action == 3);
	enter_buffer(buf);
	return OK;
}

/*
 * enter a new current buffer.
 * (old curbuf must have been freed already)
 */
	static void
enter_buffer(buf)
	BUF		*buf;
{
	int		need_fileinfo = TRUE;

	if (buf->b_neverloaded)
	{
#if 1
		BUF			*wbuf;

		for (wbuf = firstbuf; wbuf != NULL; wbuf = wbuf->b_next)
		{
			if (wbuf == curbuf)
			{
				buf_copy_options(curbuf, buf);
				break;
			}
		}
#else
		buf_copy_options(curbuf, buf);
#endif
		buf->b_neverloaded = FALSE;
	}
	curwin->w_buffer = buf;
	curbuf = buf;
	++curbuf->b_nwindows;
	if (curbuf->b_ml.ml_mfp == NULL)	/* need to load the file */
	{
		open_buffer();
		need_fileinfo = FALSE;
	}
	buflist_getlnum();					/* restore curpos.lnum */
	maketitle();
	updateScreen(NOT_VALID);
	if (need_fileinfo)
		fileinfo(did_cd);
}

/*
 * functions for dealing with the buffer list
 */

/*
 * 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 use_curbuf is TRUE, may use current buffer.
 * This is the ONLY way to create a new buffer.
 */
	BUF *
buflist_new(fname, sfname, lnum, use_curbuf)
	char_u		*fname;
	char_u		*sfname;
	linenr_t	lnum;
	int			use_curbuf;
{
	static int	top_file_num = 1;			/* highest file number */
	BUF			*buf;

	fname_expand(&fname, &sfname);

/*
 * If file name already exists in the list, update the entry
 */
	if (fname != NULL && (buf = buflist_findname(fname)) != NULL)
	{
		if (lnum != 0)
			buflist_setlnum(buf, lnum);
		if (buf->b_neverloaded && curbuf != NULL && buf != curbuf)
			buf_copy_options(curbuf, buf);
		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!
 */
	if (use_curbuf && curbuf != NULL && curbuf->b_filename == NULL &&
				curbuf->b_nwindows <= 1 &&
				(curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_flags == ML_EMPTY))
		buf = curbuf;
	else
	{
		buf = (BUF *)alloc((unsigned)sizeof(BUF));
		if (buf == NULL)
			return NULL;
		memset((char *)buf, 0, sizeof(BUF));
	}

	if (fname != NULL)
	{
		buf->b_filename = strsave(fname);
		buf->b_sfilename = strsave(sfname);
	}
	if (buf->b_winlnum == NULL)
		buf->b_winlnum = (WINLNUM *)alloc((unsigned)sizeof(WINLNUM));
	if ((fname != NULL && (buf->b_filename == NULL || buf->b_sfilename == NULL)) ||
								buf->b_winlnum == NULL)
	{
		free(buf->b_filename);
		buf->b_filename = NULL;
		free(buf->b_sfilename);
		buf->b_sfilename = NULL;
		if (buf != curbuf)
		{
			free(buf->b_winlnum);
			free(buf);
		}
		return NULL;
	}

	if (buf == curbuf)
	{
		buf_freeall(buf);		/* free all things allocated for this buffer */
		buf->b_nwindows = 0;
	}
	else
	{
		if (curbuf != NULL)		/* don't do this for first buffer */
			buf_copy_options(curbuf, buf);
	
		/*
		 * put 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 the list */
		{
			lastbuf->b_next = buf;
			buf->b_prev = lastbuf;
		}
		lastbuf = buf;

		buf->b_fnum = top_file_num++;
		if (top_file_num < 0)			/* wrap around (may cause duplicates) */
		{
			EMSG("Warning: List of file names overflow");
			sleep(3);					/* make sure it is noticed */
			top_file_num = 1;
		}

		buf->b_winlnum->wl_lnum = lnum;
		buf->b_winlnum->wl_next = NULL;
		buf->b_winlnum->wl_prev = NULL;
		buf->b_winlnum->wl_win = curwin;
	}

	if (did_cd)
		buf->b_xfilename = buf->b_filename;
	else
		buf->b_xfilename = buf->b_sfilename;
	buf->b_u_synced = TRUE;
	buf->b_neverloaded = TRUE;
	buf_clear(buf);
	clrallmarks(buf);				/* clear marks */

	return buf;
}

/*
 * get alternate file n
 * set linenr to lnum or altlnum if lnum == 0
 * if (setpm) setpcmark
 * return FAIL for failure, OK for success
 */
	int
buflist_getfile(n, lnum, setpm)
	int			n;
	linenr_t	lnum;
	int			setpm;
{
	BUF		*buf;

	buf = buflist_findnr(n);
	if (buf == NULL)
	{
		emsg(e_noalt);
		return FAIL;
	}
	if (lnum == 0)
		lnum = buflist_findlnum(buf);	/* altlnum may be changed by getfile() */
	RedrawingDisabled = TRUE;
	if (getfile(buf->b_filename, buf->b_sfilename, setpm, lnum) <= 0)
	{
		RedrawingDisabled = FALSE;
		return OK;
	}
	RedrawingDisabled = FALSE;
	return FAIL;
}

/*
 * go to the last know line number for the current buffer
 */
	void
buflist_getlnum()
{
	linenr_t	lnum;

	curwin->w_cursor.lnum = 1;
	curwin->w_cursor.col = 0;
	lnum = buflist_findlnum(curbuf);
	if (lnum != 0 && lnum <= curbuf->b_ml.ml_line_count)
		curwin->w_cursor.lnum = lnum;
}

/*
 * find file in buffer list by name (it has to be for the current window)
 * 'fname' must have a full path.
 */
	static BUF	*
buflist_findname(fname)
	char_u		*fname;
{
	BUF			*buf;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		if (buf->b_filename != NULL && fnamecmp(fname, buf->b_filename) == 0)
			return (buf);
	return NULL;
}

/*
 * find file in buffer name list by number
 */
	static BUF	*
buflist_findnr(nr)
	int			nr;
{
	BUF			*buf;

	if (nr == 0)
		nr = curwin->w_alt_fnum;
	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		if (buf->b_fnum == nr)
			return (buf);
	return NULL;
}

/*
 * get name of file 'n' in the buffer list
 */
 	char_u *
buflist_nr2name(n)
	int n;
{
	BUF		*buf;
	char_u	*fname;

	buf = buflist_findnr(n);
	if (buf == NULL)
		return NULL;
	fname = did_cd ? buf->b_filename : buf->b_sfilename;
#if 1
	if (fname == NULL)
		return NULL;
#endif
	home_replace(fname, NameBuff, MAXPATHL);
	return NameBuff;
}

/*
 * set the lnum for the buffer 'buf' and the current window
 */
	static void
buflist_setlnum(buf, lnum)
	BUF			*buf;
	linenr_t	lnum;
{
	WINLNUM		*wlp;
	
	for (wlp = buf->b_winlnum; wlp != NULL; wlp = wlp->wl_next)
		if (wlp->wl_win == curwin)
			break;
	if (wlp == NULL)			/* make new entry */
	{
		wlp = (WINLNUM *)alloc((unsigned)sizeof(WINLNUM));
		if (wlp == NULL)
			return;
		wlp->wl_win = curwin;
	}
	else						/* remove entry from list */
	{
		if (wlp->wl_prev)
			wlp->wl_prev->wl_next = wlp->wl_next;
		else
			buf->b_winlnum = wlp->wl_next;
		if (wlp->wl_next)
			wlp->wl_next->wl_prev = wlp->wl_prev;
	}
	wlp->wl_lnum = lnum;
/*
 * insert entry in front of the list
 */
	wlp->wl_next = buf->b_winlnum;
	buf->b_winlnum = wlp;
	wlp->wl_prev = NULL;
	if (wlp->wl_next)
		wlp->wl_next->wl_prev = wlp;

	return;
}

/*
 * find the lnum for the buffer 'buf' for the current window
 */
	static linenr_t
buflist_findlnum(buf)
	BUF		*buf;
{
	WINLNUM 	*wlp;

	for (wlp = buf->b_winlnum; wlp != NULL; wlp = wlp->wl_next)
		if (wlp->wl_win == curwin)
			break;

	if (wlp == NULL)		/* if no lnum for curwin, use the first in the list */
		wlp = buf->b_winlnum;

	if (wlp)
		return wlp->wl_lnum;
	else
		return (linenr_t)1;
}

/*
 * list all know file names (for :files and :buffers command)
 */
	void
buflist_list()
{
	BUF			*buf;
	int			len;

	gotocmdline(TRUE, NUL);
	for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next)
	{
		if (buf != firstbuf)
			msg_outchar('\n');
		if (buf->b_xfilename == NULL)
			STRCPY(NameBuff, "No File");
		else
			/* careful: home_replace calls vimgetenv(), which uses IObuff! */
			home_replace(buf->b_xfilename, NameBuff, MAXPATHL);

		sprintf((char *)IObuff, "%3d %c%c%c \"",
				buf->b_fnum,
				buf == curbuf ? '%' :
						(curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '),
				buf->b_ml.ml_mfp == NULL ? '-' :
						(buf->b_nwindows == 0 ? 'h' : ' '),
				buf->b_changed ? '+' : ' ');

		len = STRLEN(IObuff);
		STRNCPY(IObuff + len, NameBuff, (size_t)IOSIZE - 20 - len);

		len = STRLEN(IObuff);
		IObuff[len++] = '"';
		/*
		 * try to put the "line" strings in column 40
		 */
		do
		{
			IObuff[len++] = ' ';
		} while (len < 40 && len < IOSIZE - 18);
		sprintf((char *)IObuff + len, "line %ld",
				buf == curbuf ? curwin->w_cursor.lnum :
								(long)buflist_findlnum(buf));
		msg_outstr(IObuff);
		flushbuf();			/* output one line at a time */
		breakcheck();
	}
	msg_end();
}

/*
 * get file name and line number for file 'fnum'
 * used by DoOneCmd() for translating '%' and '#'
 * return FAIL if not found, OK for success
 */
	int
buflist_name_nr(fnum, fname, lnum)
	int			fnum;
	char_u		**fname;
	linenr_t	*lnum;
{
	BUF			*buf;

	buf = buflist_findnr(fnum);
	if (buf == NULL || buf->b_filename == NULL)
		return FAIL;

	if (did_cd)
		*fname = buf->b_filename;
	else
		*fname = buf->b_sfilename;
	*lnum = buflist_findlnum(buf);

	return OK;
}

/*
 * Set the current file name to 's', short file name to 'ss'.
 * 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(fname, sfname, message)
	char_u *fname, *sfname;
	int		message;
{
	BUF		*buf;

	if (fname == NULL || *fname == NUL)
	{
		curbuf->b_filename = NULL;
		curbuf->b_sfilename = NULL;
	}
	else
	{
		fname_expand(&fname, &sfname);
		/*
		 * 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
		 */
		buf = buflist_findname(fname);
		if (buf != NULL && buf != curbuf)
		{
			if (buf->b_ml.ml_mfp != NULL)		/* it's loaded, fail */
			{
				if (message)
					EMSG("Buffer with this name already exists");
				return FAIL;
			}
			close_buffer(buf, TRUE, TRUE);		/* delete from the list */
		}
		fname = strsave(fname);
		sfname = strsave(sfname);
		if (fname == NULL || sfname == NULL)
		{
			free(sfname);
			free(fname);
			return FAIL;
		}
		free(curbuf->b_filename);
		free(curbuf->b_sfilename);
		curbuf->b_filename = fname;
		curbuf->b_sfilename = sfname;
	}
	if (did_cd)
		curbuf->b_xfilename = curbuf->b_filename;
	else
		curbuf->b_xfilename = curbuf->b_sfilename;

#if !defined(MSDOS) || defined(NT)
	curbuf->b_shortname = FALSE;
#endif
	return OK;
}

/*
 * set alternate file name for current window
 *
 * used by dowrite() and doecmd()
 */
	void
setaltfname(fname, sfname, lnum)
	char_u		*fname;
	char_u		*sfname;
	linenr_t	lnum;
{
	BUF		*buf;

	buf = buflist_new(fname, sfname, lnum, FALSE);
	if (buf != NULL)
		curwin->w_alt_fnum = buf->b_fnum;
}

/*
 * add a file name to the buflist and return its number
 *
 * used by qf_init(), main() and doarglist()
 */
	int
buflist_add(fname)
	char_u		*fname;
{
	BUF		*buf;

	buf = buflist_new(fname, NULL, (linenr_t)0, FALSE);
	if (buf != NULL)
		return buf->b_fnum;
	return 0;
}

/*
 * set alternate lnum for current window
 */
	void
buflist_altlnum()
{
	buflist_setlnum(curbuf, curwin->w_cursor.lnum);
}

/*
 * return nonzero if 'fname' is not the same file as current file
 * fname must have a full path (expanded by FullName)
 */
	int
otherfile(fname)
	char_u	*fname;
{									/* no name is different */
	if (fname == NULL || *fname == NUL || curbuf->b_filename == NULL)
		return TRUE;
	return fnamecmp(fname, curbuf->b_filename);
}

	void
fileinfo(fullname)
	int fullname;
{
	char_u		*name;

#if 0		/* this message is quite useless */
	if (bufempty())
	{
		MSG("Buffer Empty");
		return;
	}
#endif

	if (curbuf->b_filename == NULL)
		STRCPY(IObuff, "\"No File");
	else
	{
		if (!fullname && curbuf->b_sfilename != NULL)
			name = curbuf->b_sfilename;
		else
			name = curbuf->b_filename;
			/* careful: home_replace cals vimgetenv(), which also uses IObuff! */
		home_replace(name, IObuff + 1, IOSIZE - 1);
		IObuff[0] = '"';
	}

#ifdef KANJI
	{
		char_u	*p		= ml_get(curwin->w_cursor.lnum);
		int		pcol	= getvcol(curwin, &curwin->w_cursor, 3) + 1;
		int		lastcol = STRLEN(p);
		int		code	= *ml_get_cursor() & 0xff;
		FPOS	last;
		char_u	k1, k2;

		last.lnum = curwin->w_cursor.lnum;
		last.col  = lastcol;
		lastcol   = getvcol(curwin, &last, 3);
		if (ISkanjiCur() == 1)
		{
			k1 = code;
			k2 = p[curwin->w_cursor.col+1];
			kanjito(&k1, &k2, toupper(*curbuf->b_p_jc));
			code = (k1 << 8) | k2;
			--pcol;
		}
# ifdef UCODE
		else if (toupper(*curbuf->b_p_jc) == JP_WIDE)	/* UNICODE */
		{
			k1 = code;
			kanato(&k1, &k2, toupper(*curbuf->b_p_jc));
			code = (k1 << 8) | k2;
		}
# endif
		else if (ISkana(code))
		{
			k1 = code;
			kanato(&k1, &k2, toupper(*curbuf->b_p_jc));
			if (k2)
				code = (k1 << 8) | k2;
			else
				code = k1;
		}
		else if (code == 0x0a)		/* Ctrl('J') */
		{
			code = 0x00;
		}
		sprintf((char *)IObuff + STRLEN(IObuff),
					"\"%s%s%s line %ld of %ld --%d%%-- col %d/%d ch 0x%x [%c]",
				curbuf->b_changed ? " [Modified]" : "",
				curbuf->b_notedited ? " [Not edited]" : "",
				curbuf->b_p_ro ? " [readonly]" : "",
				(long)curwin->w_cursor.lnum,
				(long)curbuf->b_ml.ml_line_count,
				(int)(((long)curwin->w_cursor.lnum * 100L) / (long)curbuf->b_ml.ml_line_count),
				pcol,
				lastcol,
				code,
				toupper(*curbuf->b_p_jc)
		);
	}
#else
	sprintf((char *)IObuff + STRLEN(IObuff),
						"\"%s%s%s line %ld of %ld --%d%%-- col %d",
			curbuf->b_changed ? " [Modified]" : "",
			curbuf->b_notedited ? " [Not edited]" : "",
			curbuf->b_p_ro ? " [readonly]" : "",
			(long)curwin->w_cursor.lnum,
			(long)curbuf->b_ml.ml_line_count,
			(int)(((long)curwin->w_cursor.lnum * 100L) / (long)curbuf->b_ml.ml_line_count),
			(int)curwin->w_cursor.col + 1);
#endif

	if (arg_count > 1)
		sprintf((char *)IObuff + STRLEN(IObuff), " (file %d of %d)", curwin->w_arg_idx + 1, arg_count);
	msg(IObuff);
}

/*
 * put filename in title bar of window and in icon title
 */

static char_u *lasttitle = NULL;
static char_u *lasticon = NULL;

	void
maketitle()
{
	char_u		*t;
	char_u		*i;

	if (!p_title && !p_icon)
		return;

	if (curbuf->b_filename == NULL)
	{
		t = (char_u *)"";
		i = (char_u *)"No File";
	}
	else
	{
		home_replace(curbuf->b_filename, IObuff, IOSIZE);
		if (arg_count > 1)
			sprintf((char *)IObuff + STRLEN(IObuff), " (%d of %d)", curwin->w_arg_idx + 1, arg_count);
		t = IObuff;
		i = gettail(curbuf->b_filename);		/* use filename only for icon */
	}

	free(lasttitle);
	if (p_title)
		lasttitle = alloc((unsigned)(STRLEN(t) + 7));
	else
		lasttitle = NULL;
	if (lasttitle != NULL)
		sprintf((char *)lasttitle, "VIM - %s", (char *)t);

	free(lasticon);
	if (p_icon)
		lasticon = strsave(i);
	else
		lasticon = NULL;

	resettitle();
}

	void
resettitle()
{
#ifdef KANJI
	char_u	*	t = NULL;
	char_u	*	i = NULL;

	if (lasttitle != NULL)
		t = kanjiconvsto(lasttitle, toupper(JP_DISP), TRUE);
	if (lasticon != NULL)
		i = kanjiconvsto(lasticon, toupper(JP_DISP), TRUE);
	mch_settitle(t, i);
	if (t != NULL)
		free(t);
	if (i != NULL)
		free(i);
#else
	mch_settitle(lasttitle, lasticon);
#endif
}

/*
 * If fname is not a full path, make it a full path
 */
	char_u	*
fix_fname(fname)
	char_u	*fname;
{
	if (fname != NameBuff)			/* if not already expanded */
	{
#ifdef KANJI
		fname = fileconvsto(fname);
#endif
		if (!isFullName(fname))
		{
			(void)FullName(fname, NameBuff, MAXPATHL);
			fname = NameBuff;
		}
#if !defined(notdef) && defined(NT)
		fname_case(fname);
#endif
#ifdef AMIGA
		else
		{
			STRNCPY(NameBuff, fname, (size_t)MAXPATHL);	/* make copy so we can change it */
			fname = NameBuff;
			fname_case(fname);			/* set correct case for filename */
		}
#endif
#ifdef KANJI
		STRCPY(fname, fileconvsfrom(fname));
#endif
	}
	return fname;
}

/*
 * make fname a full file name, set sfname to fname if not NULL
 */
	void
fname_expand(fname, sfname)
	char_u		**fname;
	char_u		**sfname;
{
	if (*fname == NULL)			/* if no file name given, nothing to do */
		return;
	if (*sfname == NULL)		/* if no short file name given, use fname */
		*sfname = *fname;
	*fname = fix_fname(*fname);	/* expand to full path */
}

/*
 * do_arg_all: open a window for each argument
 */
	void
do_arg_all()
{
	int		win_count;
	int		i;

	if (arg_count <= 1)
	{
		EMSG("Argument list contains less than 2 files");
		return;
	}
	/*
	 * 1. close all but first window
	 * 2. make the desired number of windows
	 * 3. start editing the first window (hide the current window contents)
	 * 4. stuff commands to fill the other windows
	 */
	close_others(FALSE);
	curwin->w_arg_idx = 0;
	win_count = make_windows(arg_count);
	for (i = 0; i < win_count; ++i)
	{
												/* edit file i */
		(void)doecmd(arg_files[i], NULL, NULL, TRUE, (linenr_t)1);
		curwin->w_arg_idx = i;
		if (curwin->w_next == NULL)				/* just checking */
			break;
		win_enter(curwin->w_next, FALSE);
	}
	win_enter(firstwin, FALSE);					/* back to first window */
}

/*
 * do_arg_all: open a window for each buffer
 *
 * when 'all' is TRUE, also load inactive buffers
 */
	void
do_buffer_all(all)
	int		all;
{
	int		win_count;
	BUF		*buf;
	int		i;

/*
 * count number of desired windows
 */
	win_count = 0; 
	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
		if (all || buf->b_ml.ml_mfp != NULL)
			++win_count;

	if (win_count == 0)				/* Cannot happen? */
	{
		EMSG("No relevant entries in buffer list");
		return;
	}

	/*
	 * 1. close all but first window
	 * 2. make the desired number of windows
	 * 3. stuff commands to fill the windows
	 */
	close_others(FALSE);
	curwin->w_arg_idx = 0;
	win_count = make_windows(win_count);
	buf = firstbuf;
	for (i = 0; i < win_count; ++i)
	{
		for ( ; buf != NULL; buf = buf->b_next)
			if (all || buf->b_ml.ml_mfp != NULL)
				break;
		if (buf == NULL)			/* Cannot happen? */
			break;
		if (i != 0)
			stuffReadbuff((char_u *)"\n\027\027:");	/* CTRL-W CTRL-W */
		stuffReadbuff((char_u *)":buf ");			/* edit Nth buffer */
		stuffnumReadbuff((long)buf->b_fnum);
		buf = buf->b_next;
	}
	stuffReadbuff((char_u *)"\n100\027k");		/* back to first window */
}
