/*
 * STEVIE - Simply Try this Editor for VI Enthusiasts
 *
 * Code Contributions By : Tim Thompson           twitch!tjt
 *                         Tony Andrews           onecom!wldrdg!tony 
 *                         G. R. (Fred) Walter    watmath!grwalter 
 */

/*
 * This file contains the main routine for processing characters in command
 * mode as well as routines for handling the operators. 
 */

#ifdef __ORCAC__
segment "normal";
#endif

#include "bugstevie.h"

static void     doshift();
static void     dodelete();
static void     doput();
static void     dochange();
static void     startinsert();
static bool_t   dojoin();
static bool_t   doyank();

/*
 * Macro evaluates true if char 'c' is a valid identifier character 
 */
#define IDCHAR(c)       (isalpha(c) || isdigit(c) || (c) == '_')

/*
 * Operators 
 */
#define NOP     0		/* no pending operation */
#define DELETE  1
#define YANK    2
#define CHANGE  3
#define LSHIFT  4
#define RSHIFT  5

#define CLEAROP (operator = NOP)/* clear any pending operator */

static int      operator = NOP;	/* current pending operator */

/*
 * When a cursor motion command is made, it is marked as being a character or
 * line oriented motion. Then, if an operator is in effect, the operation
 * becomes character or line oriented accordingly. 
 *
 * Character motions are marked as being inclusive or not. Most char. motions
 * are inclusive, but some (e.g. 'w') are not. 
 *
 * Generally speaking, every command in normal() should either clear any pending
 * operator (with CLEAROP), or set the motion type variable. 
 */

/*
 * Motion types 
 */
#define MBAD    (-1)		/* 'bad' motion type marks unusable yank buf */
#define MCHAR   0
#define MLINE   1

static int      mtype;		/* type of the current cursor motion */
static bool_t   mincl;		/* true if char motion is inclusive */
static int      ybtype = MBAD;
static int      ybcrossline = FALSE;

static LPtr     startop;	/* cursor pos. at start of operator */

/*
 * Operators can have counts either before the operator, or between the
 * operator and the following cursor motion as in: 
 *
 * d3w or 3dw 
 *
 * If a count is given before the operator, it is saved in opnum. If normal() is
 * called with a pending operator, the count in opnum (if present) overrides
 * any count that came later. 
 */
static int      opnum = 0;

#define DEFAULT1(x)     (((x) == 0) ? 1 : (x))

/*
 * normal 
 *
 * Execute a command in normal mode. 
 */

