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

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

#include "vim.h"

#define DO_DECLARE_EXCMD
#include "ex_cmds.h"	/* Declare the cmdnames struct. */

#ifdef HAVE_FCNTL_H
# include <fcntl.h>	    /* for chdir() */
#endif

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

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

struct condstack
{
    char    cs_flags[CSTACK_LEN];   /* CSF_ flags */
    int	    cs_line[CSTACK_LEN];    /* line number of ":while" line */
    int	    cs_idx;		    /* current entry, or -1 if none */
    int	    cs_whilelevel;	    /* number of nested ":while"s */
    char    cs_had_while;	    /* just found ":while" */
    char    cs_had_continue;	    /* just found ":continue" */
    char    cs_had_endwhile;	    /* just found ":endwhile" */
};

#define CSF_TRUE	1	/* condition was TRUE */
#define CSF_ACTIVE	2	/* current state is active */
#define CSF_WHILE	4	/* is a ":while" */
#endif

#ifdef WANT_EVAL
static void free_cmdlines __ARGS((struct growarray *gap));
static char_u	*do_one_cmd __ARGS((char_u **, int, struct condstack *, char_u *(*getline)(int, void *, int), void *cookie));
#else
static char_u	*do_one_cmd __ARGS((char_u **, int, char_u *(*getline)(int, void *, int), void *cookie));
#endif
static int	buf_write_all __ARGS((BUF *));
static int	do_write __ARGS((EXARG *eap));
static char_u	*getargcmd __ARGS((char_u **));
static char_u	*skip_cmd_arg __ARGS((char_u *p));
#ifdef QUICKFIX
static void	do_make __ARGS((char_u *));
static char_u	*get_mef_name __ARGS((int newname));
static void	do_cfile __ARGS((EXARG *eap));
#endif
static int	do_arglist __ARGS((char_u *));
static int	rem_backslash __ARGS((char_u *str));
static int	check_readonly __ARGS((int));
static int	check_changed __ARGS((BUF *, int, int, int));
static int	check_more __ARGS((int, int));
static linenr_t get_address __ARGS((char_u **));
static void	do_quit __ARGS((EXARG *eap));
static void	do_quit_all __ARGS((int forceit));
static void	do_close __ARGS((EXARG *eap));
static void	do_suspend __ARGS((int forceit));
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_recover __ARGS((EXARG *eap));
static void	do_args __ARGS((EXARG *eap));
static void	do_wnext __ARGS((EXARG *eap));
static void	do_resize __ARGS((EXARG *eap));
static void	do_splitview __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_swapname __ARGS((void));
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_winsize __ARGS((char_u *arg));
static void	do_exops __ARGS((EXARG *eap));
static void	do_copymove __ARGS((EXARG *eap));
static void	do_exjoin __ARGS((EXARG *eap));
static void	do_exat __ARGS((EXARG *eap));
static void	do_redir __ARGS((EXARG *eap));
static void	close_redir __ARGS((void));
static void	do_mkrc __ARGS((EXARG *eap));
static FILE	*open_exfile __ARGS((EXARG *eap, char *mode));
static void	do_setmark __ARGS((EXARG *eap));
#ifdef EX_EXTRA
static void	do_normal __ARGS((EXARG *eap));
#endif
#ifdef FIND_IN_PATH
static char_u	*do_findpat __ARGS((EXARG *eap, int action));
#endif
static void	do_ex_tag __ARGS((EXARG *eap, int dt));
#ifdef WANT_EVAL
static char_u	*do_if __ARGS((EXARG *eap, struct condstack *cstack));
static char_u	*do_else __ARGS((EXARG *eap, struct condstack *cstack));
static char_u	*do_while __ARGS((EXARG *eap, struct condstack *cstack));
static char_u	*do_continue __ARGS((struct condstack *cstack));
static char_u	*do_break __ARGS((struct condstack *cstack));
static char_u	*do_endwhile __ARGS((struct condstack *cstack));
static int	has_while_cmd __ARGS((char_u *p));
#endif

/*
 * Table used to quickly search for a command, based on its first character.
 */
CMDIDX cmdidxs[27] =
{
	CMD_append,
	CMD_buffer,
	CMD_change,
	CMD_delete,
	CMD_edit,
	CMD_file,
	CMD_global,
	CMD_help,
	CMD_insert,
	CMD_join,
	CMD_k,
	CMD_list,
	CMD_move,
	CMD_next,
	CMD_open,
	CMD_print,
	CMD_quit,
	CMD_read,
	CMD_substitute,
	CMD_t,
	CMD_undo,
	CMD_vglobal,
	CMD_write,
	CMD_xit,
	CMD_yank,
	CMD_z,
	CMD_Next
};

/*
 * 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;
#ifdef USE_SNIFF
    want_sniff_request = 0;    /* No K_SNIFF wanted */
#endif

    MSG("Entering Ex mode.  Type \"visual\" 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;
#ifdef USE_SNIFF
	ProcessSniffRequests();
#endif
	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. Execute "cmdline" when it is not NULL.
 *    If "cmdline" is NULL, or more lines are needed, getline() is used.
 * 2. Split up in parts separated with '|'.
 *
 * This function can 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 cmdline 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	*next_cmdline;		/* next cmd to execute */
    char_u	*cmdline_copy = NULL;	/* copy of cmd line */
    static int	recursive = 0;		/* recursive depth */
    int		msg_didout_before_start = 0;
    int		count = 0;		/* line number count */
    int		did_inc = FALSE;	/* incremented RedrawingDisabled */
    int		retval = OK;
#ifdef WANT_EVAL
    struct condstack cstack;		/* conditional stack */
    struct growarray lines_ga;		/* keep lines for ":while" */
    int		current_line = 0;	/* active line in lines_ga */
    int		did_endwhile = FALSE;	/* ended with ":endwhile" */
#endif

#ifdef WANT_EVAL
    cstack.cs_idx = -1;
    cstack.cs_whilelevel = 0;
    cstack.cs_had_while = FALSE;
    cstack.cs_had_endwhile = FALSE;
    cstack.cs_had_continue = FALSE;
    ga_init(&lines_ga);
    lines_ga.ga_itemsize = sizeof(char_u *);
    lines_ga.ga_growsize = 10;
#endif

    /*
     * "did_emsg" will be set to TRUE when emsg() is used, in which case we
     * cancel the whole command line, and any if/endif while/endwhile loop.
     */
    did_emsg = FALSE;

    /*
     * Continue executing command lines:
     * - when inside an ":if" or ":while"
     * - for multiple commands on one line, separated with '|'
     * - when repeating until there are no more lines (for ":source")
     */
    next_cmdline = cmdline;
    do
    {
	/* stop skipping cmds for an error msg after all endifs and endwhiles */
	if (next_cmdline == NULL
#ifdef WANT_EVAL
				&& cstack.cs_idx < 0
#endif
							)
	    did_emsg = FALSE;

	/*
	 * 1. If repeating a line with ":while", get a line from lines_ga.
	 * 2. If no line given: Get an allocated line with getline().
	 * 3. If a line is given: Make a copy, so we can mess with it.
	 */

#ifdef WANT_EVAL
	/* 1. If repeating, get a previous line from lines_ga. */
	if (cstack.cs_whilelevel && current_line < lines_ga.ga_len)
	{
	    /* Each '|' separated command is stored separately in lines_ga, to
	     * be able to jump to it.  Don't use next_cmdline now. */
	    vim_free(cmdline_copy);
	    cmdline_copy = NULL;
	    next_cmdline = ((char_u **)(lines_ga.ga_data))[current_line];
	    line_breakcheck();		/* check if CTRL-C typed */
	}
#endif

	/* 2. If no line given, get an allocated line with getline(). */
	if (next_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 (getline == NULL || (next_cmdline = getline(':', cookie,
#ifdef WANT_EVAL
		    cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2
#else
		    0
#endif
			    )) == NULL)
	    {
		/* don't call wait_return for aborted command line */
		if (KeyTyped)
		    need_wait_return = FALSE;
		retval = FAIL;
		break;
	    }
	}

	/* 3. Make a copy of the command so we can mess with it. */
	else if (cmdline_copy == NULL)
	{
	    next_cmdline = vim_strsave(next_cmdline);
	    if (next_cmdline == NULL)
	    {
		retval = FAIL;
		break;
	    }
	}
	cmdline_copy = next_cmdline;

#ifdef WANT_EVAL
	/*
	 * Save the current line when inside a ":while", and when the command
	 * looks like a ":while", because we may need it later.
	 * When there is a '|' and another command, it is stored separately,
	 * because we need to be able to jump back to it from an :endwhile.
	 */
	if (	   current_line == lines_ga.ga_len
		&& (cstack.cs_whilelevel || has_while_cmd(next_cmdline))
		&& ga_grow(&lines_ga, 1) == OK)
	{
	    ((char_u **)(lines_ga.ga_data))[current_line] =
						    vim_strsave(next_cmdline);
	    ++lines_ga.ga_len;
	    --lines_ga.ga_room;
	}
	did_endwhile = FALSE;
#endif

	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 */
		++no_wait_return;   /* dont wait for return until finished */
		++RedrawingDisabled;
		did_inc = TRUE;
	    }
	}

	/*
	 * 2. Execute one '|' separated command.
	 *    do_one_cmd() will return NULL if there is no trailing '|'.
	 *    "cmdline_copy" can change, e.g. for '%' and '#' expansion.
	 */
	++recursive;
	next_cmdline = do_one_cmd(&cmdline_copy, flags & DOCMD_VERBOSE,
#ifdef WANT_EVAL
				&cstack,
#endif
				getline, cookie);
	--recursive;
	if (next_cmdline == NULL)
	{
	    vim_free(cmdline_copy);
	    cmdline_copy = NULL;

	    /*
	     * If the command was typed, remember it for the ':' 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;
	    }
	}
	else
	{
	    /* need to copy the command after the '|' to cmdline_copy, for the
	     * next do_one_cmd() */
	    STRCPY(cmdline_copy, next_cmdline);
	    next_cmdline = cmdline_copy;
	}


