ftp.nice.ch/pub/next/unix/editor/vim-5.0f.s.tar.gz#/vim-5.0f/src/ex_docmd.c

This is ex_docmd.c in view mode; [Download] [Up]

/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * ex_docmd.c: functions for executing an Ex command line.
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"
#include "ex_docmd.h"
#include "ex_cmds.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>			/* for chdir() */
#endif

static int			quitmore = 0;
static int			ex_pressedreturn = FALSE;

/*
 * For conditional commands a stack is kept of nested conditionals.
 * When cs_idx < 0, there is not conditional command.
 */
#define CSTACK_LEN 50

struct condstack
{
	char	cs_active[CSTACK_LEN];	/* flag set if executing commands */
	int		cs_idx;					/* current entry, or -1 if none */
};

static char_u	*do_one_cmd __ARGS((char_u **, int, struct condstack *, char_u *(*getline)(int, void *, int), void *cookie));
static int		buf_write_all __ARGS((BUF *));
static int		do_write __ARGS((EXARG *eap));
static char_u	*getargcmd __ARGS((char_u **));
static void		do_make __ARGS((char_u *));
static int		do_arglist __ARGS((char_u *));
static int		is_backslash __ARGS((char_u *str));
static int		check_readonly __ARGS((int));
static int		check_changed __ARGS((BUF *, int, int, int));
static int		check_changed_any __ARGS((void));
static int		check_more __ARGS((int, int));
static linenr_t get_address __ARGS((char_u **));
static void		not_exiting __ARGS((void));
static void		do_quit __ARGS((EXARG *eap));
static void		do_suspend __ARGS((EXARG *eap));
static void		do_exit __ARGS((EXARG *eap));
static void		do_wqall __ARGS((EXARG *eap));
static void		do_print __ARGS((EXARG *eap));
static void		do_argfile __ARGS((EXARG *eap, int argn));
static void		do_next __ARGS((EXARG *eap));
static void		do_args __ARGS((EXARG *eap));
static void		do_resize __ARGS((EXARG *eap));
static void		do_exedit __ARGS((EXARG *eap, WIN *old_curwin));
#ifdef USE_GUI
static void		do_gui __ARGS((EXARG *eap));
#endif
static void		do_read __ARGS((EXARG *eap));
static void		do_cd __ARGS((EXARG *eap));
static void		do_pwd __ARGS((void));
static void		do_sleep __ARGS((EXARG *eap));
static void		do_exmap __ARGS((EXARG *eap, int isabbrev));
static void		do_exops __ARGS((EXARG *eap));
static void		do_copymove __ARGS((EXARG *eap));
static void		do_exjoin __ARGS((EXARG *eap));
static void		do_at __ARGS((EXARG *eap));
static void		do_mkrc __ARGS((EXARG *eap));
static void		do_setmark __ARGS((EXARG *eap));
static void		do_normal __ARGS((EXARG *eap));
static char_u	*do_findpat __ARGS((EXARG *eap, int action));
static char_u	*do_if __ARGS((EXARG *eap, struct condstack *cstack));
static char_u	*do_else __ARGS((EXARG *eap, struct condstack *cstack));

/*
 * do_exmode(): Repeatedly get commands for the "Ex" mode, until the ":vi"
 * command is given.
 */
	void
do_exmode()
{
	int			save_msg_scroll;
	int			prev_msg_row;
	linenr_t	prev_line;

	save_msg_scroll = msg_scroll;
	++RedrawingDisabled;			/* don't redisplay the window */
	++no_wait_return;				/* don't wait for return */
	settmode(TMODE_COOK);

	State = NORMAL;
	exmode_active = TRUE;

	MSG("Entering Ex mode.  Type \"vi\" to get out.");
	while (exmode_active)
	{

		msg_scroll = TRUE;
		need_wait_return = FALSE;
		ex_pressedreturn = FALSE;
		ex_no_reprint = FALSE;
		prev_msg_row = msg_row;
		prev_line = curwin->w_cursor.lnum;

		do_cmdline(NULL, getexmodeline, NULL, DOCMD_NOWAIT);
		lines_left = Rows - 1;

		if (prev_line != curwin->w_cursor.lnum && !ex_no_reprint)
		{
			if (ex_pressedreturn)
			{
				/* go up one line, to overwrite the ":<CR>" line, so the
				 * output doensn't contain empty lines. */
				msg_row = prev_msg_row;
				if (prev_msg_row == Rows - 1)
					msg_row--;
			}
			msg_col = 0;
			print_line_no_prefix(curwin->w_cursor.lnum, FALSE);
			msg_clr_eos();
		}
		else if (ex_pressedreturn)		/* must be at EOF */
			EMSG("At end-of-file");
	}

	settmode(TMODE_RAW);
	--RedrawingDisabled;
	--no_wait_return;
	update_screen(CLEAR);
	need_wait_return = FALSE;
	msg_scroll = save_msg_scroll;
}

/*
 * do_cmdline(): execute one Ex command line
 *
 * 1. If no line given, get one from getline().
 * 2. Split up in parts separated with '|'.
 *
 * This function may be called recursively!
 *
 * flags:
 * DOCMD_VERBOSE - The command will be included in the error message.
 * DOCMD_NOWAIT  - Don't call wait_return() and friends.
 * DOCMD_REPEAT  - Repeat execution until getline() returns NULL.
 *
 * return FAIL if commandline could not be executed, OK otherwise
 */
	int
do_cmdline(cmdline, getline, cookie, flags)
	char_u		*cmdline;
	char_u		*(*getline) __ARGS((int, void *, int));
	void		*cookie;				/* argument for getline() */
	int			flags;
{
	char_u		*nextcomm;
	static int	recursive = 0;			/* recursive depth */
	int			msg_didout_before_start = 0;
	struct condstack cstack;			/* conditional stack */
	int			count = 0;				/* line number count */
	int			did_inc = FALSE;		/* incremented RedrawingDisabled */
	int			retval = OK;

	/*
	 * Continue executing command lines when inside an ":if" command.
	 */
	cstack.cs_idx = -1;
	do
	{
		/*
		 * 1. If no line given: Get a line in cmdbuff.
		 *	  If a line is given: Copy it into cmdbuff.
		 *	  After this we don't use cmdbuff but cmdline, because of
		 *	  recursiveness
		 */
		if (cmdline == NULL)
		{
			/*
			 * Need to set msg_didout for the first line after an ":if",
			 * otherwise the ":if" will be overwritten.
			 */
			if (count == 1 && getline == getexline)
				msg_didout = TRUE;
			if ((cmdline = getline(':', cookie,
					cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2)) == NULL)
			{
					/* don't call wait_return for aborted command line */
				need_wait_return = FALSE;
				retval = FAIL;
				break;
			}
		}
		else
		{
			/* Make a copy of the command so we can mess with it. */
			if ((cmdline = vim_strsave(cmdline)) == NULL)
			{
				retval = FAIL;
				break;
			}
		}

		if (count++ == 0)
		{
			/*
			 * All output from the commands is put below each other, without
			 * waiting for a return. Don't do this when executing commands
			 * from a script or when being called recursive (e.g. for ":e
			 * +command file").
			 */
			if (!(flags & DOCMD_NOWAIT) && !recursive)
			{
				msg_didout_before_start = msg_didout;
				msg_didany = FALSE;	/* no output yet */
				msg_start();
				msg_scroll = TRUE;	/* put messages below each other */
#ifdef SLEEP_IN_EMSG
				++dont_sleep;		/* don't sleep in emsg() */
#endif
				++no_wait_return;	/* dont wait for return until finished */
				++RedrawingDisabled;
				did_inc = TRUE;
			}
		}

		/*
		 * 2. Loop for each '|' separated command.
		 *	  do_one_cmd() will return NULL if there is no trailing '|'.
		 *	  "cmdline" may change, e.g. for '%' and '#' expansion.
		 */
		++recursive;
		for (;;)
		{
			nextcomm = do_one_cmd(&cmdline, flags & DOCMD_VERBOSE, &cstack,
															 getline, cookie);
			if (nextcomm == NULL)
				break;
			STRCPY(cmdline, nextcomm);
		}
		--recursive;
		vim_free(cmdline);

		/*
		 * If the command was typed, remember it for register :
		 * Do this AFTER executing the command to make :@: work.
		 */
		if (getline == getexline && new_last_cmdline != NULL)
		{
			vim_free(last_cmdline);
			last_cmdline = new_last_cmdline;
			new_last_cmdline = NULL;
		}
		cmdline = NULL;

	} while (cstack.cs_idx >= 0 || (flags & DOCMD_REPEAT));

	/*
	 * If there was too much output to fit on the command line, ask the user to
	 * hit return before redrawing the screen. With the ":global" command we do
	 * this only once after the command is finished.
	 */
	if (did_inc)
	{
		--RedrawingDisabled;
#ifdef SLEEP_IN_EMSG
		--dont_sleep;
#endif
		--no_wait_return;
		msg_scroll = FALSE;

		/*
		 * When just finished an ":if"-":else" which was typed, no need to
		 * wait for hit-return.  Also for an error situation.
		 */
		if ((count > 1 && KeyTyped) || retval == FAIL)
		{
			need_wait_return = FALSE;
			redraw_later(NOT_VALID);
		}
		else if ((need_wait_return || (msg_check() && !dont_wait_return)))
		{
			/*
			 * The msg_start() above clears msg_didout. The wait_return we do
			 * here should not overwrite the command that may be shown before
			 * doing that.
			 */
			msg_didout = msg_didout_before_start;
			wait_return(FALSE);
		}
	}

	return retval;
}

/*
 * Execute one Ex command.
 *
 * If 'sourcing' is TRUE, the command will be included in the error message.
 *
 * 2. skip comment lines and leading space
 * 3. parse range
 * 4. parse command
 * 5. parse arguments
 * 6. switch on command name
 *
 * This function may be called recursively!
 */
	static char_u *