void
normal(c)
    char            c;
{
    char           *p;
    int             n;
    int             nn;
    bool_t          flag = FALSE;
    int             type = 0;	/* used in some operations to modify type */
    int             dir = FORWARD;	/* search direction */
    char            nchar = NUL;
    bool_t          finish_op;
    LPtr            temp_Curschar;

    last_command = NUL;
    /*
     * 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 we're in the middle of an operator 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 (finish_op) {
	if (opnum != 0)
	    Prenum = opnum;
    } else
	opnum = 0;

    switch (c) {

      case K_HELP:
	CLEAROP;
	if (help())
	    s_clear();
	break;

      case CTRL('L'):
	CLEAROP;
	s_clear();
	break;

      case CTRL('D'):
	CLEAROP;
	if (Prenum)
	    P(P_SS) = (Prenum > Rows - 1) ? Rows - 1 : Prenum;
	scrollup((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	onedown((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	break;

      case CTRL('U'):
	CLEAROP;
	if (Prenum)
	    P(P_SS) = (Prenum > Rows - 1) ? Rows - 1 : Prenum;
	scrolldown((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	oneup((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	break;

      case CTRL('F'):
	CLEAROP;
	if (nextline(Topchar) == NULL) {
	    beep();
	    break;
	}
	Prenum = DEFAULT1(Prenum);
	while (Prenum > 0) {
	    *Curschar = *prevline(Botchar);
	    *Topchar = *Curschar;
	    Topchar->index = 0;
	    Update_Botchar();
	    Prenum--;
	}
	beginline(TRUE);
	s_clear();
	break;

      case CTRL('B'):
	CLEAROP;
	if (prevline(Topchar) == NULL) {
	    beep();
	    break;
	}
	Prenum = DEFAULT1(Prenum);
	while (Prenum > 0) {
	    *Curschar = *Topchar;
	    n = Rows - 1;
	    {
		LPtr           *lp = Curschar;
		int             l = 0;

		while ((l < n) && (lp != NULL)) {
		    l += plines(lp->linep->s);
		    *Topchar = *lp;
		    lp = prevline(lp);
		}
	    }
	    Topchar->index = 0;
	    Prenum--;
	}
	beginline(TRUE);
	s_clear();
	break;

      case CTRL('E'):
	CLEAROP;
	scrollup(DEFAULT1(Prenum));
	if (LINEOF(Curschar) < LINEOF(Topchar))
	    Curschar->linep = Topchar->linep;
	break;

      case CTRL('Y'):
	CLEAROP;
	scrolldown(DEFAULT1(Prenum));
	Update_Botchar();
	if (LINEOF(Curschar) >= LINEOF(Botchar)) {
	    LPtr           *lp;

	    lp = prevline(Botchar);
	    if (lp == NULL)
		lp = Topchar;
	    Curschar->linep = lp->linep;
	}
	break;

      case 'z':
	CLEAROP;
	S_CHECK_TOPCHAR_AND_BOTCHAR;
	switch (vgetc()) {
	  case NL:		/* put Curschar at top of screen */
	  case CR:
	    *Topchar = *Curschar;
	    Topchar->index = 0;
	    break;

	  case '.':		/* put Curschar in middle of screen */
	    n = Rows / 2;
	    goto dozcmd;

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

    dozcmd:
	    {
		register LPtr  *lp = Curschar;
		register int    l = 0;

		while ((l < n) && (lp != NULL)) {
		    l += plines(lp->linep->s);
		    *Topchar = *lp;
		    lp = prevline(lp);
		}
	    }
	    Topchar->index = 0;
	    break;

	  default:
	    beep();
	}
	break;

      case CTRL('G'):
	CLEAROP;
	fileinfo();
	break;

      case 'G':
	mtype = MLINE;
	*Curschar = *gotoline(Prenum);
	if (!UndoInProgress) {
	    beginline(TRUE);
	    S_CHECK_TOPCHAR_AND_BOTCHAR;
	}
	break;

      case 'H':
	mtype = MLINE;
	*Curschar = *Topchar;
	for (n = Prenum; n && onedown(1); n--);
	beginline(TRUE);
	break;

      case 'M':
	mtype = MLINE;
	*Curschar = *Topchar;
	for (n = 0; n < Rows / 2 && onedown(1); n++);
	beginline(TRUE);
	break;

      case 'L':
	mtype = MLINE;
	*Curschar = *prevline(Botchar);
	for (n = Prenum; n && oneup(1); n--);
	beginline(TRUE);
	break;

      case 'l':
      case K_RARROW:
      case ' ':
	mtype = MCHAR;
	mincl = FALSE;
	n = DEFAULT1(Prenum);
	while (n--) {
	    if (!oneright()) {
		if (operator != DELETE && operator != CHANGE) {
		    beep();
		} else {
		    if (lineempty(Curschar)) {
			CLEAROP;
			beep();
		    } else {
			mincl = TRUE;
		    }
		}
		break;
	    }
	}
	set_want_col = TRUE;
	break;

      case 'h':
      case K_LARROW:
      case CTRL('H'):
	mtype = MCHAR;
	mincl = FALSE;
	Prenum = DEFAULT1(Prenum);
	n = Prenum;
	while (n--) {
	    if (!oneleft()) {
		if (operator != DELETE && operator != CHANGE) {
		    beep();
		} else if (Prenum == 1) {
		    CLEAROP;
		    beep();
		}
		break;
	    }
	}
	set_want_col = TRUE;
	break;

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

      case 'k':
      case K_UARROW:
      case CTRL('P'):
	mtype = MLINE;
	if (!oneup(DEFAULT1(Prenum))) {
	    CLEAROP;
	    beep();
	} else if (flag)
	    beginline(TRUE);
	break;

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

      case 'j':
      case K_DARROW:
      case CTRL('N'):
	mtype = MLINE;
	if (!onedown(DEFAULT1(Prenum))) {
	    CLEAROP;
	    beep();
	} 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:
	mtype = MLINE;
	if (!onedown(DEFAULT1(Prenum) - 1)) {
	    CLEAROP;
	    beep();
	} else
	    beginline(TRUE);
	break;

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

      case CTRL(']'):		/* :ta to current identifier */
	CLEAROP;
	{
	    char            ch;
	    LPtr            save;

	    save = *Curschar;
	    /*
	     * First 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. 
	     */
	    ch = gchar(Curschar);
	    while (IDCHAR(ch)) {
		if (!oneleft())
		    break;
		ch = gchar(Curschar);
	    }
	    if (!IDCHAR(ch))
		oneright();

	    stuffReadbuff(":ta ");
	    /*
	     * Now grab the chars in the identifier 
	     */
	    ch = gchar(Curschar);
	    while (IDCHAR(ch)) {
		stuffReadbuff(mkstr(ch));
		if (!oneright())
		    break;
		ch = gchar(Curschar);
	    }
	    stuffReadbuff("\n");

	    *Curschar = save;	/* restore, in case of error */
	}
	break;

      case '%':
	S_CHECK_TOPCHAR_AND_BOTCHAR;
	mtype = MCHAR;
	mincl = TRUE;
	{
	    LPtr           *pos;

	    if ((pos = showmatch()) == NULL) {
		CLEAROP;
		beep();
	    } else {
		setpcmark();
		*Curschar = *pos;
		set_want_col = TRUE;
	    }
	}
	break;

	/*
	 * Word Motions 
	 */

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

      case 'b':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPtr           *pos;

	    if ((Curschar->linep->prev == Filetop->linep)
		&& (Curschar->index == 0)) {
		CLEAROP;
		beep();
		break;
	    }
	    pos = bck_word(Curschar, type);
	    if (pos == NULL) {
		CLEAROP;
		beep();
		*Curschar = *gotoline(1);	/* goto top of file */
	    } else
		*Curschar = *pos;
	}
	break;

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

      case 'w':
	/*
	 * This is a little strange. To match what the real vi does, we
	 * effectively map 'cw' to 'ce', and 'cW' to 'cE'. This seems
	 * impolite at first, but it's really more what we mean when we say
	 * 'cw'. 
	 */
	if (operator == CHANGE)
	    goto do_e_cmd;

	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPtr           *pos;

	    if ((pos = fwd_word(Curschar, type)) == NULL) {
		CLEAROP;
		beep();
		break;
	    } else
		*Curschar = *pos;
	}
	if (operator == DELETE && DEFAULT1(Prenum) == 1) {
	    if (LINEOF(&startop) != LINEOF(Curschar)) {
		*Curschar = startop;
		while (oneright());
		mincl = TRUE;
	    }
	}
	break;

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

      case 'e':