#ifdef WANT_EVAL
	if (cstack.cs_whilelevel)
	{
	    ++current_line;

	    /*
	     * An ":endwhile" and ":continue" is handled here.
	     * If we were executing commands, jump back to the ":while".
	     * If we were not executing commands, decrement whilelevel.
	     */
	    if (cstack.cs_had_endwhile || cstack.cs_had_continue)
	    {
		if (cstack.cs_had_endwhile)
		{
		    cstack.cs_had_endwhile = FALSE;
		    did_endwhile = TRUE;
		}
		else
		    cstack.cs_had_continue = FALSE;

		/* jump back to the matching ":while"? */
		if (!did_emsg && cstack.cs_idx >= 0
			&& (cstack.cs_flags[cstack.cs_idx] & CSF_ACTIVE))
		{
		    current_line = cstack.cs_line[cstack.cs_idx];
		    cstack.cs_had_while = TRUE;	    /* note we jumped there */
		}
		else /* can only get here with ":endwhile" */
		{
		    --cstack.cs_whilelevel;
		    if (cstack.cs_idx >= 0)
			--cstack.cs_idx;
		}
	    }

	    /*
	     * For a ":while" we need to remember the line number.
	     */
	    else if (cstack.cs_had_while)
	    {
		cstack.cs_had_while = FALSE;
		cstack.cs_line[cstack.cs_idx] = current_line - 1;
	    }
	}

	/*
	 * When not inside a ":while", clear remembered lines.
	 */
	if (!cstack.cs_whilelevel)
	{
	    free_cmdlines(&lines_ga);
	    current_line = 0;
	}
#endif /* WANT_EVAL */

    }
    /*
     * Continue executing command lines when:
     * - no CTRL-C typed
     * - didn't get an error message or lines are not typed
     * - there is a command after '|', inside a :if or :while, or looping for
     *	 ":source" command.
     */
    while (!got_int
	    && !(did_emsg && (getline == getexmodeline || getline == getexline))
	    && (next_cmdline != NULL
#ifdef WANT_EVAL
			|| cstack.cs_idx >= 0
#endif
			|| (flags & DOCMD_REPEAT)));

    vim_free(cmdline_copy);
#ifdef WANT_EVAL
    free_cmdlines(&lines_ga);
#endif

    /*
     * 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;
	--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 && !did_emsg
#ifdef WANT_EVAL
			    && !did_endwhile
#endif
					    ) || retval == FAIL)
	{
	    need_wait_return = FALSE;
	    msg_didany = FALSE;		/* don't wait when restarting edit */
	    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;
}

#ifdef WANT_EVAL
    static void
free_cmdlines(gap)
    struct growarray *gap;
{
    while (gap->ga_len)
    {
	vim_free(((char_u **)(gap->ga_data))[gap->ga_len - 1]);
	--gap->ga_len;
	++gap->ga_room;
    }
}
#endif

/*
 * 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
 *
 * Note: "getline" can be NULL.
 *
 * This function may be called recursively!
 */
    static char_u *
do_one_cmd(cmdlinep, sourcing,
#ifdef WANT_EVAL
			    cstack,
#endif
				    getline, cookie)
    char_u		**cmdlinep;
    int			sourcing;
#ifdef WANT_EVAL
    struct condstack	*cstack;
#endif
    char_u		*(*getline) __ARGS((int, void *, int));
    void		*cookie;		/* argument for getline() */
{
    char_u		*p;
    char_u		*new_cmdline;
    int			i;
    int			len;
    long		argt;
    linenr_t		lnum;
    long		n;
    char_u		*errormsg = NULL;	/* error message */
    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;
/*
 * 2. skip comment lines and leading space and colons
 */
    for (ea.cmd = *cmdlinep; *ea.cmd == ' ' || *ea.cmd == '\t'
						  || *ea.cmd == ':'; 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;

#ifdef WANT_EVAL
    ea.skip = did_emsg || (cstack->cs_idx >= 0
			 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE));
#endif

/*
 * 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.
 */

#ifdef WANT_EVAL
    if (ea.skip)
	goto skip_address;
#endif

    /* repeat for all ',' or ';' separated addresses */
    for (;;)
    {
	ea.line1 = ea.line2;
	ea.line2 = curwin->w_cursor.lnum;   /* default is current line number */
	ea.cmd = skipwhite(ea.cmd);
	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 == ';')
	    curwin->w_cursor.lnum = ea.line2;
	else if (*ea.cmd != ',')
	    break;
	++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;
    }

    /* Don't leave the cursor on an illegal line (caused by ';') */
    check_cursor_lnum();

#ifdef WANT_EVAL
skip_address:
#endif