do_one_cmd(cmdlinep, sourcing, cstack, getline, cookie)
	char_u				**cmdlinep;
	int					sourcing;
	struct condstack	*cstack;
	char_u				*(*getline) __ARGS((int, void *, int));
	void				*cookie;				/* argument for getline() */
{
	char_u				*p;
	char_u				*new_cmdline;
	int 				i = 0;					/* init to shut up gcc */
	int					len;
	long				argt;
	linenr_t			lnum;
	long				n = 0;					/* init to shut up gcc */
	char_u				*errormsg = NULL;		/* error message */
	static int			if_level = 0;			/* depth in :if */
	EXARG				ea;						/* Ex command arguments */

	vim_memset(&ea, 0, sizeof(ea));
	ea.line1 = 1;
	ea.line2 = 1;

		/* when not editing the last file :q has to be typed twice */
	if (quitmore)
		--quitmore;
	did_emsg = FALSE;		/* will be set to TRUE when emsg() used, in which
							 * case we set ea.nextcomm to NULL to cancel the
							 * whole command line */
/*
 * 2. skip comment lines and leading space and colons
 */
	for (ea.cmd = *cmdlinep; vim_strchr((char_u *)" \t:", *ea.cmd) != NULL;
																	 ea.cmd++)
		;

	/* in ex mode, an empty line works like :+ */
	if (*ea.cmd == NUL && exmode_active && getline == getexmodeline)
	{
		ea.cmd = (char_u *)"+";
		ex_pressedreturn = TRUE;
	}

	if (*ea.cmd == '"' || *ea.cmd == NUL)	/* ignore comment and empty lines */
		goto doend;

/*
 * 3. parse a range specifier of the form: addr [,addr] [;addr] ..
 *
 * where 'addr' is:
 *
 * %		  (entire file)
 * $  [+-NUM]
 * 'x [+-NUM] (where x denotes a currently defined mark)
 * .  [+-NUM]
 * [+-NUM]..
 * NUM
 *
 * The ea.cmd pointer is updated to point to the first character following the
 * range spec. If an initial address is found, but no second, the upper bound
 * is equal to the lower.
 */

	if (if_level)
		goto skip_address;

	--ea.cmd;
	do
	{
		ea.line1 = ea.line2;
		ea.line2 = curwin->w_cursor.lnum;	/* default is current line number */
		ea.cmd = skipwhite(ea.cmd + 1);		/* skip ',' or ';' and blanks */
		lnum = get_address(&ea.cmd);
		if (ea.cmd == NULL)					/* error detected */
			goto doend;
		if (lnum == MAXLNUM)
		{
			if (*ea.cmd == '%')				/* '%' - all lines */
			{
				++ea.cmd;
				ea.line1 = 1;
				ea.line2 = curbuf->b_ml.ml_line_count;
				++ea.addr_count;
			}
			else if (*ea.cmd == '*')		/* '*' - visual area */
			{
				FPOS		*fp;

				++ea.cmd;
				fp = getmark('<', FALSE);
				if (check_mark(fp) == FAIL)
					goto doend;
				ea.line1 = fp->lnum;
				fp = getmark('>', FALSE);
				if (check_mark(fp) == FAIL)
					goto doend;
				ea.line2 = fp->lnum;
				++ea.addr_count;
			}
		}
		else
			ea.line2 = lnum;
		ea.addr_count++;

		if (*ea.cmd == ';')
		{
			if (ea.line2 == 0)
				curwin->w_cursor.lnum = 1;
			else
				curwin->w_cursor.lnum = ea.line2;
		}
	} while (*ea.cmd == ',' || *ea.cmd == ';');

	/* One address given: set start and end lines */
	if (ea.addr_count == 1)
	{
		ea.line1 = ea.line2;
			/* ... but only implicit: really no address given */
		if (lnum == MAXLNUM)
			ea.addr_count = 0;
	}
skip_address:

/*
 * 4. parse command
 */

	/*
	 * Skip ':' and any white space
	 */
	ea.cmd = skipwhite(ea.cmd);
	while (*ea.cmd == ':')
		ea.cmd = skipwhite(ea.cmd + 1);

	/*
	 * If we got a line, but no command, then go to the line.
	 * If we find a '|' or '\n' we set ea.nextcomm.
	 */
	if (*ea.cmd == NUL || *ea.cmd == '"' ||
							   (ea.nextcomm = check_nextcomm(ea.cmd)) != NULL)
	{
		/*
		 * strange vi behaviour:
		 * ":3"			jumps to line 3
		 * ":3|..."		prints line 3
		 * ":|"			prints current line
		 */
		if (if_level)				/* skip this if inside :if */
			goto doend;
		if (*ea.cmd == '|')
		{
			ea.cmdidx = CMD_print;
			do_print(&ea);
		}
		else if (ea.addr_count != 0)
		{
			if (ea.line2 == 0)
				curwin->w_cursor.lnum = 1;
			else if (ea.line2 > curbuf->b_ml.ml_line_count)
				curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
			else
				curwin->w_cursor.lnum = ea.line2;
			beginline(MAYBE);
		}
		goto doend;
	}

	/*
	 * Isolate the command and search for it in the command table.
	 * Exeptions:
	 * - the 'k' command can directly be followed by any character.
	 * - the 's' command can be followed directly by 'c', 'g' or 'r'
	 *		but :sre[wind] is another command.
	 */
	if (*ea.cmd == 'k')
	{
		ea.cmdidx = CMD_k;
		p = ea.cmd + 1;
	}
	else if (  *ea.cmd == 's'
			&& vim_strchr((char_u *)"cgr", ea.cmd[1]) != NULL
			&& STRNCMP("sre", ea.cmd, (size_t)3) != 0)
	{
		ea.cmdidx = CMD_substitute;
		p = ea.cmd + 1;
	}
	else
	{
		p = ea.cmd;
		while (isalpha(*p))
			++p;
			/* check for non-alpha command */
		if (p == ea.cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL)
			++p;
		i = (int)(p - ea.cmd);

		for (ea.cmdidx = 0; ea.cmdidx < CMD_SIZE; ++ea.cmdidx)
			if (STRNCMP(cmdnames[ea.cmdidx].cmd_name, (char *)ea.cmd,
															  (size_t)i) == 0)
				break;
		if (i == 0 || ea.cmdidx == CMD_SIZE)
		{
			if (if_level == 0)
			{
				STRCPY(IObuff, "Not an editor command");
				if (!sourcing)
				{
					STRCAT(IObuff, ": ");
					STRNCAT(IObuff, *cmdlinep, 40);
				}
				errormsg = IObuff;
			}
			goto doend;
		}
	}

	if (*p == '!')					/* forced commands */
	{
		++p;
		ea.forceit = TRUE;
	}
	else
		ea.forceit = FALSE;

/*
 * 5. parse arguments
 */
	argt = cmdnames[ea.cmdidx].cmd_argt;

	if (!(argt & RANGE) && ea.addr_count)		/* no range allowed */
	{
		errormsg = e_norange;
		goto doend;
	}

	if (!(argt & BANG) && ea.forceit)			/* no <!> allowed */
	{
		errormsg = e_nobang;
		if (ea.cmdidx == CMD_help)
			errormsg = (char_u *)"Don't panic!";
		goto doend;
	}

	/*
	 * If the range is backwards, ask for confirmation and, if given, swap
	 * ea.line1 & ea.line2 so it's forwards again.
	 * When global command is busy, don't ask, will fail below.
	 */
	if (!global_busy && ea.line1 > ea.line2)
	{
		if (sourcing)
		{
			errormsg = (char_u *)"Backwards range given";
			goto doend;
		}
		else if (ask_yesno((char_u *)
						   "Backwards range given, OK to swap", FALSE) != 'y')
			goto doend;
		lnum = ea.line1;
		ea.line1 = ea.line2;
		ea.line2 = lnum;
	}
	/*
	 * don't complain about the range if it is not used
	 * (could happen if line_count is accidently set to 0)
	 */
	if (	   ea.line1 < 0
			|| ea.line2 < 0
			|| ea.line1 > ea.line2
			|| ((argt & RANGE)
				&& !(argt & NOTADR)
				&& ea.line2 > curbuf->b_ml.ml_line_count))
	{
		errormsg = e_invrange;
		goto doend;
	}

	if ((argt & NOTADR) && ea.addr_count == 0)	/* default is 1, not cursor */
		ea.line2 = 1;

	if (!(argt & ZEROR))			/* zero in range not allowed */
	{
		if (ea.line1 == 0)
			ea.line1 = 1;
		if (ea.line2 == 0)
			ea.line2 = 1;
	}

	/*
	 * For the :make command we insert the 'makeprg' option here,
	 * so things like % get expanded.
	 */
	if (ea.cmdidx == CMD_make)
	{
		if ((new_cmdline = alloc((int)(STRLEN(p_mp) + STRLEN(p) + 2))) == NULL)
			goto doend;				/* out of memory */
		STRCPY(new_cmdline, p_mp);
		STRCAT(new_cmdline, " ");
		STRCAT(new_cmdline, p);
		msg_make(p);
		/* 'ea.cmd' is not set here, because it is not used at CMD_make */
		vim_free(*cmdlinep);
		*cmdlinep = new_cmdline;
		p = new_cmdline;
	}

	/*
	 * Skip to start of argument.
	 * Don't do this for the ":!" command, because ":!! -l" needs the space.
	 */
	if (ea.cmdidx == CMD_bang)
		ea.arg = p;
	else
		ea.arg = skipwhite(p);

	if (ea.cmdidx == CMD_write)
	{
		if (*ea.arg == '>')						/* append */
		{
			if (*++ea.arg != '>')				/* typed wrong */
			{
				errormsg = (char_u *)"Use w or w>>";
				goto doend;
			}
			ea.arg = skipwhite(ea.arg + 1);
			ea.append = TRUE;
		}
		else if (*ea.arg == '!')				/* :w !filter */
		{
			++ea.arg;
			ea.usefilter = TRUE;
		}
	}

	if (ea.cmdidx == CMD_read)
	{
		if (ea.forceit)
		{
			ea.usefilter = TRUE;				/* :r! filter if ea.forceit */
			ea.forceit = FALSE;
		}
		else if (*ea.arg == '!')				/* :r !filter */
		{
			++ea.arg;
			ea.usefilter = TRUE;
		}
	}

	if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift)
	{
		ea.amount = 1;
		while (*ea.arg == *ea.cmd)				/* count number of '>' or '<' */
		{
			++ea.arg;
			++ea.amount;
		}
		ea.arg = skipwhite(ea.arg);
	}

	/*
	 * Check for "+command" argument, before checking for next command.
	 * Don't do this for ":read !cmd" and ":write !cmd".
	 */
	if ((argt & EDITCMD) && !ea.usefilter)
		ea.do_ecmd_cmd = getargcmd(&ea.arg);

	/*
	 * Check for '|' to separate commands and '"' to start comments.
	 * Don't do this for ":read !cmd" and ":write !cmd".
	 */
	if ((argt & TRLBAR) && !ea.usefilter)
	{
		for (p = ea.arg; *p; ++p)
		{
			if (*p == Ctrl('V'))
			{
				if (argt & (USECTRLV | XFILE))
					++p;				/* skip CTRL-V and next char */
				else
					STRCPY(p, p + 1);	/* remove CTRL-V and skip next char */
				if (*p == NUL)			/* stop at NUL after CTRL-V */
					break;
			}
			else if ((*p == '"' && !(argt & NOTRLCOM)) ||
													  *p == '|' || *p == '\n')
			{
				/*
				 * We remove the '\' before the '|', unless USECTRLV is used
				 * AND 'b' is present in 'cpoptions'.
				 */
				if ((vim_strchr(p_cpo, CPO_BAR) == NULL ||
									   !(argt & USECTRLV)) && *(p - 1) == '\\')
				{
					STRCPY(p - 1, p);	/* remove the backslash */
					--p;
				}
				else
				{
					ea.nextcomm = check_nextcomm(p);
					*p = NUL;
					break;
				}
			}
		}
		if (!(argt & NOTRLCOM))			/* remove trailing spaces */
			del_trailing_spaces(ea.arg);
	}

	/*
	 * Check for <newline> to end a shell command.
	 * Also do this for ":read !cmd" and ":write !cmd".
	 */
	else if (ea.cmdidx == CMD_bang || ea.usefilter)
	{
		for (p = ea.arg; *p; ++p)
		{
			if (*p == '\\' && p[1])
				++p;
			else if (*p == '\n')
			{
				ea.nextcomm = p + 1;
				*p = NUL;
				break;
			}
		}
	}

	if ((argt & DFLALL) && ea.addr_count == 0)
	{
		ea.line1 = 1;
		ea.line2 = curbuf->b_ml.ml_line_count;
	}

		/* accept numbered register only when no count allowed (:put) */
	if (	   (argt & REGSTR)
			&& *ea.arg != NUL
			&& is_yank_register(*ea.arg, FALSE)
			&& !((argt & COUNT) && isdigit(*ea.arg)))
	{
		ea.regname = *ea.arg;
		ea.arg = skipwhite(ea.arg + 1);
	}

	if ((argt & COUNT) && isdigit(*ea.arg))
	{
		n = getdigits(&ea.arg);
		ea.arg = skipwhite(ea.arg);
		if (n <= 0)
		{
			errormsg = e_zerocount;
			goto doend;
		}
		if (argt & NOTADR)		/* e.g. :buffer 2, :sleep 3 */
		{
			ea.line2 = n;
			if (ea.addr_count == 0)
				ea.addr_count = 1;
		}
		else
		{
			ea.line1 = ea.line2;
			ea.line2 += n - 1;
			++ea.addr_count;
			/*
			 * Be vi compatible: no error message for out of range.
			 */
			if (ea.line2 > curbuf->b_ml.ml_line_count)
				ea.line2 = curbuf->b_ml.ml_line_count;
		}
	}
												/* no arguments allowed */
	if (!(argt & EXTRA) && *ea.arg != NUL &&
								 vim_strchr((char_u *)"|\"", *ea.arg) == NULL)
	{
		errormsg = e_trailing;
		goto doend;
	}

	if ((argt & NEEDARG) && *ea.arg == NUL)
	{
		errormsg = e_argreq;
		goto doend;
	}

	if (argt & XFILE)
	{
		int			expand_wildcards;		/* need to expand wildcards */
		char_u		*repl;
		int			srclen;

		/*
		 * Decide to expand wildcards *before* replacing '%', '#', etc.  If
		 * the file name contains a wildcard it should not cause expanding.
		 * (it will be expanded anyway if there is a wildcard before replacing).
		 */
		expand_wildcards = mch_has_wildcard(ea.arg);
		for (p = ea.arg; *p; )
		{
			/*
			 * Quick check if this cannot be the start of a special string.
			 */
			if (vim_strchr((char_u *)"%#<", *p) == NULL)
			{
				++p;
				continue;
			}

			/*
			 * Try to find a match at this position.
			 */
			repl = eval_vars(p, &srclen, &(ea.do_ecmd_lnum), &errormsg);
			if (errormsg != NULL)	/* error detected */
				goto doend;
			if (repl == NULL)		/* no match found */
			{
				p += srclen;
				continue;
			}

			/*
			 * The new command line is build in new_cmdline[].
			 * First allocate it.
			 */
			len = STRLEN(repl);
			i = STRLEN(*cmdlinep) + len + 3;
			if (ea.nextcomm)
				i += STRLEN(ea.nextcomm);	/* add space for next command */
			if ((new_cmdline = alloc(i)) == NULL)
			{
				vim_free(repl);
				goto doend;					/* out of memory! */
			}

			/*
			 * Copy the stuff before the expanded part.
			 * Copy the expanded stuff.
			 * Copy what came after the expanded part.
			 * Copy the next commands, if there are any.
			 */
			i = p - *cmdlinep;				/* length of part before match */
			vim_memmove(new_cmdline, *cmdlinep, (size_t)i);
			vim_memmove(new_cmdline + i, repl, (size_t)len);
			vim_free(repl);
			i += len; 						/* remember the end of the string */
			STRCPY(new_cmdline + i, p + srclen);
			p = new_cmdline + i;			/* remember where to continue */

			if (ea.nextcomm)				/* append next command */
			{
				i = STRLEN(new_cmdline) + 1;
				STRCPY(new_cmdline + i, ea.nextcomm);
				ea.nextcomm = new_cmdline + i;
			}
			ea.cmd = new_cmdline + (ea.cmd - *cmdlinep);
			ea.arg = new_cmdline + (ea.arg - *cmdlinep);
			vim_free(*cmdlinep);
			*cmdlinep = new_cmdline;
		}

		/*
		 * One file argument: Expand wildcards.
		 * Don't do this with ":r !command" or ":w !command".
		 */
		if ((argt & NOSPC) && !ea.usefilter)
		{
			/*
			 * May do this twice:
			 * 1. Replace environment variables.
			 * 2. Replace any other wildcards, remove backslashes.
			 */
			for (n = 1; n <= 2; ++n)
			{
				if (n == 2)
				{
#if defined(UNIX)
					/*
					 * Only for Unix we check for more than one file name.
					 * For other systems spaces are considered to be part
					 * of the file name.
					 * Only check here if there is no wildcard, otherwise
					 * ExpandOne will check for errors. This allows
					 * ":e `ls ve*.c`" on Unix.
					 */
					if (!expand_wildcards)
						for (p = ea.arg; *p; ++p)
						{
							/* skip escaped characters */
							if (p[1] && (*p == '\\' || *p == Ctrl('V')))
								++p;
							else if (vim_iswhite(*p))
							{
								errormsg = (char_u *)
												 "Only one file name allowed";
								goto doend;
							}
						}
#endif
					/*
					 * halve the number of backslashes (this is Vi compatible)
					 */
					backslash_halve(ea.arg, expand_wildcards);
				}

				if (expand_wildcards)
				{
					if (n == 1)
					{
						/*
						 * First loop: May expand environment variables.  This
						 * can be done much faster with expand_env() than with
						 * something else (e.g., calling a shell).
						 * After expanding environment variables, check again
						 * if there are still wildcards present.
						 */
						if (vim_strchr(ea.arg, '$')
#if defined(UNIX) || defined(OS2)
								 || vim_strchr(ea.arg, '~')
#endif
														 )
						{
							expand_env(ea.arg, NameBuff, MAXPATHL);
							expand_wildcards = mch_has_wildcard(NameBuff);
							p = NameBuff;
						}
						else
							p = NULL;
					}
					else /* n == 2 */
					{
						if ((p = ExpandOne(ea.arg, NULL, WILD_LIST_NOTFOUND,
												   WILD_EXPAND_FREE)) == NULL)
							goto doend;
					}
					if (p != NULL)
					{
						/*
						 * The tricky bit here is to replace the argument,
						 * while keeping the "ea.cmd" and "ea.nextcomm" the
						 * pointers correct.
						 */
						len = ea.arg - *cmdlinep;
						i = STRLEN(p) + len;
						if (ea.nextcomm)
							i += STRLEN(ea.nextcomm);
						if ((new_cmdline = alloc((unsigned)i + 2)) != NULL)
						{
							STRNCPY(new_cmdline, *cmdlinep, len);
							STRCPY(new_cmdline + len, p);
							if (ea.nextcomm)		/* append next command */
							{
								i = STRLEN(new_cmdline) + 1;
								STRCPY(new_cmdline + i, ea.nextcomm);
								ea.nextcomm = new_cmdline + i;
							}
							ea.cmd = new_cmdline + (ea.cmd - *cmdlinep);
							ea.arg = new_cmdline + len;
							vim_free(*cmdlinep);
							*cmdlinep = new_cmdline;
						}
						if (n == 2)		/* p came from ExpandOne() */
							vim_free(p);
					}
				}
			}
		}
	}

	/*
	 * Skip the command when it's not going to be executed.
	 *
	 * TODO: Make an exception for commands that handle a trailing command
	 * themselves.
	 */
	if (cstack->cs_idx >= 0 && !cstack->cs_active[cstack->cs_idx])
	{
		switch (ea.cmdidx)
		{
			case CMD_if:
			case CMD_elseif:
			case CMD_else:
			case CMD_endif:		break;

			default:			goto doend;
		}
	}


	/*
	 * Accept buffer name.  Cannot be used at the same time with a buffer
	 * number.
	 */
	if ((argt & BUFNAME) && *ea.arg && ea.addr_count == 0)
	{
		/*
		 * :bdelete and :bunload take several arguments, separated by spaces:
		 * find next space (skipping over escaped characters).
		 * The others take one argument: ignore trailing spaces.
		 */
		if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bunload)
			p = skiptowhite_esc(ea.arg);
		else
		{
			p = ea.arg + STRLEN(ea.arg);
			while (p > ea.arg && vim_iswhite(p[-1]))
				--p;
		}
		ea.line2 = buflist_findpat(ea.arg, p);
		if (ea.line2 < 0)			/* failed */
			goto doend;
		ea.addr_count = 1;
		ea.arg = skipwhite(p);
	}