do_e_cmd:
	mtype = MCHAR;
	mincl = TRUE;
	set_want_col = TRUE;
	if (c == 'e' || c == 'E') {
	    if (inc(Curschar) == -1) {
		CLEAROP;
		beep();
		break;
	    }
	}
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPtr           *pos;

	    if ((pos = end_word(Curschar, type)) == NULL) {
		CLEAROP;
		beep();
		break;
	    } else
		*Curschar = *pos;
	}
	break;

      case '$':
	mtype = MCHAR;
	mincl = TRUE;
	while (oneright());
	Curswant = 999;		/* so we stay at the end */
	break;

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

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

      case 'R':
	ResetBuffers();
	AppendToRedobuff("R");
	CLEAROP;
	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("R");

	last_command = 'R';
	startinsert(FALSE);
	break;

      case 'A':
	set_want_col = TRUE;
	while (oneright());
	ResetBuffers();
	AppendToRedobuff("A");
	goto doAPPENDcmd;

      case 'a':
	ResetBuffers();
	AppendToRedobuff("a");

doAPPENDcmd:
	CLEAROP;
	/* Works just like an 'i'nsert on the next character. */
	n = RowNumber(Curschar);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("a");

	if (!lineempty(Curschar))
	    inc(Curschar);

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);

	startinsert(FALSE);
	break;

      case 'I':
	beginline(TRUE);
	ResetBuffers();
	AppendToRedobuff("I");
	goto doINSERTcmd;
	/* FALLTHROUGH */

      case 'i':
      case K_INSERT:
	ResetBuffers();
	AppendToRedobuff("i");

