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

/*
 * Contains the main routine for processing characters in command mode.
 * Communicates closely with the code in ops.c to handle the operators.
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#ifdef JP
#include "jp.h"
#endif
#ifdef FEPCTRL
#include "fepctrl.h"
#endif

#undef EXTERN
#undef INIT
#define EXTERN
#define INIT(x) x
#include "ops.h"

/*
 * Generally speaking, every command in normal() should either clear any
 * pending operator (with CLEAROP), or set the motion type variable.
 */

#define CLEAROP (operator = NOP)		/* clear any pending operator */
#define CLEAROPBEEP 	clearopbeep()	/* CLEAROP plus a beep() */
#define CHECKCLEAROP	if (checkclearop()) break;
#define CHECKCLEAROPQ	if (checkclearopq()) break;

/*
 * If a count is given before the operator, it is saved in opnum.
 */
static linenr_t	opnum = 0;
static linenr_t	Prenum; 		/* The (optional) number before a command. */
int				redo_Visual_busy = FALSE;	/* TRUE when redo-ing a visual */

#ifdef JP
static void		prep_redo __ARGS((long, int, int, int, int));
#else
static void		prep_redo __ARGS((long, int, int, int));
#endif
static void		premsg __ARGS((int, int));
static int		checkclearop __ARGS((void));
static int		checkclearopq __ARGS((void));
static void		clearopbeep __ARGS((void));

extern int		restart_edit;	/* this is in edit.c */

/*
 * normal
 *
 * Execute a command in normal mode.
 *
 * This is basically a big switch with the cases arranged in rough categories
 * in the following order:
 *
 *	  0. Macros (q, @)
 *	  1. Screen positioning commands (^U, ^D, ^F, ^B, ^E, ^Y, z)
 *	  2. Control commands (:, <help>, ^L, ^G, ^^, ZZ, *, ^], ^T)
 *	  3. Cursor motions (G, H, M, L, l, K_RARROW,  , h, K_LARROW, ^H, k, K_UARROW, ^P, +, CR, LF, j, K_DARROW, ^N, _, |, B, b, W, w, E, e, $, ^, 0)
 *	  4. Searches (?, /, n, N, T, t, F, f, ,, ;, ], [, %, (, ), {, })
 *	  5. Edits (., u, K_UNDO, ^R, U, r, J, p, P, ^A, ^S)
 *	  6. Inserts (A, a, I, i, o, O, R)
 *	  7. Operators (~, d, c, y, >, <, !, =, Q)
 *	  8. Abbreviations (x, X, D, C, s, S, Y, &)
 *	  9. Marks (m, ', `, ^O, ^I)
 *	 10. Buffer setting (")
 *	 11. Visual (v, V, ^V)
 *   12. Suspend (^Z)
 */

	void