/*
 * 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.nextcmd.
     */
    if (*ea.cmd == NUL || *ea.cmd == '"' ||
			       (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL)
    {
	/*
	 * strange vi behaviour:
	 * ":3"		jumps to line 3
	 * ":3|..."	prints line 3
	 * ":|"		prints current line
	 */
#ifdef WANT_EVAL
	if (ea.skip)	    /* skip this if inside :if */
	    goto doend;
#endif
	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(BL_SOL | BL_FIX);
	}
	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[0] == 's'
	    && (ea.cmd[1] == 'c'
		|| ea.cmd[1] == 'g'
		|| (ea.cmd[1] == 'r' && ea.cmd[2] != 'e')))
    {
	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);

	if (*ea.cmd >= 'a' && *ea.cmd <= 'z')
	    ea.cmdidx = cmdidxs[*ea.cmd - 'a'];
	else
	    ea.cmdidx = cmdidxs[26];

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

    if (*p == '!' && ea.cmdidx != CMD_substitute)    /* 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;
    }

#ifdef QUICKFIX
    /*
     * 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;
    }
#endif

    /*
     * 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 || ea.cmdidx == CMD_update)
    {
	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 == '!' && ea.cmdidx == CMD_write)  /* :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.nextcmd = check_nextcmd(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.nextcmd = 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
	    && valid_yank_reg(*ea.arg, ea.cmdidx != CMD_put)
	    && !((argt & COUNT) && isdigit(*ea.arg)))
    {
	ea.regname = *ea.arg++;
#ifdef WANT_EVAL
	/* for '=' register: accept the rest of the line as an expression */
	if (ea.arg[-1] == '=' && ea.arg[0] != NUL)
	{
	    set_expr_line(vim_strsave(ea.arg));
	    ea.arg += STRLEN(ea.arg);
	}
#endif
	ea.arg = skipwhite(ea.arg);
    }

    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	    has_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).
	 */
	has_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.nextcmd)
		i += STRLEN(ea.nextcmd);   /* 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.nextcmd)		    /* append next command */
	    {
		i = STRLEN(new_cmdline) + 1;
		STRCPY(new_cmdline + i, ea.nextcmd);
		ea.nextcmd = new_cmdline + i;
	    }
	    ea.cmd = new_cmdline + (ea.cmd - *cmdlinep);
	    ea.arg = new_cmdline + (ea.arg - *cmdlinep);
	    if (ea.do_ecmd_cmd != NULL)
		ea.do_ecmd_cmd = new_cmdline + (ea.do_ecmd_cmd - *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 (!has_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, has_wildcards);
#ifdef macintosh
		    /*
		     * translate unix-like path components
		     */
		    slash_n_colon_adjust (ea.arg);
#endif
		}

		if (has_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, '$') || vim_strchr(ea.arg, '~'))
			{
			    expand_env(ea.arg, NameBuff, MAXPATHL);
			    has_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.nextcmd" the
			 * pointers correct.
			 */
			len = ea.arg - *cmdlinep;
			i = STRLEN(p) + len;
			if (ea.nextcmd)
			    i += STRLEN(ea.nextcmd);
			if ((new_cmdline = alloc((unsigned)i + 2)) != NULL)
			{
			    STRNCPY(new_cmdline, *cmdlinep, len);
			    STRCPY(new_cmdline + len, p);
			    if (ea.nextcmd)	    /* append next command */
			    {
				i = STRLEN(new_cmdline) + 1;
				STRCPY(new_cmdline + i, ea.nextcmd);
				ea.nextcmd = 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);
		    }
		}
	    }
	}
    }

#ifdef WANT_EVAL
    /*
     * Skip the command when it's not going to be executed.
     * The commands like :if, :endif, etc. always need to be executed.
     * Also make an exception for commands that handle a trailing command
     * themselves.
     */
    if (ea.skip)
    {
	switch (ea.cmdidx)
	{
	    /* commands that need evaluation */
	    case CMD_while:
	    case CMD_endwhile:
	    case CMD_if:
	    case CMD_elseif:
	    case CMD_else:
	    case CMD_endif:	break;

	    /* commands that handle '|' themselves */
	    case CMD_djump:
	    case CMD_dlist:
	    case CMD_dsearch:
	    case CMD_dsplit:
	    case CMD_echo:
	    case CMD_echon:
	    case CMD_execute:
	    case CMD_help:
	    case CMD_ijump:
	    case CMD_ilist:
	    case CMD_isearch:
	    case CMD_isplit:
	    case CMD_let:
	    case CMD_substitute:
	    case CMD_syntax:
	    case CMD_and:
	    case CMD_tilde:	break;

	    default:		goto doend;
	}
    }