/*
 * 6. switch on command name
 *
 * The "ea" structure holds the arguments that can be used.
 */
	switch (ea.cmdidx)
	{
		case CMD_quit:
				do_quit(&ea);
				break;

		/*
		 * try to quit all windows
		 */
		case CMD_qall:
				exiting = TRUE;
				if (ea.forceit || !check_changed_any())
					getout(0);
				not_exiting();
				break;

		/*
		 * close current window, unless it is the last one
		 */
		case CMD_close:
		case CMD_hide:
				close_window(curwin, FALSE);	/* don't free buffer */
				break;

		/*
		 * close all but current window, unless it is the last one
		 */
		case CMD_only:
				close_others(TRUE);
				break;

		case CMD_stop:
		case CMD_suspend:
				do_suspend(&ea);
				break;

		case CMD_exit:
		case CMD_xit:
		case CMD_wq:
				do_exit(&ea);
				break;

		case CMD_xall:		/* write all changed files and exit */
		case CMD_wqall:		/* write all changed files and quit */
				exiting = TRUE;
				/* FALLTHROUGH */

		case CMD_wall:		/* write all changed files */
				do_wqall(&ea);
				break;

		case CMD_preserve:					/* put everything in .swp file */
				ml_preserve(curbuf, TRUE);
				break;

		case CMD_recover:					/* recover file */
				recoverymode = TRUE;
				if (!check_changed(curbuf, FALSE, TRUE, ea.forceit) &&
					   (*ea.arg == NUL || setfname(ea.arg, NULL, TRUE) == OK))
					ml_recover();
				recoverymode = FALSE;
				break;

		case CMD_args:
				do_args(&ea);
				break;

		case CMD_wnext:
		case CMD_wNext:
		case CMD_wprevious:
				if (ea.cmd[1] == 'n')
					i = curwin->w_arg_idx + (int)ea.line2;
				else
					i = curwin->w_arg_idx - (int)ea.line2;
				ea.line1 = 1;
				ea.line2 = curbuf->b_ml.ml_line_count;
				if (do_write(&ea) != FAIL)
					do_argfile(&ea, i);
				break;

		case CMD_next:
		case CMD_snext:
				do_next(&ea);
				break;

		case CMD_previous:
		case CMD_sprevious:
		case CMD_Next:
		case CMD_sNext:
				do_argfile(&ea, curwin->w_arg_idx - (int)ea.line2);
				break;

		case CMD_rewind:
		case CMD_srewind:
				do_argfile(&ea, 0);
				break;

		case CMD_last:
		case CMD_slast:
				do_argfile(&ea, arg_file_count - 1);
				break;

		case CMD_argument:
		case CMD_sargument:
				if (ea.addr_count)
					i = ea.line2 - 1;
				else
					i = curwin->w_arg_idx;
				do_argfile(&ea, i);
				break;

		/*
		 * open a window for each argument
		 */
		case CMD_all:
		case CMD_sall:
				if (ea.addr_count == 0)
					ea.line2 = 9999;
				do_arg_all((int)ea.line2);
				break;

		case CMD_buffer:			/* :[N]buffer [N]	 to buffer N */
		case CMD_sbuffer:			/* :[N]sbuffer [N]	 to buffer N */
				if (*ea.arg)
					errormsg = e_trailing;
				else
				{
					if (ea.addr_count == 0)		/* default is current buffer */
						(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT
													   : DOBUF_GOTO,
												DOBUF_CURRENT, FORWARD, 0, 0);
					else
						(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT
													   : DOBUF_GOTO,
									  DOBUF_FIRST, FORWARD, (int)ea.line2, 0);
				}
				break;

		case CMD_bmodified:			/* :[N]bmod	[N]	  to next modified buffer */
		case CMD_sbmodified:		/* :[N]sbmod [N]  to next modified buffer */
				(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
										DOBUF_MOD, FORWARD, (int)ea.line2, 0);
				break;

		case CMD_bnext:				/* :[N]bnext [N]	 to next buffer */
		case CMD_sbnext:			/* :[N]sbnext [N]	 to next buffer */
				(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
									DOBUF_CURRENT, FORWARD, (int)ea.line2, 0);
				break;

		case CMD_bNext:				/* :[N]bNext [N]	 to previous buffer */
		case CMD_bprevious:			/* :[N]bprevious [N] to previous buffer */
		case CMD_sbNext:			/* :[N]sbNext [N]	  to previous buffer */
		case CMD_sbprevious:		/* :[N]sbprevious [N] to previous buffer */
				(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
								   DOBUF_CURRENT, BACKWARD, (int)ea.line2, 0);
				break;

		case CMD_brewind:			/* :brewind			 to first buffer */
		case CMD_sbrewind:			/* :sbrewind		 to first buffer */
				(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
												  DOBUF_FIRST, FORWARD, 0, 0);
				break;

		case CMD_blast:				/* :blast			 to last buffer */
		case CMD_sblast:			/* :sblast			 to last buffer */
				(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
												   DOBUF_LAST, FORWARD, 0, 0);
				break;

		case CMD_bunload:		/* :[N]bunload[!] [N] [bufname] unload buffer */
		case CMD_bdelete:		/* :[N]bdelete[!] [N] [bufname] delete buffer */
				errormsg = do_bufdel(ea.cmdidx == CMD_bdelete ? DOBUF_DEL
															  : DOBUF_UNLOAD,
										 ea.arg, ea.addr_count, (int)ea.line1,
												   (int)ea.line2, ea.forceit);
				break;

		case CMD_unhide:
		case CMD_sunhide:	/* open a window for loaded buffers */
				if (ea.addr_count == 0)
					ea.line2 = 9999;
				(void)do_buffer_all((int)ea.line2, FALSE);
				break;

		case CMD_ball:
		case CMD_sball:		/* open a window for every buffer */
				if (ea.addr_count == 0)
					ea.line2 = 9999;
				(void)do_buffer_all((int)ea.line2, TRUE);
				break;

		case CMD_buffers:
		case CMD_files:
		case CMD_ls:
				buflist_list();
				break;

		case CMD_write:
				if (ea.usefilter)		/* input lines to shell command */
					do_bang(1, ea.line1, ea.line2, FALSE, ea.arg, TRUE, FALSE);
				else
					(void)do_write(&ea);
				break;

		/*
		 * set screen mode
		 * if no argument given, just get the screen size and redraw
		 */
		case CMD_mode:
				if (*ea.arg == NUL || mch_screenmode(ea.arg) != FAIL)
					set_winsize(0, 0, FALSE);
				break;

		case CMD_resize:
				do_resize(&ea);
				break;

		/*
		 * :sview [+command] file	split window with new file, read-only
		 * :split [[+command] file] split window with current or new file
		 * :new [[+command] file]	split window with no or new file
		 */
		case CMD_sview:
		case CMD_split:
		case CMD_new:
				{
					WIN		*old_curwin;

					old_curwin = curwin;
					if (win_split(ea.addr_count ? (int)ea.line2
												: 0, FALSE) != FAIL)
						do_exedit(&ea, old_curwin);
				}
				break;

		case CMD_edit:
		case CMD_ex:
		case CMD_visual:
		case CMD_view:
				do_exedit(&ea, NULL);
				break;

#ifdef USE_GUI
		/*
		 * Change from the terminal version to the GUI version.  File names
		 * may be given to redefine the args list -- webb
		 */
		case CMD_gvim:
		case CMD_gui:
				do_gui(&ea);
				break;
#endif

		case CMD_file:
				do_file(ea.arg, ea.forceit);
				break;

		case CMD_swapname:
				if (curbuf->b_ml.ml_mfp == NULL ||
								(p = curbuf->b_ml.ml_mfp->mf_fname) == NULL)
					MSG("No swap file");
				else
					msg(p);
				break;

#if 0
		case CMD_mfstat:		/* print memfile statistics, for debugging */
				mf_statistics();
				break;
#endif

		case CMD_read:
				do_read(&ea);
				break;

		case CMD_cd:
		case CMD_chdir:
				do_cd(&ea);
				break;

		case CMD_pwd:
				do_pwd();
				break;

		case CMD_equal:
				smsg((char_u *)"line %ld", (long)ea.line2);
				break;

		case CMD_list:
				i = curwin->w_p_list;
				curwin->w_p_list = 1;
				do_print(&ea);
				curwin->w_p_list = i;
				break;

		case CMD_number:				/* :nu */
		case CMD_pound:					/* :# */
		case CMD_print:					/* :p */
				do_print(&ea);
				break;

		case CMD_shell:
				do_shell(NULL);
				break;

		case CMD_sleep:
				do_sleep(&ea);
				break;

		case CMD_stag:
				postponed_split = TRUE;
				/*FALLTHROUGH*/
		case CMD_tag:
				do_tag(ea.arg, 0, ea.addr_count ? (int)ea.line2
												: 1, ea.forceit);
				break;

		case CMD_pop:
				do_tag((char_u *)"", 1, ea.addr_count ? (int)ea.line2
													  : 1, ea.forceit);
				break;

		case CMD_tags:
				do_tags();
				break;

		case CMD_marks:
				do_marks(ea.arg);
				break;

		case CMD_jumps:
				do_jumps();
				break;

		case CMD_ascii:
				do_ascii();
				break;

		case CMD_checkpath:
				find_pattern_in_path(NULL, 0, FALSE, FALSE, CHECK_PATH, 1L,
								   ea.forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
											  (linenr_t)1, (linenr_t)MAXLNUM);
				break;

		case CMD_digraphs:
#ifdef DIGRAPHS
				if (*ea.arg)
					putdigraph(ea.arg);
				else
					listdigraphs();
#else
				EMSG("No digraphs in this version");
#endif /* DIGRAPHS */
				break;

		case CMD_set:
				(void)do_set(ea.arg);
				break;

		case CMD_fixdel:
				do_fixdel();
				break;

#ifdef AUTOCMD
		case CMD_autocmd:
				/*
				 * Disallow auto commands from .exrc and .vimrc in current
				 * directory for security reasons.
				 */
				if (secure)
				{
					secure = 2;
					errormsg = e_curdir;
				}
				else
					do_autocmd(ea.arg, ea.forceit);
				break;

		case CMD_doautocmd:
				do_doautocmd(ea.arg);		/* apply the automatic commands */
				do_modelines();
				break;
#endif

		case CMD_abbreviate:
		case CMD_noreabbrev:
		case CMD_unabbreviate:
		case CMD_cabbrev:
		case CMD_cnoreabbrev:
		case CMD_cunabbrev:
		case CMD_iabbrev:
		case CMD_inoreabbrev:
		case CMD_iunabbrev:
				do_exmap(&ea, TRUE);		/* almost the same as mapping */
				break;

		case CMD_nmap:
		case CMD_vmap:
		case CMD_cmap:
		case CMD_imap:
		case CMD_map:
		case CMD_nnoremap:
		case CMD_vnoremap:
		case CMD_cnoremap:
		case CMD_inoremap:
		case CMD_noremap:
				/*
				 * If we are sourcing .exrc or .vimrc in current directory we
				 * print the mappings for security reasons.
				 */
				if (secure)
				{
					secure = 2;
					msg_outtrans(ea.cmd);
					msg_putchar('\n');
				}
		case CMD_nunmap:
		case CMD_vunmap:
		case CMD_cunmap:
		case CMD_iunmap:
		case CMD_unmap:
				do_exmap(&ea, FALSE);
				break;

		case CMD_mapclear:
		case CMD_imapclear:
		case CMD_nmapclear:
		case CMD_vmapclear:
		case CMD_cmapclear:
				map_clear(*ea.cmd, ea.forceit, FALSE);
				break;

		case CMD_abclear:
		case CMD_iabclear:
		case CMD_cabclear:
				map_clear(*ea.cmd, TRUE, TRUE);
				break;

#ifdef USE_GUI
		case CMD_menu:		case CMD_noremenu:		case CMD_unmenu:
		case CMD_nmenu:		case CMD_nnoremenu:		case CMD_nunmenu:
		case CMD_vmenu:		case CMD_vnoremenu:		case CMD_vunmenu:
		case CMD_imenu:		case CMD_inoremenu:		case CMD_iunmenu:
		case CMD_cmenu:		case CMD_cnoremenu:		case CMD_cunmenu:
				gui_do_menu(ea.cmd, ea.arg, ea.forceit);
				break;
#endif /* USE_GUI */

		case CMD_display:
		case CMD_registers:
				do_dis(ea.arg);		/* display buffer contents */
				break;

		case CMD_help:
				do_help(ea.arg);
				break;

		case CMD_version:
				do_version(ea.arg);
				break;

		case CMD_winsize:					/* obsolete command */
				ea.line1 = getdigits(&ea.arg);
				ea.arg = skipwhite(ea.arg);
				ea.line2 = getdigits(&ea.arg);
				set_winsize((int)ea.line1, (int)ea.line2, TRUE);
				break;

		case CMD_delete:
		case CMD_yank:
		case CMD_rshift:
		case CMD_lshift:
				do_exops(&ea);
				break;

		case CMD_put:
				/*
				 * ":0put" works like ":1put!".
				 */
				if (ea.line2 == 0)
				{
					ea.line2 = 1;
					ea.forceit = TRUE;
				}
				curwin->w_cursor.lnum = ea.line2;
				do_put(ea.regname, ea.forceit ? BACKWARD : FORWARD, -1L, FALSE);
				break;

		case CMD_t:
		case CMD_copy:
		case CMD_move:
				do_copymove(&ea);
				break;

		case CMD_coffee:
				MSG("Insert the beans please.");
				break;

		case CMD_and:			/* :& */
		case CMD_tilde:			/* :~ */
		case CMD_substitute:	/* :s */
				do_sub(&ea);
				break;

		case CMD_join:
				do_exjoin(&ea);
				break;

		case CMD_global:
				if (ea.forceit)
					*ea.cmd = 'v';
		case CMD_vglobal:
				do_glob(&ea);
				break;

		case CMD_at:				/* :[addr]@r */
				do_at(&ea);
				break;

		case CMD_bang:
				do_bang(ea.addr_count, ea.line1, ea.line2,
											  ea.forceit, ea.arg, TRUE, TRUE);
				break;

		case CMD_undo:
				u_undo(1);
				break;

		case CMD_redo:
				u_redo(1);
				break;

		case CMD_source:
				if (ea.forceit)					/* :so! read vi commands */
					(void)openscript(ea.arg);
												/* :so read ex commands */
				else if (do_source(ea.arg, FALSE) == FAIL)
					emsg2(e_notopen, ea.arg);
				break;

#ifdef VIMINFO
		case CMD_rviminfo:
				p = p_viminfo;
				if (*p_viminfo == NUL)
					p_viminfo = (char_u *)"'100";
				if (read_viminfo(ea.arg, TRUE, TRUE, ea.forceit) == FAIL)
					EMSG("Cannot open viminfo file for reading");
				p_viminfo = p;
				break;

		case CMD_wviminfo:
				p = p_viminfo;
				if (*p_viminfo == NUL)
					p_viminfo = (char_u *)"'100";
				write_viminfo(ea.arg, ea.forceit);
				p_viminfo = p;
				break;
#endif /* VIMINFO */

		case CMD_mkvimrc:
				if (*ea.arg == NUL)
					ea.arg = (char_u *)VIMRC_FILE;
				/*FALLTHROUGH*/

		case CMD_mkexrc:
				do_mkrc(&ea);
				break;

		case CMD_cc:
				qf_jump(0, ea.addr_count ? (int)ea.line2 : 0, ea.forceit);
				break;

		case CMD_cfile:
				if (*ea.arg != NUL)
				{
					/*
					 * Great trick: Insert 'ef=' before ea.arg.
					 * Always ok, because "cf " must be there.
					 */
					ea.arg -= 3;
					ea.arg[0] = 'e';
					ea.arg[1] = 'f';
					ea.arg[2] = '=';
					(void)do_set(ea.arg);
				}
				if (qf_init() == OK)
					qf_jump(0, 0, ea.forceit);		/* display first error */
				break;

		case CMD_clist:
				qf_list(ea.forceit);
				break;

		case CMD_cnext:
				qf_jump(FORWARD, ea.addr_count ? (int)ea.line2 : 1, ea.forceit);
				break;

		case CMD_cNext:
		case CMD_cprevious:
				qf_jump(BACKWARD, ea.addr_count ? (int)ea.line2 : 1,
																  ea.forceit);
				break;

		case CMD_cquit:
				getout(1);		/* this does not always pass on the exit
								   code to the Manx compiler. why? */

		case CMD_mark:
		case CMD_k:
				do_setmark(&ea);
				break;

		case CMD_center:
		case CMD_right:
		case CMD_left:
				do_align(&ea);
				break;

		case CMD_retab:
				do_retab(&ea);
				break;

		case CMD_make:
				do_make(ea.arg);
				break;

		case CMD_normal:
				do_normal(&ea);
				break;

		case CMD_isearch:
		case CMD_dsearch:
				errormsg = do_findpat(&ea, ACTION_SHOW);
				break;

		case CMD_ilist:
		case CMD_dlist:
				errormsg = do_findpat(&ea, ACTION_SHOW_ALL);
				break;

		case CMD_ijump:
		case CMD_djump:
				errormsg = do_findpat(&ea, ACTION_GOTO);
				break;

		case CMD_isplit:
		case CMD_dsplit:
				errormsg = do_findpat(&ea, ACTION_SPLIT);
				break;

		/*
		 * ":echo arg .." and ":echon arg .."; Echo the arguments.
		 */
		case CMD_echo:
		case CMD_echon:
				do_echo(ea.arg, ea.cmdidx == CMD_echo);
				break;

		case CMD_syntax:
				do_syntax(ea.arg, &ea.nextcomm);
				break;

		case CMD_highlight:
				do_highlight(ea.arg);
				break;

		/*
		 * The conditional commands.
		 */
		case CMD_if:
				errormsg = do_if(&ea, cstack);
				break;

		case CMD_elseif:
		case CMD_else:
				errormsg = do_else(&ea, cstack);
				break;

		case CMD_endif:
				if (cstack->cs_idx < 0)
					errormsg = (char_u *)":endif without :if";
				else
					--cstack->cs_idx;
				break;

		/*
		 * ":let var = expr".
		 */
		case CMD_let:
				do_let(ea.arg);
				break;

		case CMD_unlet:
				do_unlet(ea.arg);
				break;

		case CMD_insert:
				do_append(ea.line2 - 1, getline, cookie);
				ex_no_reprint = TRUE;
				break;

		case CMD_append:
				do_append(ea.line2, getline, cookie);
				ex_no_reprint = TRUE;
				break;

		case CMD_change:
				do_change(ea.line1, ea.line2, getline, cookie);
				ex_no_reprint = TRUE;
				break;

		case CMD_z:
				do_z(ea.line2, ea.arg);
				ex_no_reprint = TRUE;
  				break;

		case CMD_intro:
				do_intro();
				break;

#ifdef HAVE_PERL_INTERP
				/*
				 * ":perl cmd"
				 * ":[range]perldo cmd"
				 */
		case CMD_perl:
				do_perl(&ea);
				break;

		case CMD_perldo:
				do_perldo(&ea);
				break;
#endif

#ifdef HAVE_PYTHON
		case CMD_python:
				do_python(&ea);
				break;

		case CMD_pyfile:
				do_pyfile(&ea);
				break;
#endif
		default:
					/* Normal illegal commands have already been handled */
				errormsg = (char_u *)"Sorry, this command is not implemented";
	}


