/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

/*
 * cmdline.c: functions for reading in the command line and executing it
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "cmdtab.h"
#include "ops.h"			/* included because we call functions in ops.c */
#include "fcntl.h"			/* for chdir() */
#ifdef JP
#include "jp.h"
#endif
#ifdef FEPCTRL
#include "fepctrl.h"
#endif

#ifdef LATTICE
# define mktemp(a)	tmpnam(a)
#endif

/*
 * the history list of alternate files
 */
#define NUMALTFILES 20

static char    *altfiles[NUMALTFILES];	/* alternate files */
static char    *saltfiles[NUMALTFILES];	/* alternate files without path */
static linenr_t altlnum[NUMALTFILES];	/* line # in alternate file */
static linenr_t doecmdlnum = 0;			/* line # in new file for doecmd() */

/*
 * variables shared between getcmdline() and redrawcmdline()
 */
static int		 cmdlen;		/* number of chars on command line */
static int		 cmdpos;		/* current cursor position */
static int		 cmdslen;		/* lenght of command line on screen */
static int		 cmdspos;		/* cursor position on screen */
static int		 cmdfirstc; 	/* ':', '/' or '?' */
static u_char	*cmdbuff;		/* pointer to command line buffer */

/*
 * The next two variables contain the bounds of any range given in a command.
 * They are set by docmdline().
 */
static linenr_t 	line1, line2;

static int			forceit;
static int			regname;
static int			quitmore = 0;
static int  		cmd_numfiles = -1;	  /* number of files found by
													filename completion */

static void		putcmdline __ARGS((int, u_char *));
static void		cmdchecklen __ARGS((void));
void		cursorcmd __ARGS((void));
static int		ccheck_abbr __ARGS((int));
static u_char	*DoOneCmd __ARGS((u_char *));
static void		dobang __ARGS((int, u_char *));
static int		autowrite __ARGS((void));
static int		dowrite __ARGS((u_char *, int));
static int		doecmd __ARGS((char *, char *));
static void		doshell __ARGS((char *));
static void		dofilter __ARGS((u_char *, int, int));
static void		domake __ARGS((char *));
static int		doarglist __ARGS((char *));
static int		check_readonly __ARGS((void));
static int		check_changed __ARGS((int));
static int		check_more __ARGS((int));
static void		setaltfname __ARGS((char *, char *, linenr_t, int));
static void		nextwild __ARGS((u_char *, int));
static void		showmatches __ARGS((char *, int));
static char		*addstar __ARGS((char *, int));
static linenr_t get_address __ARGS((u_char **));
static void		do_align __ARGS((linenr_t, linenr_t, int, int));

extern char		*mktemp __ARGS((char *));

extern int global_busy, global_wait;	/* shared with csearch.c, message.c */

/*
 * getcmdline() - accept a command line starting with ':', '!', '/', or '?'
 *
 * For searches the optional matching '?' or '/' is removed.
 */

	int
getcmdline(firstc, buff)
	int			firstc; 	/* either ':', '/', or '?' */
	u_char		*buff;	 	/* buffer for command string */
{
#ifdef JP
			 u_char		c;
			 u_char		k;
#else
	register u_char 	c;
#endif
			 int		nextc = 0;
	register int		i;
			 int		retval;
			 int		hiscnt;				/* current history line in use */
	static	 char 		**history = NULL;	/* history table */
	static	 int		hislen = 0; 		/* actual lengt of history table */
			 int		newlen;				/* new length of history table */
	static	 int		hisidx = -1;		/* last entered entry */
			 char		**temp;
			 char		*lookfor = NULL;	/* string to match */
			 int		j = -1;
			 int		gotesc = FALSE;		/* TRUE when last char typed was <ESC> */
#ifdef JPFEP
			 int 		orgKanjiInput;
#endif

/*
 * set some variables for redrawcmd()
 */
	cmdfirstc = firstc;
	cmdbuff = buff;
	cmdlen = cmdpos = 0;
	cmdslen = cmdspos = 1;
	State = CMDLINE;
	gotocmdline(TRUE, firstc);

/*
 * if size of history table changed, reallocate it
 */
	newlen = (int)p_hi;
	if (newlen != hislen)						/* history length changed */
	{
		if (newlen)
			temp = (char **)lalloc((u_long)(newlen * sizeof(char *)), TRUE);
		else
			temp = NULL;
		if (newlen == 0 || temp != NULL)
		{
			if (newlen > hislen)			/* array becomes bigger */
			{
				for (i = 0; i <= hisidx; ++i)
					temp[i] = history[i];
				j = i;
				for ( ; i <= newlen - (hislen - hisidx); ++i)
					temp[i] = NULL;
				for ( ; j < hislen; ++i, ++j)
					temp[i] = history[j];
			}
			else							/* array becomes smaller */
			{
				j = hisidx;
				for (i = newlen - 1; ; --i)
				{
					if (i >= 0)
						temp[i] = history[j];	/* copy newest entries */
					else
						free(history[j]);		/* remove older entries */
					if (--j < 0)
						j = hislen - 1;
					if (j == hisidx)
						break;
				}
				hisidx = newlen - 1;
			}
			free(history);
			history = temp;
			hislen = newlen;
		}
	}
	hiscnt = hislen;			/* set hiscnt to impossible history value */

#ifdef DIGRAPHS
	dodigraph(-1);				/* init digraph typahead */
#endif
#ifdef JPFEP
	orgKanjiInput = KanjiInput;
	fep_mode_switch(KanjiInput = FALSE);
#endif

	/* collect the command string, handling '\b', @ and much more */
	for (;;)
	{
		cursorcmd();	/* set the cursor on the right spot */
		if (nextc)		/* character remaining from CTRL-V */
		{
			c = nextc;
			nextc = 0;
		}
  		else
		{
#ifdef JP
			jp_getchar(&c, &k, NULL);
#else
			c = vgetc();
#endif
			if (c == Ctrl('C') && got_int)
				got_int = FALSE;
		}

#ifdef DOSGEN	/* may be used for UNIX */
		if (c == Ctrl('Q') || c == Ctrl(']'))
		{
#ifdef JP
			jp_getchar(&c, &k, NULL);
#else
			c = vgetc();
#endif
			switch (c) {
				case 'h':
				case Ctrl('B'):
					c = K_LARROW;
					break;
				case 'j':
				case Ctrl('N'):
					c = K_DARROW;
					break;
				case 'k':
				case Ctrl('P'):
					c = K_UARROW;
					break;
				case 'l':
				case Ctrl('F'):
					c = K_RARROW;
					break;
				case 'H':
				case 'b':
					c = K_SLARROW;
					break;
				case 'J':
					c = K_SDARROW;
					break;
				case 'K':
					c = K_SUARROW;
					break;
				case 'L':
				case 'w':
					c = K_SRARROW;
					break;
				default:
					beep();
					continue;
			}
		}
#endif

		if (lookfor && c != K_SDARROW && c != K_SUARROW)
		{
			free(lookfor);
			lookfor = NULL;
		}

		if (cmd_numfiles > 0 && !(c == p_wc && KeyTyped) && c != Ctrl('N') &&
						c != Ctrl('P') && c != Ctrl('A') && c != Ctrl('L'))
			(void)ExpandOne(NULL, FALSE, -2);	/* may free expanded file names */

#ifdef DIGRAPHS
		c = dodigraph(c);
#endif

		if (c == '\n' || c == '\r' || (c == ESC && !KeyTyped))
		{
			if (ccheck_abbr(c + 0x100))
				continue;
			outchar('\r');
			flushbuf();
			break;
		}

			/* hitting <ESC> twice means: abandon command line */
			/* wildcard expansion is only done when the key is really typed, not
			   when it comes from a macro */
		if (c == p_wc && !gotesc && KeyTyped)
		{
			if (cmd_numfiles > 0)	/* typed p_wc twice */
				nextwild(buff, 3);
			else					/* typed p_wc first time */
				nextwild(buff, 0);
			if (c == ESC)
				gotesc = TRUE;
			continue;
		}
		gotesc = FALSE;

#ifndef JPFEP
		if (c == K_ZERO)		/* NUL is stored as NL */
			c = '\n';
#endif

		switch (c)
		{
		case BS:
		case DEL:
		case Ctrl('W'):
				/*
				 * delete current character is the same as backspace on next
				 * character, except at end of line
				 */
				if (c == DEL && cmdpos != cmdlen)
				{
#ifdef JP
					if (IsKanji(buff[cmdpos]))
						cmdpos+=2;
					else
#endif
  					++cmdpos;
				}
				if (cmdpos > 0)
				{
					j = cmdpos;
					if (c == Ctrl('W'))
					{
						while (cmdpos && isspace(buff[cmdpos - 1]))
							--cmdpos;
						i = isidchar(buff[cmdpos - 1]);
						while (cmdpos && !isspace(buff[cmdpos - 1]) && isidchar(buff[cmdpos - 1]) == i)
#ifdef JP
							cmdpos -= IsKanji(buff[cmdpos - 1]) ? 2 : 1;
#else
							cmdpos--;
#endif
					}
					else
#ifdef JP
						cmdpos -= IsKanji(buff[cmdpos - 1]) ? 2 : 1;
#else
						cmdpos--;
#endif
					cmdlen -= j - cmdpos;
					i = cmdpos;
					while (i < cmdlen)
						buff[i++] = buff[j++];
					redrawcmd();
				}
				else if (cmdlen == 0 && c != Ctrl('W'))
				{
					retval = FALSE;
					msg("");
					screen_msg(FALSE, NULL);
					goto returncmd; 	/* back to cmd mode */
				}
				continue;

/*		case '@':	only in very old vi */
		case Ctrl('U'):
clearline:
				cmdpos = 0;
				cmdlen = 0;
				cmdslen = 1;
				cmdspos = 1;
				redrawcmd();
				continue;

		case ESC:			/* get here if p_wc != ESC or when ESC typed twice */
		case Ctrl('C'):
				retval = FALSE;
				msg("");
				goto returncmd; 	/* back to cmd mode */

		case Ctrl('D'):
			{
				for (i = cmdpos; i > 0 && buff[i - 1] != ' '; --i)
						;
				showmatches((char *)&buff[i], cmdpos - i);
				for (i = Rows_max - Rows; i; --i)
					outchar('\n');

				redrawcmd();
				continue;
			}

		case Ctrl('F'):
		case K_RARROW:
		case K_SRARROW:
				do
				{
						if (cmdpos >= cmdlen)
								break;
#ifdef JP
						if (IsKanji(buff[cmdpos]))
						{
							cmdspos += 2;
							cmdpos ++;
						}
						else
#endif
						cmdspos += charsize(buff[cmdpos]);
						++cmdpos;
				}
				while (c == K_SRARROW && buff[cmdpos] != ' ');
				continue;

		case Ctrl('B'):
		case K_LARROW:
		case K_SLARROW:
				do
				{
						if (cmdpos <= 0)
								break;
						--cmdpos;
#ifdef JP
						if (IsKanji(buff[cmdpos]))
						{
							cmdspos -= 2;
							cmdpos --;
						}
						else
#endif
						cmdspos -= charsize(buff[cmdpos]);
				}
				while (c == K_SLARROW && buff[cmdpos - 1] != ' ');
				continue;

		case Ctrl('A'):		/* all matches */
				if (cmd_numfiles > 0)
				{
					nextwild(buff, 4);
					continue;
				}
							/* begin of command line */
				cmdpos = 0;
				cmdspos = 1;
				continue;

		case Ctrl('E'):		/* end of command line */
				cmdpos = cmdlen;
				buff[cmdlen] = NUL;
				cmdspos = strsize((char *)buff) + 1;
				continue;

		case Ctrl('L'):		/* longest common part */
				nextwild(buff, 5);
				continue;

		case Ctrl('N'):		/* next match */
		case Ctrl('P'):		/* previous match */
				if (cmd_numfiles > 0)
				{
					nextwild(buff, (c == Ctrl('P')) ? 2 : 1);
					continue;
				}

		case K_UARROW:
		case K_DARROW:
		case K_SUARROW:
		case K_SDARROW:
				if (hislen == 0)		/* no history */
					continue;

				i = hiscnt;
			
					/* save current command string */
				if (c == K_SUARROW || c == K_SDARROW)
				{
					buff[cmdpos] = NUL;
					if (lookfor == NULL && (lookfor = strsave((char *)buff)) == NULL)
						continue;

					j = strlen(lookfor);
				}
				for (;;)
				{
						/* one step backwards */
					if (c == K_UARROW || c == K_SUARROW || c == Ctrl('P'))
					{
						if (hiscnt == hislen)	/* first time */
							hiscnt = hisidx;
						else if (hiscnt == 0 && hisidx != hislen - 1)
							hiscnt = hislen - 1;
						else if (hiscnt != hisidx + 1)
							--hiscnt;
						else					/* at top of list */
							break;
					}
					else	/* one step forwards */
					{
						if (hiscnt == hisidx)	/* on last entry, clear the line */
						{
							hiscnt = hislen;
							goto clearline;
						}
						if (hiscnt == hislen)	/* not on a history line, nothing to do */
							break;
						if (hiscnt == hislen - 1)	/* wrap around */
							hiscnt = 0;
						else
							++hiscnt;
					}
					if (hiscnt < 0 || history[hiscnt] == NULL)
					{
						hiscnt = i;
						break;
					}
					if ((c != K_SUARROW && c != K_SDARROW) || hiscnt == i ||
							strncmp(history[hiscnt], lookfor, (size_t)j) == 0)
						break;
				}

				if (hiscnt != i)		/* jumped to other entry */
				{
					strcpy((char *)buff, history[hiscnt]);
					cmdpos = cmdlen = strlen((char *)buff);
					redrawcmd();
				}
				continue;

#ifdef JPFEP
		case K_ZERO:
		case Ctrl('\\'):
				fep_mode_switch(KanjiInput = ! KanjiInput);
				cursorcmd();
				continue;
#endif
#ifdef FEPCTRL
		case Ctrl('\\'):
				if (p_fc)
				{
					if (fep_get_mode())
						fep_force_off();
					else
						fep_force_on();
				}
				continue;
#endif

		case Ctrl('V'):
				putcmdline('^', buff);
#ifdef JP
				get_literal(&nextc, &c, &k);/* get next (two) character(s) */
#ifdef ONEW
				if (p_oh)
					redrawcmd();
#endif
#else
				c = get_literal(&nextc);	/* get next (two) character(s) */
#endif
				break;

#ifdef DIGRAPHS
		case Ctrl('K'):
				putcmdline('?', buff);
			  	c = vgetc();
				putcmdline(c, buff);
				c = getdigraph(c, vgetc());
				break;
#endif /* DIGRAPHS */
		}

		/* we come here if we have a normal character */

		if (!isidchar(c) && ccheck_abbr(c))
			continue;

		if (cmdlen < CMDBUFFSIZE - 2)
		{
			char str[3];
			str[i = 0] = c;
#ifdef JP
			if (IsKanji(c))
				str[++ i] = k;
#endif
			str[++ i] = NUL;
			cmd_insertstr(str);
		}
		cmdchecklen();
	}
	retval = TRUE;				/* when we get here we have a valid command line */

returncmd:

#ifdef JPFEP
	fep_mode_switch(KanjiInput = orgKanjiInput);
#endif
	buff[cmdlen] = NUL;
	if (hislen != 0 && cmdlen != 0)		/* put line in history buffer */
	{
		if (++hisidx == hislen)
			hisidx = 0;
		free(history[hisidx]);
		history[hisidx] = strsave((char *)buff);
	}

	/*
	 * If the screen was shifted up, redraw the whole screen (later).
	 * If the line is too long, clear it, so ruler and shown command do
	 * not get printed in the middle of it.
	 */
	if (cmdoffset)
		must_redraw = CLEAR;
	else if (cmdslen >= sc_col)
		gotocmdline(TRUE, NUL);
	State = NORMAL;
	script_winsize_pp();
#ifdef FEPCTRL
	if (p_fc)
		fep_force_off();
#endif
	return retval;
}