doINSERTcmd:
	CLEAROP;

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("i");

	startinsert(FALSE);
	break;

      case 'o':
	CLEAROP;
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendToRedobuff("o");
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("o");

	if (OpenForward(!RedrawingDisabled))
	    startinsert(TRUE);

	last_command = 'o';
	break;

      case 'O':
	CLEAROP;
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendToRedobuff("O");
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("O");

	if (OpenBackward(!RedrawingDisabled))
	    startinsert(TRUE);

	last_command = 'O';
	break;

      case 'd':
	if (operator == DELETE)	/* handle 'dd' */
	    goto lineop;
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = DELETE;
	break;

	/*
	 * Some convenient abbreviations... 
	 */

      case 'x':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("dl");
	break;

      case 'X':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("dh");
	break;

      case 'D':
	stuffReadbuff("d$");
	break;

      case 'Y':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("yy");
	break;

      case 'C':
	stuffReadbuff("c$");
	break;

      case 'c':
	if (operator == CHANGE) {	/* handle 'cc' */
	    CLEAROP;
	    stuffReadbuff("0c$");
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = CHANGE;
	break;

      case 'y':
	if (operator == YANK)	/* handle 'yy' */
	    goto lineop;
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = YANK;
	break;

      case ENABLE_REDRAWING:
	RedrawingDisabled = FALSE;
	S_NOT_VALID;
	break;

      case 'p':
	if (Yankbuffptr != NULL) {
	    doput(FORWARD);

	    stuffReadbuff(ENABLE_REDRAWING_STR);
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case 'P':
	if (Yankbuffptr != NULL) {
	    doput(BACKWARD);

	    stuffReadbuff(ENABLE_REDRAWING_STR);
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case '>':
	if (operator == RSHIFT)	/* handle >> */
	    goto lineop;
	if (operator == LSHIFT) {
	    CLEAROP;
	    beep();
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;	/* save current position */
	operator = RSHIFT;
	break;

      case '<':
	if (operator == LSHIFT)	/* handle << */
	    goto lineop;
	if (operator == RSHIFT) {
	    CLEAROP;
	    beep();
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;	/* save current position */
	operator = LSHIFT;
	break;

      case 's':		/* substitute characters */
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("cl");
	break;

      case '?':
      case '/':
      case ':':
	CLEAROP;
	readcmdline(c, (char *) NULL);
	break;

      case 'n':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	if (!repsearch(0)) {
	    CLEAROP;
	    beep();
	}
	break;

      case 'N':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	if (!repsearch(1)) {
	    CLEAROP;
	    beep();
	}
	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;
	if ((nchar = vgetc()) == ESC)	/* search char */
	    break;
	if (!searchc(nchar, dir, type)) {
	    CLEAROP;
	    beep();
	}
	break;

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

      case ';':
	mtype = MCHAR;
	mincl = TRUE;
	set_want_col = TRUE;
	if (!crepsearch(flag)) {
	    CLEAROP;
	    beep();
	}
	break;

	/*
	 * Function searches 
	 */

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

      case ']':
	mtype = MLINE;
	set_want_col = TRUE;
	if (vgetc() != c) {
	    CLEAROP;
	    beep();
	    break;
	}
	if (!findfunc(dir)) {
	    CLEAROP;
	    beep();
	}
	break;

	/*
	 * Marks 
	 */

      case 'm':
	CLEAROP;
	if (!setmark(vgetc()))
	    beep();
	break;

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

      case '`':
	S_CHECK_TOPCHAR_AND_BOTCHAR;
	{
	    LPtr            mtmp;
	    LPtr           *mark = getmark(vgetc());

	    if (mark == NULL) {
		CLEAROP;
		beep();
	    } else {
		mtmp = *mark;
		setpcmark();
		*Curschar = mtmp;
		if (flag)
		    beginline(TRUE);
	    }
	    mtype = flag ? MLINE : MCHAR;
	    mincl = TRUE;	/* ignored if not MCHAR */
	    set_want_col = TRUE;
	}
	break;

      case 'r':
	CLEAROP;
	if (lineempty(Curschar)) {	/* Nothing to replace */
	    beep();
	    break;
	}
	nosuspend();
        nchar = vgetc();
	dosuspend();
	if (nchar == ESC) break;

	Prenum = DEFAULT1(Prenum);
	n = strlen(Curschar->linep->s) - Curschar->index;
	if (n < Prenum) {
	    beep();
	    break;
	}
	ResetBuffers();

	nn = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, nn);
	AppendPositionToUndoUndobuff(Curschar->index, nn);

	while (Prenum > 0) {
	    AppendToRedobuff("r");
	    AppendToRedobuff(mkstr(nchar));

	    AppendToUndobuff("r");
	    AppendToUndobuff(mkstr(gchar(Curschar)));

	    AppendToUndoUndobuff("r");
	    AppendToUndoUndobuff(mkstr(nchar));

	    pchar(Curschar, nchar);	/* Change current character. */

	    if (Prenum > 1) {
		oneright();
		AppendToRedobuff("l");
		AppendToUndobuff("l");
		AppendToUndoUndobuff("l");
	    }
	    Prenum--;
	}

	CHANGED;
	S_LINE_NOT_VALID;
	break;

      case '~':		/* swap case */
	CLEAROP;
	if (lineempty(Curschar)) {
	    beep();
	    break;
	}
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);

	Prenum = DEFAULT1(Prenum);
	if (Prenum > 0) {
	    AppendNumberToRedobuff(Prenum);
	    AppendNumberToUndobuff(Prenum);
	    AppendNumberToUndoUndobuff(Prenum);
	}
	AppendToRedobuff("~");
	AppendToUndobuff("~");
	AppendToUndoUndobuff("~");

	while (Prenum > 0) {
	    c = gchar(Curschar);
	    if (isalpha(c)) {
		if (islower(c))
		    pchar(Curschar, toupper(c));
		else
		    pchar(Curschar, tolower(c));
	    }
	    if (!oneright())
		break;
	    Prenum--;
	}

	CHANGED;
	S_LINE_NOT_VALID;
	break;

      case UNDO_SHIFTJ:
	CLEAROP;
	if (UndoInProgress) {
	    (void) dojoin(FALSE, FALSE);
	    break;
	}
	goto doSHIFTJcommand;

      case 'J':
	CLEAROP;
doSHIFTJcommand:
	if (nextline(Curschar) == NULL) {	/* on last line */
	    beep();
	    break;
	}
	ResetBuffers();

	temp_Curschar = *Curschar;
	nn = strlen(Curschar->linep->s);
	if (nn < 0)
	    nn = 0;
	n = RowNumber(&temp_Curschar);

	AppendToRedobuff("J");

	AppendPositionToUndobuff(nn, n);

	AppendPositionToUndoUndobuff(0, n);
	AppendToUndoUndobuff("J");

	if (linewhite(nextline(Curschar))) {
	    AppendToUndobuff("a\n");
	    if (!dojoin(FALSE, TRUE)) {
		beep();
		break;
	    }
	} else if (lineempty(Curschar)) {
	    AppendToUndobuff("i\n");
	    if (!dojoin(FALSE, TRUE)) {
		beep();
		break;
	    }
	} else {
	    AppendToUndobuff("dli\n");
	    if (!dojoin(TRUE, TRUE)) {
		beep();
		break;
	    }
	}

	AppendToUndobuff(ESC_STR);
	AppendPositionToUndobuff(nn, n);
	break;

      case K_CGRAVE:		/* shorthand command */
	CLEAROP;
	stuffReadbuff(":e #\n");
	break;

      case 'Z':		/* write, if changed, and exit */
	if (vgetc() != 'Z') {
	    beep();
	    break;
	}
	if (Changed) {
	    if (Filename != NULL) {
		if (!writeit(Filename, (LPtr *) NULL, (LPtr *) NULL))
		    return;
	    } else {
		emsg("No output file");
		return;
	    }
	}
	getout(0);
	break;

      case '.':
	CLEAROP;
	if (Redobuffptr != NULL) {
	    stuffReadbuff(Redobuff);

	    stuffReadbuff(ENABLE_REDRAWING_STR);
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case 'u':
      case K_UNDO:
	CLEAROP;
	if (UndoInProgress) {
	    p = UndoUndobuff;
	    UndoUndobuff = Undobuff;
	    Undobuff = p;
	    p = UndoUndobuffptr;
	    UndoUndobuffptr = Undobuffptr;
	    Undobuffptr = p;

	    UndoInProgress = FALSE;
	    RedrawingDisabled = FALSE;
	    S_NOT_VALID;
	} else if (Undobuffptr != NULL) {
	    stuffReadbuff(Undobuff);
	    stuffReadbuff("u");
	    UndoInProgress = TRUE;
	    RedrawingDisabled = TRUE;
	} else {
	    beep();
	}
	break;

      default:
	CLEAROP;
	beep();
	break;
    }

    /*
     * If an operation is pending, handle it... 
     */
    if (finish_op) {		/* we just finished an operator */
	if (operator == NOP)	/* ... but it was cancelled */
	    return;

	switch (operator) {

	  case LSHIFT:
	  case RSHIFT:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndobuff(startop.index, n);
	    AppendPositionToUndoUndobuff(startop.index, n);
	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff((operator == LSHIFT) ? "<" : ">");
	    AppendToUndobuff((operator == LSHIFT) ? ">" : "<");
	    AppendToUndoUndobuff((operator == LSHIFT) ? "<" : ">");
	    AppendToRedobuff(mkstr(c));
	    if (c == '>')
		AppendToUndobuff("<");
	    else if (c == '<')
		AppendToUndobuff(">");
	    else
		AppendToUndobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));

	    doshift(operator);
	    break;

	  case DELETE:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndoUndobuff(startop.index, n);

	    if (lt(&startop, Curschar))
		temp_Curschar = startop;
	    else
	        temp_Curschar = *Curschar;
	    n = RowNumber(&temp_Curschar);
	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff("d");
	    AppendToUndoUndobuff("d");
	    AppendToRedobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));
	    if (nchar != NUL) {
		AppendToRedobuff(mkstr(nchar));
		AppendToUndoUndobuff(mkstr(nchar));
	    }
	    AppendPositionToUndobuff(temp_Curschar.index, n);

	    dodelete(!UndoInProgress, !UndoInProgress, !UndoInProgress);

	    AppendPositionToUndobuff(temp_Curschar.index, n);
	    break;

	  case YANK:
	    ResetBuffers();	/* no redo/undo/(undo of undo) on yank... */
	    if (!doyank())
		msg("yank buffer exceeded");
	    if (!ybcrossline)
		*Curschar = startop;
	    else if (lt(&startop, Curschar))
		*Curschar = startop;
	    break;

	  case CHANGE:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndoUndobuff(startop.index, n);

	    if (lt(&startop, Curschar))
		temp_Curschar = startop;
	    else
	        temp_Curschar = *Curschar;
	    n = RowNumber(&temp_Curschar);
	    if (mtype == MLINE)
		AppendPositionToUndobuff(0, n);
	    else
		AppendPositionToUndobuff(temp_Curschar.index, n);

	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff("c");
	    AppendToUndoUndobuff("c");
	    AppendToRedobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));
	    if (nchar != NUL) {
		AppendToRedobuff(mkstr(nchar));
		AppendToUndoUndobuff(mkstr(nchar));
	    }
	    dochange();

	    last_command = 'c';
	    break;

	  default:
	    beep();
	}
	operator = NOP;
    }
}