doend:
	if (errormsg != NULL && *errormsg != NUL)
	{
		emsg(errormsg);
		if (sourcing)
		{
			MSG_PUTS(": ");
			msg_outtrans(*cmdlinep);
		}
	}
	if (did_emsg)
		ea.nextcomm = NULL;				/* cancel ea.nextcomm at an error */
	if (ea.nextcomm && *ea.nextcomm == NUL)		/* not really a next command */
		ea.nextcomm = NULL;
	return ea.nextcomm;
}

/*
 * This is all pretty much copied from do_one_cmd(), with all the extra stuff
 * we don't need/want deleted.  Maybe this could be done better if we didn't
 * repeat all this stuff.  The only problem is that they may not stay perfectly
 * compatible with each other, but then the command line syntax probably won't
 * change that much -- webb.
 */
	char_u *
set_one_cmd_context(buff)
	char_u		*buff;	 	/* buffer for command string */
{
	char_u				*p;
	char_u				*cmd, *arg;
	int 				i;
	int					cmdidx;
	long				argt;
	char_u				delim;
	int					forceit = FALSE;
	int					usefilter = FALSE;	/* filter instead of file name */

	expand_pattern = buff;
	expand_context = EXPAND_COMMANDS;	/* Default until we get past command */

/*
 * 2. skip comment lines and leading space, colons or bars
 */
	for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++)
		;
	expand_pattern = cmd;

	if (*cmd == NUL)
		return NULL;
	if (*cmd == '"')		/* ignore comment lines */
	{
		expand_context = EXPAND_NOTHING;
		return NULL;
	}

/*
 * 3. parse a range specifier of the form: addr [,addr] [;addr] ..
 */
	/*
	 * Backslashed delimiters after / or ? will be skipped, and commands will
	 * not be expanded between /'s and ?'s or after "'". -- webb
	 */
	while (*cmd != NUL && (vim_isspace(*cmd) || isdigit(*cmd) ||
							vim_strchr((char_u *)".$%'/?-+,;", *cmd) != NULL))
	{
		if (*cmd == '\'')
		{
			if (*++cmd == NUL)
				expand_context = EXPAND_NOTHING;
		}
		else if (*cmd == '/' || *cmd == '?')
		{
			delim = *cmd++;
			while (*cmd != NUL && *cmd != delim)
				if (*cmd++ == '\\' && *cmd != NUL)
					++cmd;
			if (*cmd == NUL)
				expand_context = EXPAND_NOTHING;
		}
		if (*cmd != NUL)
			++cmd;
	}

/*
 * 4. parse command
 */

	cmd = skipwhite(cmd);
	expand_pattern = cmd;
	if (*cmd == NUL)
		return NULL;
	if (*cmd == '"')
	{
		expand_context = EXPAND_NOTHING;
		return NULL;
	}

	if (*cmd == '|' || *cmd == '\n')
		return cmd + 1;					/* There's another command */

	/*
	 * Isolate the command and search for it in the command table.
	 * Exeptions:
	 * - the 'k' command can directly be followed by any character.
	 * - the 's' command can be followed directly by 'c', 'g' or 'r'
	 */
	if (*cmd == 'k')
	{
		cmdidx = CMD_k;
		p = cmd + 1;
	}
	else
	{
		p = cmd;
		while (isalpha(*p) || *p == '*')	/* Allow * wild card */
			++p;
			/* check for non-alpha command */
		if (p == cmd && vim_strchr((char_u *)"@!=><&~#", *p) != NULL)
			++p;
		i = (int)(p - cmd);

		if (i == 0)
		{
			expand_context = EXPAND_UNSUCCESSFUL;
			return NULL;
		}
		for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx)
			if (STRNCMP(cmdnames[cmdidx].cmd_name, cmd, (size_t)i) == 0)
				break;
	}

	/*
	 * If the cursor is touching the command, and it ends in an alphabetic
	 * character, complete the command name.
	 */
	if (*p == NUL && isalpha(p[-1]))
		return NULL;

	if (cmdidx == CMD_SIZE)
	{
		if (*cmd == 's' && vim_strchr((char_u *)"cgr", cmd[1]) != NULL)
		{
			cmdidx = CMD_substitute;
			p = cmd + 1;
		}
		else
		{
			/* Not still touching the command and it was an illegal command */
			expand_context = EXPAND_UNSUCCESSFUL;
			return NULL;
		}
	}

	expand_context = EXPAND_NOTHING; /* Default now that we're past command */

	if (*p == '!')					/* forced commands */
	{
		forceit = TRUE;
		++p;
	}

/*
 * 5. parse arguments
 */
	argt = cmdnames[cmdidx].cmd_argt;

	arg = skipwhite(p);

	if (cmdidx == CMD_write)
	{
		if (*arg == '>')						/* append */
		{
			if (*++arg == '>')				/* It should be */
				++arg;
			arg = skipwhite(arg);
		}
		else if (*arg == '!')					/* :w !filter */
		{
			++arg;
			usefilter = TRUE;
		}
	}

	if (cmdidx == CMD_read)
	{
		usefilter = forceit;					/* :r! filter if forced */
		if (*arg == '!')						/* :r !filter */
		{
			++arg;
			usefilter = TRUE;
		}
	}

	if (cmdidx == CMD_lshift || cmdidx == CMD_rshift)
	{
		while (*arg == *cmd)		/* allow any number of '>' or '<' */
			++arg;
		arg = skipwhite(arg);
	}

	/* Does command allow "+command"? */
	if ((argt & EDITCMD) && !usefilter && *arg == '+')
	{
		/* Check if we're in the +command */
		p = arg + 1;
		arg = skiptowhite(arg);

		/* Still touching the command after '+'? */
		if (*arg == NUL)
			return p;

		/* Skip space after +command to get to the real argument */
		arg = skipwhite(arg);
	}

	/*
	 * Check for '|' to separate commands and '"' to start comments.
	 * Don't do this for ":read !cmd" and ":write !cmd".
	 */
	if ((argt & TRLBAR) && !usefilter)
	{
		p = arg;
		while (*p)
		{
			if (*p == Ctrl('V'))
			{
				if (p[1] != NUL)
					++p;
			}
			else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|' || *p == '\n')
			{
				if (*(p - 1) != '\\')
				{
					if (*p == '|' || *p == '\n')
						return p + 1;
					return NULL;	/* It's a comment */
				}
			}
			++p;
		}
	}

												/* no arguments allowed */
	if (!(argt & EXTRA) && *arg != NUL &&
									vim_strchr((char_u *)"|\"", *arg) == NULL)
		return NULL;

	/* Find start of last argument (argument just before cursor): */
	p = buff + STRLEN(buff);
	while (p != arg && *p != ' ' && *p != TAB)
		p--;
	if (*p == ' ' || *p == TAB)
		p++;
	expand_pattern = p;

	if (argt & XFILE)
	{
		int in_quote = FALSE;
		char_u *bow = NULL;		/* Beginning of word */

		/*
		 * Allow spaces within back-quotes to count as part of the argument
		 * being expanded.
		 */
		expand_pattern = skipwhite(arg);
		for (p = expand_pattern; *p; ++p)
		{
			if (*p == '\\' && p[1])
				++p;
#ifdef SPACE_IN_FILENAME
			else if (vim_iswhite(*p) && (!(argt & NOSPC) || usefilter))
#else
			else if (vim_iswhite(*p))
#endif
			{
				p = skipwhite(p);
				if (in_quote)
					bow = p;
				else
					expand_pattern = p;
				--p;
			}
			else if (*p == '`')
			{
				if (!in_quote)
				{
					expand_pattern = p;
					bow = p + 1;
				}
				in_quote = !in_quote;
			}
		}

		/*
		 * If we are still inside the quotes, and we passed a space, just
		 * expand from there.
		 */
		if (bow != NULL && in_quote)
			expand_pattern = bow;
		expand_context = EXPAND_FILES;
	}

/*
 * 6. switch on command name
 */
	switch (cmdidx)
	{
		case CMD_cd:
		case CMD_chdir:
			expand_context = EXPAND_DIRECTORIES;
			break;
		case CMD_global:
		case CMD_vglobal:
			delim = *arg; 			/* get the delimiter */
			if (delim)
				++arg;				/* skip delimiter if there is one */

			while (arg[0] != NUL && arg[0] != delim)
			{
				if (arg[0] == '\\' && arg[1] != NUL)
					++arg;
				++arg;
			}
			if (arg[0] != NUL)
				return arg + 1;
			break;
		case CMD_and:
		case CMD_substitute:
			delim = *arg;
			if (delim)
				++arg;
			for (i = 0; i < 2; i++)
			{
				while (arg[0] != NUL && arg[0] != delim)
				{
					if (arg[0] == '\\' && arg[1] != NUL)
						++arg;
					++arg;
				}
				if (arg[0] != NUL)		/* skip delimiter */
					++arg;
			}
			while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL)
				++arg;
			if (arg[0] != NUL)
				return arg;
			break;
		case CMD_isearch:
		case CMD_dsearch:
		case CMD_ilist:
		case CMD_dlist:
		case CMD_ijump:
		case CMD_djump:
		case CMD_isplit:
		case CMD_dsplit:
			arg = skipwhite(skipdigits(arg));		/* skip count */
			if (*arg == '/')	/* Match regexp, not just whole words */
			{
				for (++arg; *arg && *arg != '/'; arg++)
					if (*arg == '\\' && arg[1] != NUL)
						arg++;
				if (*arg)
				{
					arg = skipwhite(arg + 1);

					/* Check for trailing illegal characters */
					if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL)
						expand_context = EXPAND_NOTHING;
					else
						return arg;
				}
			}
			break;
#ifdef AUTOCMD
		case CMD_autocmd:
			return set_context_in_autocmd(arg, FALSE);

		case CMD_doautocmd:
			return set_context_in_autocmd(arg, TRUE);
#endif
		case CMD_set:
			set_context_in_set_cmd(arg);
			break;
		case CMD_stag:
		case CMD_tag:
			expand_context = EXPAND_TAGS;
			expand_pattern = arg;
			break;
		case CMD_help:
			expand_context = EXPAND_HELP;
			expand_pattern = arg;
			break;
		case CMD_bdelete:
		case CMD_bunload:
			while ((expand_pattern = vim_strchr(arg, ' ')) != NULL)
				arg = expand_pattern + 1;
		case CMD_buffer:
		case CMD_sbuffer:
			expand_context = EXPAND_BUFFERS;
			expand_pattern = arg;
			break;
#ifdef USE_GUI
		case CMD_menu:		case CMD_noremenu:		case CMD_unmenu:
		case CMD_nmenu:		case CMD_nnoremenu:		case CMD_nunmenu:
		case CMD_vmenu:		case CMD_vnoremenu:		case CMD_vunmenu:
		case CMD_imenu:		case CMD_inoremenu:		case CMD_iunmenu:
		case CMD_cmenu:		case CMD_cnoremenu:		case CMD_cunmenu:
			return gui_set_context_in_menu_cmd(cmd, arg, forceit);
			break;
#endif
		default:
			break;
	}
	return NULL;
}

/*
 * get a single EX address
 *
 * Set ptr to the next character after the part that was interpreted.
 * Set ptr to NULL when an error is encountered.
 *
 * Return MAXLNUM when no Ex address was found.
 */
	static linenr_t