normal()
{
	register u_char	c;
	long 			n;
	int				flag = FALSE;
	int 			type = 0;		/* used in some operations to modify type */
	int 			dir = FORWARD;	/* search direction */
	u_char			nchar = NUL;
#ifdef JP
	u_char			kchar = NUL;
#endif
	int				finish_op;
	linenr_t		Prenum1;
	char			searchbuff[CMDBUFFSIZE];		/* buffer for search string */
	FPOS			*pos;
	register char	*ptr;
	int				command_busy = FALSE;
	static int		didwarn = FALSE;		/* warned for broken inversion */

		/* the visual area is remembered for reselection */
	static linenr_t	resel_Visual_nlines;		/* number of lines */
	static int		resel_Visual_type = 0;	/* type 'v', 'V' or CTRL-V */
	static colnr_t	resel_Visual_col;		/* number of columns or end column */
		/* the visual area is remembered for redo */
	static linenr_t	redo_Visual_nlines;		/* number of lines */
	static int		redo_Visual_type = 0;	/* type 'v', 'V' or CTRL-V */
	static colnr_t	redo_Visual_col;			/* number of columns or end column */

	extern char *tracktab_next(), *tracktab_prev();


	if (Track)
		showtrack();

	Prenum = 0;
	/*
	 * If there is an operator pending, then the command we take this time
	 * will terminate it. Finish_op tells us to finish the operation before
	 * returning this time (unless the operation was cancelled).
	 */
	finish_op = (operator != NOP);

	if (!finish_op && !yankbuffer)
		opnum = 0;

	if (p_sc && (vpeekc() == NUL || KeyTyped == TRUE))
		premsg(NUL, NUL);
	State = NORMAL_BUSY;
	c = vgetc();

	/* Pick up any leading digits and compute 'Prenum' */
	while ((c >= '1' && c <= '9') || (Prenum != 0 && (c == DEL || c == '0')))
	{
		if (c == DEL)
				Prenum /= 10;
		else
				Prenum = Prenum * 10 + (c - '0');
		if (Prenum < 0)			/* got too large! */
			Prenum = 999999999;
		premsg(' ', NUL);
		c = vgetc();
	}

	/*
	 * If we're in the middle of an operator (including after entering a yank
	 * buffer with ") AND we had a count before the
	 * operator, then that count overrides the current value of Prenum. What
	 * this means effectively, is that commands like "3dw" get turned into
	 * "d3w" which makes things fall into place pretty neatly.
	 * If you give a count before AND after the operator, they are multiplied.
	 */
	if (opnum != 0)
	{
			if (Prenum)
				Prenum *= opnum;
			else
				Prenum = opnum;
			opnum = 0;
	}

	Prenum1 = (Prenum == 0 ? 1 : Prenum);		/* Prenum often defaults to 1 */
	premsg(c, NUL);

	/*
	 * get an additional character if we need one
	 */
	if (c == Ctrl('X'))
		smsg("^H:help ^X:track mode g:nget");

#if defined(JPFEP) || (defined(JP) && defined(MSDOS))
	if (strchr("@zZ[]m'`\"", c) || (c == 'q' && !Recording && !Exec_reg)
														|| c == Ctrl('X'))
	{
		State = NOMAPPING;
		nchar = vgetc();		/* no macro mapping for this char */
		premsg(c, nchar);
	}
	else if (strchr("tTfF", c) || (c == 'r' && !Visual.lnum))
	{
		State = NOMAPPING;
		nchar = vgetc();
		if (nchar == K_ZERO || nchar == Ctrl('\\'))
#if defined(FEPCTRL)	/* ken */
		{
			if (p_fc)
				fep_force_on();
			jp_getc1(&nchar, &kchar);
			if (p_fc)
				fep_force_off();
		}
#else
			jp_getc1(&nchar, &kchar);
#endif
		else if (IsKanji(nchar))
			kchar = vgetc();
		premsg(c, nchar);
	}
#else
	if (strchr("@zZtTfF[]m'`\"", c) || (c == 'q' && !Recording && !Exec_reg) ||
								(c == 'r' && !Visual.lnum) || c == Ctrl('X'))
	{
		State = NOMAPPING;
		nchar = vgetc();		/* no macro mapping for this char */
		premsg(c, nchar);
	}
#endif
	if (p_sc)
		flushbuf();		/* flush the premsg() characters onto the screen so we can
							see them while the command is being executed */

	if (c != 'z')	/* the 'z' command gets another character */
	{
		State = NORMAL;
		script_winsize_pp();
	}
	if (nchar == ESC)
	{
		CLEAROP;
		goto normal_end;
	}
	switch (c)
	{

/*
 * 0: Macros
 */
	  case 'q': 		/* (stop) recording into a named register */
		CHECKCLEAROP;
						/* command is ignored while executing a register */
		if (!Exec_reg && !dorecord(nchar))
			CLEAROPBEEP;
		break;

	 case '@':			/* execute a named buffer */
		CHECKCLEAROP;
		while (Prenum1--)
			if (!doexecbuf(nchar))
			{
				CLEAROPBEEP;
				break;
			}
		break;

/*
 * 1: Screen positioning commands
 */
	  case Ctrl('D'):
		flag = TRUE;

	  case Ctrl('U'):
		CHECKCLEAROP;
		if (Prenum)
			p_scroll = (Prenum > Rows - 1) ? Rows - 1 : Prenum;
		n = (p_scroll < Rows) ? p_scroll : Rows - 1;
		if (flag)
		{
				Topline += n;
				if (Topline > line_count)
					Topline = line_count;
				comp_Botline();		/* compute Botline */
				onedown(n);
		}
		else
		{
				if (n >= Curpos.lnum)
					n = Curpos.lnum - 1;
				Prenum1 = Curpos.lnum - n;
				scrolldown(n);
				if (Prenum1 < Curpos.lnum)
					Curpos.lnum = Prenum1;
		}
		beginline(TRUE);
		updateScreen(VALID);
		break;

	  case Ctrl('B'):
	  case K_SUARROW:
		dir = BACKWARD;

	  case Ctrl('F'):
	  case K_SDARROW:
		CHECKCLEAROP;
		onepage(dir, Prenum1);
		break;

	  case Ctrl('E'):
		CHECKCLEAROP;
		scrollup(Prenum1);
		updateScreen(VALID);
		break;

	  case Ctrl('Y'):
		CHECKCLEAROP;
		scrolldown(Prenum1);
		updateScreen(VALID);
		break;

	  case 'z':
		CHECKCLEAROP;
		if (isdigit(nchar))
		{
			/*
			 * we misuse some variables to be able to call premsg()
			 */
			operator = c;
			opnum = Prenum;
			Prenum = nchar - '0';
			for (;;)
			{
				premsg(' ', NUL);
				nchar = vgetc();
				State = NORMAL;
				script_winsize_pp();
				if (nchar == DEL)
					Prenum /= 10;
				else if (isdigit(nchar))
					Prenum = Prenum * 10 + (nchar - '0');
				else if (nchar == CR)
				{
					set_winheight((int)Prenum);
					break;
				}
				else
				{
					CLEAROPBEEP;
					break;
				}
			}
			operator = NOP;
			break;
		}

		if (Prenum)		/* line number given */
		{
			setpcmark();
			if (Prenum > line_count)
				Curpos.lnum = line_count;
			else
				Curpos.lnum = Prenum;
		}
		State = NORMAL;
		script_winsize_pp();
		switch (nchar)
		{
		  case NL:				/* put Curpos at top of screen */
		  case CR:
			Topline = Curpos.lnum;
			updateScreen(VALID);
			break;

		  case '.': 			/* put Curpos in middle of screen */
			n = (Rows + plines(Curpos.lnum)) / 2;
			goto dozcmd;

		  case '-': 			/* put Curpos at bottom of screen */
			n = Rows - 1;
			/* FALLTHROUGH */

	dozcmd:
			{
				register linenr_t	lp = Curpos.lnum;
				register long		l = plines(lp);

				do
				{
					Topline = lp;
					if (--lp == 0)
						break;
					l += plines(lp);
				} while (l <= n);
			}
			updateScreen(VALID);
			beginline(TRUE);
			break;

		  default:
			CLEAROPBEEP;
		}
		break;

/*
 *	  2: Control commands
 */
	  case ':':
	    if (Visual.lnum)
			goto dooperator;
		CHECKCLEAROP;
		docmdline(NULL);
		break;

	  case K_HELP:
		CHECKCLEAROP;
		help();
		break;

	  case Ctrl('L'):
		CHECKCLEAROP;
		updateScreen(CLEAR);
		break;

	  case Ctrl('G'):
		CHECKCLEAROP;
		fileinfo(did_cd || Prenum);	/* print full name if count given or :cd used */
		break;

	  case K_CCIRCM:			/* shorthand command */
		CHECKCLEAROPQ;
		getaltfile((int)Prenum, (linenr_t)0, TRUE);
		break;

	  case 'Z': 		/* write, if changed, and exit */
		CHECKCLEAROPQ;
		if (nchar != 'Z')
		{
			CLEAROPBEEP;
			break;
		}
		stuffReadbuff(":x\n");
		break;

	  case Ctrl(']'):			/* :ta to current identifier */
		CHECKCLEAROPQ;
	  case '*': 				/* / to current identifier */
	  case '#': 				/* ? to current identifier */
	  case 'K':					/* run program for current identifier */
		{
			register int 	col;

			ptr = nr2ptr(Curpos.lnum);
			col = Curpos.col;

			/*
			 * skip to start of identifier.
			 */
#ifdef JP
			while (ptr[col] != NUL && !isidchar(ptr[col])
												 && !IsKanji(ptr[col]))
#else
			while (ptr[col] != NUL && !isidchar(ptr[col]))
#endif
				++col;

			/*
			 * Back up to start of identifier. This doesn't match the
			 * real vi but I like it a little better and it shouldn't bother
			 * anyone.
			 */
#ifdef JP
			if (IsKanji(ptr[col]))
			{
				char class;
				class = ptr[col];
				while (col > 0 && class == ptr[col - 2])
					col -= 2;
			}
			else
#endif
			while (col > 0 && isidchar(ptr[col - 1]))
				--col;

#ifdef JP
			if (!isidchar(ptr[col]) && !IsKanji(ptr[col]))
#else
			if (!isidchar(ptr[col]))
#endif
			{
				CLEAROPBEEP;
				break;
			}

			if (Prenum)
				stuffnumReadbuff(Prenum);
			switch (c)
			{
				case '*':
					stuffReadbuff("/");
					break;
				case '#':
					stuffReadbuff("?");
					break;
				case 'K':
					stuffReadbuff(":! ");
					stuffReadbuff(p_kp);
					stuffReadbuff(" ");
					break;
				default:
					stuffReadbuff(":ta ");
			}

			/*
			 * Now grab the chars in the identifier
			 */
#ifdef JP
			if (IsKanji(ptr[col]))
			{
				char class;
				class = ptr[col];
				while (class == ptr[col])
				{
					stuffcharReadbuff(ptr[col++]);
					stuffcharReadbuff(ptr[col++]);
				}
			}
			else
#endif
			while (isidchar(ptr[col]))
			{
				stuffcharReadbuff(ptr[col]);
				++col;
			}
			stuffReadbuff("\n");
		}
		break;

	  case Ctrl('T'):		/* backwards in tag stack */
			CHECKCLEAROPQ;
	  		dotag("", 2, (int)Prenum1);
			break;

	  case Ctrl('X'):		/* extented commands */
		switch(nchar)
		{
			case Ctrl('H'):			/* help */
				help();
				break;

			case Ctrl('X'):			/* toggle track mode */
				if ((Track = !Track))
					showtrack();
				else
					smsg("");
				break;

#ifdef MDOMAIN
			case 'r':				/* read from (proxy)http server */
			case 'g':				/* download from (proxy)http server */
				{
					char *start, *end;
					char c;

					ptr = nr2ptr(Curpos.lnum);
					start = end = ptr + Curpos.col;
					while(start != ptr && !strchr("<\"'` \t", *start))
						start --;
					if (strchr("<\"'` \t", *start))
						start++;
					if (!strncmp(start, "URL:", 4))
						start += 4;
					while(*end && !strchr(">\"'` \t", *end))
						end ++;
					c = *end;
					*end = NUL;
					if (nchar == 'g')
						stuffReadbuff(":nget ");
					else
						stuffReadbuff(":r ");
					stuffReadbuff(start);
					stuffReadbuff(" ");
					*end = c;
				}
				break;
#endif

			default:
				smsg("%c : unknown extented command", nchar);
				beep();
				break;
		}
		break;
/*
 * Cursor motions
 */
	  case 'G':
		mtype = MLINE;
		setpcmark();
		if (Prenum == 0 || Prenum > line_count)
				Curpos.lnum = line_count;
		else
				Curpos.lnum = Prenum;
		beginline(TRUE);
		break;

	  case 'H':
	  case 'M':
		if (c == 'M')
				n = (Rows - emptyrows - 1) / 2;
		else
				n = Prenum;
		mtype = MLINE;
		setpcmark();
		Curpos.lnum = Topline;
		while (n && onedown((long)1))
				--n;
		beginline(TRUE);
		break;

	  case 'L':
		mtype = MLINE;
		setpcmark();
		Curpos.lnum = Botline - 1;
		for (n = Prenum; n && oneup((long)1); n--)
				;
		beginline(TRUE);
		break;

	  case 'l':
	  case K_RARROW:
	  case ' ':
		mtype = MCHAR;
		mincl = FALSE;
		n = Prenum1;
		while (n--)
		{
			if (Track && c == 'l')
					track_right();

			if (!oneright())
			{
				if (operator == NOP)
					beep();
				else
				{
					if (lineempty(Curpos.lnum))
						CLEAROPBEEP;
					else
					{
						mincl = TRUE;
						if (n)
							beep();
					}
				}
				break;
			}
		}
		set_want_col = TRUE;
		break;

	  case 'h':
	  case K_LARROW:
	  case Ctrl('H'):
	  case DEL:
		mtype = MCHAR;
		mincl = FALSE;
		n = Prenum1;
		while (n--)
		{
			if (Track && c == 'h')
					track_left();

			if (!oneleft())
			{
				if (operator != DELETE && operator != CHANGE)
					beep();
				else if (Prenum1 == 1)
					CLEAROPBEEP;
				break;
			}
		}
		set_want_col = TRUE;
		break;

	  case '-':
		flag = TRUE;
		/* FALLTHROUGH */

	  case Ctrl('P'):
	  	if (Track)
		{
			p_trs = tracktab_prev(p_trs);
			break;
		}
	  case 'k':
		if (Track)
			track_up();

	  case K_UARROW:
		mtype = MLINE;

		if (!oneup(Prenum1))
			CLEAROPBEEP;
		else if (flag)
			beginline(TRUE);
		break;

	  case '+':
	  case CR:
		flag = TRUE;
		/* FALLTHROUGH */

	  case Ctrl('N'):
	  	if (Track)
		{
			p_trs = tracktab_next(p_trs);
			break;
		}
	  case 'j':
		if (Track)
			track_down();

	  case K_DARROW:
	  case NL:

		mtype = MLINE;
		if (!onedown(Prenum1))
			CLEAROPBEEP;
		else if (flag)
			beginline(TRUE);
		break;

		/*
		 * This is a strange motion command that helps make operators more
		 * logical. It is actually implemented, but not documented in the
		 * real 'vi'. This motion command actually refers to "the current
		 * line". Commands like "dd" and "yy" are really an alternate form of
		 * "d_" and "y_". It does accept a count, so "d3_" works to delete 3
		 * lines.
		 */
	  case '_':
lineop:
		if (operator == CHANGE && p_ai)		/* do not delete the indent */
		{
			beginline(TRUE);
			startop = Curpos;
			mtype = MCHAR;
			mincl = TRUE;
		}
		else
			mtype = MLINE;
		if (!onedown((long)(Prenum1 - 1)))
			CLEAROPBEEP;
		else if (mtype == MCHAR)	/* 'c' with autoindent */
		{
			Curpos.col = MAXCOL;	/* put cursor on last char in line */
			adjustCurpos();
		}
		else if (operator != YANK)	/* 'Y' does not move cursor */
			beginline(TRUE);
		break;

	  case '|':
		mtype = MCHAR;
		mincl = TRUE;
		beginline(FALSE);
		if (Prenum > 0)
			coladvance((colnr_t)(Prenum - 1));
		Curswant = (colnr_t)(Prenum - 1);
		break;

		/*
		 * Word Motions
		 */

	  case 'B':
		type = 1;
		/* FALLTHROUGH */

	  case 'b':
	  case K_SLARROW:
		mtype = MCHAR;
		mincl = FALSE;
		set_want_col = TRUE;
		if (bck_word(Prenum1, type))
			CLEAROPBEEP;
		break;

	  case 'E':
		type = 1;
		/* FALLTHROUGH */

	  case 'e':
		mincl = TRUE;
		goto dowrdcmd;

	  case 'W':
		type = 1;
		/* FALLTHROUGH */

	  case 'w':
	  case K_SRARROW:
		mincl = FALSE;
		flag = TRUE;
		/*
		 * This is a little strange. To match what the real vi does, we
		 * effectively map 'cw' to 'ce', and 'cW' to 'cE', provided that we are
		 * not on a space or a TAB. This seems
		 * impolite at first, but it's really more what we mean when we say
		 * 'cw'.
		 */
		if (operator == CHANGE && (n = gcharCurpos()) != ' ' && n != TAB &&
																n != NUL)
		{
			mincl = TRUE;
			flag = FALSE;
		}

dowrdcmd:
		mtype = MCHAR;
		set_want_col = TRUE;
		if (flag)
			n = fwd_word(Prenum1, type, operator != NOP);
		else
			n = end_word(Prenum1, type, operator == CHANGE);
		if (n)
		{
			CLEAROPBEEP;
			break;
		}
#if 0
		/*
		 * If we do a 'dw' for the last word in a line, we only delete the rest
		 * of the line, not joining the two lines, unless the current line is empty.
		 */
		if (operator == DELETE && Prenum1 == 1 && startop.lnum != Curpos.lnum &&
			!lineempty(startop.lnum))
		{
				Curpos = startop;
				while (oneright())
					;
				mincl = TRUE;
		}
#endif
		break;

	  case '$':
		mtype = MCHAR;
		mincl = TRUE;
		Curswant = MAXCOL;				/* so we stay at the end */
		if (!onedown((long)(Prenum1 - 1)))
		{
			CLEAROPBEEP;
			break;
		}
		break;

	  case '^':
		flag = TRUE;
		/* FALLTHROUGH */

	  case '0':
		mtype = MCHAR;
		mincl = FALSE;
		beginline(flag);
		break;

/*
 * 4: Searches
 */
	  case '?':
	  case '/':
		if (!getcmdline(c, (u_char *)searchbuff))
		{
			CLEAROP;
			break;
		}
		mtype = MCHAR;
		mincl = FALSE;
		set_want_col = TRUE;

		n = dosearch(c, searchbuff, FALSE, Prenum1, TRUE);
#ifdef FEPCTRL
		if (p_fc)
			fep_force_off();
#endif
		if (n == 0)
			CLEAROP;
		else if (n == 2)
			mtype = MLINE;
		break;

	  case 'N':
		flag = 1;

	  case 'n':
		mtype = MCHAR;
		mincl = FALSE;
		set_want_col = TRUE;
		if (!dosearch(0, NULL, flag, Prenum1, TRUE))
			CLEAROP;
		break;

		/*
		 * Character searches
		 */
	  case 'T':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case 't':
		type = 1;
		goto docsearch;

	  case 'F':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case 'f':
docsearch:
		mtype = MCHAR;
		mincl = TRUE;
		set_want_col = TRUE;
#ifdef JP
		if (!searchc(nchar, kchar, dir, type, Prenum1))
#else
		if (!searchc(nchar, dir, type, Prenum1))
#endif
			CLEAROPBEEP;
#ifdef FEPCTRL
		if (p_fc)
			fep_force_off();
#endif
		break;

	  case ',':
		flag = 1;
		/* FALLTHROUGH */

	  case ';':
	    dir = flag;
	    goto docsearch;		/* nchar == NUL, thus repeat previous search */

		/*
		 * section or C function searches
		 */

	  case '[':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case ']':
		mtype = MLINE;
		set_want_col = TRUE;
		flag = '{';
		if (nchar != c)
		{
			if (nchar == '[' || nchar == ']')
				flag = '}';
			else
			{
				CLEAROPBEEP;
				break;
			}
		}
		if (dir == FORWARD && operator != NOP)	/* e.g. y]] searches for '}' */
			flag = '}';
		if (!findpar(dir, Prenum1, flag))
		{
			CLEAROPBEEP;
		}
		break;

	  case '%':
		mincl = TRUE;
	    if (Prenum)		/* {cnt}% : goto {cnt} percentage in file */
		{
			if (Prenum > 100)
				CLEAROPBEEP;
			else
			{
				mtype = MLINE;
				setpcmark();
						/* round up, so CTRL-G will give same value */
				Curpos.lnum = (line_count * Prenum + 99) / 100;
				Curpos.col = 0;
			}
		}
		else			/* % : go to matching paren */
		{
			mtype = MCHAR;
			if ((pos = showmatch()) == NULL)
				CLEAROPBEEP;
			else
			{
				setpcmark();
				Curpos = *pos;
				set_want_col = TRUE;
			}
		}
		break;

	  case '(':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case ')':
		mtype = MCHAR;
		if (c == ')')
			mincl = FALSE;
		else
			mincl = TRUE;
		set_want_col = TRUE;

		if (!findsent(dir, Prenum1))
			CLEAROPBEEP;
		break;

	  case '{':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case '}':
		mtype = MCHAR;
		mincl = FALSE;
		set_want_col = TRUE;
		if (!findpar(dir, Prenum1, NUL))
			CLEAROPBEEP;
		break;

/*
 * 5: Edits
 */
	  case '.':
		CHECKCLEAROPQ;
		if (!start_redo(Prenum))
			CLEAROPBEEP;
		break;

	  case 'u':
	    if (Visual.lnum)
			goto dooperator;
	  case K_UNDO:
		CHECKCLEAROPQ;
		u_undo((int)Prenum1);
		set_want_col = TRUE;
		break;

	  case Ctrl('R'):
		CHECKCLEAROPQ;
	  	u_redo((int)Prenum1);
		set_want_col = TRUE;
		break;

	  case 'U':
	    if (Visual.lnum)
			goto dooperator;
		CHECKCLEAROPQ;
		u_undoline();
		set_want_col = TRUE;
		break;

	  case 'r':
	    if (Visual.lnum)
		{
			c = 'c';
			goto dooperator;
		}
		CHECKCLEAROPQ;
		ptr = nr2ptr(Curpos.lnum) + Curpos.col;
		if (strlen(ptr) < (unsigned)Prenum1)	/* not enough characters to replace */
		{
			CLEAROPBEEP;
			break;
		}
		/*
		 * Replacing with a line break or tab is done by edit(), because it
         * is complicated.
		 * Other characters are done below to avoid problems with things like
		 * CTRL-V 048 (for edit() this would be R CTRL-V 0 ESC).
		 */
		if (nchar == '\r' || nchar == '\n' || nchar == '\t')
		{
#ifdef JP
			prep_redo(Prenum1, 'r', nchar, NUL, NUL);
#else
			prep_redo(Prenum1, 'r', nchar, NUL);
#endif
			stuffnumReadbuff(Prenum1);
			stuffcharReadbuff('R');
			stuffcharReadbuff(nchar);
			stuffcharReadbuff(ESC);
			break;
		}

		if (nchar == Ctrl('V'))				/* get another character */
		{
			c = Ctrl('V');
#ifdef JP
			get_literal(&type, &nchar, &kchar);
#else
			nchar = get_literal(&type);
#endif
			if (type)						/* typeahead */
				stuffcharReadbuff(type);
		}
		else
			c = NUL;
#ifdef JP
		prep_redo(Prenum1, 'r', c, nchar, kchar);
#else
		prep_redo(Prenum1, 'r', c, nchar);
#endif
		if (!u_saveCurpos())				/* save line for undo */
			break;
#ifdef JP
		{									/* replace the characters */
			int state;
			FPOS curpos;

			state = State;
			curpos = Curpos;

			State = REPLACE;
			while (Prenum1--)
			  inschar(nchar, kchar);
			State = state;
			Curpos = curpos;
		}
		set_want_col = TRUE;
#else
		Curpos.col += Prenum1 - 1;
		while (Prenum1--)					/* replace the characters */
			*ptr++ = nchar;
		set_want_col = TRUE;
		CHANGED;
#endif
#ifdef FEPCTRL
		if (p_fc)
			fep_force_off();
#endif
		updateline();
		break;

	  case 'J':
	    if (Visual.lnum)		/* join the visual lines */
		{
			if (Curpos.lnum > Visual.lnum)
			{
				Prenum = Curpos.lnum - Visual.lnum + 1;
				Curpos.lnum = Visual.lnum;
			}
			else
				Prenum = Visual.lnum - Curpos.lnum + 1;
			Visual.lnum = 0;
		}
		CHECKCLEAROP;
		if (Prenum <= 1)
			Prenum = 2; 			/* default for join is two lines! */
		if (Curpos.lnum + Prenum - 1 > line_count)	/* beyond last line */
		{
			CLEAROPBEEP;
			break;
		}

#ifdef JP
		prep_redo(Prenum, 'J', NUL, NUL, NUL);
#else
		prep_redo(Prenum, 'J', NUL, NUL);
#endif
		dodojoin(Prenum, TRUE, TRUE);
		break;

	  case 'P':
		dir = BACKWARD;
		/* FALLTHROUGH */

	  case 'p':
		CHECKCLEAROPQ;
#ifdef JP
		prep_redo(Prenum, c, NUL, NUL, NUL);
#else
		prep_redo(Prenum, c, NUL, NUL);
#endif
		doput(dir, Prenum1);
		break;

	  case Ctrl('A'):			/* add to number */
	  case Ctrl('S'):			/* subtract from number */
		CHECKCLEAROPQ;
		if (doaddsub((int)c, Prenum1))
#ifdef JP
			prep_redo(Prenum1, c, NUL, NUL, NUL);
#else
			prep_redo(Prenum1, c, NUL, NUL);
#endif
		break;

/*
 * 6: Inserts
 */
	  case 'A':
		set_want_col = TRUE;
		while (oneright())
				;
		/* FALLTHROUGH */

	  case 'a':
		CHECKCLEAROPQ;
		/* Works just like an 'i'nsert on the next character. */
		if (u_saveCurpos())
		{
			if (!lineempty(Curpos.lnum))
				incCurpos();
			startinsert(c, FALSE, Prenum1);
			command_busy = TRUE;
		}
		break;

	  case 'I':
		beginline(TRUE);
		/* FALLTHROUGH */

	  case 'i':
		CHECKCLEAROPQ;
		if (u_saveCurpos())
		{
			startinsert(c, FALSE, Prenum1);
			command_busy = TRUE;
		}
		break;

	  case 'o':
	  	if (Visual.lnum)	/* switch start and end of visual */
		{
			Prenum = Visual.lnum;
			Visual.lnum = Curpos.lnum;
			Curpos.lnum = Prenum;
			if (Visual.col != VISUALLINE)
			{
				n = Visual.col;
				Visual.col = Curpos.col;
				Curpos.col = (int)n;
				set_want_col = TRUE;
			}
			break;
		}
		CHECKCLEAROP;
		if (u_save(Curpos.lnum, (linenr_t)(Curpos.lnum + 1)) && Opencmd(FORWARD, TRUE, TRUE))
		{
			startinsert('o', TRUE, Prenum1);
			command_busy = TRUE;
		}
		break;

	  case 'O':
		CHECKCLEAROPQ;
		if (u_save((linenr_t)(Curpos.lnum - 1), Curpos.lnum) && Opencmd(BACKWARD, TRUE, TRUE))
		{
			startinsert('O', TRUE, Prenum1);
			command_busy = TRUE;
		}
		break;

	  case 'R':
	    if (Visual.lnum)
		{
			c = 'c';
			Visual.col = VISUALLINE;
			goto dooperator;
		}
		CHECKCLEAROPQ;
		if (u_saveCurpos())
		{
			startinsert('R', FALSE, Prenum1);
			command_busy = TRUE;
		}
		break;

/*
 * 7: Operators
 */
	  case '~': 		/* swap case */
	  /*
	   * if tilde is not an operator and Visual is off: swap case
	   * of a single character
	   */
		if (!p_to && !Visual.lnum)
		{
			CHECKCLEAROPQ;
			if (lineempty(Curpos.lnum))
			{
				CLEAROPBEEP;
				break;
			}
#ifdef JP
			prep_redo(Prenum, '~', NUL, NUL, NUL);
#else
			prep_redo(Prenum, '~', NUL, NUL);
#endif

			if (!u_saveCurpos())
				break;

			for (; Prenum1 > 0; --Prenum1)
			{
				if (gcharCurpos() == NUL)
					break;
				swapchar(&Curpos);
				incCurpos();
			}

			set_want_col = TRUE;
			CHANGED;
			updateline();
			break;
		}
		/*FALLTHROUGH*/

	  case 'd':
	  case 'c':
	  case 'y':
	  case '>':
	  case '<':
	  case '!':
	  case '=':
	  case 'Q':
dooperator:
		n = strchr(opchars, c) - opchars + 1;
		if (n == operator)		/* double operator works on lines */
			goto lineop;
		CHECKCLEAROP;
		if (Prenum != 0)
			opnum = Prenum;
		startop = Curpos;
		operator = (int)n;
		break;

/*
 * 8: Abbreviations
 */

	 /* when Visual the next commands are operators */
	  case 'S':
	  case 'Y':
	  case 'D':
	  case 'C':
	  case 'x':
	  case 'X':
	  case 's':
	  	if (Visual.lnum)
		{
			static char trans[] = "ScYyDdCcxdXdsc";

			if (isupper(c) && !Visual_block)		/* uppercase means linewise */
				Visual.col = VISUALLINE;
			c = *(strchr(trans, c) + 1);
			goto dooperator;
		}

	  case '&':
		CHECKCLEAROPQ;
		if (Prenum)
			stuffnumReadbuff(Prenum);

		if (c == 'Y' && p_ye)
			c = 'Z';
		{
				static char *(ar[9]) = {"dl", "dh", "d$", "c$", "cl", "cc", "yy", "y$", ":s\r"};
				static char *str = "xXDCsSYZ&";

				stuffReadbuff(ar[(int)(strchr(str, c) - str)]);
		}
		break;

/*
 * 9: Marks
 */

	  case 'm':
		CHECKCLEAROP;
		if (!setmark(nchar))
			CLEAROPBEEP;
		break;

	  case '\'':
		flag = TRUE;
		/* FALLTHROUGH */

	  case '`':
		pos = getmark(nchar, (operator == NOP));
		if (pos == (FPOS *)-1)	/* jumped to other file */
		{
			if (flag)
				beginline(TRUE);
			break;
		}

		if (pos != NULL)
			setpcmark();

cursormark:
		if (pos == NULL)
			CLEAROPBEEP;
		else
		{
			Curpos = *pos;
			if (flag)
				beginline(TRUE);
#ifdef JP
			else
				kanji_align();
#endif
		}
		mtype = flag ? MLINE : MCHAR;
		mincl = FALSE;		/* ignored if not MCHAR */
		set_want_col = TRUE;
		break;

	case Ctrl('O'):			/* goto older pcmark */
		Prenum1 = -Prenum1;
		/* FALLTHROUGH */

	case Ctrl('I'):			/* goto newer pcmark */
		CHECKCLEAROPQ;
		pos = movemark((int)Prenum1);
		if (pos == (FPOS *)-1)	/* jump to other file */
		{
			set_want_col = TRUE;
			break;
		}
		goto cursormark;

/*
 * 10. Buffer setting
 */
	  case '"':
		CHECKCLEAROP;
		if (isalnum(nchar) || nchar == '.' || nchar == '%' || nchar == '"')
		{
			yankbuffer = nchar;
			opnum = Prenum;		/* remember count before '"' */
		}
		else
			CLEAROPBEEP;
		break;

/*
 * 11. Visual
 */
 	  case 'v':
	  case 'V':
	  case Ctrl('V'):
		CHECKCLEAROP;
		Visual_block = FALSE;

			/* stop Visual */
		if (Visual.lnum)
		{
			Visual.lnum = 0;
			updateScreen(NOT_VALID);		/* delete the inversion */
		}
			/* start Visual */
		else
		{
			if (!didwarn && (T_TI == NULL || *T_TI == NUL))	/* cannot invert */
			{
				emsg("Warning: terminal cannot invert");
				didwarn = TRUE;
			}
			if (Prenum)						/* use previously selected part */
			{
				if (!resel_Visual_type)		/* there is none */
				{
					beep();
					break;
				}
				Visual = Curpos;
				if (resel_Visual_nlines > 1)
					Curpos.lnum += resel_Visual_nlines * Prenum - 1;
				switch (resel_Visual_type)
				{
				case 'V':	Visual.col = VISUALLINE;
							break;

				case Ctrl('V'):
							Visual_block = TRUE;
							break;

				case 'v':		
							if (resel_Visual_nlines <= 1)
								Curpos.col += resel_Visual_col * Prenum - 1;
							else
								Curpos.col = resel_Visual_col;
							break;
				}
				if (resel_Visual_col == MAXCOL)
				{
					Curswant = MAXCOL;
					coladvance(MAXCOL);
				}
				else if (Visual_block)
					coladvance((colnr_t)(Cursvcol + resel_Visual_col * Prenum - 1));
				updateScreen(NOT_VALID);	/* show the inversion */
			}
			else
			{
				Visual = Curpos;
				if (c == 'V')				/* linewise */
					Visual.col = VISUALLINE;
				else if (c == Ctrl('V'))	/* blockwise */
					Visual_block = TRUE;
				updateline();				/* start the inversion */
			}
		}
		break;

/*
 * 12. Suspend
 */

 	case Ctrl('Z'):
		CLEAROP;
		Visual.lnum = 0;					/* stop Visual */
		stuffReadbuff(":st\r");				/* with autowrite */
		break;

/*
 * The end
 */
	  case ESC:
		if (Track)
		{
			Track = FALSE;				/* stop track mode */
			smsg("");
			break;
		}

	    if (Visual.lnum)
		{
			Visual.lnum = 0;			/* stop Visual */
			updateScreen(NOT_VALID);
		}

	  default:					/* not a known command */
		CLEAROPBEEP;
		break;

	}	/* end of switch on command character */

/*
 * if we didn't start or finish an operator, reset yankbuffer, unless we
 * need it later.
 */
	if (!finish_op && !operator && strchr("\"DCYSsXx", c) == NULL)
		yankbuffer = 0;

	/*
	 * If an operation is pending, handle it...
	 */
	if ((Visual.lnum || finish_op) && operator != NOP)
	{
		if (operator != YANK && !Visual.lnum)		/* can't redo yank */
		{
#ifdef JP
				prep_redo(Prenum, opchars[operator - 1], c, nchar, kchar);
#else
				prep_redo(Prenum, opchars[operator - 1], c, nchar);
#endif
				if (c == '/' || c == '?')		/* was a search */
				{
						AppendToRedobuff(searchbuff);
						AppendToRedobuff(NL_STR);
				}
		}

		if (redo_Visual_busy)
		{
			startop = Curpos;
			Curpos.lnum += redo_Visual_nlines - 1;
			switch (redo_Visual_type)
			{
			case 'V':	Visual.col = VISUALLINE;
						break;

			case Ctrl('V'):
						Visual_block = TRUE;
						break;

			case 'v':		
						if (redo_Visual_nlines <= 1)
							Curpos.col += redo_Visual_col - 1;
						else
							Curpos.col = redo_Visual_col;
#ifdef JP
						coladvance(Curpos.col);
#endif
						break;
			}
			if (redo_Visual_col == MAXCOL)
			{
				Curswant = MAXCOL;
				coladvance(MAXCOL);
			}
		}
		else if (Visual.lnum)
			startop = Visual;

		if (lt(startop, Curpos))
		{
			endop = Curpos;
			if (!Visual.lnum || !strchr("y", c))
								 /* don't move cursor at block mode yank */
				Curpos = startop;
		}
		else
		{
			endop = startop;
			startop = Curpos;
		}
		nlines = endop.lnum - startop.lnum + 1;

		if (Visual.lnum || redo_Visual_busy)
		{
			if (Visual_block)				/* block mode */
			{
				startvcol = getvcol(&startop, 2);
				n = getvcol(&endop, 2);
				if (n < startvcol)
					startvcol = (colnr_t)n;

			/* if '$' was used, get endvcol from longest line */
				if (Curswant == MAXCOL)
				{
					Curpos.col = MAXCOL;
					endvcol = 0;
					for (Curpos.lnum = startop.lnum; Curpos.lnum <= endop.lnum; ++Curpos.lnum)
						if ((n = getvcol(&Curpos, 3)) > endvcol)
							endvcol = (colnr_t)n;
					Curpos = startop;
				}
				else if (redo_Visual_busy)
					endvcol = startvcol + redo_Visual_col - 1;
				else
				{
					endvcol = getvcol(&startop, 3);
					n = getvcol(&endop, 3);
					if (n > endvcol)
						endvcol = (colnr_t)n;
				}
				coladvance(startvcol);
			}

	/*
	 * prepare to reselect and redo Visual: this is based on the size
	 * of the Visual text
	 */
			if (Visual_block)
				resel_Visual_type = Ctrl('V');
			else if (Visual.col == VISUALLINE)
				resel_Visual_type = 'V';
			else
				resel_Visual_type = 'v';
			if (Curswant == MAXCOL)
				resel_Visual_col = MAXCOL;
			else if (Visual_block)
				resel_Visual_col = endvcol - startvcol + 1;
			else if (nlines > 1)
				resel_Visual_col = endop.col;
			else
				resel_Visual_col = endop.col - startop.col + 1;
			resel_Visual_nlines = nlines;
			if (operator != YANK && operator != COLON)	/* can't redo yank and : */
			{
#ifdef JP
				prep_redo(0L, 'v', opchars[operator - 1], NUL, NUL);
#else
				prep_redo(0L, 'v', opchars[operator - 1], NUL);
#endif
				redo_Visual_type = resel_Visual_type;
				redo_Visual_col = resel_Visual_col;
				redo_Visual_nlines = resel_Visual_nlines;
			}

			mincl = TRUE;
			if (Visual.col == VISUALLINE)
				mtype = MLINE;
			else
				mtype = MCHAR;

			redo_Visual_busy = FALSE;
			/*
			 * Switch Visual off now, so screen updating does
			 * not show inverted text when the screen is redrawn.
			 * With YANK and sometimes with COLON and FILTER there is no screen
			 * redraw, so it is done here to remove the inverted part.
			 */
			Visual.lnum = 0;
			if (operator == YANK || operator == COLON || operator == FILTER)
				updateScreen(NOT_VALID);
		}

		set_want_col = 1;

			/* no_op is set when start and end are the same */
		no_op = (mtype == MCHAR && !mincl && equal(startop, endop));

#ifdef JP
		if (mincl && endop.col != VISUALLINE && IsKanji(gchar(&endop)))
		{
			mincl = FALSE;
			endop.col += 2;
		}
#endif

	/*
	 * If the end of an operator is in column one while mtype is MCHAR and mincl
	 * is FALSE, we put endop after the last character in the previous line.
	 * If startop is on or before the first non-blank in the line, the operator
	 * becomes linewise (strange, but that's the way vi does it).
	 */
		if (mtype == MCHAR && mincl == FALSE && endop.col == 0 && nlines > 1)
		{
			--nlines;
			--endop.lnum;
			if (startinmargin())
				mtype = MLINE;
			else
			{
				endop.col = strlen(nr2ptr(endop.lnum));
				if (endop.col)
				{
					--endop.col;
					mincl = TRUE;
				}
			}
		}
		switch (operator)
		{
		  case LSHIFT:
		  case RSHIFT:
			doshift(operator);
			break;

		  case DELETE:
			if (!no_op)
				dodelete();
			break;

		  case YANK:
			if (!no_op)
				doyank(FALSE);
			break;

		  case CHANGE:
			dochange();
			command_busy = TRUE;
			break;

		  case FILTER:
			bangredo = TRUE;			/* dobang() will put cmd in redo buffer */

		  case INDENT:
		  case COLON:
dofilter:
			sprintf(IObuff, ":%ld,%ld", (long)startop.lnum, (long)endop.lnum);
			stuffReadbuff(IObuff);
			if (operator != COLON)
				stuffReadbuff("!");
			if (operator == INDENT)
			{
				stuffReadbuff(p_ep);
				stuffReadbuff("\n");
			}
			else if (operator == FORMAT)
			{
				stuffReadbuff(p_fp);
				stuffReadbuff("\n");
			}
				/*	docmdline() does the rest */
			break;

		  case TILDE:
		  case UPPER:
		  case LOWER:
			if (!no_op)
				dotilde();
			break;

		  case FORMAT:
			if (*p_fp != NUL)
				goto dofilter;		/* use external command */
			doformat();				/* use internal function */
			break;

		  default:
			CLEAROPBEEP;
		}
		operator = NOP;
		Visual_block = FALSE;
		yankbuffer = 0;
	}

normal_end:
	premsg(-1, NUL);
	if (restart_edit && operator == NOP && Visual.lnum == 0 && !command_busy && stuff_empty() && yankbuffer == 0)
		startinsert(restart_edit, FALSE, 1L);
}

	static void