/*
 * put a character on the command line.
 * Used for CTRL-V and CTRL-K
 */
	static void
putcmdline(c, buff)
	int		c;
	u_char	*buff;
{
	int		len;
	char	buf[2];

	buf[0] = c;
	buf[1] = 0;
	len = outtrans(buf, 1);
	outtrans((char *)(buff + cmdpos), cmdlen - cmdpos);
	cmdslen += len;
	cmdchecklen();
	cmdslen -= len;
	cursorcmd();
}

/*
 * Check if the command line spans more than one screen line.
 * The maximum number of lines is remembered.
 */
	static void
cmdchecklen()
{
	if (cmdslen / (int)Columns > cmdoffset)
		cmdoffset = cmdslen / (int)Columns;
}

/*
 * this fuction is called when the screen size changes
 */
	void
redrawcmdline()
{
		cmdoffset = 0;
		redrawcmd();
		cursorcmd();
}

/*
 * Redraw what is currently on the command line.
 */
	void
redrawcmd()
{
	register int i;

	windgoto((int)Rows - 1 - cmdoffset, 0);
	outchar(cmdfirstc);
	cmdslen = 1;
	cmdspos = 1;
	outtrans((char *)cmdbuff, cmdlen);
	for (i = 0; i < cmdlen; )
	{
		cmdslen += charsize(cmdbuff[i]);
		if (++i == cmdpos)
				cmdspos = cmdslen;
	}
#ifdef UNIX
	outstr(T_EL);
#else
	for (i = (cmdoffset + 1) * (int)Columns - cmdslen; --i > 0; )
		outchar(' ');
#endif
	cmdchecklen();
}

	void
cursorcmd()
{
	windgoto((int)Rows - 1 - cmdoffset + (cmdspos / (int)Columns), cmdspos % (int)Columns);
}

	int
cmd_insertstr(s)
	char *s;
{
	int	len, slen;
	u_char *cmdcurs;

	len = strlen(s);
	if (cmdlen + len < CMDBUFFSIZE - 1)
	{
		cursorcmd();
		cmdcurs = cmdbuff + cmdpos;
		memmove(cmdcurs + len, cmdcurs, cmdlen - cmdpos + 2);
		memmove(cmdcurs, s, len);
		cmdlen += len;
#ifdef ONEW
		if (Kconvlnum)
		{
			if (KconvAltStart == -1)
			{
				outstr(T_US);
				outtrans((char *)cmdbuff + cmdpos, len);
				outstr(T_UE);
			}
			else
			{
				outstr(T_US);
				outtrans((char *)cmdbuff + cmdpos,	KconvAltStart);
				outstr(T_UE);
				outstr(T_TI);
				outtrans((char *)cmdbuff + cmdpos + KconvAltStart,
												KconvAltEnd - KconvAltStart);
				outstr(T_TP);
				outstr(T_US);
				outtrans((char *)cmdbuff + cmdpos + KconvAltEnd,
														len - KconvAltEnd);
				outstr(T_UE);
			}
			outtrans((char *)cmdbuff + cmdpos + len, cmdlen - cmdpos - len);
		}
		else
#endif
		outtrans((char *)(cmdbuff + cmdpos), cmdlen - cmdpos);
		cmdpos += len;
		for (slen = 0; *s; s++)
#ifdef JP
			if (IsKanji(*s))
			{
				slen += 2;
				if (* ++s == NUL)		/* adjust kanji error */
				{
				  *s = 'X';
				  *(s + 1) = NUL;
				}
			}
			else
#endif
			slen += charsize(*s);
		cmdslen += slen;
		cmdspos += slen;

		outstr(T_EL);
		return len;
	}
	else
		return 0;
}

#ifdef ONEW
	void
cmd_delchars(n)
	int n;
{
	int i, slen;
	slen = 0;
	for(i = cmdpos - n ; i < cmdpos; i++)
		slen += IsKanji(cmdbuff[i]) ? 1 : charsize(cmdbuff[i]);
	for(i = cmdpos; i <= cmdlen; i++)
		cmdbuff[i - n] = cmdbuff[i];
	cmdpos -= n;
	cmdlen -= n;
	cmdslen -= slen;
	cmdspos -= slen;
	cursorcmd();
}