#endif

    /*
     * 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;

	case CMD_qall:
		do_quit_all(ea.forceit);
		break;

	case CMD_close:
		do_close(&ea);
		break;

	case CMD_hide:
		close_window(curwin, FALSE);	/* don't free buffer */
		break;

	case CMD_only:
		close_others(TRUE, ea.forceit);
		break;

	case CMD_stop:
	case CMD_suspend:
		do_suspend(ea.forceit);
		break;

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

	case CMD_xall:
	case CMD_wqall:
		exiting = TRUE;
		/* FALLTHROUGH */

	case CMD_wall:
		do_wqall(&ea);
		break;

	case CMD_preserve:
		ml_preserve(curbuf, TRUE);
		break;

	case CMD_recover:
		do_recover(&ea);
		break;

	case CMD_args:
		do_args(&ea);
		break;

	case CMD_wnext:
	case CMD_wNext:
	case CMD_wprevious:
		do_wnext(&ea);
		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;

	case CMD_all:
	case CMD_sall:
		if (ea.addr_count == 0)
		    ea.line2 = 9999;
		do_arg_all((int)ea.line2, ea.forceit);
		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, ea.forceit);
		    else
			(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT
						       : DOBUF_GOTO,
			     DOBUF_FIRST, FORWARD, (int)ea.line2, ea.forceit);
		}
		break;

	case CMD_bmodified:	/* :[N]bmod [N]		to next mod. buffer */
	case CMD_sbmodified:	/* :[N]sbmod [N]	to next mod. buffer */
		(void)do_buffer(*ea.cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
			       DOBUF_MOD, FORWARD, (int)ea.line2, ea.forceit);
		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, ea.forceit);
		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, ea.forceit);
		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, ea.forceit);
		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, ea.forceit);
		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:
		if (ea.addr_count == 0)
		    ea.line2 = 9999;
		(void)do_buffer_all((int)ea.line2, FALSE);
		break;

	case CMD_ball:
	case CMD_sball:
		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_update:
		if (curbuf_changed())
		    (void)do_write(&ea);
		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;

	case CMD_sview:
	case CMD_split:
	case CMD_new:
		do_splitview(&ea);
		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:
		do_swapname();
		break;

	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:
	case CMD_pound:			/* :# */
	case CMD_print:
		do_print(&ea);
		break;

	case CMD_shell:
		do_shell(NULL, 0);
		break;

	case CMD_sleep:
		do_sleep(&ea);
		break;

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

	case CMD_stselect:
		postponed_split = -1;
		/*FALLTHROUGH*/
	case CMD_tselect:
		do_tag(ea.arg, DT_SELECT, 0, ea.forceit);
		break;

	case CMD_stjump:
		postponed_split = -1;
		/*FALLTHROUGH*/
	case CMD_tjump:
		do_tag(ea.arg, DT_JUMP, 0, ea.forceit);
		break;

	case CMD_pop:		do_ex_tag(&ea, DT_POP); break;
	case CMD_tnext:		do_ex_tag(&ea, DT_NEXT); break;
	case CMD_tNext:
	case CMD_tprevious:	do_ex_tag(&ea, DT_PREV); break;
	case CMD_trewind:	do_ex_tag(&ea, DT_FIRST); break;
	case CMD_tlast:		do_ex_tag(&ea, DT_LAST); 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;

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

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

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

	case CMD_fixdel:
		do_fixdel();
		break;

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

	/*
	 * Apply the automatic commands to all loaded buffers.
	 */
	case CMD_doautoall:
		do_autoall(ea.arg);
		break;

	/*
	 * Apply the automatic commands to the current buffer.
	 */
	case CMD_doautocmd:
		(void)do_doautocmd(ea.arg, TRUE);
		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_map:
	case CMD_nmap:
	case CMD_vmap:
	case CMD_omap:
	case CMD_cmap:
	case CMD_imap:
	case CMD_noremap:
	case CMD_nnoremap:
	case CMD_vnoremap:
	case CMD_onoremap:
	case CMD_cnoremap:
	case CMD_inoremap:
		/*
		 * 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_unmap:
	case CMD_nunmap:
	case CMD_vunmap:
	case CMD_ounmap:
	case CMD_cunmap:
	case CMD_iunmap:
		do_exmap(&ea, FALSE);
		break;

	case CMD_mapclear:
	case CMD_nmapclear:
	case CMD_vmapclear:
	case CMD_omapclear:
	case CMD_cmapclear:
	case CMD_imapclear:
		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_amenu:	    case CMD_anoremenu:	    case CMD_aunmenu:
	case CMD_nmenu:	    case CMD_nnoremenu:	    case CMD_nunmenu:
	case CMD_vmenu:	    case CMD_vnoremenu:	    case CMD_vunmenu:
	case CMD_omenu:	    case CMD_onoremenu:	    case CMD_ounmenu:
	case CMD_imenu:	    case CMD_inoremenu:	    case CMD_iunmenu:
	case CMD_cmenu:	    case CMD_cnoremenu:	    case CMD_cunmenu:
		gui_do_menu(&ea);
		break;
#endif

	case CMD_display:
	case CMD_registers:
		do_dis(ea.arg);
		break;

	case CMD_help:
		do_help(&ea);
		break;

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

	case CMD_winsize:
		do_winsize(ea.arg);
		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_exat(&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, 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_redir:
		do_redir(&ea);
		break;

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

	case CMD_mkexrc:
		do_mkrc(&ea);
		break;

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

	case CMD_cfile:
		do_cfile(&ea);
		break;

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

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

	case CMD_clast:
		qf_jump(0, ea.addr_count ? (int)ea.line2 : 32767, 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;
#endif

	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;

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

	case CMD_retab:
		do_retab(&ea);
		break;

	case CMD_normal:
		do_normal(&ea);
		break;
#endif

#ifdef QUICKFIX
	case CMD_make:
		do_make(ea.arg);
		break;
#endif

#ifdef FIND_IN_PATH
	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;
#endif

#ifdef SYNTAX_HL
	case CMD_syntax:
		do_syntax(&ea);
		break;
#endif

	case CMD_highlight:
		do_highlight(ea.arg, ea.forceit, FALSE);
		break;

#ifdef WANT_EVAL
	case CMD_echo:
	case CMD_echon:
		do_echo(&ea, ea.cmdidx == CMD_echo);
		break;

	case CMD_echohl:
		do_echohl(ea.arg);
		break;

	case CMD_execute:
		do_execute(&ea, getline, cookie);
		break;

	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
			|| (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE))
		    errormsg = (char_u *)":endif without :if";
		else
		    --cstack->cs_idx;
		break;

	case CMD_while:
		errormsg = do_while(&ea, cstack);
		break;

	case CMD_continue:
		errormsg = do_continue(cstack);
		break;

	case CMD_break:
		errormsg = do_break(cstack);
		break;

	case CMD_endwhile:
		errormsg = do_endwhile(cstack);
		break;

	case CMD_let:
		do_let(&ea);
		break;

	case CMD_unlet:
		do_unlet(ea.arg);
		break;
#endif /* WANT_EVAL */

	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
	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

#ifdef USE_SNIFF
	case CMD_sniff:
		do_sniff(ea.arg);
		break;
#endif

	default:
		/* Illegal commands have already been handled */
		errormsg = (char_u *)"Sorry, this command is not implemented";
    }


doend:
    if (curwin->w_cursor.lnum == 0)	/* can happen with zero line number */
	curwin->w_cursor.lnum = 1;

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

/*
 * 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;
    CMDIDX		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 */
    expand_set_path = FALSE;

/*
 * 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 = (CMDIDX)0; cmdidx < CMD_SIZE;
					   cmdidx = (CMDIDX)((int)cmdidx + 1))
	    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 || cmdidx == CMD_update)
    {
	if (*arg == '>')			/* append */
	{
	    if (*++arg == '>')
		++arg;
	    arg = skipwhite(arg);
	}
	else if (*arg == '!' && cmdidx == CMD_write)	/* :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 = skip_cmd_arg(arg);

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

	/* Skip space(s) 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:
	case CMD_stselect:
	case CMD_tselect:
	    expand_context = EXPAND_TAGS;
	    expand_pattern = arg;
	    break;
	case CMD_help:
	    expand_context = EXPAND_HELP;
	    expand_pattern = arg;
	    break;
	case CMD_augroup:
	    expand_context = EXPAND_AUGROUP;
	    expand_pattern = arg;
	    break;
#ifdef SYNTAX_HL
	case CMD_syntax:
	    set_context_in_syntax_cmd(arg);
	    break;
#endif
#ifdef WANT_EVAL
	case CMD_echohl:
	    expand_context = EXPAND_HIGHLIGHT;
	    expand_pattern = arg;
	    break;
#endif
	case CMD_highlight:
	    set_context_in_highlight_cmd(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_amenu:	    case CMD_anoremenu:	    case CMD_aunmenu:
	case CMD_nmenu:	    case CMD_nnoremenu:	    case CMD_nunmenu:
	case CMD_vmenu:	    case CMD_vnoremenu:	    case CMD_vunmenu:
	case CMD_omenu:	    case CMD_onoremenu:	    case CMD_ounmenu:
	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);
#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))
			{
			    cmd = NULL;
			    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))
			{
			    curwin->w_cursor = pos;
			    cmd = NULL;
			    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(curbuf, &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_changed(buf) && !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_changed(buf) && (mult_win || buf->b_nwindows <= 1) &&
	    (!checkaw || autowrite(buf, forceit) == FAIL))
    {
	emsg(e_nowrtmsg);
	return TRUE;
    }
    return FALSE;
}

/*
 * Return TRUE if the buffer "buf" can be abandoned, either by making it
 * hidden, autowriting it or unloading it.
 */
    int