get_address(ptr)
	char_u		**ptr;
{
	int			c;
	int			i;
	long		n;
	char_u  	*cmd;
	FPOS		pos;
	FPOS		*fp;
	linenr_t	lnum;

	cmd = skipwhite(*ptr);
	lnum = MAXLNUM;
	do
	{
		switch (*cmd)
		{
			case '.': 						/* '.' - Cursor position */
						++cmd;
						lnum = curwin->w_cursor.lnum;
						break;

			case '$': 						/* '$' - last line */
						++cmd;
						lnum = curbuf->b_ml.ml_line_count;
						break;

			case '\'': 						/* ''' - mark */
						if (*++cmd == NUL || (check_mark(
										fp = getmark(*cmd++, FALSE)) == FAIL))
							goto error;
						lnum = fp->lnum;
						break;

			case '/':
			case '?':						/* '/' or '?' - search */
						c = *cmd++;
						pos = curwin->w_cursor;		/* save curwin->w_cursor */
						/*
						 * When '/' or '?' follows another address, start from
						 * there.
						 */
						if (lnum != MAXLNUM)
							curwin->w_cursor.lnum = lnum;
						/*
						 * Start a forward search at the end of the line.
						 * Start a backward search at the start of the line.
						 * This makes sure we never match in the current line,
						 * and can match anywhere in the next/previous line.
						 */
						if (c == '/')
							curwin->w_cursor.col = MAXCOL;
						else
							curwin->w_cursor.col = 0;
						searchcmdlen = 0;
						if (!do_search(NULL, c, cmd, 1L,
									  SEARCH_HIS + SEARCH_MSG + SEARCH_START))
						{
							cmd = NULL;
							curwin->w_cursor = pos;
							goto error;
						}
						lnum = curwin->w_cursor.lnum;
						curwin->w_cursor = pos;
											/* adjust command string pointer */
						cmd += searchcmdlen;
						break;

			case '\\':				/* "\?", "\/" or "\&", repeat search */
						++cmd;
						if (*cmd == '&')
							i = RE_SUBST;
						else if (*cmd == '?' || *cmd == '/')
							i = RE_SEARCH;
						else
						{
							emsg(e_backslash);
							cmd = NULL;
							goto error;
						}

						/*
						 * When search follows another address, start from
						 * there.
						 */
						if (lnum != MAXLNUM)
							pos.lnum = lnum;
						else
							pos.lnum = curwin->w_cursor.lnum;

						/*
						 * Start the search just like for the above do_search().
						 */
						if (*cmd != '?')
							pos.col = MAXCOL;
						else
							pos.col = 0;
						if (searchit(&pos, *cmd == '?' ? BACKWARD : FORWARD,
															 (char_u *)"", 1L,
										  SEARCH_MSG + SEARCH_START, i) == OK)
							lnum = pos.lnum;
						else
						{
							cmd = NULL;
							goto error;
						}
						++cmd;
						break;

			default:
						if (isdigit(*cmd))		/* absolute line number */
							lnum = getdigits(&cmd);
		}

		for (;;)
		{
			cmd = skipwhite(cmd);
			if (*cmd != '-' && *cmd != '+' && !isdigit(*cmd))
				break;

			if (lnum == MAXLNUM)
				lnum = curwin->w_cursor.lnum;	/* "+1" is same as ".+1" */
			if (isdigit(*cmd))
				i = '+';				/* "number" is same as "+number" */
			else
				i = *cmd++;
			if (!isdigit(*cmd))			/* '+' is '+1', but '+0' is not '+1' */
				n = 1;
			else
				n = getdigits(&cmd);
			if (i == '-')
				lnum -= n;
			else
				lnum += n;
		}
	} while (*cmd == '/' || *cmd == '?');

error:
	*ptr = cmd;
	return lnum;
}

/*
 * If 'autowrite' option set, try to write the file.
 *
 * return FAIL for failure, OK otherwise
 */
	int
autowrite(buf, forceit)
	BUF		*buf;
	int		forceit;
{
	if (!p_aw || (!forceit && buf->b_p_ro) || buf->b_ffname == NULL)
		return FAIL;
	return buf_write_all(buf);
}

/*
 * flush all buffers, except the ones that are readonly
 */
	void
autowrite_all()
{
	BUF		*buf;

	if (!p_aw)
		return;
	for (buf = firstbuf; buf; buf = buf->b_next)
		if (buf->b_changed && !buf->b_p_ro)
		{
			(void)buf_write_all(buf);
#ifdef AUTOCMD
			/* an autocommand may have deleted the buffer */
			if (!buf_valid(buf))
				buf = firstbuf;
#endif
		}
}

	static int
check_readonly(forceit)
	int		forceit;
{
	if (!forceit && curbuf->b_p_ro)
	{
		emsg(e_readonly);
		return TRUE;
	}
	return FALSE;
}

/*
 * return TRUE if buffer was changed and cannot be abandoned.
 */
	static int
check_changed(buf, checkaw, mult_win, forceit)
	BUF		*buf;
	int		checkaw;		/* do autowrite if buffer was changed */
	int		mult_win;		/* check also when several windows for the buffer */
	int		forceit;
{
	if (	!forceit &&
			buf->b_changed && (mult_win || buf->b_nwindows <= 1) &&
			(!checkaw || autowrite(buf, forceit) == FAIL))
	{
		emsg(e_nowrtmsg);
		return TRUE;
	}
	return FALSE;
}

/*
 * return TRUE if any buffer was changed and cannot be abandoned.
 * That changed buffer becomes the current buffer.
 */
	static int
check_changed_any()
{
	BUF		*buf;
	int		save;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
	{
		if (buf->b_changed)
		{
			/* There must be a wait_return for this message, do_buffer
			 * will cause a redraw */
			exiting = FALSE;
			if (EMSG2("No write since last change for buffer \"%s\"",
					buf->b_fname == NULL ? (char_u *)"No File" :
					buf->b_fname))
			{
				save = no_wait_return;
				no_wait_return = FALSE;
				wait_return(FALSE);
				no_wait_return = save;
			}
			(void)do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
			return TRUE;
		}
	}
	return FALSE;
}

/*
 * return FAIL if there is no filename, OK if there is one
 * give error message for FAIL
 */
	int
check_fname()
{
	if (curbuf->b_ffname == NULL)
	{
		emsg(e_noname);
		return FAIL;
	}
	return OK;
}

/*
 * flush the contents of a buffer, unless it has no file name
 *
 * return FAIL for failure, OK otherwise
 */
	static int
buf_write_all(buf)
	BUF		*buf;
{
	int		retval;
#ifdef AUTOCMD
	BUF		*old_curbuf = curbuf;
#endif

	retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
										 (linenr_t)1, buf->b_ml.ml_line_count,
												  FALSE, FALSE, TRUE, FALSE));
#ifdef AUTOCMD
	if (curbuf != old_curbuf)
		MSG("Warning: Entered other buffer unexpectedly (check autocommands)");
#endif
	return retval;
}

/*
 * get + command from ex argument
 */
	static char_u *
getargcmd(argp)
	char_u **argp;
{
	char_u *arg = *argp;
	char_u *command = NULL;

	if (*arg == '+')		/* +[command] */
	{
		++arg;
		if (vim_isspace(*arg))
			command = (char_u *)"$";
		else
		{
			/*
			 * should check for "\ " (but vi has a bug that prevents it to work)
			 */
			command = arg;
			arg = skiptowhite(command);
			if (*arg)
				*arg++ = NUL;	/* terminate command with NUL */
		}

		arg = skipwhite(arg);	/* skip over spaces */
		*argp = arg;
	}
	return command;
}

/*
 * Return TRUE if "str" starts with a backslash that should be removed.
 * For MS-DOS, WIN32 and OS/2 this is only done when the character after the
 * backslash is not a normal file name character.
 * Although '$' is a valid filename character, we remove the backslash before
 * it, to be able to disginguish between a filename that starts with '$' and
 * the name of an environment variable.
 */
	static int
is_backslash(str)
	char_u	*str;
{
#ifdef BACKSLASH_IN_FILENAME
	return (str[0] == '\\' && (str[1] == '$' ||
			(str[1] != NUL && str[1] != '*' && str[1] != '?'
						&& !(isfilechar(str[1]) && str[1] != '\\'))));
#else
	return (str[0] == '\\' && str[1] != NUL);
#endif
}

/*
 * Halve the number of backslashes in a file name argument.
 * For MS-DOS we only do this if the character after the backslash
 * is not a normal file character.
 * For Unix, when wildcards are going to be expanded, don't remove
 * backslashes before special characters.
 */
	void
backslash_halve(p, expand_wildcards)
	char_u	*p;
	int		expand_wildcards;		/* going to expand wildcards later */
{
	for ( ; *p; ++p)
		if (is_backslash(p)
#if defined(UNIX) || defined(OS2)
				&& !(expand_wildcards &&
						vim_strchr((char_u *)" *?[{`$\\", p[1]))
#endif
											   )
			STRCPY(p, p + 1);
}

/*
 * write current buffer to file 'eap->arg'
 * if 'eap->append' is TRUE, append to the file
 *
 * if *eap->arg == NUL write to current file
 * if b_notedited is TRUE, check for overwriting current file
 *
 * return FAIL for failure, OK otherwise
 */
	static int
do_write(eap)
	EXARG		*eap;
{
	int		other;
	char_u	*fname = NULL;				/* init to shut up gcc */
	char_u	*ffname;
	int		retval = FAIL;
	char_u	*free_fname = NULL;

	ffname = eap->arg;
	if (*ffname == NUL)
		other = FALSE;
	else
	{
		fname = ffname;
		free_fname = fix_fname(ffname);
		/*
		 * When out-of-memory, keep unexpanded filename, because we MUST be
		 * able to write the file in this situation.
		 */
		if (free_fname != NULL)
			ffname = free_fname;
		other = otherfile(ffname);
	}

	/*
	 * If we have a new file, name put it in the list of alternate file names.
	 */
	if (other && vim_strchr(p_cpo, CPO_ALTWRITE) != NULL)
		setaltfname(ffname, fname, (linenr_t)1);

	/*
	 * writing to the current file is not allowed in readonly mode
	 * and need a file name
	 */
	if (!other && (check_readonly(eap->forceit) || check_fname() == FAIL))
		goto theend;

	if (!other)
	{
		ffname = curbuf->b_ffname;
		fname = curbuf->b_fname;
		/*
		 * Not writing the whole file is only allowed with '!'.
		 */
		if (	   (eap->line1 != 1
					|| eap->line2 != curbuf->b_ml.ml_line_count)
				&& !eap->forceit
				&& !eap->append
				&& !p_wa)
		{
			EMSG("Use ! to write partial buffer");
			goto theend;
		}
	}

	/*
	 * write to other file or b_notedited set or not writing the whole file:
	 * overwriting only allowed with '!'
	 */
	if (	   (other
				|| curbuf->b_notedited)
			&& !eap->forceit
			&& !eap->append
			&& !p_wa
			&& vim_fexists(ffname))
	{
#ifdef UNIX
			/* with UNIX it is possible to open a directory */
		if (mch_isdir(ffname))
			EMSG2("\"%s\" is a directory", ffname);
		else
#endif
			emsg(e_exists);
		goto theend;
	}
	retval = (buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
									 eap->append, eap->forceit, TRUE, FALSE));
theend:
	vim_free(free_fname);
	return retval;
}

/*
 * try to abandon current file and edit a new or existing file
 * 'fnum' is the number of the file, if zero use ffname/sfname
 *
 * return 1 for "normal" error, 2 for "not written" error, 0 for success
 * -1 for succesfully opening another file
 * 'lnum' is the line number for the cursor in the new file (if non-zero).
 */
	int
getfile(fnum, ffname, sfname, setpm, lnum, forceit)
	int			fnum;
	char_u		*ffname;
	char_u		*sfname;
	int			setpm;
	linenr_t	lnum;
	int			forceit;
{
	int			other;
	int			retval;
	char_u		*free_me = NULL;

	if (fnum == 0)
	{
		fname_expand(&ffname, &sfname);	/* make ffname full path, set sfname */
		other = otherfile(ffname);
		free_me = ffname;				/* has been allocated, free() later */
	}
	else
		other = (fnum != curbuf->b_fnum);

	if (other)
		++no_wait_return;			/* don't wait for autowrite message */
	if (other && !forceit && curbuf->b_nwindows == 1 &&
			!p_hid && curbuf->b_changed && autowrite(curbuf, forceit) == FAIL)
	{
		if (other)
			--no_wait_return;
		emsg(e_nowrtmsg);
		retval = 2;		/* file has been changed */
		goto theend;
	}
	if (other)
		--no_wait_return;
	if (setpm)
		setpcmark();
	if (!other)
	{
		if (lnum != 0)
			curwin->w_cursor.lnum = lnum;
		check_cursor_lnum();
		beginline(MAYBE);
		retval = 0;		/* it's in the same file */
	}
	else if (do_ecmd(fnum, ffname, sfname, NULL, lnum,
				(p_hid ? ECMD_HIDE : 0) + (forceit ? ECMD_FORCEIT : 0)) == OK)
		retval = -1;	/* opened another file */
	else
		retval = 1;		/* error encountered */

theend:
	vim_free(free_me);
	return retval;
}

/*
 * start editing a new file
 *
 *	   fnum: file number; if zero use ffname/sfname
 *   ffname: the file name
 *				- full path if sfname used,
 *				- any file name if sfname is NULL
 *				- empty string to re-edit with the same file name (but may be
 *					in a different directory)
 *				- NULL to start an empty buffer
 *   sfname: the short file name (or NULL)
 *  command: the command to be executed after loading the file
 *  newlnum: put cursor on this line number (if possible)
 *	  flags:
 *		   ECMD_HIDE: if TRUE don't free the current buffer
 *	   ECMD_SET_HELP: set b_help flag of (new) buffer before opening file
 *		 ECMD_OLDBUF: use existing buffer if it exists
 *		ECMD_FORCEIT: ! used for Ex command
 *
 * return FAIL for failure, OK otherwise
 */
	int