/*
 * tabinout(shift_type, num) 
 *
 * If shift_type == RSHIFT, add a tab to the begining of the next num lines;
 * otherwise delete a tab from the beginning of the next num lines. 
 */
static void
tabinout(shift_type, num)
    int             shift_type;
    int             num;
{
    LPtr           *p;

    beginline(FALSE);
    while (num-- > 0) {
	beginline(FALSE);
	if (shift_type == RSHIFT)
	    inschar(TAB);
	else {
	    if (gchar(Curschar) == TAB)
		delchar(TRUE, FALSE);
	}
	if (num > 0) {
	    if ((p = nextline(Curschar)) != NULL)
		*Curschar = *p;
	    else
		break;
	}
    }
}

/*
 * doshift - handle a shift operation 
 */
static void
doshift(op)
    int             op;
{
    LPtr            top, bot;
    int             nlines;

    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
    pswap(top, bot);

    nlines = cntllines(&top, &bot);
    *Curschar = top;
    tabinout(op, nlines);

    /*
     * The cursor position afterward is the prior of the two positions. 
     */
    *Curschar = top;

    /*
     * If we were on the last char of a line that got shifted left, then move
     * left one so we aren't beyond the end of the line 
     */
    if (gchar(Curschar) == NUL && Curschar->index > 0)
	Curschar->index--;

    if (op == RSHIFT)
	oneright();
    else
	oneleft();

    S_NOT_VALID;

    if (nlines > P(P_RP))
	smsg("%d lines %ced", nlines, (op == RSHIFT) ? '>' : '<');
}