#ifdef JP
prep_redo(num, cmd, c, nchar, kchar)
#else
prep_redo(num, cmd, c, nchar)
#endif
	long 	num;
	int		cmd;
	int		c;
	int		nchar;
#ifdef JP
	int		kchar;
#endif
{
	ResetRedobuff();
	if (yankbuffer != 0)	/* yank from specified buffer */
	{
		AppendCharToRedobuff('\"');
		AppendCharToRedobuff(yankbuffer);
	}
	if (num)
		AppendNumberToRedobuff(num);
	AppendCharToRedobuff(cmd);
	if (c != NUL)
		AppendCharToRedobuff(c);
	if (nchar != NUL)
#ifdef JP
	{
		AppendCharToRedobuff(nchar);
		if (IsKanji(nchar))
			AppendCharToRedobuff(kchar);
	}
#else
		AppendCharToRedobuff(nchar);
#endif
}

/*
 * check for operator active
 */
	static int
checkclearop()
{
	if (operator == NOP)
		return (FALSE);
	clearopbeep();
	return (TRUE);
}

/*
 * check for operator or Visual active
 */
	static int
checkclearopq()
{
	if (operator == NOP && Visual.lnum == 0)
		return (FALSE);
	clearopbeep();
	return (TRUE);
}

	static void