do_ecmd(fnum, ffname, sfname, command, newlnum, flags)
	int			fnum;
	char_u		*ffname;
	char_u		*sfname;
	char_u		*command;
	linenr_t	newlnum;
 	int			flags;
{
	int			other_file;				/* TRUE if editing another file */
	int			oldbuf;					/* TRUE if using existing buffer */
#ifdef AUTOCMD
	int			auto_buf = FALSE;		/* TRUE if autocommands brought us
										   into the buffer unexpectedly */
#endif
	BUF			*buf;
	char_u		*free_fname = NULL;
	int			retval = FAIL;

	if (fnum != 0)
	{
		if (fnum == curbuf->b_fnum)		/* file is already being edited */
			return OK;					/* nothing to do */
		other_file = TRUE;
	}
	else
	{
			/* if no short name given, use ffname for short name */
		if (sfname == NULL)
			sfname = ffname;
#ifdef USE_FNAME_CASE
# ifdef USE_LONG_FNAME
		if (USE_LONG_FNAME)
# endif
			fname_case(sfname);			/* set correct case for short filename */
#endif

		if (ffname == NULL)
			other_file = TRUE;
											/* there is no file name */
		else if (*ffname == NUL && curbuf->b_ffname == NULL)
			other_file = FALSE;
		else
		{
			if (*ffname == NUL)				/* re-edit with same file name */
			{
				ffname = curbuf->b_ffname;
				sfname = curbuf->b_fname;
			}
			free_fname = fix_fname(ffname);	/* may expand to full path name */
			if (free_fname != NULL)
				ffname = free_fname;
			other_file = otherfile(ffname);
		}
	}
/*
 * if the file was changed we may not be allowed to abandon it
 * - if we are going to re-edit the same file
 * - or if we are the only window on this file and if ECMD_HIDE is FALSE
 */
	if (((!other_file && !(flags & ECMD_OLDBUF)) ||
			(curbuf->b_nwindows == 1 && !(flags & ECMD_HIDE))) &&
			check_changed(curbuf, FALSE, !other_file, (flags & ECMD_FORCEIT)))
	{
		if (fnum == 0 && other_file && ffname != NULL)
			setaltfname(ffname, sfname, (linenr_t)1);
		goto theend;
	}

/*
 * End Visual mode before switching to another buffer, so the text can be
 * copied into the GUI selection buffer.
 */
	if (VIsual_active)
		end_visual_mode();

/*
 * If we are starting to edit another file, open a (new) buffer.
 * Otherwise we re-use the current buffer.
 */
	if (other_file)
	{
		curwin->w_alt_fnum = curbuf->b_fnum;
		buflist_altlnum();

		if (fnum)
			buf = buflist_findnr(fnum);
		else
			buf = buflist_new(ffname, sfname, 1L, TRUE);
		if (buf == NULL)
			goto theend;
		if (buf->b_ml.ml_mfp == NULL)		/* no memfile yet */
		{
			oldbuf = FALSE;
			buf->b_nwindows = 0;
		}
		else								/* existing memfile */
		{
			oldbuf = TRUE;
			buf_check_timestamp(buf);
		}

		/*
		 * Make the (new) buffer the one used by the current window.
		 * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE.
		 * If the current buffer was empty and has no file name, curbuf
		 * is returned by buflist_new().
		 */
		if (buf != curbuf)
		{
#ifdef AUTOCMD
			BUF		*old_curbuf;
			char_u	*new_name = NULL;

			/*
			 * Be careful: The autocommands may delete any buffer and change
			 * the current buffer.
			 * - If the buffer we are going to edit is deleted, give up.
			 * - If we ended up in the new buffer already, need to skip a few
			 *   things, set auto_buf.
			 */
			old_curbuf = curbuf;
			if (buf->b_fname != NULL)
				new_name = vim_strsave(buf->b_fname);
			apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE);
			if (!buf_valid(buf))		/* new buffer has been deleted */
			{
				EMSG2("Autocommands unexpectedly deleted new buffer %s",
						new_name == NULL ? (char_u *)"" : new_name);
				vim_free(new_name);
				goto theend;
			}
			vim_free(new_name);
			if (buf == curbuf)			/* already in new buffer */
				auto_buf = TRUE;
			else
			{
				if (curbuf == old_curbuf)
#endif
					buf_copy_options(curbuf, buf, TRUE, FALSE);
				close_buffer(curwin, curbuf, !(flags & ECMD_HIDE), FALSE);
				curwin->w_buffer = buf;
				curbuf = buf;
				++curbuf->b_nwindows;
#ifdef AUTOCMD
			}
#endif
		}
		else
			++curbuf->b_nwindows;

		curwin->w_pcmark.lnum = 1;
		curwin->w_pcmark.col = 0;
	}
	else
	{
		if (check_fname() == FAIL)
			goto theend;
		oldbuf = (flags & ECMD_OLDBUF);
	}

/*
 * If we get here we are sure to start editing
 */
	/* don't redraw until the cursor is in the right line */
	++RedrawingDisabled;
	if (flags & ECMD_SET_HELP)
		curbuf->b_help = TRUE;

/*
 * other_file	oldbuf
 *	FALSE		FALSE		re-edit same file, buffer is re-used
 *	FALSE		TRUE		re-edit same file, nothing changes
 *  TRUE		FALSE		start editing new file, new buffer
 *  TRUE		TRUE		start editing in existing buffer (nothing to do)
 */
	if (!other_file && !oldbuf)			/* re-use the buffer */
	{
		if (newlnum == 0)
			newlnum = curwin->w_cursor.lnum;
		buf_freeall(curbuf);			/* free all things for buffer */
		buf_clear(curbuf);
		curbuf->b_op_start.lnum = 0;	/* clear '[ and '] marks */
		curbuf->b_op_end.lnum = 0;
	}

	/*
	 * Reset cursor position, could be used by autocommands.
	 */
	adjust_cursor();

	/*
	 * Check if we are editing the w_arg_idx file in the argument list.
	 */
	check_arg_idx();

#ifdef AUTOCMD
	if (!auto_buf)
#endif
	{
		/*
		 * Set cursor and init window before reading the file and executing
		 * autocommands.  This allows for the autocommands to position the
		 * cursor.
		 */
		win_init(curwin);

		/*
		 * Careful: open_buffer() and apply_autocmds() may change the current
		 * buffer and window.
		 */
		if (!oldbuf)						/* need to read the file */
			(void)open_buffer(FALSE);
#ifdef AUTOCMD
		else
			apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE);
		check_arg_idx();
#endif
		maketitle();
	}

	if (command == NULL)
	{
		if (newlnum)
		{
			curwin->w_cursor.lnum = newlnum;
			check_cursor_lnum();
			beginline(MAYBE);
		}
		else
		{
			if (exmode_active)
			{
				curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
				check_cursor_lnum();
			}
			beginline(TRUE);
		}
	}

	/*
	 * Did not read the file, need to show some info about the file.
	 * Do this after setting the cursor.
	 */
	if (oldbuf
#ifdef AUTOCMD
				&& !auto_buf
#endif
							)
		fileinfo(FALSE, TRUE, FALSE);

	if (command != NULL)
		do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
	--RedrawingDisabled;
	if (!skip_redraw)
	{
		update_topline();
		update_screen(NOT_VALID);			/* redraw now */
	}

	if (p_im)
		need_start_insertmode = TRUE;
	retval = OK;

theend:
	vim_free(free_fname);
	return retval;
}

	static void
do_make(arg)
	char_u *arg;
{
	if (*p_ef == NUL)
	{
		EMSG("errorfile option not set");
		return;
	}

	autowrite_all();
	vim_remove(p_ef);

	/*
	 * If 'shellpipe' empty: don't redirect to 'errorfile'.
	 */
	if (*p_sp == NUL)
		sprintf((char *)IObuff, "%s%s%s", p_shq, arg, p_shq);
	else
		sprintf((char *)IObuff, "%s%s%s %s %s", p_shq, arg, p_shq, p_sp, p_ef);
	/*
	 * Output a newline if there's something else than the :make command that
	 * was typed (in which case the cursor is in column 0).
	 */
	if (msg_col != 0)
		msg_putchar('\n');
	MSG_PUTS(":!");
	msg_outtrans(IObuff);				/* show what we are doing */
	do_shell(IObuff);

#ifdef AMIGA
	flushbuf();
				/* read window status report and redraw before message */
	(void)char_avail();
#endif

	if (qf_init() == OK)
		qf_jump(0, 0, FALSE);			/* display first error */

	vim_remove(p_ef);
}

/*
 * Redefine the argument list to 'str'.
 *
 * Return FAIL for failure, OK otherwise.
 */
	static int
do_arglist(str)
	char_u *str;
{
	int		new_count = 0;
	char_u	**new_files = NULL;
	int		exp_count;
	char_u	**exp_files;
	char_u	**t;
	char_u	*p;
	int		inquote;
	int		i;

	while (*str)
	{
		/*
		 * create a new entry in new_files[]
		 */
		t = (char_u **)lalloc((long_u)(sizeof(char_u *) * (new_count + 1)), TRUE);
		if (t != NULL)
			for (i = new_count; --i >= 0; )
				t[i] = new_files[i];
		vim_free(new_files);
		if (t == NULL)
			return FAIL;
		new_files = t;
		new_files[new_count++] = str;

		/*
		 * isolate one argument, taking quotes
		 */
		inquote = FALSE;
		for (p = str; *str; ++str)
		{
			/*
			 * for MSDOS et.al. a backslash is part of a file name.
			 * Only skip ", space and tab.
			 */
			if (is_backslash(str))
				*p++ = *++str;
			else
			{
				if (!inquote && vim_isspace(*str))
					break;
				if (*str == '"')
					inquote ^= TRUE;
				else
					*p++ = *str;
			}
		}
		str = skipwhite(str);
		*p = NUL;
	}

	i = ExpandWildCards(new_count, new_files, &exp_count,
												&exp_files, FALSE, TRUE);
	vim_free(new_files);
	if (i == FAIL)
		return FAIL;
	if (exp_count == 0)
	{
		emsg(e_nomatch);
		return FAIL;
	}
	FreeWild(arg_file_count, arg_files);
	arg_files = exp_files;
	arg_file_count = exp_count;
	arg_had_last = FALSE;

	/*
	 * put all file names in the buffer list
	 */
	for (i = 0; i < arg_file_count; ++i)
		(void)buflist_add(arg_files[i]);

	return OK;
}

/*
 * Check if we are editing the w_arg_idx file in the argument list.
 */
	void
check_arg_idx()
{
	int		t;

	if (arg_file_count > 1 && (curbuf->b_ffname == NULL ||
						  curwin->w_arg_idx >= arg_file_count ||
				(t = fullpathcmp(arg_files[curwin->w_arg_idx],
						   curbuf->b_ffname)) == FPC_DIFF || t == FPC_DIFFX))
		curwin->w_arg_idx_invalid = TRUE;
	else
		curwin->w_arg_idx_invalid = FALSE;
}

	int
ends_excmd(c)
	int		c;
{
	return (c == NUL || c == '|' || c == '\"' || c == '\n');
}

/*
 * Check if *p is a separator between Ex commands.
 * Return NULL if it isn't, (p + 1) if it is.
 */
	char_u *
check_nextcomm(p)
	char_u		*p;
{
	if (*p == '|' || *p == '\n')
		return (p + 1);
	else
		return NULL;
}

/*
 * - if there are more files to edit
 * - and this is the last window
 * - and forceit not used
 * - and not repeated twice on a row
 *	  return FAIL and give error message if 'message' TRUE
 * return OK otherwise
 */
	static int
check_more(message, forceit)
	int message;			/* when FALSE check only, no messages */
	int forceit;
{
	if (!forceit && only_one_window() && arg_file_count > 1 && !arg_had_last &&
									quitmore == 0)
	{
		if (message)
		{
			EMSGN("%ld more files to edit",
									  arg_file_count - curwin->w_arg_idx - 1);
			quitmore = 2;			/* next try to quit is allowed */
		}
		return FAIL;
	}
	return OK;
}

/*
 * Structure used to store info for each sourced file.
 * It is shared between do_source() and getsourceline().
 * This is required, because it needs to be handed to do_cmdline() and
 * sourcing can be done recursively.
 */
struct source_cookie
{
	FILE		*fp;			/* opened file for sourcing */
#ifdef USE_CRNL
	int			textmode;		/* -1 = unknown, 0 = NL, 1 = CR-NL */
	int			error;			/* TRUE if LF found after CR-LF */
#endif
};

/*
 * do_source: Read the file "fname" and execute its lines as EX commands.
 *
 * This function may be called recursively!
 *
 * return FAIL if file could not be opened, OK otherwise
 */
	int
do_source(fname, check_other)
	char_u 		*fname;
	int			check_other;		/* check for .vimrc and _vimrc */
{
	struct source_cookie	*cookie;
	char_u					*save_sourcing_name;
	linenr_t				save_sourcing_lnum;
	char_u					*p;

	cookie = (struct source_cookie *)alloc((unsigned)sizeof(struct source_cookie));
	if (cookie == NULL)
		return FAIL;

	/* use NameBuff for expanded name */
	expand_env(fname, NameBuff, MAXPATHL);
	cookie->fp = fopen((char *)NameBuff, READBIN);
	if (cookie->fp == NULL && check_other)
	{
		/*
		 * Try again, replacing file name ".vimrc" by "_vimrc" or vice versa,
		 * and ".exrc" by "_exrc" or vice versa.
		 */
		p = gettail(NameBuff);
		if ((*p == '.' || *p == '_') &&
				(STRICMP(p + 1, "vimrc") == 0 ||
				 STRICMP(p + 1, "gvimrc") == 0 ||
				 STRICMP(p + 1, "exrc") == 0))
		{
			if (*p == '_')
				*p = '.';
			else
				*p = '_';
			cookie->fp = fopen((char *)NameBuff, READBIN);
		}
	}

	if (cookie->fp == NULL)
	{
		vim_free(cookie);
		return FAIL;
	}

#ifdef USE_CRNL
	/* If no automatic textmode: Set default to CR-NL. */
	if (!p_ta)
		cookie->textmode = 1;
	else
		cookie->textmode = -1;
	cookie->error = FALSE;
#endif

	/*
	 * Keep the sourcing name, for recursive calls.
	 */
	save_sourcing_name = sourcing_name;
	save_sourcing_lnum = sourcing_lnum;
	sourcing_name = fname;
	sourcing_lnum = 0;
#ifdef SLEEP_IN_EMSG
	++dont_sleep;			/* don't call sleep() in emsg() */
#endif

	/*
	 * Call do_cmdline, which will call getsourceline() to get the lines.
	 */
	do_cmdline(NULL, getsourceline, (void *)cookie,
									 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT);

	fclose(cookie->fp);
	if (got_int)
		emsg(e_interr);
#ifdef SLEEP_IN_EMSG
	--dont_sleep;
#endif
	sourcing_name = save_sourcing_name;
	sourcing_lnum = save_sourcing_lnum;
	vim_free(cookie);
	return OK;
}

/*
 * Get one full line from a sourced file.
 * Called by do_source() and do_cmdline().
 *
 * Return a pointer to the line (IObuff) or NULL for end-of-file.
 */
	char_u *
getsourceline(c, cookie, indent)
	int		c;				/* not used */
	void	*cookie;
	int		indent;			/* not used */
{
	struct source_cookie	*sp = (struct source_cookie *)cookie;
	int						len = 0;
#ifdef USE_CRNL
	int						has_cr;			/* CR-LF found */
#endif

	/*
	 * Loop until there is a finished line (or end-of-file).
	 */
	for (;;)
	{
		if (fgets((char *)IObuff + len, IOSIZE - len, sp->fp) == NULL ||
				got_int)
			break;

		++sourcing_lnum;
		len = STRLEN(IObuff) - 1;
#ifdef USE_CRNL
		/* Ignore a trailing CTRL-Z, when in textmode */
		if (len == 0 && sp->textmode == 1 && IObuff[0] == Ctrl('Z'))
			break;
#endif
		if (len >= 0 && IObuff[len] == '\n')	/* remove trailing newline */
		{
#ifdef USE_CRNL
			has_cr = (len > 0 && IObuff[len - 1] == '\r');
			if (sp->textmode == -1)
			{
				if (has_cr)
					sp->textmode = 1;
				else
					sp->textmode = 0;
			}

			if (sp->textmode)
			{
				if (has_cr) 		/* remove trailing CR */
					--len;
				else		/* lines like ":map xx yy^M" will have failed */
				{
					if (!sp->error)
						EMSG("Warning: Wrong line separator, ^M may be missing");
					sp->error = TRUE;
					sp->textmode = 0;
				}
			}
#endif
				/* escaped newline, read more */
			if (len > 0 && len < IOSIZE - 2 && IObuff[len - 1] == Ctrl('V'))
			{
				IObuff[len - 1] = '\n';		/* remove CTRL-V */
				continue;
			}
			IObuff[len] = NUL;
		}

		/*
		 * Check for ^C here now and then, so recursive :so can be broken.
		 */
		line_breakcheck();
		return vim_strsave(IObuff);
	}

	return NULL;
}

	int