#define MINDIST (Columns / 4)
	void
cmd_message(msg)
	char *msg;
{
	int vslen, msglen;

	msglen = strlen(msg);

	vslen = p_fm ? cmdslen % (int)Columns + 2
				 : Columns - msglen - 1;

	if (vslen < MINDIST)
		vslen = MINDIST;

	windgoto((int)Rows - 1, cmdslen);
	outstr(T_EL);

	if (vslen + msglen <= Columns)
	{
		windgoto((int)Rows - 1, vslen);
		outtrans(msg, -1);
		cursorcmd();
	}
}
#endif

/*
 * Check the word in front of the cursor for an abbreviation.
 * Called when the non-id character "c" has been entered.
 * When an abbreviation is recognized it is removed from the text with
 * backspaces and the replacement string is inserted, followed by "c".
 */
	static int
ccheck_abbr(c)
	int c;
{
	if (p_paste || no_abbr)			/* no abbreviations or in paste mode */
		return FALSE;
	
	return check_abbr(c, (char *)cmdbuff, cmdpos, 0);
}

/*
 * docmdline(): execute an Ex command line
 *
 * 1. If no line given, get one.
 * 2. Split up in parts separated with '|'.
 *
 * This function may be called recursively!
 */
	void
docmdline(cmdline)
	u_char		*cmdline;
{
	u_char		buff[CMDBUFFSIZE];		/* command line */
	u_char		*nextcomm;

/*
 * 1. If no line given: get one.
 */
	if (cmdline == NULL)
	{
		if (!getcmdline(':', buff))
			return;
	}
	else
	{
		if (strlen((char *)cmdline) > (size_t)(CMDBUFFSIZE - 2))
		{
			emsg(e_toolong);
			return;
		}
		/* Make a copy of the command so we can mess with it. */
		strcpy((char *)buff, (char *)cmdline);
	}

/*
 * 2. Loop for each '|' separated command.
 *    DoOneCmd will set nextcommand to NULL if there is no trailing '|'.
 */
	for (;;)
	{
		nextcomm = DoOneCmd(buff);
		if (nextcomm == NULL)
			break;
		strcpy((char *)buff, (char *)nextcomm);
	}
}

/*
 * Execute one Ex command.
 *
 * 2. skip comment lines and leading space
 * 3. parse range
 * 4. parse command
 * 5. parse arguments
 * 6. switch on command name
 *
 * This function may be called recursively!
 */
	static u_char *