can_abandon(buf, forceit)
    BUF	    *buf;
    int	    forceit;
{
    return (	   p_hid
		|| !buf_changed(buf)
		|| buf->b_nwindows > 1
		|| autowrite(buf, forceit) == OK
		|| forceit);
}

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

    /* check curbuf first: if it was changed we can't abondon it */
    if (buf_changed(curbuf))
	buf = curbuf;
    else
    {
	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
	    if (buf_changed(buf))
		break;
    }
    if (buf == NULL)
	return FALSE;

    exiting = FALSE;
    /* There must be a wait_return for this message, do_buffer()
     * may cause a redraw.  But wait_return() is a no-op when vgetc()
     * is busy (Quit used from window menu), then make sure we don't
     * cause a scroll up. */
    if (vgetc_busy)
    {
	msg_row = cmdline_row;
	msg_col = 0;
	msg_didout = 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 FAIL if there is no file name, 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
	{
	    command = arg;
	    arg = skip_cmd_arg(command);
	    if (*arg)
		*arg++ = NUL;	/* terminate command with NUL */
	}

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

/*
 * Find end of "+command" argument.  Skip over "\ " and "\\".
 */
    static char_u *
skip_cmd_arg(p)
    char_u *p;
{
    while (*p && !vim_isspace(*p))
    {
	if (*p == '\\' && p[1] != NUL)
	    ++p;
	++p;
    }
    return p;
}

/*
 * 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 file name character, we remove the backslash before
 * it, to be able to disginguish between a file name that starts with '$' and
 * the name of an environment variable.
 * Make sure that "\\mch\file" isn't translated into "\mch\file".
 */
    static int
rem_backslash(str)
    char_u  *str;
{
#ifdef BACKSLASH_IN_FILENAME
    return (str[0] == '\\'
	    && (str[1] == '$'
		|| (str[1] != NUL
		    && str[1] != '*'
		    && str[1] != '?'
		    && !vim_isfilec(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, has_wildcards)
    char_u  *p;
    int	    has_wildcards;	/* going to expand wildcards later */
{
    for ( ; *p; ++p)
	if (rem_backslash(p)
#if defined(UNIX) || defined(OS2)
		&& !(has_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 file name, 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_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(BL_SOL | BL_FIX);
	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;
    long	n;

    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 file name */
#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, newlnum);
	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, BCO_ENTER);
		close_buffer(curwin, curbuf, !(flags & ECMD_HIDE), FALSE);
		curwin->w_buffer = buf;
		curbuf = buf;
		++curbuf->b_nwindows;
		/* set 'fileformat' */
		if (*p_ffs && !oldbuf)
		    set_fileformat(default_fileformat());
#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, FALSE);	/* 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 */
	{
	    curbuf->b_flags |= BF_CHECK_RO; /* set/reset 'ro' flag */
	    (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(BL_SOL | BL_FIX);
	}
	else
	{
	    if (exmode_active)
	    {
		curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
		check_cursor_lnum();
	    }
	    beginline(BL_WHITE | BL_FIX);
	}
    }

    /* Check if cursors in other windows on the same buffer are still valid */
    check_lnums(FALSE);

    /*
     * 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)
    {
	n = p_so;
	if (command == NULL)
	    p_so = 999;			/* force cursor halfway the window */
	update_topline();
	p_so = n;
	update_curbuf(NOT_VALID);	/* redraw now */
    }

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

theend:
    vim_free(free_fname);
    return retval;
}

#ifdef QUICKFIX
    static void
do_make(arg)
    char_u *arg;
{
    char_u	*name;

    autowrite_all();
    name = get_mef_name(TRUE);
    if (name == NULL)
	return;
    vim_remove(name);	    /* in case it's not unique */

    /*
     * 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, name);
    /*
     * 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 */

    /* let the shell know if we are redirecting output or not */
    do_shell(IObuff, *p_sp ? SHELL_DOOUT : 0);

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

    if (qf_init(name) > 0)
	qf_jump(0, 0, FALSE);		/* display first error */

    vim_remove(name);
    vim_free(name);
}

/*
 * Return the name for the errorfile, in allocated memory.
 * When "newname" is TRUE, find a new unique name when 'makeef' contains
 * "##".  Returns NULL for error.
 */
    static char_u *
get_mef_name(newname)
    int		newname;
{
    char_u	*p;
    char_u	*name;
    static int	start = -1;
    static int	off = 0;

    if (*p_mef == NUL)
    {
	EMSG("makeef option not set");
	return NULL;
    }

    for (p = p_mef; *p; ++p)
	if (p[0] == '#' && p[1] == '#')
	    break;

    if (*p == NUL)
	return vim_strsave(p_mef);

    /* When "newname" set: keep trying until the name doesn't exist yet. */
    for (;;)
    {
	if (newname)
	{
	    if (start == -1)
		start = mch_get_pid();
	    ++off;
	}

	name = alloc((unsigned)STRLEN(p_mef) + 30);
	if (name == NULL)
	    break;
	STRCPY(name, p_mef);
	sprintf((char *)name + (p - p_mef), "%d%d", start, off);
	STRCAT(name, p + 2);
	if (!newname || mch_getperm(name) < 0)
	    break;
	vim_free(name);
    }
    return name;
}

/*
 * ":cfile" command.
 */
    static void
do_cfile(eap)
    EXARG	*eap;
{
    if (*eap->arg != NUL)
	set_string_option_direct((char_u *)"ef", -1, eap->arg, TRUE);
    if (qf_init(p_ef) > 0)
	qf_jump(0, 0, eap->forceit);		/* display first error */
}
#endif /* QUICKFIX */

/*
 * 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 (rem_backslash(str))
		*p++ = *++str;
	    else
	    {
		if (!inquote && vim_isspace(*str))
		    break;
		if (*str == '"')
		    inquote ^= TRUE;
		else
		    *p++ = *str;
	    }
	}
	str = skipwhite(str);
	*p = NUL;
    }

    i = expand_wildcards(new_count, new_files, &exp_count, &exp_files,
						  EW_DIR|EW_FILE|EW_NOTFOUND);
    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()
{
    if (arg_file_count > 1
	    && (curbuf->b_ffname == NULL
		|| curwin->w_arg_idx >= arg_file_count
		|| !(fullpathcmp(arg_files[curwin->w_arg_idx],
					 curbuf->b_ffname, TRUE) & FPC_SAME)))
	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');
}

/*
 * Return the next command, after the first '|' or '\n'.
 * Return NULL if not found.
 */
    char_u *
find_nextcmd(p)
    char_u	*p;
{
    while (*p != '|' && *p != '\n')
    {
	if (*p == NUL)
	    return NULL;
	++p;
    }
    return (p + 1);
}

/*
 * Check if *p is a separator between Ex commands.
 * Return NULL if it isn't, (p + 1) if it is.
 */
    char_u *
check_nextcmd(p)
    char_u	*p;
{
    p = skipwhite(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		fileformat;	/* EOL_UNKNOWN, EOL_UNIX or EOL_DOS */
    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, is_vimrc)
    char_u	*fname;
    int		check_other;	    /* check for .vimrc and _vimrc */
    int		is_vimrc;	    /* call vimrc_found() when file exists */
{
    struct source_cookie    cookie;
    char_u		    *save_sourcing_name;
    linenr_t		    save_sourcing_lnum;
    char_u		    *p;
    char_u		    *fname_exp;
    int			    retval = FAIL;

    fname_exp = expand_env_save(fname);
    if (fname_exp == NULL)
	goto theend;
#ifdef macintosh
    slash_n_colon_adjust(fname_exp);
#endif
    cookie.fp = fopen((char *)fname_exp, 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(fname_exp);
	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 *)fname_exp, READBIN);
	}
    }

    if (cookie.fp == NULL)
    {
	if (p_verbose > 0)
	    smsg((char_u *)"could not source \"%s\"", fname);
	goto theend;
    }

    /*
     * The file exists.
     * - In verbose mode, give a message.
     * - For a vimrc file, may want to set 'compatible', call vimrc_found().
     */
    if (p_verbose > 0)
	smsg((char_u *)"sourcing \"%s\"", fname);
    if (is_vimrc)
	vimrc_found();


#ifdef USE_CRNL
    /* If no automatic file format: Set default to CR-NL. */
    if (*p_ffs == NUL)
	cookie.fileformat = EOL_DOS;
    else
	cookie.fileformat = EOL_UNKNOWN;
    cookie.error = FALSE;
#endif

    /*
     * Keep the sourcing name, for recursive calls.
     */
    save_sourcing_name = sourcing_name;
    save_sourcing_lnum = sourcing_lnum;
    sourcing_name = fname_exp;
    sourcing_lnum = 0;

    /*
     * 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);
    sourcing_name = save_sourcing_name;
    sourcing_lnum = save_sourcing_lnum;
    retval = OK;

theend:
    vim_free(fname_exp);
    return retval;
}

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

    /* use a growarray to store the sourced line */
    ga_init(&ga);
    ga.ga_itemsize = 1;
    ga.ga_growsize = 200;

    /*
     * Loop until there is a finished line (or end-of-file).
     */
    sourcing_lnum++;
    for (;;)
    {
	/* make room to read at least 80 (more) characters */
	if (ga_grow(&ga, 80) == FAIL)
	    break;
	buf = (char_u *)ga.ga_data;

	if (fgets((char *)buf + ga.ga_len, ga.ga_room, sp->fp) == NULL
								   || got_int)
	    break;

	len = STRLEN(buf);
#ifdef USE_CRNL
	/* Ignore a trailing CTRL-Z, when in Dos mode.	Only recognize the
	 * CTRL-Z by its own, or after a NL. */
	if (	   (len == 1 || (len >= 2 && buf[len - 2] == '\n'))
		&& sp->fileformat == EOL_DOS
		&& buf[len - 1] == Ctrl('Z'))
	{
	    buf[len - 1] = NUL;
	    break;
	}
#endif
	have_read = TRUE;
	ga.ga_room -= len - ga.ga_len;
	ga.ga_len = len;

	/* If the line was longer than the buffer, read more. */
	if (ga.ga_room == 1 && buf[len - 1] != '\n')
	    continue;

	if (len >= 1 && buf[len - 1] == '\n')	/* remove trailing NL */
	{
#ifdef USE_CRNL
	    has_cr = (len >= 2 && buf[len - 2] == '\r');
	    if (sp->fileformat == EOL_UNKNOWN)
	    {
		if (has_cr)
		    sp->fileformat = EOL_DOS;
		else
		    sp->fileformat = EOL_UNIX;
	    }

	    if (sp->fileformat == EOL_DOS)
	    {
		if (has_cr)	    /* replace trailing CR */
		{
		    buf[len - 2] = '\n';
		    --len;
		    --ga.ga_len;
		    ++ga.ga_room;
		}
		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->fileformat = EOL_UNIX;
		}
	    }
#endif
	    /* The '\n' is escaped if there is an odd number of ^V's just
	     * before it, first set "c" just before the 'V's and then check
	     * len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo */
	    for (c = len - 2; c >= 0 && buf[c] == Ctrl('V'); c--)
		;
	    if ((len & 1) != (c & 1))	/* escaped NL, read more */
	    {
		sourcing_lnum++;
		continue;
	    }

	    buf[len - 1] = NUL;		/* remove the NL */
	}

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

    if (have_read)
	return (char_u *)ga.ga_data;

    vim_free(ga.ga_data);
    return NULL;
}

/*
 * Function given to ExpandGeneric() to obtain the list of command names.
 */
    char_u *
get_command_name(idx)
    int	    idx;
{
    if (idx >= (int)CMD_SIZE)
	return NULL;
    return cmdnames[idx].cmd_name;
}

/*
 * 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.
 */
    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 ((!p_hid && 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, !p_hid || eap->forceit); /* may free buffer */
    }
}

/*
 * ":qall": try to quit all windows
 */
    static void
do_quit_all(forceit)
    int		forceit;
{
    exiting = TRUE;
    if (forceit || !check_changed_any())
	getout(0);
    not_exiting();
}

/*
 * ":close": close current window, unless it is the last one
 */
    static void
do_close(eap)
    EXARG	*eap;
{
    int	    need_hide;

    need_hide = (buf_changed(curbuf) && curbuf->b_nwindows <= 1);
    if (need_hide && !p_hid && !eap->forceit)
	emsg(e_nowrtmsg);
    else
	close_window(curwin, !need_hide);	    /* may free buffer */
}

/*
 * ":stop" and ":suspend": Suspend Vim.
 */
    static void
do_suspend(forceit)
    int		forceit;
{
    /*
     * Disallow suspending for "rvim".
     */
    if (!check_restricted()
#ifdef WIN32
	/*
	 * Check if external commands are allowed now.
	 */
	&& can_end_termcap_mode(TRUE)
#endif
					)
    {
	if (!forceit)
	    autowrite_all();
	windgoto((int)Rows - 1, 0);
	out_char('\n');
	out_flush();
	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_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, !p_hid); /* 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_changed(buf))
	{
	    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;
	out_flush();	    /* show one line at a time */
    }
    setpcmark();
    /* put cursor at last line */
    curwin->w_cursor.lnum = eap->line2;
    beginline(BL_SOL | BL_FIX);

    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;
    char_u	*ffname;
    BUF		*buf;

    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, 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;

	/*
	 * If no line number given, use the last known line number.
	 */
	if (eap->do_ecmd_lnum == 0)
	{
	    ffname = fix_fname(arg_files[curwin->w_arg_idx]);
	    if (ffname != NULL)
	    {
		buf = buflist_findname(ffname);
		if (buf != NULL)
		    eap->do_ecmd_lnum = buflist_findlnum(buf);
		vim_free(ffname);
	    }
	}

	(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);
    }
}