ExpandCommands(prog, num_file, file)
	vim_regexp	*prog;
	int			*num_file;
	char_u		***file;
{
	int		cmdidx;
	int		count;
	int		round;

	/*
	 * round == 1: Count the matches.
	 * round == 2: Save the matches into the array.
	 */
	for (round = 1; round <= 2; ++round)
	{
		count = 0;
		for (cmdidx = 0; cmdidx < CMD_SIZE; cmdidx++)
			if (vim_regexec(prog, cmdnames[cmdidx].cmd_name, TRUE))
			{
				if (round == 1)
					count++;
				else
					(*file)[count++] = vim_strsave(cmdnames[cmdidx].cmd_name);
			}
		if (round == 1)
		{
			*num_file = count;
			if (count == 0 || (*file = (char_u **)
						 alloc((unsigned)(count * sizeof(char_u *)))) == NULL)
				return FAIL;
		}
	}
	return OK;
}

/*
 * Call this function if we thought we were going to exit, but we won't
 * (because of an error).  May need to restore the terminal mode.
 */
	static void
not_exiting()
{
	exiting = FALSE;
	if (!exmode_active)
		settmode(TMODE_RAW);
}

/*
 * ":quit": quit current window, quit Vim if closed the last window.
 */
	static void
do_quit(eap)
	EXARG		*eap;
{
	/*
	 * If there are more files or windows we won't exit.
	 */
	if (check_more(FALSE, eap->forceit) == OK && only_one_window())
		exiting = TRUE;
	if (check_changed(curbuf, FALSE, FALSE, eap->forceit)
			|| check_more(TRUE, eap->forceit) == FAIL
			|| (only_one_window()
				&& !eap->forceit && check_changed_any()))
	{
		not_exiting();
	}
	else
	{
		if (only_one_window())		/* quit last window */
			getout(0);
		close_window(curwin, TRUE);	/* may free buffer */
	}
}

/*
 * ":stop" and ":suspend": Suspend Vim.
 */
	static void
do_suspend(eap)
	EXARG		*eap;
{
	/*
	 * Disallow suspending for "rvim".
	 */
	if (!check_restricted()
#ifdef WIN32
		/*
		 * Check if external commands are allowed now.
		 */
		&& can_end_termcap_mode(TRUE)
#endif
										)
	{
		if (!eap->forceit)
			autowrite_all();
		windgoto((int)Rows - 1, 0);
		outchar('\n');
		flushbuf();
		stoptermcap();
		mch_restore_title(3);	/* restore window titles */
		ui_suspend();			/* call machine specific function */
		maketitle();
		starttermcap();
		scroll_start();			/* scroll screen before redrawing */
		must_redraw = CLEAR;
		set_winsize(0, 0, FALSE); /* May have resized window */
	}
}

/*
 * ":exit", ":xit" and ":wq": Write file and exit Vim.
 */
	static void
do_exit(eap)
	EXARG		*eap;
{
	/*
	 * if more files or windows we won't exit
	 */
	if (check_more(FALSE, eap->forceit) == OK && only_one_window())
		exiting = TRUE;
	if (	   ((eap->cmdidx == CMD_wq
					|| curbuf->b_changed)
				&& do_write(eap) == FAIL)
			|| check_more(TRUE, eap->forceit) == FAIL
			|| (only_one_window()
				&& !eap->forceit && check_changed_any()))
	{
		not_exiting();
	}
	else
	{
		if (only_one_window())		/* quit last window, exit Vim */
			getout(0);
		close_window(curwin, TRUE);	/* quit current window, may free buffer */
	}
}

/*
 * ":wall", ":wqall" and ":xall": Write all changed files (and exit).
 */
	static void
do_wqall(eap)
	EXARG		*eap;
{
	BUF		*buf;
	int		error = 0;

	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
	{
		if (buf->b_changed)
		{
			if (buf->b_ffname == NULL)
			{
				emsg(e_noname);
				++error;
			}
			else if (!eap->forceit && buf->b_p_ro)
			{
				EMSG2("\"%s\" is readonly, use ! to write anyway",
																buf->b_fname);
				++error;
			}
			else
			{
				if (buf_write_all(buf) == FAIL)
					++error;
#ifdef AUTOCMD
				/* an autocommand may have deleted the buffer */
				if (!buf_valid(buf))
					buf = firstbuf;
#endif
			}
		}
	}
	if (exiting)
	{
		if (!error)
			getout(0);			/* exit Vim */
		not_exiting();
	}
}

	static void
do_print(eap)
	EXARG		*eap;
{
	for ( ;!got_int; ui_breakcheck())
	{
		print_line(eap->line1,
				   (eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound));
		if (++eap->line1 > eap->line2)
			break;
		flushbuf();			/* show one line at a time */
	}
	setpcmark();
	/* put cursor at last line */
	curwin->w_cursor.lnum = eap->line2;

	ex_no_reprint = TRUE;
}

/*
 * Edit file "argn" from the arguments.
 */
	static void
do_argfile(eap, argn)
	EXARG	*eap;
	int		argn;
{
	int 	other;
	char_u	*p;

	if (argn < 0 || argn >= arg_file_count)
	{
		if (arg_file_count <= 1)
			EMSG("There is only one file to edit");
		else if (argn < 0)
			EMSG("Cannot go before first file");
		else
			EMSG("Cannot go beyond last file");
	}
	else
	{
		setpcmark();
		if (*eap->cmd == 's')		/* split window first */
		{
			if (win_split(0, FALSE) == FAIL)
				return;
		}
		else
		{
			/*
			 * if 'hidden' set, only check for changed file when re-editing
			 * the same buffer
			 */
			other = TRUE;
			if (p_hid)
			{
				p = fix_fname(arg_files[argn]);
				other = otherfile(p);
				vim_free(p);
			}
			if ((!p_hid || !other)
						 && check_changed(curbuf, TRUE, !other, eap->forceit))
				return;
		}

		curwin->w_arg_idx = argn;
		if (argn == arg_file_count - 1)
			arg_had_last = TRUE;
		(void)do_ecmd(0, arg_files[curwin->w_arg_idx],
					  NULL, eap->do_ecmd_cmd, eap->do_ecmd_lnum,
					  (p_hid ? ECMD_HIDE : 0) +
										   (eap->forceit ? ECMD_FORCEIT : 0));
	}
}

/*
 * Do ":next" command, and commands that behave like it.
 */
	static void
do_next(eap)
	EXARG		*eap;
{
	int		i;

	/*
	 * check for changed buffer now, if this fails the argument list is not
	 * redefined.
	 */
	if (	   p_hid
			|| eap->cmdidx == CMD_snext
			|| !check_changed(curbuf, TRUE, FALSE, eap->forceit))
	{
		if (*eap->arg != NUL)				/* redefine file list */
		{
			if (do_arglist(eap->arg) == FAIL)
				return;
			i = 0;
		}
		else
			i = curwin->w_arg_idx + (int)eap->line2;
		do_argfile(eap, i);
	}
}

/*
 * Handle ":args" command.
 */
	static void
do_args(eap)
	EXARG		*eap;
{
	int		i;

	/* ":args file": handle like :next */
	if (!ends_excmd(*eap->arg))
		do_next(eap);
	else
	{
		if (arg_file_count == 0)			/* no file name list */
		{
			if (check_fname() == OK)		/* check for no file name */
				smsg((char_u *)"[%s]", curbuf->b_ffname);
		}
		else
		{
			/*
			 * Overwrite the command, in most cases there is no scrolling
			 * required and no wait_return().
			 */
			gotocmdline(TRUE);
			for (i = 0; i < arg_file_count; ++i)
			{
				if (i == curwin->w_arg_idx)
					msg_putchar('[');
				msg_outtrans(arg_files[i]);
				if (i == curwin->w_arg_idx)
					msg_putchar(']');
				msg_putchar(' ');
			}
		}
	}
}

/*
 * Handle ":resize" command.
 * set, increment or decrement current window height
 */
	static void
do_resize(eap)
	EXARG		*eap;
{
	int			n;

	n = atol((char *)eap->arg);
	if (*eap->arg == '-' || *eap->arg == '+')
		n += curwin->w_height;
	else if (n == 0)		/* default is very high */
		n = 9999;
	win_setheight((int)n);
}

	static void
do_exedit(eap, old_curwin)
	EXARG		*eap;
	WIN			*old_curwin;
{
	int		n;

	/*
	 * ":vi" command ends Ex mode.
	 */
	if (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)
	{
		exmode_active = FALSE;
		if (*eap->arg == NUL)
			return;
	}

	if ((eap->cmdidx == CMD_new) && *eap->arg == NUL)
	{
		setpcmark();
		(void)do_ecmd(0, NULL, NULL, eap->do_ecmd_cmd, (linenr_t)1,
							   ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0));
	}
	else if (eap->cmdidx != CMD_split || *eap->arg != NUL)
	{
		n = readonlymode;
		if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview)
			readonlymode = TRUE;
		setpcmark();
		(void)do_ecmd(0, eap->arg, NULL, eap->do_ecmd_cmd, eap->do_ecmd_lnum,
										 (p_hid ? ECMD_HIDE : 0) +
									(eap->forceit ? ECMD_FORCEIT : 0));
		readonlymode = n;
	}
	else
		update_screen(NOT_VALID);

	/*
	 * if ":split file" worked, set alternate filename in old window to new
	 * file
	 */
	if (	   (eap->cmdidx == CMD_new
				|| eap->cmdidx == CMD_split)
			&& *eap->arg != NUL
			&& curwin != old_curwin
			&& win_valid(old_curwin)
			&& old_curwin->w_buffer != curbuf)
		old_curwin->w_alt_fnum = curbuf->b_fnum;

	ex_no_reprint = TRUE;
}

#ifdef USE_GUI
/*
 * Handle ":gui" or ":gvim" command.
 */
	static void
do_gui(eap)
	EXARG		*eap;
{
	/*
	 * Check for "-f" argument: foreground, don't fork.
	 */
	if (eap->arg[0] == '-' && eap->arg[1] == 'f' &&
							 (eap->arg[2] == NUL || vim_iswhite(eap->arg[2])))
	{
		gui.dofork = FALSE;
		eap->arg = skipwhite(eap->arg + 2);
	}
	else
		gui.dofork = TRUE;
	if (!gui.in_use)
		gui_start();
	if (!ends_excmd(*eap->arg))
		do_next(eap);
}
#endif

	static void
do_read(eap)
	EXARG		*eap;
{
	int		i;

	if (eap->usefilter)					/* :r!cmd */
		do_bang(1, eap->line1, eap->line2, FALSE, eap->arg, FALSE, TRUE);
	else
	{
		if (u_save(eap->line2, (linenr_t)(eap->line2 + 1)) == FAIL)
			return;

		if (*eap->arg == NUL)
		{
			if (check_fname() == FAIL)	/* check for no file name */
				return;
			i = readfile(curbuf->b_ffname, curbuf->b_fname,
										 eap->line2, (linenr_t)0, MAXLNUM, 0);
		}
		else
		{
			if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL)
				setaltfname(eap->arg, eap->arg, (linenr_t)1);
			i = readfile(eap->arg, NULL, eap->line2, (linenr_t)0, MAXLNUM, 0);

		}
		if (i == FAIL)
			emsg2(e_notopen, eap->arg);
		else
			update_screen(NOT_VALID);
	}
}

	static void
do_cd(eap)
	EXARG		*eap;
{
	BUF		*buf;
	char_u	*p;

#ifdef UNIX
	/*
	 * for UNIX ":cd" means: go to home directory
	 */
	if (*eap->arg == NUL)	 /* use NameBuff for home directory name */
	{
		expand_env((char_u *)"$HOME", NameBuff, MAXPATHL);
		eap->arg = NameBuff;
	}
#endif
	if (*eap->arg == NUL)
		do_pwd();
	else
	{
		if (vim_chdir((char *)eap->arg))
			emsg(e_failed);
		else
		{
			/*
			 * Use full path from now on for files currently being
			 * edited, both for filename and swap file name.  Try
			 * to shorten the file names a bit if safe to do so.
			 */
			mch_dirname(IObuff, IOSIZE);
			for (buf = firstbuf; buf != NULL; buf = buf->b_next)
			{
				if (buf->b_fname != NULL)
				{
					vim_free(buf->b_sfname);
					buf->b_sfname = NULL;
					p = shorten_fname(buf->b_ffname, IObuff);
					if (p != NULL)
					{
						buf->b_sfname = vim_strsave(p);
						buf->b_fname = buf->b_sfname;
					}
					if (p == NULL || buf->b_fname == NULL)
						buf->b_fname = buf->b_ffname;
					mf_fullname(buf->b_ml.ml_mfp);
				}
			}
			status_redraw_all();
		}
	}
}

	static void
do_pwd()
{
	if (mch_dirname(NameBuff, MAXPATHL) == OK)
		msg(NameBuff);
	else
		emsg(e_unknown);
}

	static void
do_sleep(eap)
	EXARG		*eap;
{
	int		n;

	if (cursor_valid())
	{
		n = curwin->w_winpos + curwin->w_wrow - msg_scrolled;
		if (n >= 0)
		{
			windgoto((int)n, curwin->w_wcol);
			flushbuf();
		}
	}
	ui_delay(eap->line2 * 1000L, TRUE);
}

	static void
do_exmap(eap, isabbrev)
	EXARG		*eap;
	int			isabbrev;
{
	int		i;

	if (*eap->cmd == 'c')				/* cmap, cunmap, cnoremap, etc. */
	{
		i = CMDLINE;
		++eap->cmd;
	}
	else if (*eap->cmd == 'i')			/* imap, iunmap, inoremap, etc. */
	{
		i = INSERT;
		++eap->cmd;
	}
										/* nmap, nunmap, nnoremap */
	else if (*eap->cmd == 'n' && *(eap->cmd + 1) != 'o')
	{
		i = NORMAL;
		++eap->cmd;
	}
	else if (*eap->cmd == 'v')			/* vmap, vunmap, vnoremap */
	{
		i = VISUAL;
		++eap->cmd;
	}
	else if (eap->forceit || isabbrev)	/* map!, unmap!, noremap!, abbrev */
		i = INSERT + CMDLINE;
	else								/* map, unmap, noremap */
		i = NORMAL + VISUAL;
	switch (do_map((*eap->cmd == 'n') ? 2 : (*eap->cmd == 'u'),
													   eap->arg, i, isabbrev))
	{
		case 1: emsg(e_invarg);
				break;
		case 2: emsg(e_nomap);
				break;
		case 3: emsg(e_ambmap);
				break;
	}
}

/*
 * Handle command that work like operators: ":delete", ":yank", ":>" and ":<".
 */
	static void
do_exops(eap)
	EXARG		*eap;
{
	OPARG		oa;

	clear_oparg(&oa);
	oa.regname = eap->regname;
	oa.start.lnum = eap->line1;
	oa.end.lnum = eap->line2;
	oa.line_count = eap->line2 - eap->line1 + 1;
	oa.motion_type = MLINE;
	if (eap->cmdidx != CMD_yank)		/* position cursor for undo */
	{
		setpcmark();
		curwin->w_cursor.lnum = eap->line1;
		beginline(MAYBE);
	}

	switch (eap->cmdidx)
	{
		case CMD_delete:
			oa.op_type = OP_DELETE;
			op_delete(&oa);
			break;

		case CMD_yank:
			oa.op_type = OP_YANK;
			(void)op_yank(&oa, FALSE, TRUE);
			break;

		default:	/* CMD_rshift or CMD_lshift */
			if ((eap->cmdidx == CMD_rshift)
#ifdef RIGHTLEFT
									^ curwin->w_p_rl
#endif
													)
				oa.op_type = OP_RSHIFT;
			else
				oa.op_type = OP_LSHIFT;
			op_shift(&oa, FALSE, eap->amount);
			break;
	}
}

/*
 * Handle ":copy" and ":move".
 */
	static void