DoOneCmd(buff)
	u_char *buff;
{
	u_char				cmdbuf[CMDBUFFSIZE];	/* for '%' and '#' expansion */
	u_char				c;
	register u_char		*p;
	char				*q;
	u_char				*cmd, *arg;
	int 				i;
	int					cmdidx;
	int					argt;
	register linenr_t	lnum;
	long				n;
	int					addr_count;	/* number of address specifications */
	FPOS				pos;
	int					append = FALSE;			/* write with append */
	int					usefilter = FALSE;		/* filter instead of file name */
	u_char				*nextcomm;

	if (quitmore)
		--quitmore;		/* when not editing the last file :q has to be typed twice */
/*
 * 2. skip comment lines and leading space, colons or bars
 */
	for (cmd = buff; *cmd && strchr(" \t:|", *cmd) != NULL; cmd++)
		;

	nextcomm = NULL;		/* default: no next command */
	if (strchr("#\"", *cmd) != NULL)	/* ignore comment and empty lines */
		goto doend;

/*
 * 3. parse a range specifier of the form: addr [,addr] [;addr] ..
 *
 * where 'addr' is:
 *
 * %		  (entire file)
 * $  [+-NUM]
 * 'x [+-NUM] (where x denotes a currently defined mark)
 * .  [+-NUM]
 * [+-NUM]..
 * NUM
 *
 * The 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.
 */

	addr_count = 0;
	--cmd;
	do
	{
		++cmd;							/* skip ',' or ';' */
		line1 = line2;
		line2 = Curpos.lnum;			/* default is current line number */
		skipspace((char **)&cmd);
		lnum = get_address(&cmd);
		if (lnum == INVLNUM)
		{
			if (*cmd == '%')            /* '%' - all lines */
			{
				++cmd;
				line1 = 1;
				line2 = line_count;
				++addr_count;
			}
		}
		else
			line2 = lnum;
		addr_count++;

		if (*cmd == ';')
		{
			if (line2 == 0)
				Curpos.lnum = 1;
			else
				Curpos.lnum = line2;
		}
	} while (*cmd == ',' || *cmd == ';');

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

	if (line1 > line2 || line2 > line_count)
	{
		emsg(e_invrange);
		goto doend;
	}

/*
 * 4. parse command
 */

	skipspace((char **)&cmd);

	/*
	 * If we got a line, but no command, then go to the line.
	 */
	if (*cmd == NUL || *cmd == '"' || (*cmd == '|' && (nextcomm = cmd) != NULL))
	{
		if (addr_count != 0)
		{
			if (line2 == 0)
				Curpos.lnum = 1;
			else
				Curpos.lnum = line2;
			Curpos.col = 0;
			cursupdate();
		}
		goto doend;
	}

	/*
	 * isolate the command and search for it in the command table
	 */
	p = cmd;
	if (*cmd != 'k')
		while (isalpha(*p))
			++p;
	if (p == cmd && strchr("@!=><&k", *p) != NULL)	/* non-alpha or 'k' command */
		++p;
	i = (int)(p - cmd);

	for (cmdidx = 0; cmdidx < CMD_SIZE; ++cmdidx)
		if (strncmp(cmdnames[cmdidx].cmd_name, (char *)cmd, (size_t)i) == 0)
			break;

	if (i == 0 || cmdidx == CMD_SIZE)
	{
		emsg(e_invcmd);
		goto doend;
	}

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

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

	if (!(argt & RANGE) && addr_count)
	{
		emsg(e_norange);
		goto doend;
	}

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

	/*
	 * for the :make command we insert the 'makeprg' option here,
	 * so things like % get expanded
	 */
	if (cmdidx == CMD_make)
	{
		if (strlen(p_mp) + strlen((char *)p) + 2 >= (unsigned)CMDBUFFSIZE)
		{
			emsg(e_toolong);
			goto doend;
		}
		strcpy((char *)cmdbuf, p_mp);
		strcat((char *)cmdbuf, " ");
		strcat((char *)cmdbuf, (char *)p);
		strcpy((char *)buff, (char *)cmdbuf);
		p = buff;
	}

	arg = p;						/* remember start of argument */
	skipspace((char **)&arg);

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

	/*
	 * check for '|' to separate commands and '"' to start comments
	 */
	if (argt & TRLBAR)
	{
		while (*p)
		{
			if (*p == Ctrl('V'))
			{
				if (argt & USECTRLV)	/* skip the CTRL-V and next char */
					++p;
				else					/* remove CTRL-V and skip next char */
					strcpy((char *)p, (char *)p + 1);
			}
			else if ((*p == '"' && !(argt & NOTRLCOM)) || *p == '|')
			{
				if (*(p - 1) == '\\')	/* remove the backslash */
				{
					strcpy((char *)p - 1, (char *)p);
					--p;
				}
				else
				{
					if (*p == '|')
						nextcomm = p + 1;
					*p = NUL;
					break;
				}
			}
			++p;
		}
		if (!(argt & NOTRLCOM))			/* remove trailing spaces */
		{
			q = (char *)arg + strlen((char *)arg);
			while (--q > (char *)arg && isspace(q[0]) && q[-1] != '\\' && q[-1] != Ctrl('V'))
				*q = NUL;
		}
	}

	if ((argt & DFLALL) && addr_count == 0)
	{
		line1 = 1;
		line2 = line_count;
	}

	regname = 0;
		/* accept numbered register only when no count allowed (:put) */
	if ((argt & REGSTR) && (isalpha(*arg) || *arg == '.' || *arg == '"' || (!(argt & COUNT) && isdigit(*arg))))
	{
		regname = *arg;
		++arg;
		skipspace((char **)&arg);
	}

	if ((argt & COUNT) && isdigit(*arg))
	{
		n = getdigits((char **)&arg);
		skipspace((char **)&arg);
		if (n <= 0)
		{
			emsg(e_zerocount);
			goto doend;
		}
		line1 = line2;
		line2 += n - 1;
	}

	if (!(argt & EXTRA) && strchr("|\"#", *arg) == NULL)	/* no arguments allowed */
	{
		emsg(e_trailing);
		goto doend;
	}

	if (cmdidx == CMD_write)
	{
		if (*arg == '>')						/* append */
		{
			if (*++arg != '>')				/* typed wrong */
			{
				emsg("Use w or w>>");
				goto doend;
			}
			++arg;
			skipspace((char **)&arg);
			append = TRUE;
		}
		else if (*arg == '!')					/* :w !filter */
		{
			++arg;
			usefilter = TRUE;
		}
	}

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

	/*
	 * change '%' to Filename, '#' to altfile
	 */
	if (argt & XFILE)
	{
		for (p = arg; *p; ++p)
		{
			c = *p;
			if (c != '%' && c != '#')	/* nothing to expand */
				continue;
			if (*(p - 1) == '\\')		/* remove escaped char */
			{
				strcpy((char *)p - 1, (char *)p);
				--p;
				continue;
			}

			n = 1;				/* length of what we expand */
			if (c == '#' && *(p + 1) == '<')
			{					/* "#<": current file name without extension */
				n = 2;
				c = '<';
			}
			if (c == '%' || c == '<')
			{
				if (check_fname())
					goto doend;
				q = xFilename;
			}
			else
			{
				q = (char *)p + 1;
				i = (int)getdigits(&q);
				n = q - (char *)p;

				if (i >= NUMALTFILES || altfiles[i] == NULL)
				{
						emsg(e_noalt);
						goto doend;
				}
				doecmdlnum = altlnum[i];
				if (did_cd)
					q = altfiles[i];
				else
					q = saltfiles[i];
			}
			i = strlen((char *)arg) + strlen(q) + 3;
			if (nextcomm)
				i += strlen((char *)nextcomm);
			if (i > CMDBUFFSIZE)
			{
				emsg(e_toolong);
				goto doend;
			}
			/*
			 * we built the new argument in cmdbuf[], then copy it back to buff[]
			 */
			*p = NUL;							/* truncate at the '#' or '%' */
			strcpy((char *)cmdbuf, (char *)arg);/* copy up to there */
			i = p - arg;						/* remember the lenght */
			strcat((char *)cmdbuf, q);			/* append the file name */
			if (c == '<' && (arg = (u_char *)strrchr(q, '.')) != NULL &&
							arg >= (u_char *)gettail(q))	/* remove extension */
				*((char *)cmdbuf + ((char *)arg - q) + i) = NUL;
			i = strlen((char *)cmdbuf);			/* remember the end of the filename */
			strcat((char *)cmdbuf, (char *)p+n);/* append what is after '#' or '%' */
			p = buff + i - 1;					/* remember where to continue */
			if (nextcomm)						/* append next command */
			{
				i = strlen((char *)cmdbuf) + 1;
				strcpy((char *)cmdbuf + i, (char *)nextcomm);
				nextcomm = buff + i;
			}
			strcpy((char *)buff, (char *)cmdbuf);/* copy back to buff[] */
			arg = buff;
		}

		/*
		 * One file argument: expand wildcards.
		 * Don't do this with ":r !command" or ":w !command".
		 */
		if (argt & NOSPC)
		{
			if (has_wildcard((char *)arg) && !usefilter)
			{
				if ((p = (u_char *)ExpandOne(arg, TRUE, -1)) == NULL)
					goto doend;
				if (strlen((char *)p) + arg - buff < CMDBUFFSIZE - 2)
					strcpy((char *)arg, (char *)p);
				else
					emsg(e_toolong);
				free(p);
			}
		}
	}

/*
 * 6. switch on command name
 */
	switch (cmdidx)
	{
		case CMD_quit:
				if (!check_more(FALSE))		/* if more files we won't exit */
					exiting = TRUE;
				if (check_changed(FALSE) || check_more(TRUE))
				{
					exiting = FALSE;
					settmode(1);
					break;
				}
				getout(0);

		case CMD_stop:
		case CMD_suspend:
				if (!forceit && Changed)
					autowrite();
				gotocmdend();
				flushbuf();
				stoptermcap();
				mch_suspend();		/* call machine specific function */
				starttermcap();
				must_redraw = CLEAR;
				break;

		case CMD_xit:
		case CMD_wq:
				if (!check_more(FALSE))		/* if more files we won't exit */
					exiting = TRUE;
				if (((cmdidx == CMD_wq || Changed) &&
				     (check_readonly() || !dowrite(arg, FALSE))) ||
					check_more(TRUE))
				{
					exiting = FALSE;
					settmode(1);
					break;
				}
				getout(0);

		case CMD_args:
				if (numfiles == 0)			/* no file name list */
				{
					if (!check_fname())		/* check for no file name at all */
						smsg("[%s]", Filename);
					break;
				}
				gotocmdline(TRUE, NUL);
				for (i = 0; i < numfiles; ++i)
				{
					if (i == curfile)
						outchar('[');
					outstrn(files[i]);
					if (i == curfile)
						outchar(']');
					outchar(' ');
				}
				outchar('\n');
				wait_return(TRUE);
				break;

		case CMD_wnext:
				n = line2;
				line1 = 1;
				line2 = line_count;
				dowrite(arg, FALSE);
				line2 = n;
				arg = (u_char *)"";		/* no file list */
				/*FALLTHROUGH*/

		case CMD_next:
				if (check_changed(TRUE))
					break;
				if (*arg != NUL)		/* redefine file list */
				{
					if (doarglist((char *)arg))
						break;
					i = 0;
				}
				else
				{
					if (addr_count == 0)
						i = curfile + 1;
					else
						i = curfile + (int)line2;
				}

donextfile:		if (i < 0 || i >= numfiles)
				{
					emsg(e_nomore);
					break;
				}
				if (check_changed(TRUE))
					break;
				curfile = i;
				doecmd(files[curfile], NULL);
				break;

		case CMD_previous:
		case CMD_Next:
				if (addr_count == 0)
					i = curfile - 1;
				else
					i = curfile - (int)line2;
				goto donextfile;

		case CMD_rewind:
				i = 0;
				goto donextfile;

		case CMD_write:
				if (usefilter)		/* input lines to shell command */
					dofilter(arg, TRUE, FALSE);
				else
					dowrite(arg, append);
				break;

		case CMD_edit:
		case CMD_ex:
		case CMD_visual:
				if (!forceit && Changed)	/* ken */
					autowrite();
				doecmd((char *)arg, NULL);
				break;

		case CMD_file:
				if (*arg != NUL)
				{
					setfname((char *)arg, NULL);
					NotEdited = TRUE;
					maketitle();
				}
				fileinfo(did_cd);		/* print full filename if :cd used */
				break;

		case CMD_files:
#ifdef AMIGA
				settmode(0);			/* set cooked mode, so output can be halted */
#endif
				for (i = 0; i < NUMALTFILES; ++i)
				{
					if (altfiles[i])
					{
						sprintf(IObuff, "%2d \"%s\" line %ld\n", i, altfiles[i], (long)altlnum[i]);
						outstrn(IObuff);
					}
					flushbuf();
				}
#ifdef AMIGA
				settmode(1);
#endif
				wait_return(TRUE);
				break;

		case CMD_read:
				if (usefilter)
				{
					dofilter(arg, FALSE, TRUE);			/* :r!cmd */
					break;
				}
				if (!u_save(line2, (linenr_t)(line2 + 1)))
					break;
				if (readfile((char *)arg, NULL, line2, FALSE))
				{
					emsg(e_notopen);
					break;
				}
				updateScreen(NOT_VALID);
				break;

		case CMD_cd:
		case CMD_chdir:
#ifdef UNIX
				/*
				 * for UNIX ":cd" means: go to home directory
				 */
				if (*arg == NUL)	 /* use IObuff for home directory name */
				{
					expand_env("$HOME", IObuff, IOSIZE);
					arg = (u_char *)IObuff;
				}
#endif
				if (*arg != NUL)
				{
					if (!did_cd)
					{
						scriptfullpath();
						xFilename = Filename;
					}
					did_cd = TRUE;
					if (chdir((char *)arg))
						emsg(e_failed);
					break;
				}
				/*FALLTHROUGH*/

		case CMD_pwd:
				if (dirname(IObuff, IOSIZE))
					msg(IObuff);
				else
					emsg(e_unknown);
				break;

		case CMD_equal:
				smsg("line %ld", (long)line2);
				break;

		case CMD_list:
				i = p_list;
				p_list = 1;
		case CMD_number:
		case CMD_print:
#ifdef AMIGA
				settmode(0);			/* set cooked mode, so output can be halted */
#endif
				gotocmdline(TRUE, NUL);	/* clear command line */
				n = 0;
				for (;;)
				{
					if (p_nu || cmdidx == CMD_number)
					{
						sprintf(IObuff, "%7ld ", (long)line1);
						outstrn(IObuff);
						n += 8;
					}
					n += prt_line(nr2ptr(line1));
					if (++line1 > line2)
						break;
					outchar('\n');
					flushbuf();
					n = Columns;		/* call wait_return later */
				}
#ifdef AMIGA
				settmode(1);
#endif

				if (cmdidx == CMD_list)
					p_list = i;

					/*
					 * if we have one line that runs into the shown command,
					 * or more than one line, call wait_return()
					 */
				if (n >= sc_col || global_busy)
				{
					outchar('\n');
					wait_return(TRUE);
				}
				break;

		case CMD_shell:
				doshell(NULL);
				break;

		case CMD_tag:
				dotag((char *)arg, 0, addr_count ? (int)line2 : 1);
				break;

		case CMD_pop:
				dotag("", 1, addr_count ? (int)line2 : 1);
				break;

		case CMD_tags:
				dotags();
				break;

		case CMD_marks:
				domarks();
				break;

		case CMD_jumps:
				dojumps();
				break;

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

		case CMD_set:
				doset((char *)arg);
				break;

		case CMD_abbreviate:
		case CMD_cabbrev:
		case CMD_iabbrev:
		case CMD_cnoreabbrev:
		case CMD_inoreabbrev:
		case CMD_noreabbrev:
		case CMD_unabbreviate:
		case CMD_cunabbrev:
		case CMD_iunabbrev:
				i = ABBREV;
				goto doabbr;		/* almost the same as mapping */

		case CMD_cmap:
		case CMD_imap:
		case CMD_map:
		case CMD_cnoremap:
		case CMD_inoremap:
		case CMD_noremap:
				/*
				 * If we are sourcing .exrc or .vimrc in current directory we
				 * print the mappings for security reasons.
				 */
				if (secure)
				{
					secure = 2;
					outtrans((char *)cmd, -1);
					outchar('\n');
				}
		case CMD_cunmap:
		case CMD_iunmap:
		case CMD_unmap:
				i = 0;
doabbr:
				if (*cmd == 'c')		/* cmap, cunmap, cnoremap, etc. */
				{
					i += CMDLINE;
					++cmd;
				}
				else if (*cmd == 'i')	/* imap, iunmap, inoremap, etc. */
				{
					i += INSERT;
					++cmd;
				}
				else if (forceit || i)	/* map!, unmap!, noremap!, abbrev */
					i += INSERT + CMDLINE;
				else
					i += NORMAL;			/* map, unmap, noremap */
				switch (domap((*cmd == 'n') ? 2 : (*cmd == 'u'), (char *)arg, i))
				{
					case 1: emsg(e_invarg);
							break;
					case 2: emsg(e_nomap);
							break;
					case 3: emsg(e_ambmap);
							break;
				}
				break;

		case CMD_display:
				outchar('\n');
				dodis();		/* display buffer contents */
				break;

		case CMD_help:
				help();
				break;

		case CMD_version:
#ifdef JP
				{
					static char longversion[MAX_COLUMNS + 1] = "";
#ifdef ONEW
					extern char *Onew_version();
#endif
#ifdef DOSGEN
					extern char *longDosVersion();
#endif

					if (!longversion[0])
#ifdef ONEW
						sprintf(longversion, "%s [on %s] + %s",
								 longJpVersion, longVersion, Onew_version());
#else
# ifdef DOSGEN
						sprintf(longversion, "%s, %s [on %s]",
								 longJpVersion, longDosVersion, longVersion);
# else
						sprintf(longversion, "%s [on %s]",
								 longJpVersion, longVersion);
# endif
#endif
					msg(longversion);
				}
#else
				msg(longVersion);
#endif
				break;

		case CMD_winsize:
				line1 = getdigits((char **)&arg);
				skipspace((char **)&arg);
				line2 = getdigits((char **)&arg);
				set_winsize((int)line1, (int)line2, TRUE);
				break;

		case CMD_delete:
		case CMD_yank:
		case CMD_rshift:
		case CMD_lshift:
				yankbuffer = regname;
				startop.lnum = line1;
				endop.lnum = line2;
				nlines = line2 - line1 + 1;
				mtype = MLINE;
				Curpos.lnum = line1;
				switch (cmdidx)
				{
				case CMD_delete:
					dodelete();
					break;
				case CMD_yank:
					doyank(FALSE);
					break;
				case CMD_rshift:
					doshift(RSHIFT);
					break;
				case CMD_lshift:
					doshift(LSHIFT);
					break;
				}
				break;

		case CMD_put:
				yankbuffer = regname;
				Curpos.lnum = line2;
				doput(forceit ? BACKWARD : FORWARD, -1L);
				break;

		case CMD_t:
		case CMD_copy:
		case CMD_move:
				n = get_address(&arg);
				if (n == INVLNUM)
				{
					emsg(e_invaddr);
					break;
				}

				if (cmdidx == CMD_move)
				{
					if (n >= line1 && n < line2 && line2 > line1)
					{
						emsg("Move lines into themselves");
						break;
					}
					if (n >= line1)
					{
						--n;
						Curpos.lnum = n - (line2 - line1) + 1;
					}
					else
						Curpos.lnum = n + 1;
					while (line1 <= line2)
					{
							/* this undo is not efficient, but it works */
						u_save(line1 - 1, line1 + 1);
						q = delsline(line1, FALSE);
						u_save(n, n + 1);
						appendline(n, q);
						if (n < line1)
						{
							++n;
							++line1;
						}
						else
							--line2;
					}
				}
				else
				{
					/*
					 * there are three situations:
					 * 1. destination is above line1
					 * 2. destination is between line1 and line2
					 * 3. destination is below line2
					 *
					 * n = destination (when starting)
					 * Curpos.lnum = destination (while copying)
					 * line1 = start of source (while copying)
					 * line2 = end of source (while copying)
					 */
					u_save(n, n + 1);
					Curpos.lnum = n;
					lnum = line2 - line1 + 1;
					while (line1 <= line2)
					{
						appendline(Curpos.lnum, save_line(nr2ptr(line1)));
								/* situation 2: skip already copied lines */
						if (line1 == n)
							line1 = Curpos.lnum;
						++line1;
						if (Curpos.lnum < line1)
							++line1;
						if (Curpos.lnum < line2)
							++line2;
						++Curpos.lnum;
					}
					msgmore((long)lnum);
				}
				u_clearline();
				Curpos.col = 0;
				updateScreen(NOT_VALID);
				break;

		case CMD_and:
		case CMD_substitute:
				dosub(line1, line2, (char *)arg, &nextcomm);
				break;

		case CMD_join:
				Curpos.lnum = line1;
				if (line1 == line2)
				{
					if (line2 == line_count)
					{
						beep();
						break;
					}
					++line2;
				}
				dodojoin(line2 - line1 + 1, !forceit, TRUE);
				break;

		case CMD_global:
				if (forceit)
					*cmd = 'v';
		case CMD_vglobal:
				doglob(*cmd, line1, line2, (char *)arg);
				break;

		case CMD_at:				/* :[addr]@r */
				Curpos.lnum = line2;
				if (!doexecbuf(*arg))		/* put the register in mapbuf */
					beep();
				else
					docmdline(NULL);		/* execute from the mapbuf */
				break;

		case CMD_bang:
				dobang(addr_count, arg);
				break;

		case CMD_undo:
				u_undo(1);
				break;

		case CMD_redo:
				u_redo(1);
				break;

		case CMD_source:
				if (forceit)	/* :so! read vi commands */
					openscript((char *)arg);
				else if (dosource((char *)arg))		/* :so read ex commands */
					emsg(e_notopen);
				break;

#ifdef JP
		case CMD_mkjvimrc:
#ifdef JVIMRC_FILE
				if (*arg == NUL)
					arg = (u_char *)JVIMRC_FILE;
				/*FALLTHROUGH*/
#endif
#endif
		case CMD_mkvimrc:
				if (*arg == NUL)
					arg = (u_char *)VIMRC_FILE;
				/*FALLTHROUGH*/

		case CMD_mkexrc:
				{
					FILE	*fd;

					if (*arg == NUL)
						arg = (u_char *)EXRC_FILE;
#ifdef UNIX
						/* with Unix it is possible to open a directory */
					if (isdir((char *)arg) > 0)
					{
						emsg2("\"%s\" is a directory", (char *)arg);
						break;
					}
#endif
					if (!forceit && (fd = fopen((char *)arg, "r")) != NULL)
					{
						fclose(fd);
						emsg2("\"%s\" exists (use ! to override)", (char *)arg);
						break;
					}

					if ((fd = fopen((char *)arg, "w")) == NULL)
					{
						emsg2("Cannot open \"%s\" for writing", (char *)arg);
						break;
					}
					if (makemap(fd) || makeset(fd) || fclose(fd))
						emsg(e_write);
					break;
				}

		case CMD_cc:
					qf_jump(atoi((char *)arg));
					break;

		case CMD_cf:
					if (*arg != NUL)
					{
						/*
						 * Great trick: Insert 'ef=' before arg.
						 * Always ok, because "cf " must be there.
						 */
						arg -= 3;
						arg[0] = 'e';
						arg[1] = 'f';
						arg[2] = '=';
						doset((char *)arg);
					}
					qf_init();
					break;

		case CMD_cl:
					qf_list();
					break;

		case CMD_cn:
					qf_jump(-1);
					break;

		case CMD_cp:
					qf_jump(-2);
					break;

		case CMD_cq:
					getout(1);		/* this does not always work. why? */

		case CMD_mark:
		case CMD_k:
					pos = Curpos;			/* save Curpos */
					Curpos.lnum = line2;
					Curpos.col = 0;
					setmark(*arg);			/* set mark */
					Curpos = pos;			/* restore Curpos */
					break;

#ifdef SETKEYMAP
		case CMD_setkeymap:
					set_keymap(arg);
					break;
#endif

		case CMD_center:
		case CMD_right:
		case CMD_left:
					do_align(line1, line2, atoi((char *)arg),
							cmdidx == CMD_center ? 0 : cmdidx == CMD_right ? 1 : -1);
					break;

		case CMD_make:
					domake((char *)arg);
					break;
#ifdef MDOMAIN
		case CMD_nget:
					do_nget((char *)arg);
					break;
#endif
		default:
					emsg(e_invcmd);
	}


doend:
	forceit = FALSE;		/* reset now so it can be used in getfile() */
	return nextcomm;
}