/*
 * dodelete - handle a delete operation 
 */

static void
dodelete(redraw, setup_for_undo, try_to_yank)
    bool_t          redraw;
    bool_t          setup_for_undo;
    bool_t          try_to_yank;
{
    LPtr            top, bot;
    int             nlines;
    int             n;

    /*
     * Do a yank of whatever we're about to delete. If there's too much stuff
     * to fit in the yank buffer, then get a confirmation before doing the
     * delete. This is crude, but simple. And it avoids doing a delete of
     * something we can't put back if we want. 
     */
    if (try_to_yank) {
	if (!doyank()) {
	    msg("yank buffer exceeded: press <y> to confirm");
	    if (vgetc() != 'y') {
		emsg("delete aborted");
		*Curschar = startop;
		return;
	    }
	}
    }
    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
	pswap(top, bot);

    *Curschar = top;
    nlines = cntllines(&top, &bot);

    if (mtype == MLINE) {
	if (operator == CHANGE) {
	    last_command_char = 'a';
	    delline(nlines - 1);
	    Curschar->index = 0;
	    while (delchar(TRUE, FALSE));
	} else {
	    if ((Filetop->linep->next == top.linep) &&
		(bot.linep->next == Fileend->linep))
		last_command_char = 'a';
	    else if (bot.linep->next == Fileend->linep)
		last_command_char = 'o';
	    else
		last_command_char = 'O';
	    if (setup_for_undo)
		AppendToUndobuff(mkstr(last_command_char));
	    delline(nlines);
	}
    } else if (top.linep == bot.linep) {	/* del. within line */
	if (!mincl)
	    dec(&bot);

	if (endofline(&bot))
	    last_command_char = 'a';
	else
	    last_command_char = 'i';
	if (setup_for_undo)
	    AppendToUndobuff(mkstr(last_command_char));
	n = bot.index - top.index + 1;
	while (n--)
	    if (!delchar(TRUE, FALSE))
		break;
    } else {			/* del. between lines */
	if (endofline(&top)) {
	    if (nextline(&top)) {
		if (lineempty(nextline(&top)))
		    last_command_char = 'a';
		else
		    last_command_char = 'i';
	    } else {
		last_command_char = 'a';
	    }
	} else {
	    last_command_char = 'i';
	}
	if (setup_for_undo)
	    AppendToUndobuff(mkstr(last_command_char));

	n = Curschar->index;
	while (Curschar->index >= n)
	    if (!delchar(TRUE, FALSE))
		break;

	top = *Curschar;
	*Curschar = *nextline(Curschar);
	delline(nlines - 2);
	Curschar->index = 0;
	n = bot.index;
	if (!mincl)
	    n--;

	while (n-- >= 0)
	    if (!delchar(TRUE, FALSE))
		break;
	*Curschar = top;
	dojoin(FALSE, FALSE);
    }

    if (mtype == MCHAR && nlines == 1 && redraw && P(P_NU) == FALSE) {
	S_LINE_NOT_VALID;
    } else {
	S_NOT_VALID;
    }

    if (nlines > P(P_RP))
	smsg("%d fewer lines", nlines);

    if (setup_for_undo) {
	AppendToUndobuff(Yankbuff);
	AppendToUndobuff(ESC_STR);
    }
}