do_copymove(eap)
	EXARG		*eap;
{
	long		n;

	n = get_address(&eap->arg);
	if (eap->arg == NULL)			/* error detected */
	{
		eap->nextcomm = NULL;
		return;
	}

	/*
	 * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n'
	 */
	if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count)
	{
		emsg(e_invaddr);
		return;
	}

	if (eap->cmdidx == CMD_move)
	{
		if (do_move(eap->line1, eap->line2, n) == FAIL)
			return;
	}
	else
		do_copy(eap->line1, eap->line2, n);
	u_clearline();
	beginline(MAYBE);
	update_screen(NOT_VALID);
}

/*
 * Handle ":join" command.
 */
	static void
do_exjoin(eap)
	EXARG		*eap;
{
	curwin->w_cursor.lnum = eap->line1;
	if (eap->line1 == eap->line2)
	{
		if (eap->addr_count >= 2)	/* :2,2join does nothing */
			return;
		if (eap->line2 == curbuf->b_ml.ml_line_count)
		{
			beep_flush();
			return;
		}
		++eap->line2;
	}
	do_do_join(eap->line2 - eap->line1 + 1, !eap->forceit, FALSE);
	beginline(TRUE);
}

/*
 * Handle ":@" command, execute from register.
 */
	static void
do_at(eap)
	EXARG		*eap;
{
	curwin->w_cursor.lnum = eap->line2;

	/*
	 * put the register in mapbuf
	 */
	if (do_execreg(*eap->arg, TRUE,
							  vim_strchr(p_cpo, CPO_EXECBUF) != NULL) == FAIL)
		beep_flush();
	else
	{
		/*
		 * execute from the mapbuf
		 */
		while (vpeekc() == ':')
		{
			(void)vgetc();
			(void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE);
		}
	}
}

/*
 * Handle ":mkexrc" and ":mkvimrc" commands.
 */
	static void
do_mkrc(eap)
	EXARG		*eap;
{
	FILE	*fd;

	if (*eap->arg == NUL)
		eap->arg = (char_u *)EXRC_FILE;
#ifdef UNIX
	/* with Unix it is possible to open a directory */
	if (mch_isdir(eap->arg))
	{
		EMSG2("\"%s\" is a directory", eap->arg);
		return;
	}
#endif
	if (!eap->forceit && vim_fexists(eap->arg))
	{
		EMSG2("\"%s\" exists (use ! to override)", eap->arg);
		return;
	}

	if ((fd = fopen((char *)eap->arg, WRITEBIN)) == NULL)
	{
		EMSG2("Cannot open \"%s\" for writing", eap->arg);
		return;
	}

	/* Write the version command for :mkvimrc */
	if (eap->cmdidx == CMD_mkvimrc)
	{
#ifdef USE_CRNL
		fprintf(fd, "version 4.0\r\n");
#else
		fprintf(fd, "version 4.0\n");
#endif
	}

	if (makemap(fd) == FAIL || makeset(fd) == FAIL || fclose(fd))
		emsg(e_write);
}

/*
 * Handle ":mark" or ":k" command.
 */
	static void
do_setmark(eap)
	EXARG		*eap;
{
	FPOS		pos;

	pos = curwin->w_cursor;			/* save curwin->w_cursor */
	curwin->w_cursor.lnum = eap->line2;
	beginline(MAYBE);
	(void)setmark(*eap->arg);		/* set mark */
	curwin->w_cursor = pos;			/* restore curwin->w_cursor */
}

/*
 * Handle ":normal[!] {commands}" - execute normal mode commands
 * Mostly used for ":autocmd".
 */
	static void
do_normal(eap)
	EXARG		*eap;
{
	OPARG		oa;
	int			len;

	/*
	 * Repeat the :normal command for each line in the range.  When no range
	 * given, execute it just once, without positioning the cursor first.
	 */
	do
	{
		clear_oparg(&oa);
		if (eap->addr_count != 0)
		{
			curwin->w_cursor.lnum = eap->line1++;
			curwin->w_cursor.col = 0;
		}

		/*
		 * Stuff the argument into the typeahead buffer.
		 * Execute normal_cmd() until there is no more
		 * typeahead than there was before this command.
		 */
		len = typelen;
		ins_typebuf(eap->arg, eap->forceit ? -1 : 0, 0, TRUE);
		while (	   (!stuff_empty()
						|| (!typebuf_typed()
							&& typelen > len))
				&& !got_int)
		{
			adjust_cursor();	/* put cursor on valid line */
			normal_cmd(&oa); 	/* execute a Normal mode cmd */
		}
	}
	while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int);
}

	static char_u *
do_findpat(eap, action)
	EXARG		*eap;
	int			action;
{
	int			whole = TRUE;
	long		n;
	char_u		*p;
	char_u		*errormsg = NULL;

	n = 1;
	if (isdigit(*eap->arg))		/* get count */
	{
		n = getdigits(&eap->arg);
		eap->arg = skipwhite(eap->arg);
	}
	if (*eap->arg == '/')	/* Match regexp, not just whole words */
	{
		whole = FALSE;
		++eap->arg;
		for (p = eap->arg; *p && *p != '/'; p++)
			if (*p == '\\' && p[1] != NUL)
				p++;
		if (*p)
		{
			*p++ = NUL;
			p = skipwhite(p);

			/* Check for trailing illegal characters */
			if (*p && vim_strchr((char_u *)"|\"\n", *p) == NULL)
				errormsg = e_trailing;
			else
				eap->nextcomm = p;
		}
	}
	find_pattern_in_path(eap->arg, (int)STRLEN(eap->arg), whole, !eap->forceit,
							*eap->cmd == 'd' ?  FIND_DEFINE : FIND_ANY,
							n, action, eap->line1, eap->line2);

	return errormsg;
}

	static char_u *
do_if(eap, cstack)
	EXARG				*eap;
	struct condstack	*cstack;
{
	char_u		*errormsg = NULL;
	int			error;

	if (cstack->cs_idx == CSTACK_LEN - 1)
		errormsg = (char_u *)":if nesting too deep";
	else
	{
		++cstack->cs_idx;
		if (cstack->cs_idx == 0 || cstack->cs_active[cstack->cs_idx - 1])
		{
			cstack->cs_active[cstack->cs_idx] = bool_eval(eap->arg, &error);
			if (error)
				--cstack->cs_idx;
		}
		else
			cstack->cs_active[cstack->cs_idx] = FALSE;
	}

	return errormsg;
}

/*
 * Handle ":else" and ":elseif" commands.
 */
	static char_u *
do_else(eap, cstack)
	EXARG				*eap;
	struct condstack	*cstack;
{
	char_u		*errormsg = NULL;
	int			error;

	if (cstack->cs_idx < 0)
	{
		if (eap->cmdidx == CMD_else)
			errormsg = (char_u *)":else without :if";
		else
			errormsg = (char_u *)":elseif without :if";
	}
	else
	{
		cstack->cs_active[cstack->cs_idx] =
				(!cstack->cs_active[cstack->cs_idx] &&
				 (cstack->cs_idx == 0 ||
						  cstack->cs_active[cstack->cs_idx - 1]));
		if (eap->cmdidx == CMD_elseif &&
								cstack->cs_active[cstack->cs_idx])
		{
			cstack->cs_active[cstack->cs_idx] = bool_eval(eap->arg, &error);
			if (error)
				--cstack->cs_idx;
		}
	}

	return errormsg;
}

/*
 * Evaluate cmdline variables.
 *
 * change '%'  		to curbuf->b_ffname
 * 		  '#'  		to curwin->w_altfile
 *		  '<cword>' to word under the cursor
 *		  '<cWORD>' to WORD under the cursor
 *		  '<cfile>' to path name under the cursor
 *		  '<sfile>" to sourced filename
 *		  '<afile>' to file name for autocommand
 *
 * When an error is detected, "errormsg" is set to a non-NULL pointer (may be
 * "" for error without a message) and NULL is returned.
 * Returns an allocated string if a valid match was found.
 * Returns NULL if no match was found.  "usedlen" then still contains the
 * number of characters to skip.
 */
	char_u *
eval_vars(src, usedlen, lnump, errormsg)
	char_u		*src;			/* pointer into commandline */
	int			*usedlen;		/* characters after src that are used */
	linenr_t	*lnump;			/* line number for :e command */
	char_u		**errormsg;		/* error message, or NULL */
{
	int			i;
	char_u		*s;
	char_u		*tail;
	char_u		*result;
	int			resultlen;
	char_u		*buf = NULL;
	int			spec_idx;
	static char *(spec_str[]) =
				{
					"%",
#define SPEC_PERC	0
					"#",
#define SPEC_HASH	1
					"<cword>",			/* cursor word */
#define SPEC_CWORD	2
					"<cWORD>",			/* cursor WORD */
#define SPEC_CCWORD	3
					"<cfile>",			/* cursor path name */
#define SPEC_CFILE	4
					"<sfile>",			/* ":so" file name */
#define SPEC_SFILE	5
#ifdef AUTOCMD
					"<afile>"			/* autocommand file name */
# define SPEC_AFILE	6
#endif
				};
#define SPEC_COUNT	(sizeof(spec_str) / sizeof(char *))

	*errormsg = NULL;

	/*
	 * Check if there is something to do.
	 */
	for (spec_idx = 0; spec_idx < SPEC_COUNT; ++spec_idx)
	{
		*usedlen = strlen(spec_str[spec_idx]);
		if (STRNCMP(src, spec_str[spec_idx], *usedlen) == 0)
			break;
	}
	if (spec_idx == SPEC_COUNT)		/* no match */
	{
		*usedlen = 1;
		return NULL;
	}

	/*
	 * Skip when preceded with a backslash "\%" and "\#".
	 * Note: In "\\%" the % is also not recognized!
	 */
	if (*(src - 1) == '\\')
	{
		*usedlen = 0;
		STRCPY(src - 1, src);			/* remove backslash */
		return NULL;
	}

	/*
	 * word or WORD under cursor
	 */
	if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD)
	{
		resultlen = find_ident_under_cursor(&result, spec_idx == SPEC_CWORD ?
									  (FIND_IDENT|FIND_STRING) : FIND_STRING);
		if (resultlen == 0)
		{
			*errormsg = (char_u *)"";
			return NULL;
		}
	}

	/*
	 * '#': Alternate file name
	 * '%': Current file name
	 *		File name under the cursor
	 *		File name for autocommand
	 *  and following modifiers
	 */
	else
	{
		switch (spec_idx)
		{
		case SPEC_PERC: 			/* '%': current file */
				if (curbuf->b_ffname == NULL)
				{
					*errormsg = (char_u *)"No file name to substitute for '%'";
					return NULL;
				}
				result = curbuf->b_fname;
				break;

		case SPEC_HASH:			/* '#' or "#99": alternate file */
				s = src + 1;
				i = (int)getdigits(&s);
				*usedlen = s - src;		/* length of what we expand */

				if (buflist_name_nr(i, &result, lnump) == FAIL)
				{
					*errormsg = (char_u *)"no alternate filename to substitute for '#'";
					return NULL;
				}
				break;

		case SPEC_CFILE:			/* file name under cursor */
				result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L);
				if (result == NULL)
				{
					*errormsg = (char_u *)"";
					return NULL;
				}
				buf = result;		/* remember allocated string */
				break;

#ifdef AUTOCMD
		case SPEC_AFILE:			/* file name for autocommand */
				result = autocmd_fname;
				if (result == NULL)
				{
					*errormsg = (char_u *)"no autocommand filename to substitute for \"<afile>\"";
					return NULL;
				}
				break;
#endif
		case SPEC_SFILE:			/* file name for ":so" command */
				result = sourcing_name;
				if (result == NULL)
				{
					*errormsg = (char_u *)"no :soure filename to substitute for \"<sfile>\"";
					return NULL;
				}
				break;
		}

		resultlen = STRLEN(result);		/* length of new string */
		if (src[*usedlen] == '<')		/* remove the file name extension */
		{
			++*usedlen;
			if ((s = vim_strrchr(result, '.')) != NULL && s >= gettail(result))
				resultlen = s - result;
		}
		else
		{
			/* ":p" - full path/filename */
			if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p')
			{
				*usedlen += 2;
				result = FullName_save(result, FALSE);
				vim_free(buf);		/* free any allocated file name */
				if (result == NULL)
				{
					*errormsg = (char_u *)"";
					return NULL;
				}
				resultlen = STRLEN(result);
				buf = result;
			}

			tail = gettail(result);

			/* ":h" - head, remove "/filename"  */
			/* ":h" can be repeated */
			/* Don't remove the first "/" or "c:\" */
			while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h')
			{
				*usedlen += 2;
				s = get_past_head(result);
				while (tail > s && ispathsep(tail[-1]))
					--tail;
				resultlen = tail - result;
				while (tail > s && !ispathsep(tail[-1]))
					--tail;
			}

			/* ":t" - tail, just the basename */
			if (src[*usedlen] == ':' && src[*usedlen + 1] == 't')
			{
				*usedlen += 2;
				resultlen -= tail - result;
				result = tail;
			}

			/* ":e" - extension */
			/* ":e" can be repeated */
			/* ":r" - root, without extension */
			/* ":r" can be repeated */
			while (src[*usedlen] == ':' &&
					   (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r'))
			{
				/* find a '.' in the tail:
				 * - for second :e: before the current fname
				 * - otherwise: The last '.'
				 */
				if (src[*usedlen + 1] == 'e' && result > tail)
					s = result - 2;
				else
					s = result + resultlen - 1;
				for ( ; s > tail; --s)
					if (s[0] == '.')
						break;
				if (src[*usedlen + 1] == 'e')			/* :e */
				{
					if (s > tail)
					{
						resultlen += result - (s + 1);
						result = s + 1;
					}
					else if (result <= tail)
						resultlen = 0;
				}
				else							/* :r */
				{
					if (s > tail)		/* remove one extension */
						resultlen = s - result;
				}
				*usedlen += 2;
			}
		}

		/* TODO - ":s/pat/foo/" - substitute */
		/* if (src[*usedlen] == ':' && src[*usedlen + 1] == 's') */
	}

	result = vim_strnsave(result, resultlen);
	vim_free(buf);
	return result;
}

/*
 * Expand the <sfile> string in "arg".
 *
 * Returns an allocated string, or NULL for any error.
 */
	char_u *
expand_sfile(arg)
	char_u		*arg;
{
	char_u		*errormsg;
	int			len;
	char_u		*result;
	char_u		*newres;
	char_u		*repl;
	int			srclen;
	char_u		*p;
	linenr_t	dummy;
	
	result = vim_strsave(arg);
	if (result == NULL)
		return NULL;

	for (p = result; *p; )
	{
		if (STRNCMP(p, "<sfile>", 7))
			++p;
		else
		{
			/* replace "<sfile>" with the sourced filename, and do ":" stuff */
			repl = eval_vars(p, &srclen, &dummy, &errormsg);
			if (errormsg != NULL)
			{
				if (*errormsg)
					emsg(errormsg);
				vim_free(result);
				return NULL;
			}
			if (repl == NULL)			/* no match (cannot happen) */
			{
				p += srclen;
				continue;
			}
			len = STRLEN(result) - srclen + STRLEN(repl) + 1;
			newres = alloc(len);
			if (newres == NULL)
			{
				vim_free(repl);
				vim_free(result);
				return NULL;
			}
			vim_memmove(newres, result, (size_t)(p - result));
			STRCPY(newres + (p - result), repl);
			len = STRLEN(newres);
			STRCAT(newres, p + srclen);
			vim_free(result);
			result = newres;
			p = newres + len;			/* continue after the match */
		}
	}

	return result;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.