/*
 * handle the :! command.
 * We replace the extra bangs by the previously entered command and remember
 * the command.
 */
	static void
dobang(addr_count, arg)
	int		addr_count;
	u_char	*arg;
{
	static	char	*prevcmd = NULL;		/* the previous command */
	char			*t;
	char			*trailarg;
	int 			len;

	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return;
	}
	len = strlen((char *)arg) + 1;

	if (Changed)
		autowrite();
	/*
	 * try to find an embedded bang, like in :!<cmd> ! [args]
	 * (:!! is indicated by the 'forceit' variable)
	 */
	trailarg = (char *)arg;
	skiptospace(&trailarg);
	skipspace(&trailarg);
	if (*trailarg == '!')
		*trailarg++ = NUL;
	else
		trailarg = NULL;

	if (forceit || trailarg != NULL)			/* use the previous command */
	{
		if (prevcmd == NULL)
		{
			emsg(e_noprev);
			return;
		}
		len += strlen(prevcmd) * (trailarg != NULL && forceit ? 2 : 1);
	}

	if (len > CMDBUFFSIZE)
	{
		emsg(e_toolong);
		return;
	}
	if ((t = alloc(len)) == NULL)
		return;
	*t = NUL;
	if (forceit)
		strcpy(t, prevcmd);
	strcat(t, (char *)arg);
	if (trailarg != NULL)
	{
		strcat(t, prevcmd);
		strcat(t, trailarg);
	}
	free(prevcmd);
	prevcmd = t;

	if (bangredo)			/* put cmd in redo buffer for ! command */
	{
		AppendToRedobuff(prevcmd);
		AppendToRedobuff("\n");
		bangredo = FALSE;
	}
		/* echo the command */
	gotocmdline(TRUE, ':');
	if (addr_count)						/* :range! */
	{
		outnum((long)line1);
		outchar(',');
		outnum((long)line2);
	}
	outchar('!');
	outtrans(prevcmd, -1);

	if (addr_count == 0)				/* :! */
		doshell(prevcmd); 
	else								/* :range! */
		dofilter((u_char *)prevcmd, TRUE, TRUE);
}

	static int
autowrite()
{
	if (!p_aw || check_readonly() || check_fname())
		return FALSE;
	return (writeit(Filename, sFilename, (linenr_t)1, line_count, 0, 0, TRUE));
}

	static int