/*
 * dochange - handle a change operation 
 */
static void
dochange()
{
    LPtr            l;

    if (lt(Curschar, &startop))
	l = *Curschar;
    else
	l = startop;

    dodelete(FALSE, FALSE, !UndoInProgress);

    if ((l.index > Curschar->index) && !lineempty(Curschar))
	inc(Curschar);

    startinsert(FALSE);
}

static          bool_t
doyank()
{
    LPtr            top, bot;
    char           *ybend = &Yankbuff[YANKSIZE - 1];
    int             nlines;

    Yankbuffptr = Yankbuff;

    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
	pswap(top, bot);

    nlines = cntllines(&top, &bot);

    ybtype = mtype;		/* set the yank buffer type */
    ybcrossline = FALSE;
    if (LINEOF(&top) != LINEOF(&bot))
	ybcrossline = TRUE;

    if (mtype == MLINE) {
	ybcrossline = TRUE;
	top.index = 0;
	bot.index = strlen(bot.linep->s);
	/*
	 * The following statement checks for the special case of yanking a
	 * blank line at the beginning of the file. If not handled right, we
	 * yank an extra char (a newline). 
	 */
	if (dec(&bot) == -1) {
	    *Yankbuff = NUL;
	    Yankbuffptr = NULL;
	    return TRUE;
	}
    } else {
	if (!mincl)
	    if (!equal(&top, &bot))
		dec(&bot);
    }

    for (; ltoreq(&top, &bot); inc(&top)) {
	*Yankbuffptr = (gchar(&top) != NUL) ? gchar(&top) : NL;
	Yankbuffptr++;
	if (Yankbuffptr >= ybend) {
	    *Yankbuffptr = NUL;
	    msg("yank too big for buffer");
	    ybtype = MBAD;
	    return FALSE;
	}
    }

    *Yankbuffptr = NUL;

    if (operator == YANK)
	if (nlines > P(P_RP))
	    smsg("%d lines yanked", nlines);

    return TRUE;
}