clearopbeep()
{
	CLEAROP;
	beep();
}

/*
 * display, on the last line of the window, the characters typed before
 * the last command character, plus 'c1' and 'c2'
 */
	static void
premsg(c1, c2)
	int c1, c2;
{
	char	buf[40];
	char	*p;

	if (!p_sc || !(KeyTyped || c1 == -1 || c1 == ' '))
		return;

	if (!p_fm)
	{
		cursor_off();
		windgoto((int)Rows - 1, sc_col);
	}
	else
	{
		if (strchr("hjkluHLUpP", c1))
			return;
	}

	if (c1 == -1)
		if (p_fm)
			screen_msg(TRUE, NULL);
		else
			outstrn("          ");	/* look in comp_col() for the number of spaces */
	else
	{
		p = buf;
		if (opnum)
		{
			sprintf(p, "%ld", (long)opnum);
			p = p + strlen(buf);
		}
		if (yankbuffer)
		{
			*p++ = '"';
			*p++ = yankbuffer;
		}
		if (operator == 'z')
			*p++ = 'z';
		else if (operator)
			*p++ = opchars[operator - 1];
		if (Prenum)
		{
			sprintf(p, "%ld", (long)Prenum);
			p = p + strlen(p);
		}
		*p = NUL;
		if (c1)
			strcpy(p, transchar(c1));
#ifdef JP
		if (c2 && !IsKanji(c2))
#else
		if (c2)
#endif
			strcat(p, transchar(c2));
		buf[10] = NUL;			/* truncate at maximal length */
		if (p_fm)
			screen_msg(TRUE, buf);
		else
			outstrn(buf);
	}
	setcursor();
	if (!p_fm)
		cursor_on();
}


syntax highlighted by Code2HTML, v. 0.9.1