dowrite(arg, append)
	u_char	*arg;
	int		append;
{
	FILE	*f;
	int		other;

	/*
	 * if we have a new file name put it in the list of alternate file names
	 */
	other = otherfile((char *)arg);
	if (*arg != NUL && other)
		setaltfname(strsave((char *)arg), strsave((char *)arg), (linenr_t)1, TRUE);

	/*
	 * writing to the current file is not allowed in readonly mode
	 */
	if ((*arg == NUL || !other) && check_readonly())
		return FALSE;

	/*
	 * write to current file
	 */
	if (*arg == NUL || !other)
	{
		if (check_fname())
			return FALSE;
		return (writeit(Filename, sFilename, line1, line2, append, forceit, TRUE));
	}

	/*
	 * write to other file; overwriting only allowed with '!'
	 */
	if (!forceit && !append && !p_wa && (f = fopen((char *)arg, "r")) != NULL)
	{								/* don't overwrite existing file */
			fclose(f);
#ifdef UNIX
				/* with UNIX it is possible to open a directory */
			if (isdir((char *)arg) > 0)
				emsg2("\"%s\" is a directory", (char *)arg);
			else
#endif
				emsg(e_exists);
			return 0;
	}
	return (writeit((char *)arg, NULL, line1, line2, append, forceit, TRUE));
}

	static int
doecmd(arg, sarg)
	char		*arg;
	char		*sarg;
{
	int			setalt;
	char		*command = NULL;
	int			redraw_save;
	linenr_t	newlnum;

	newlnum = doecmdlnum;
	doecmdlnum = 0;						/* reset it for next time */

	if (*arg == '+')		/* :e +[command] file */
	{
		++arg;
		if (isspace(*arg))
			command = "$";
		else
		{
			command = arg;
			while (*arg && !isspace(*arg))
				++arg;
		}
		if (*arg)
			*arg++ = NUL;
		
		skipspace(&arg);
	}

	if (sarg == NULL)
		sarg = arg;

#ifdef AMIGA
	fname_case(arg);				/* set correct case for filename */
#endif

	setalt = (*arg != NUL && otherfile(arg));
	if (check_changed(FALSE))
	{
		if (setalt)
			setaltfname(strsave(arg), strsave(sarg), (linenr_t)1, TRUE);
		return FALSE;
	}
	if (setalt)
	{
		setaltfname(Filename, sFilename, Curpos.lnum, FALSE);
		Filename = NULL;
		sFilename = NULL;
		setfname(arg, sarg);
	}
	else if (newlnum == 0)
		newlnum = Curpos.lnum;
	maketitle();
	if (check_fname())
		return FALSE;

	/* clear mem and read file */
	freeall();
	filealloc();
	startop.lnum = 0;	/* clear '[ and '] marks */
	endop.lnum = 0;

	redraw_save = RedrawingDisabled;
	RedrawingDisabled = TRUE;		/* don't redraw until the cursor is in
									 * the right line */
	startscript();					/* re-start auto script file */
	readfile(Filename, sFilename, (linenr_t)0, setalt ? TRUE : FALSE); /* ken */
	if (newlnum && command == NULL)
	{
		if (newlnum != INVLNUM)
			Curpos.lnum = newlnum;
		else
			Curpos.lnum = line_count;
		Curpos.col = 0;
	}
	UNCHANGED;
	if (command)
		docmdline((u_char *)command);
	RedrawingDisabled = redraw_save;	/* cursupdate() will redraw the screen later */
	if (p_im)
		stuffReadbuff("i");			/* start editing in insert mode */
	return TRUE;
}

	static void
doshell(cmd)
	char	*cmd;
{
	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return;
	}
	stoptermcap();
	outchar('\n');					/* shift screen one line up */

		/* warning message before calling the shell */
	if (p_warn && Changed)
	{
		gotocmdline(TRUE, NUL);
		outstr("[No write since last change]\n");
	}
	call_shell(cmd, 0, TRUE);

#ifdef AMIGA
	wait_return(!term_console);		/* see below */
#else
	wait_return(TRUE);				/* includes starttermcap() */
#endif

	/*
	 * In an Amiga window redrawing is caused by asking the window size.
	 * If we got an interrupt this will not work. The chance that the window
	 * size is wrong is very small, but we need to redraw the screen.
	 */
#ifdef AMIGA
	if (term_console)
	{
		outstr("\033[0 q"); 	/* get window size */
		if (got_int)
			must_redraw = CLEAR;	/* if got_int is TRUE we have to redraw */
		else
			must_redraw = FALSE;	/* no extra redraw needed */
	}
#endif /* AMIGA */
}

/*
 * dofilter: filter lines through a command given by the user
 *
 * We use temp files and the call_shell() routine here. This would normally
 * be done using pipes on a UNIX machine, but this is more portable to
 * the machines we usually run on. The call_shell() routine needs to be able
 * to deal with redirection somehow, and should handle things like looking
 * at the PATH env. variable, and adding reasonable extensions to the
 * command name given by the user. All reasonable versions of call_shell()
 * do this.
 * We use input redirection if do_in is TRUE.
 * We use output redirection if do_out is TRUE.
 */
	static void
dofilter(buff, do_in, do_out)
	u_char		*buff;
	int			do_in, do_out;
{
#ifdef LATTICE
	char		itmp[L_tmpnam];		/* use tmpnam() */
	char		otmp[L_tmpnam];
#else
	char		itmp[TMPNAMELEN];
	char		otmp[TMPNAMELEN];
#endif
	linenr_t 	linecount;

	/*
	 * Disallow shell commands from .exrc and .vimrc in current directory for
	 * security reasons.
	 */
	if (secure)
	{
		secure = 2;
		emsg(e_curdir);
		return;
	}
	if (*buff == NUL)		/* no filter command */
		return;
	linecount = line2 - line1 + 1;
	Curpos.lnum = line1;
	Curpos.col = 0;
	/* cursupdate(); */

	/*
	 * 1. Form temp file names
	 * 2. Write the lines to a temp file
	 * 3. Run the filter command on the temp file
	 * 4. Read the output of the command into the buffer
	 * 5. Delete the original lines to be filtered
	 * 6. Remove the temp files
	 */

#ifndef LATTICE
	/* for lattice we use tmpnam(), which will make its own name */
	strcpy(itmp, TMPNAME1);
	strcpy(otmp, TMPNAME2);
#endif

	if ((do_in && *mktemp(itmp) == NUL) || (do_out && *mktemp(otmp) == NUL))
	{
		emsg(e_notmp);
		return;
	}

/*
 * ! command will be overwritten by next mesages
 * This is a trade off between showing the command and not scrolling the
 * text one line up (problem on slow terminals).
 */
	must_redraw = CLEAR;		/* screen has been shifted up one line */
	if (do_in && !writeit(itmp, NULL, line1, line2, FALSE, 0, FALSE))
	{
		outchar('\n');			/* keep message from writeit() */
		emsg(e_notcreate);
		return;
	}
	if (!do_out)
		outchar('\n');

#ifdef UNIX
/*
 * put braces around the command (for concatenated commands)
 */
 	sprintf(IObuff, "(%s)", (char *)buff);
	if (do_in)
	{
		strcat(IObuff, " < ");
		strcat(IObuff, itmp);
	}
	if (do_out)
	{
		strcat(IObuff, " > ");
		strcat(IObuff, otmp);
	}
#else
/*
 * for shells that don't understand braces around commands, at least allow
 * the use of commands in a pipe.
 */
	strcpy(IObuff, (char *)buff);
	if (do_in)
	{
		char		*p;
	/*
	 * If there is a pipe, we have to put the '<' in front of it
	 */
		p = strchr(IObuff, '|');
		if (p)
			*p = NUL;
		strcat(IObuff, " < ");
		strcat(IObuff, itmp);
		p = strchr((char *)buff, '|');
		if (p)
			strcat(IObuff, p);
	}
	if (do_out)
	{
		strcat(IObuff, " > ");
		strcat(IObuff, otmp);
	}
#endif

	call_shell(IObuff, 1, FALSE);	/* errors are ignored, so you can see the error
								   messages from the command; use 'u' to fix the
								   text */

	if (do_out)
	{
		if (!u_save((linenr_t)(line2), (linenr_t)(line2 + 1)))
		{
			linecount = 0;
			goto error;
		}
		if (readfile(otmp, NULL, line2, FALSE))
		{
			outchar ('\n');
			emsg(e_notread);
			linecount = 0;
			goto error;
		}

		if (do_in)
		{
			Curpos.lnum = line1;
			dellines(linecount, TRUE, TRUE);
		}
	}
	else
	{
error:
		wait_return(FALSE);
	}
	updateScreen(CLEAR);		/* do this before messages below */

	if (linecount > p_report)
	{
		if (!do_in && do_out)
			msgmore(linecount);
		else
			smsg("%ld lines filtered", (long)linecount);
	}
	remove(itmp);
	remove(otmp);
	return;
}

	static void
domake(arg)
	char *arg;
{
	if (*p_ef == NUL)
	{
		emsg("errorfile option not set");
		return;
	}
	if (Changed)
		autowrite();
	remove(p_ef);
	outchar(':');
	outstr(arg);		/* show what we are doing */
#ifdef UNIX
	sprintf(IObuff, "%s |& tee %s", arg, p_ef);
#else
	sprintf(IObuff, "%s > %s", arg, p_ef);
#endif
	doshell(IObuff);
#ifdef AMIGA
	flushbuf();
	vpeekc();		/* read window status report and redraw before message */
#endif
	qf_init();
	remove(p_ef);
}

/* 
 * Redefine the argument list to 'str'.
 * Return TRUE for failure.
 */
	static int