#if defined(USE_GUI_WIN32) || defined(USE_GUI_BEOS) || defined(PROTO) || defined(macintosh)
/*
 * Handle a file drop. The code is here because a drop is *nearly* like an
 * :args command, but not quite (we have a list of exact filenames, so we
 * don't want to (a) parse a command line, or (b) expand wildcards. So the
 * code is very similar to :args and hence needs access to a lot of the static
 * functions in this file.
 *
 * Arguments:
 *	FILEC => the number of files dropped
 *	FILEV => the list of files dropped
 *
 * The list should be allocated using vim_alloc(), as should each item in the
 * list. This function takes over responsibility for freeing the list.
 *
 * XXX The list is made into the arg_files list. This is freed using
 * FreeWild(), which does a series of vim_free() calls, unless the two defines
 * __EMX__ and __ALWAYS_HAS_TRAILING_NUL_POINTER are set. In this case, a
 * routine _fnexplodefree() is used. This may cause problems, but as the drop
 * file functionality is (currently) Win32-specific (where these defines are
 * not set), this is not presently a problem.
 */

void
handle_drop(filec, filev)
    int	     filec;
    char_u **filev;
{
    EXARG ea;
    int i;
    int split = FALSE;

    /* Check whether the current buffer is changed. If so, we will need
     * to split the current window or data could be lost.
     * We don't need to check if the 'hidden' option is set, as in this
     * case the buffer won't be lost.
     */
    if (!p_hid)
    {
	int old_emsg = emsg_off;

	emsg_off = TRUE;
	split = check_changed(curbuf, TRUE, FALSE, FALSE);
	emsg_off = old_emsg;
    }

    /*
     * Set up the new argument list.
     * This code is copied from the tail end of do_arglist()
     */
    FreeWild(arg_file_count, arg_files);
    arg_file_count = filec;
    arg_files = filev;
    arg_had_last = FALSE;

    for (i = 0; i < arg_file_count; ++i)
	(void)buflist_add(arg_files[i]);

    /*
     * Move to the first file.
     */

    /* Fake up a minimal "[s]next" command for do_argfile() */
    ea.cmd = (char_u *)(split ? "snext" : "next");
    ea.forceit = FALSE;
    ea.do_ecmd_cmd = NULL;
    ea.do_ecmd_lnum = 0;

    do_argfile(&ea, 0);
}
#endif