static void
doput(dir)
    int             dir;
{
    bool_t          type;

    if (ybtype == MBAD) {
	beep();
	return;
    }
    type = (ybtype == MCHAR);
    if (dir == FORWARD)
	stuffReadbuff(type ? "a" : "o");
    else
	stuffReadbuff(type ? "i" : "O");

    stuffReadbuff(Yankbuff);
    stuffReadbuff(ESC_STR);

    if (ybtype != MCHAR)
	stuffReadbuff("^");
}

static void
startinsert(startln)
    int             startln;	/* if set, insert at start of line */
{
extern void nosuspend(void);
extern void dosuspend(void);

    nosuspend(); /* turn off the suspend character in insert mode */
    *Insstart = *Curschar;
    if (startln) {
	Insstart->index = 0;
    }
    *Insbuff = NUL;
    Insbuffptr = NULL;

    State = INSERT;
    if (P(P_MO)) {
	if (last_command == 'R')
	    msg("Replace Mode");
	else
	    msg("Insert Mode");
    }
}

void
ResetBuffers()
{
    if (UndoInProgress)
	return;

    *Redobuff = NUL;
    Redobuffptr = NULL;

    *Undobuff = NUL;
    Undobuffptr = NULL;

    *UndoUndobuff = NUL;
    UndoUndobuffptr = NULL;
}

void
AppendToInsbuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (Insbuffptr == NULL) {
	if ((strlen(s) + 1) < INSERT_SIZE) {
	    strcpy(Insbuff, s);
	    Insbuffptr = Insbuff;
	    return;
	}
    } else if ((strlen(Insbuff) + strlen(s) + 1) < INSERT_SIZE) {
	strcat(Insbuff, s);
	return;
    }
    emsg("Couldn't AppendToInsbuff() - clearing Insbuff\n");
    *Insbuff = NUL;
    Insbuffptr = NULL;
}

void
AppendToRedobuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (Redobuffptr == (char *) (-2)) {
	return;
    }
    if (Redobuffptr == (char *) (-1)) {
	Redobuffptr = (char *) (-2);
	emsg("Couldn't AppendToRedobuff() - Redobuff corrupt");
	return;
    }
    if (Redobuffptr == NULL) {
	if ((strlen(s) + 1) < REDO_UNDO_SIZE) {
	    strcpy(Redobuff, s);
	    Redobuffptr = Redobuff;
	    return;
	}
    } else if ((strlen(Redobuff) + strlen(s) + 1) < REDO_UNDO_SIZE) {
	strcat(Redobuff, s);
	return;
    }
    emsg("Couldn't AppendToRedobuff() - clearing Redobuff");
    *Redobuff = NUL;
    Redobuffptr = (char *) (-1);
}

void
AppendNumberToRedobuff(n)
    int             n;
{
    char            buf[32];

    if (UndoInProgress)
	return;

    sprintf(buf, "%d", n);
    AppendToRedobuff(buf);
}

void
AppendToUndobuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (Undobuffptr == (char *) (-2)) {
	return;
    }
    if (Undobuffptr == (char *) (-1)) {
	Undobuffptr = (char *) (-2);
	emsg("Couldn't AppendToUndobuff() - Undobuff corrupt");
	return;
    }
    if (Undobuffptr == NULL) {
	if ((strlen(s) + 1) < REDO_UNDO_SIZE) {
	    strcpy(Undobuff, s);
	    Undobuffptr = Undobuff;
	    return;
	}
    } else if ((strlen(Undobuff) + strlen(s) + 1) < REDO_UNDO_SIZE) {
	strcat(Undobuff, s);
	return;
    }
    emsg("Couldn't AppendToUndobuff() - clearing Undobuff");
    *Undobuff = NUL;
    Undobuffptr = (char *) (-1);
}

void
AppendNumberToUndobuff(n)
    int             n;
{
    char            buf[32];

    if (UndoInProgress)
	return;

    sprintf(buf, "%d", n);
    AppendToUndobuff(buf);
}

static          bool_t
dojoin(leading_space, strip_leading_spaces)
    bool_t          leading_space;
    bool_t          strip_leading_spaces;
{
}