doarglist(str)
	char *str;
{
	int		new_numfiles = 0;
	char	**new_files = NULL;
	int		exp_numfiles;
	char	**exp_files;
	char	**t;
	char	*p;
	int		inquote;
	int		i;

	while (*str)
	{
		/*
		 * create a new entry in new_files[]
		 */
		t = (char **)lalloc((u_long)(sizeof(char *) * (new_numfiles + 1)), TRUE);
		if (t != NULL)
			for (i = new_numfiles; --i >= 0; )
				t[i] = new_files[i];
		free(new_files);
		if (t == NULL)
			return TRUE;
		new_files = t;
		new_files[new_numfiles++] = str;

		/*
		 * isolate one argument, taking quotes
		 */
		inquote = FALSE;
		for (p = str; *str; ++str)
		{
			if (*str == '\\' && *(str + 1) != NUL)
				*p++ = *++str;
			else
			{
				if (!inquote && isspace(*str))
					break;
				if (*str == '"')
					inquote ^= TRUE;
				else
					*p++ = *str;
			}
		}
		skipspace(&str);
		*p = NUL;
	}
	
	if (ExpandWildCards(new_numfiles, new_files, &exp_numfiles, &exp_files, FALSE, TRUE) != 0)
		return TRUE;
	else if (exp_numfiles == 0)
	{
		emsg(e_nomatch);
		return TRUE;
	}
	if (files_exp)			/* files[] has been allocated, free it */
		FreeWild(numfiles, files);
	else
		files_exp = TRUE;
	files = exp_files;
	numfiles = exp_numfiles;

	return FALSE;
}

	void
gotocmdline(clr, firstc)
	int				clr;
	int				firstc;
{
	int		i;

	if (clr)			/* clear the bottom line(s) */
	{
		for (i = 0; i <= cmdoffset; ++i)
		{
			windgoto((int)Rows - i - 1, 0);
			clear_line();
		}
		redraw_msg = TRUE;
	}
	windgoto((int)Rows - cmdoffset - 1, 0);
	if (firstc)
		outchar(firstc);
}

	void
gotocmdend()
{
	windgoto((int)Rows - 1, 0);
	outchar('\n');
}

	static int
check_readonly()
{
	if (!forceit && p_ro)
	{
		emsg(e_readonly);
		return TRUE;
	}
	return FALSE;
}

	static int
check_changed(checkaw)
	int		checkaw;
{
	if (!forceit && Changed && (!checkaw || !autowrite()))
	{
		emsg(e_nowrtmsg);
		return TRUE;
	}
	return FALSE;
}

	int
check_fname()
{
	if (Filename == NULL)
	{
		emsg(e_noname);
		return TRUE;
	}
	return FALSE;
}

	static int
check_more(message)
	int message;			/* when FALSE check only, no messages */
{
	if (!forceit && curfile + 1 < numfiles && quitmore == 0)
	{
		if (message)
		{
			emsg(e_more);
			quitmore = 2;			/* next try to quit is allowed */
		}
		return TRUE;
	}
	return FALSE;
}

/*
 * try to abandon current file and edit "fname"
 * return 1 for "normal" error, 2 for "not written" error, 0 for success
 * -1 for succesfully opening another file
 */
	int
getfile(fname, sfname, setpm)
	char	*fname;
	char	*sfname;
	int		setpm;
{
	int other;

	other = otherfile(fname);
	if (other && !forceit && Changed && !autowrite())
	{
		emsg(e_nowrtmsg);
		return 2;		/* file has been changed */
	}
	if (setpm)
		setpcmark();
	if (!other)
		return 0;		/* it's in the same file */
	if (doecmd(fname, sfname))
		return -1;		/* opened another file */
	return 1;			/* error encountered */
}

/*
 * return TRUE if alternate file n is the same as the current file
 */
	int
samealtfile(n)
	int			n;
{
	if (n < NUMALTFILES && altfiles[n] != NULL && Filename != NULL &&
					fnamecmp(altfiles[n], Filename) == 0)
		return TRUE;
	return FALSE;
}

/*
 * get alternate file n
 * set linenr to lnum or altlnum if lnum == 0
 * if (setpm) setpcmark
 * return 1 for failure, 0 for success
 */
	int
getaltfile(n, lnum, setpm)
	int			n;
	linenr_t	lnum;
	int			setpm;
{
	if (n < 0 || n >= NUMALTFILES || altfiles[n] == NULL)
	{
		emsg(e_noalt);
		return 1;
	}
	if (lnum == 0)
		lnum = altlnum[n];		/* altlnum may be changed by getfile() */
	RedrawingDisabled = TRUE;
	if (getfile(altfiles[n], saltfiles[n], setpm) <= 0)
	{
		RedrawingDisabled = FALSE;
		if (lnum == 0 || lnum > line_count)		/* check for valid lnum */
			Curpos.lnum = 1;
		else
			Curpos.lnum = lnum;

		Curpos.col = 0;
		return 0;
	}
	RedrawingDisabled = FALSE;
	return 1;
}

/*
 * get name of "n"th alternate file
 */
 	char *
getaltfname(n)
	int n;
{
	if (n >= NUMALTFILES)
		return NULL;
	return altfiles[n];
}

/*
 * put name "arg" in the list of alternate files.
 * "arg" must have been allocated
 * "lnum" is the default line number when jumping to the file
 * "newfile" must be TRUE when "arg" != current file
 */
	static void
setaltfname(arg, sarg, lnum, newfile)
	char		*arg;
	char		*sarg;
	linenr_t	lnum;
	int			newfile;
{
	int i;

	free(altfiles[NUMALTFILES - 1]);
	free(saltfiles[NUMALTFILES - 1]);
	for (i = NUMALTFILES - 1; i > 0; --i)
	{
		altfiles[i] = altfiles[i - 1];
		saltfiles[i] = saltfiles[i - 1];
		altlnum[i] = altlnum[i - 1];
	}
	incrmarks();		/* increment file number for all jumpmarks */
	incrtags();			/* increment file number for all tags */
	if (newfile)
	{
		decrmarks();		/* decrement file number for jumpmarks in current file */
		decrtags();			/* decrement file number for tags in current file */
	}

	altfiles[0] = arg;
	saltfiles[0] = sarg;
	altlnum[0] = lnum;
}

	static void
nextwild(buff, type)
	u_char *buff;
	int		type;
{
	int		i;
	char	*p1, *p2;
	int		oldlen;
	int		difflen;

	outstr("...");		/* show that we are busy */
	flushbuf();
	i = cmdslen;
	cmdslen = cmdpos + 4;
	cmdchecklen();		/* check if we caused a scrollup */
	cmdslen = i;

	for (i = cmdpos; i > 0 && buff[i - 1] != ' '; --i)
		;
	oldlen = cmdpos - i;

		/* add a "*" to the file name and expand it */
	if ((p1 = addstar((char *)&buff[i], oldlen)) != NULL)
	{
		if ((p2 = ExpandOne((u_char *)p1, FALSE, type)) != NULL)
		{
			if (cmdlen + (difflen = strlen(p2) - oldlen) > CMDBUFFSIZE - 4)
				emsg(e_toolong);
			else
			{
				strncpy((char *)&buff[cmdpos + difflen], (char *)&buff[cmdpos], (size_t)(cmdlen - cmdpos));
				strncpy((char *)&buff[i], p2, strlen(p2));
				cmdlen += difflen;
				cmdpos += difflen;
			}
			free(p2);
		}
		free(p1);
	}
	redrawcmd();
}

/*
 * Do wildcard expansion on the string 'str'.
 * Return a pointer to alloced memory containing the new string.
 * Return NULL for failure.
 *
 * mode = -2: only release file names
 * mode = -1: normal expansion, do not keep file names
 * mode =  0: normal expansion, keep file names
 * mode =  1: use next match in multiple match
 * mode =  2: use previous match in multiple match
 * mode =  3: use next match in multiple match and wrap to first
 * mode =  4: return all matches concatenated
 * mode =  5: return longest matched part
 */
	char *
ExpandOne(str, list_notfound, mode)
	u_char	*str;
	int		list_notfound;
	int		mode;
{
	char		*ss = NULL;
	static char **cmd_files = NULL;	  /* list of input files */
	static int	findex;
	int			i, found = 0;
	int			multmatch = FALSE;
	u_long		len;
	char		*filesuf, *setsuf, *nextsetsuf;
	int			filesuflen, setsuflen;

/*
 * first handle the case of using an old match
 */
	if (mode >= 1 && mode < 4)
	{
		if (cmd_numfiles > 0)
		{
			if (mode == 2)
				--findex;
			else	/* mode == 1 || mode == 3 */
				++findex;
			if (findex < 0)
				findex = 0;
			if (findex > cmd_numfiles - 1)
			{
				if (mode == 3)
					findex = 0;
				else
					findex = cmd_numfiles - 1;
			}
			return strsave(cmd_files[findex]);
		}
		else
			return NULL;
	}

/* free old names */
	if (cmd_numfiles != -1 && mode < 4)
	{
		FreeWild(cmd_numfiles, cmd_files);
		cmd_numfiles = -1;
	}
	findex = 0;

	if (mode == -2)		/* only release file name */
		return NULL;

	if (cmd_numfiles == -1)
	{
		if (ExpandWildCards(1, (char **)&str, &cmd_numfiles, &cmd_files, FALSE, list_notfound) != 0)
			/* error: do nothing */;
		else if (cmd_numfiles == 0)
			emsg(e_nomatch);
		else if (mode < 4)
		{
			if (cmd_numfiles > 1)		/* more than one match; check suffixes */
			{
				found = -2;
				for (i = 0; i < cmd_numfiles; ++i)
				{
					if ((filesuf = strrchr(cmd_files[i], '.')) != NULL)
					{
						filesuflen = strlen(filesuf);
						for (setsuf = p_su; *setsuf; setsuf = nextsetsuf)
						{
							if ((nextsetsuf = strchr(setsuf + 1, '.')) == NULL)
								nextsetsuf = setsuf + strlen(setsuf);
							setsuflen = (int)(nextsetsuf - setsuf);
							if (filesuflen == setsuflen &&
										strncmp(setsuf, filesuf, (size_t)setsuflen) == 0)
								break;
						}
						if (*setsuf)				/* suffix matched: ignore file */
							continue;
					}
					if (found >= 0)
					{
						multmatch = TRUE;
						break;
					}
					found = i;
				}
			}
			if (multmatch || found < 0)
			{
				emsg(e_toomany);
				found = 0;				/* return first one */
				multmatch = TRUE;		/* for found < 0 */
			}
			if (found >= 0 && !(multmatch && mode == -1))
				ss = strsave(cmd_files[found]);
		}
	}

	if (mode == 5 && cmd_numfiles > 0)		/* find longest common part */
	{
		for (len = 0; cmd_files[0][len]; ++len)
		{
			for (i = 0; i < cmd_numfiles; ++i)
			{
#ifdef AMIGA
				if (toupper(cmd_files[i][len]) != toupper(cmd_files[0][len]))
#else
				if (cmd_files[i][len] != cmd_files[0][len])
#endif
					break;
			}
			if (i < cmd_numfiles)
				break;
		}
		ss = alloc((unsigned)len + 1);
		if (ss)
		{
			strncpy(ss, cmd_files[0], (size_t)len);
			ss[len] = NUL;
		}
		multmatch = TRUE;					/* don't free the names */
		findex = -1;						/* next p_wc gets first one */
	}

	if (mode == 4 && cmd_numfiles > 0)		/* concatenate all file names */
	{
		len = 0;
		for (i = 0; i < cmd_numfiles; ++i)
			len += strlen(cmd_files[i]) + 1;
		ss = lalloc(len, TRUE);
		if (ss)
		{
			*ss = NUL;
			for (i = 0; i < cmd_numfiles; ++i)
			{
				strcat(ss, cmd_files[i]);
				if (i != cmd_numfiles - 1)
					strcat(ss, " ");
			}
		}
	}

	if (!multmatch || mode == -1 || mode == 4)
	{
		FreeWild(cmd_numfiles, cmd_files);
		cmd_numfiles = -1;
	}
	return ss;
}