/*
 * Handle ":recover" command.
 */
    static void
do_recover(eap)
    EXARG	*eap;
{
    recoverymode = TRUE;
    if (!check_changed(curbuf, FALSE, TRUE, eap->forceit)
		&& (*eap->arg == NUL || setfname(eap->arg, NULL, TRUE) == OK))
	ml_recover();
    recoverymode = FALSE;
}

/*
 * 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 ":wnext", ":wNext" and ":wprevious" commands.
 */
    static void
do_wnext(eap)
    EXARG	*eap;
{
    int		i;

    if (eap->cmd[1] == 'n')
	i = curwin->w_arg_idx + (int)eap->line2;
    else
	i = curwin->w_arg_idx - (int)eap->line2;
    eap->line1 = 1;
    eap->line2 = curbuf->b_ml.ml_line_count;
    if (do_write(eap) != FAIL)
	do_argfile(eap, i);
}

/*
 * :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
 */
    static void
do_splitview(eap)
    EXARG   *eap;
{
    WIN	    *old_curwin;

    old_curwin = curwin;
    if (win_split(eap->addr_count ? (int)eap->line2 : 0, FALSE, FALSE) != FAIL)
	do_exedit(eap, old_curwin);
}

/*
 * 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
    {
	if (eap->do_ecmd_cmd != NULL)
	    do_cmdline(eap->do_ecmd_cmd, NULL, NULL, DOCMD_VERBOSE);
	update_screen(NOT_VALID);
    }

    /*
     * if ":split file" worked, set alternate file name 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)
    {
	/* Clear the command.  Needed for when forking+exiting, to avoid part
	 * of the argument ending up after the shell prompt. */
	msg_clr_eos();
	gui_start();
    }
    if (!ends_excmd(*eap->arg))
	do_next(eap);
}
#endif

    static void
do_swapname()
{
    if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL)
	MSG("No swap file");
    else
	msg(curbuf->b_ml.ml_mfp->mf_fname);
}

    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, (linenr_t)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, (linenr_t)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 file name 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);
	    out_flush();
	}
    }
    ui_delay(eap->line2 * 1000L, TRUE);
}

    static void
do_exmap(eap, isabbrev)
    EXARG	*eap;
    int		isabbrev;
{
    int	    mode;
    char_u  *cmdp;

    cmdp = eap->cmd;
    mode = get_map_mode(&cmdp, eap->forceit || isabbrev);

    switch (do_map((*cmdp == 'n') ? 2 : (*cmdp == 'u'),
						    eap->arg, mode, isabbrev))
    {
	case 1: emsg(e_invarg);
		break;
	case 2: emsg(e_nomap);
		break;
	case 3: emsg(e_ambmap);
		break;
    }
}

/*
 * ":winsize" command (obsolete).
 */
    static void
do_winsize(arg)
    char_u	*arg;
{
    int	    w, h;

    w = getdigits(&arg);
    arg = skipwhite(arg);
    h = getdigits(&arg);
    set_winsize(w, h, TRUE);
}

/*
 * 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(BL_SOL | BL_FIX);
    }

    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->nextcmd = 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(BL_SOL | BL_FIX);
    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(BL_WHITE | BL_FIX);
}

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

#ifdef USE_GUI_WIN32
    dont_scroll = TRUE;		/* disallow scrolling here */
#endif

    /* 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 ":redir" command, start/stop redirection.
 */
    static void
do_redir(eap)
    EXARG	*eap;
{
    char	*mode;

    if (STRICMP(eap->arg, "END") == 0)
	close_redir();
    else
    {
	if (*eap->arg == '>')
	{
	    ++eap->arg;
	    if (*eap->arg == '>')
	    {
		++eap->arg;
		mode = "a";
	    }
	    else
		mode = "w";
	    eap->arg = skipwhite(eap->arg);

	    close_redir();
	    redir_fd = open_exfile(eap, mode);
	}

	/* TODO: redirect to a buffer */

	/* TODO: redirect to an internal variable */

	else
	    EMSG(e_invarg);
    }
}

    static void
close_redir()
{
    if (redir_fd != NULL)
    {
	fclose(redir_fd);
	redir_fd = NULL;
    }
}

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

    if (*eap->arg == NUL)
	eap->arg = (char_u *)EXRC_FILE;
    fd = open_exfile(eap, WRITEBIN);
    if (fd == NULL)
	return;

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

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

/*
 * Open a file for writing for an Ex command, with some checks.
 * Return file descriptor, or NULL on failure.
 */
    static FILE	*
open_exfile(eap, mode)
    EXARG	*eap;
    char	*mode;	    /* "w" for create new file or "a" for append */
{
    FILE	*fd;

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

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

/*
 * 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(BL_WHITE | BL_FIX);
    (void)setmark(*eap->arg);	    /* set mark */
    curwin->w_cursor = pos;	    /* restore curwin->w_cursor */
}

#ifdef EX_EXTRA
/*
 * Handle ":normal[!] {commands}" - execute normal mode commands
 * Often used for ":autocmd".
 */
    static void
do_normal(eap)
    EXARG	*eap;
{
    OPARG	oa;
    int		len;
    int		save_msg_scroll = msg_scroll;
    int		save_restart_edit = restart_edit;
    int		save_msg_didout = msg_didout;

    msg_scroll = FALSE;	    /* no msg scrolling in Normal mode */
    restart_edit = 0;	    /* don't go to Insert mode */

    /*
     * 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 */
	    /* Make sure w_topline and w_leftcol are correct. */
	    update_topline();
	    if (!curwin->w_p_wrap)
		validate_cursor();
	    update_curswant();

	    normal_cmd(&oa, FALSE);	/* execute a Normal mode cmd */
	}
    }
    while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int);

    msg_scroll = save_msg_scroll;
    restart_edit = save_restart_edit;
    msg_didout |= save_msg_didout;	/* don't reset msg_didout now */
}
#endif

#ifdef FIND_IN_PATH
    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;
	p = skip_regexp(eap->arg, '/', p_magic);
	if (*p)
	{
	    *p++ = NUL;
	    p = skipwhite(p);

	    /* Check for trailing illegal characters */
	    if (!ends_excmd(*p))
		errormsg = e_trailing;
	    else
		eap->nextcmd = check_nextcmd(p);
	}
    }
    if (!eap->skip)
	find_pattern_in_path(eap->arg, 0, (int)STRLEN(eap->arg),
			    whole, !eap->forceit,
			    *eap->cmd == 'd' ?	FIND_DEFINE : FIND_ANY,
			    n, action, eap->line1, eap->line2);

    return errormsg;
}
#endif

    static void
do_ex_tag(eap, dt)
    EXARG	*eap;
    int		dt;
{
    do_tag((char_u *)"", dt, eap->addr_count ? (int)eap->line2
					     : 1, eap->forceit);
}