/*
 * show all filenames that match the string "file" with length "len"
 */
	static void
showmatches(file, len)
	char *file;
	int	len;
{
	char *file_str;
	int num_files;
	char **files_found;
	int i, j, k;
	int maxlen;
	int lines;
	int columns;

	file_str = addstar(file, len);		/* add star to file name */
	if (file_str != NULL)
	{
		outchar('\n');
		flushbuf();

		/* find all files that match the description */
		ExpandWildCards(1, &file_str, &num_files, &files_found, FALSE, FALSE);

		/* find the maximum length of the file names */
		maxlen = 0;
		for (i = 0; i < num_files; ++i)
		{
			j = strlen(files_found[i]);
			if (j > maxlen)
				maxlen = j;
		}

		/* compute the number of columns and lines for the listing */
		maxlen += 2;	/* two spaces between file names */
		columns = ((int)Columns + 2) / maxlen;
		if (columns < 1)
			columns = 1;
		lines = (num_files + columns - 1) / columns;

		/* list the files line by line */
#ifdef AMIGA
		settmode(0);		/* allow output to be halted */
#endif
		for (i = 0; i < lines; ++i)
		{
			for (k = i; k < num_files; k += lines)
			{
				if (k > i)
					for (j = maxlen - strlen(files_found[k - lines]); --j >= 0; )
						outchar(' ');
				j = isdir(files_found[k]);	/* highlight directories */
				if (j > 0)
				{
#ifdef AMIGA
					if (term_console)
						outstr("\033[33m");		/* use highlight color */
					else
#endif /* AMIGA */
						outstr(T_TI);
				}
				outstrn(files_found[k]);
				if (j > 0)
				{
#ifdef AMIGA
					if (term_console)
						outstr("\033[0m");		/* use normal color */
					else
#endif /* AMIGA */
						outstr(T_TP);
				}
			}
			outchar('\n');
			flushbuf();
		}
		free(file_str);
		FreeWild(num_files, files_found);
#ifdef AMIGA
		settmode(1);
#endif

		for (i = cmdoffset; --i >= 0; )	/* make room for the command */
			outchar('\n');
		must_redraw = CLEAR;			/* must redraw later */
	}
}

/*
 * copy the file name into allocated memory and add a '*' at the end
 */
	static char *
addstar(fname, len)
	char	*fname;
	int		len;
{
	char	*retval;
#ifdef MSDOS
	int		i;
#endif

	retval = alloc(len + 4);
	if (retval != NULL)
	{
		strncpy(retval, fname, (size_t)len);
#ifdef MSDOS
	/*
	 * if there is no dot in the file name, add "*.*" instead of "*".
	 */
		for (i = len - 1; i >= 0; --i)
			if (strchr(".\\/:", retval[i]))
				break;
		if (i < 0 || retval[i] != '.')
		{
			retval[len++] = '*';
			retval[len++] = '.';
		}
#endif
		retval[len] = '*';
		retval[len + 1] = 0;
	}
	return retval;
}

/*
 * dosource: read the file "fname" and execute its lines as EX commands
 *
 * This function may be called recursively!
 */
	int
dosource(fname)
	register char *fname;
{
	register FILE	*fp;
	register int	len;
#ifdef MSDOS
	int				error = FALSE;
#endif

	expand_env(fname, IObuff, IOSIZE);		/* use IObuff for expanded name */
	if ((fp = fopen(IObuff, READBIN)) == NULL)
		return 1;

	len = 0;
	while (fgets(IObuff + len, IOSIZE - len, fp) != NULL && !got_int)
	{
		len = strlen(IObuff) - 1;
		if (len >= 0 && IObuff[len] == '\n')	/* remove trailing newline */
		{
				/* escaped newline, read more */
			if (len > 0 && len < IOSIZE && IObuff[len - 1] == Ctrl('V'))
			{
				IObuff[len - 1] = '\n';		/* remove CTRL-V */
				continue;
			}
#ifdef MSDOS
			if (len > 0 && IObuff[len - 1] == '\r') /* trailing CR-LF */
				--len;
			else
			{
				if (!error)
					emsg("Warning: Wrong line separator, ^M may be missing");
				error = TRUE;		/* lines like ":map xx yy^M" will fail */
			}
#endif
			IObuff[len] = NUL;
		}
		breakcheck();		/* check for ^C here, so recursive :so will be broken */
#ifdef JP
		{
			char tmp[IOSIZE];

			len = kanjiconvsfrom(IObuff, len, tmp, IOSIZE, NULL, JP_READ, NULL);
			tmp[len] = NUL;
			docmdline((u_char *)tmp);
		}
#else
		docmdline((u_char *)IObuff);
#endif
		len = 0;
	}
	fclose(fp);
	if (got_int)
		emsg(e_interr);

	return 0;
}

/*
 * get single EX address
 */
	static linenr_t
get_address(ptr)
	u_char		**ptr;
{
	linenr_t	curpos_lnum = Curpos.lnum;
	int			c;
	int			i;
	long		n;
	u_char  	*cmd;
	FPOS		pos;
	FPOS		*fp;
	linenr_t	lnum;

	cmd = *ptr;
	skipspace((char **)&cmd);
	lnum = INVLNUM;
	do
	{
		switch (*cmd)
		{
			case '.': 						/* '.' - Cursor position */
						++cmd;
						lnum = curpos_lnum;
						break;

			case '$': 						/* '$' - last line */
						++cmd;
						lnum = line_count;
						break;

			case '\'': 						/* ''' - mark */
						if (*++cmd == NUL || (fp = getmark(*cmd++, FALSE)) == NULL)
						{
							emsg(e_umark);
							goto error;
						}
						lnum = fp->lnum;
						break;

			case '/':
			case '?':						/* '/' or '?' - search */
						c = *cmd++;
						pos = Curpos;		/* save Curpos */
						Curpos.col = -1;	/* searchit() will increment the col */
						if (c == '/')
						{
						 	if (Curpos.lnum == line_count)	/* :/pat on last line */
								Curpos.lnum = 1;
							else
								++Curpos.lnum;
						}
						searchcmdlen = 0;
						if (dosearch(c, (char *)cmd, FALSE, (long)1, FALSE))
							lnum = Curpos.lnum;
						Curpos = pos;
				
						cmd += searchcmdlen;	/* adjust command string pointer */
						break;

			default:
						if (isdigit(*cmd))				/* absolute line number */
							lnum = getdigits((char **)&cmd);
		}
		
		while (*cmd == '-' || *cmd == '+')
		{
			if (lnum == INVLNUM)
				lnum = curpos_lnum;
			i = *cmd++;
			if (!isdigit(*cmd))	/* '+' is '+1', but '+0' is not '+1' */
				n = 1;
			else 
				n = getdigits((char **)&cmd);
			if (i == '-')
				lnum -= n;
			else
				lnum += n;
		}

		curpos_lnum = lnum;
	} while (*cmd == '/' || *cmd == '?');

error:
	*ptr = cmd;
	return lnum;
}

/*
 * align text:
 * type = -1  left aligned
 * type = 0   centered
 * type = 1   right aligned
 */
	static void
do_align(start, end, width, type)
	linenr_t	start;
	linenr_t	end;
	int			width;
	int			type;
{
	FPOS	pos;
	int		len;
	int		indent = 0;

	pos = Curpos;
	if (type == -1)		/* left align: width is used for new indent */
	{
		if (width >= 0)
			indent = width;
	}
	else
	{
		if (width <= 0)
			width = p_tw;
		if (width == 0)
			width = 80;
	}

	if (!u_save((linenr_t)(line1 - 1), (linenr_t)(line2 + 1)))
		return;
	for (Curpos.lnum = start; Curpos.lnum <= end; ++Curpos.lnum)
	{
		set_indent(indent, TRUE);				/* remove existing indent */
		if (type == -1)							/* left align */
			continue;
		len = strsize(nr2ptr(Curpos.lnum));		/* get line lenght */
		if (len < width)
			switch (type)
			{
			case 0:		set_indent((width - len) / 2, FALSE);	/* center */
						break;
			case 1:		set_indent(width - len, FALSE);			/* right */
						break;
			}
	}
	Curpos = pos;
	beginline(TRUE);
	updateScreen(NOT_VALID);
}


syntax highlighted by Code2HTML, v. 0.9.1