#ifdef WANT_EVAL

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

    if (cstack->cs_idx == CSTACK_LEN - 1)
	errormsg = (char_u *)":if nesting too deep";
    else
    {
	++cstack->cs_idx;
	cstack->cs_flags[cstack->cs_idx] = 0;

	/*
	 * Don't do something when there is a surrounding conditional and it
	 * was not active.
	 */
	skip = (cstack->cs_idx > 0
		&& !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE));
	if (skip)
	    ++emsg_off;
	result = eval_to_bool(eap->arg, &error, &eap->nextcmd);
	if (skip)
	    --emsg_off;

	if (!skip)
	{
	    if (result)
		cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE;
	    if (error)
		--cstack->cs_idx;
	}
    }

    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;
    int		skip;
    int		result = FALSE;

    if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE))
    {
	if (eap->cmdidx == CMD_else)
	    errormsg = (char_u *)":else without :if";
	else
	    errormsg = (char_u *)":elseif without :if";
    }
    else
    {
	/*
	 * Don't do something when there is a surrounding conditional and it
	 * was not active.
	 */
	skip = (cstack->cs_idx > 0
		&& !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE));
	if (!skip)
	{
	    /* if the ":if" was TRUE, reset active, otherwise set it */
	    if (cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)
		cstack->cs_flags[cstack->cs_idx] = CSF_TRUE;
	    else
		cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE;
	}

	if (eap->cmdidx == CMD_elseif)
	{
	    if (skip)
		++emsg_off;
	    result = eval_to_bool(eap->arg, &error, &eap->nextcmd);
	    if (skip)
		--emsg_off;

	    if (!skip && (cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))
	    {
		if (result)
		    cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE;
		else
		    cstack->cs_flags[cstack->cs_idx] = 0;
		if (error)
		    --cstack->cs_idx;
	    }
	}
    }

    return errormsg;
}

/*
 * Handle ":while".
 */
    static char_u *
do_while(eap, cstack)
    EXARG		*eap;
    struct condstack	*cstack;
{
    char_u	*errormsg = NULL;
    int		error;
    int		skip;
    int		result;

    if (cstack->cs_idx == CSTACK_LEN - 1)
	errormsg = (char_u *)":while nesting too deep";
    else
    {
	/*
	 * cs_had_while is set when we have jumped back from the matching
	 * ":endwhile".  When not set, need to init this cstack entry.
	 */
	if (!cstack->cs_had_while)
	{
	    ++cstack->cs_idx;
	    ++cstack->cs_whilelevel;
	}
	cstack->cs_flags[cstack->cs_idx] = CSF_WHILE;

	/*
	 * Don't do something when there is a surrounding conditional and it
	 * was not active.
	 */
	skip = (cstack->cs_idx > 0
		&& !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE));
	if (skip)
	    ++emsg_off;
	result = eval_to_bool(eap->arg, &error, &eap->nextcmd);
	if (skip)
	    --emsg_off;

	if (!skip)
	{
	    if (result)
		cstack->cs_flags[cstack->cs_idx] |= CSF_ACTIVE | CSF_TRUE;
	    if (error)
		--cstack->cs_idx;
	    else
		/*
		 * Set cs_had_while flag, so do_cmdline() will set the line
		 * number in cs_line[].
		 */
		cstack->cs_had_while = TRUE;
	}
    }

    return errormsg;
}

/*
 * Handle ":continue".
 */
    static char_u *
do_continue(cstack)
    struct condstack	*cstack;
{
    char_u	*errormsg = NULL;

    if (cstack->cs_whilelevel <= 0 || cstack->cs_idx < 0)
	errormsg = (char_u *)":continue without :while";
    else
    {
	/* Find the matching ":while". */
	while (cstack->cs_idx > 0
		    && !(cstack->cs_flags[cstack->cs_idx] & CSF_WHILE))
	    --cstack->cs_idx;

	/*
	 * Set cs_had_continue, so do_cmdline() will jump back to the matching
	 * ":while".
	 */
	cstack->cs_had_continue = TRUE;	    /* let do_cmdline() handle it */
    }

    return errormsg;
}

/*
 * Handle ":break".
 */
    static char_u *
do_break(cstack)
    struct condstack	*cstack;
{
    char_u	*errormsg = NULL;
    int		idx;

    if (cstack->cs_whilelevel <= 0 || cstack->cs_idx < 0)
	errormsg = (char_u *)":break without :while";
    else
    {
	/* Find the matching ":while". */
	for (idx = cstack->cs_idx; idx >= 0; --idx)
	{
	    cstack->cs_flags[idx] &= ~CSF_ACTIVE;
	    if (cstack->cs_flags[idx] & CSF_WHILE)
		break;
	}
    }

    return errormsg;
}

/*
 * Handle ":endwhile".
 */
    static char_u *
do_endwhile(cstack)
    struct condstack	*cstack;
{
    char_u	*errormsg = NULL;

    if (cstack->cs_whilelevel <= 0 || cstack->cs_idx < 0)
	errormsg = (char_u *)":endwhile without :while";
    else
    {
	if (!(cstack->cs_flags[cstack->cs_idx] & CSF_WHILE))
	{
	    errormsg = (char_u *)":enwhile without :while";
	    while (cstack->cs_idx > 0
		    && !(cstack->cs_flags[cstack->cs_idx] & CSF_WHILE))
		--cstack->cs_idx;
	}
	/*
	 * Set cs_had_endwhile, so do_cmdline() will jump back to the matching
	 * ":while".
	 */
	cstack->cs_had_endwhile = TRUE;
    }

    return errormsg;
}

/*
 * Return TRUE if the string "p" looks like a ":while" command.
 */
    static int
has_while_cmd(p)
    char_u	*p;
{
    p = skipwhite(p);
    while (*p == ':')
	++p;
    p = skipwhite(p);
    if (p[0] == 'w' && p[1] == 'h')
	return TRUE;
    return FALSE;
}

#endif /* WANT_EVAL */

/*
 * 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 file name
 *	  '<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, or NULL */
    char_u	**errormsg;	/* error message, or NULL */
{
    int		i;
    char_u	*s;
    char_u	*tail;
    char_u	*result;
    int		resultlen;
    char_u	*buf = NULL;
    BUF		*buffer;
#define VALID_P	    1
#define VALID_H	    2
    int		valid = VALID_H + VALID_P;    /* assume valid result */
    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_fname == NULL)
		{
		    result = (char_u *)"";
		    valid = 0;	    /* Must have ":p:h" to be valid */
		}
		else
		    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 */

		buffer = buflist_findnr(i);
		if (buffer == NULL)
		{
		    *errormsg = (char_u *)"No alternate file name to substitute for '#'";
		    return NULL;
		}
		if (lnump != NULL)
		    *lnump = buflist_findlnum(buffer);
		if (buffer->b_fname == NULL)
		{
		    result = (char_u *)"";
		    valid = 0;	    /* Must have ":p:h" to be valid */
		}
		else
		    result = buffer->b_fname;
		break;

#ifdef FILE_IN_PATH
	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;
#endif

#ifdef AUTOCMD
	case SPEC_AFILE:	    /* file name for autocommand */
		result = autocmd_fname;
		if (result == NULL)
		{
		    *errormsg = (char_u *)"no autocommand file name 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 file name 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/file_name */
	    if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p')
	    {
		valid |= VALID_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 "/file_name", can be repeated  */
	    /* Don't remove the first "/" or "c:\" */
	    while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h')
	    {
		valid |= VALID_H;
		*usedlen += 2;
		s = get_past_head(result);
		while (tail > s && vim_ispathsep(tail[-1]))
		    --tail;
		resultlen = tail - result;
		while (tail > s && !vim_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, can be repeated */
	    /* ":r" - root, without extension, 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') */
    }

    if (resultlen == 0 || valid != VALID_H + VALID_P)
    {
	if (valid != VALID_H + VALID_P)
	    *errormsg = (char_u *)"Empty file name for '%' or '#', only works with \":p:h\"";
	else
	    *errormsg = (char_u *)"Evaluates to an empty string";
	result = NULL;
    }
    else
	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 file name, 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;
}
