/*
 * 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!watcgl!grwalter 
 */

#include "stevie.h"

/*
 * This flag is used to make auto-indent work right on lines where only a
 * <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
 * reset when any other editting is done on the line. If an <ESC> or <RETURN>
 * is received, and did_ai is TRUE, the line is truncated. 
 */
bool_t          did_ai = FALSE;

void
edit()
{
    char            c;
    bool_t          literal_next_flag = FALSE;

    Prenum = 0;

    /* position the display and the cursor at the top of the file. */
    *Topchar = *Filemem;
    *Curschar = *Filemem;
    Cursrow = Curscol = 0;

    for (;;) {

	if (!RedrawingDisabled) {
	    cursupdate();	/* Figure out where the cursor is based on
				 * Curschar. */
	    if (MustRedrawLine)
		redrawline();
	    if (MustRedrawScreen)
		updateRealscreen();

	    windgoto(Cursrow, Curscol);
	}
	c = vgetc();

	if (State == NORMAL) {
	    /* We're in the normal (non-insert) mode. */

	    /* Pick up any leading digits and compute 'Prenum' */
	    if ((Prenum > 0 && isdigit(c)) || (isdigit(c) && c != '0')) {
		Prenum = Prenum * 10 + (c - '0');
		continue;
	    }
	    /* execute the command */
	    normal(c);
	    Prenum = 0;

	} else {
	    if (c == CTRL('V') && !literal_next_flag) {
		literal_next_flag = TRUE;
		outchar('^');
		continue;
	    }
	    if (literal_next_flag) {
		literal_next_flag = FALSE;
		outchar('\b');
		if (c != NL) {
		    did_ai = FALSE;
		    insertchar(c);
		    continue;
		}
	    }
	    switch (c) {	/* We're in insert mode */

	      case CR:
	      case NL:
		*Insbuffptr++ = NL;
		*Insbuffptr = NUL;
		if (!opencmd(FORWARD, TRUE))
		    goto doESCkey;	/* out of memory */

		if (!RedrawingDisabled)
		    windgoto(Cursrow, Curscol);
		break;

	      case ESC:	/* an escape ends input mode */
	doESCkey:
		set_want_col = TRUE;

		/* Don't end up on a '\n' if you can help it. */
		if (gchar(Curschar) == NUL && Curschar->index != 0)
		    dec(Curschar);

		/*
		 * The cursor should end up on the last inserted character.
		 * This is an attempt to match the real 'vi', but it may not
		 * be quite right yet. 
		 */
		if (Curschar->index != 0 && !endofline(Curschar))
		    dec(Curschar);

		State = NORMAL;
		msg("");

		if (!UndoInProgress) {
		    int             n;
		    char           *p;

		    if (last_command == 'o')
			AppendToUndobuff(UNDO_SHIFTJ_STR);

		    if (Insbuff != Insbuffptr) {
			if (last_command == 'O')
			    AppendToUndobuff("0");
			AppendToRedobuff(Insbuff);
			AppendToUndoUndobuff(Insbuff);
			n = 0;
			for (p = Insbuff; p < Insbuffptr; p++) {
			    if (*p == NL) {
				if (n) {
				    AppendNumberToUndobuff(n);
				    AppendToUndobuff("dl");
				    n = 0;
				}
				AppendToUndobuff(UNDO_SHIFTJ_STR);
			    } else
				n++;
			}
			if (n) {
			    AppendNumberToUndobuff(n);
			    AppendToUndobuff("dl");
			}
		    }
		    if (last_command == 'c') {
			AppendToUndobuff(mkstr(last_command_char));
			AppendToUndobuff(Yankbuff);
			AppendToUndobuff(ESC_STR);
		    }
		    AppendToRedobuff(ESC_STR);
		    AppendToUndoUndobuff(ESC_STR);
		    if (last_command == 'O')
			AppendToUndobuff(UNDO_SHIFTJ_STR);
		}
		break;

	      case CTRL('D'):
		/*
		 * Control-D is treated as a backspace in insert mode to make
		 * auto-indent easier. This isn't completely compatible with
		 * vi, but it's a lot easier than doing it exactly right, and
		 * the difference isn't very noticeable. 
		 */
	      case BS:
		/* can't backup past starting point */
		if (Curschar->linep == Insstart->linep &&
		    Curschar->index <= Insstart->index) {
		    beep();
		    break;
		}
		/* can't backup to a previous line */
		if (Curschar->linep != Insstart->linep &&
		    Curschar->index <= 0) {
		    beep();
		    break;
		}
		did_ai = FALSE;
		dec(Curschar);
		delchar(TRUE, FALSE);
		Insbuffptr--;
		*Insbuffptr = NUL;
		cursupdate();
		updateline();
		break;

	      default:
		did_ai = FALSE;
		insertchar(c);
		break;
	    }
	}
    }
}

/*
 * Special characters in this context are those that need processing other
 * than the simple insertion that can be performed here. This includes ESC
 * which terminates the insert, and CR/NL which need special processing to
 * open up a new line. This routine tries to optimize insertions performed by
 * the "redo" command, so it needs to know when it should stop and defer
 * processing to the "normal" mechanism. 
 */
#define	ISSPECIAL(c)	((c) == NL || (c) == CR || (c) == ESC)

void
insertchar(c)
    char            c;
{
    if (anyinput()) {		/* If there's any pending input, grab it all
				 * at once. */
	char           *p;

	p = Insbuffptr;
	*Insbuffptr++ = c;
	for (c = vpeekc(); !ISSPECIAL(c) && anyinput(); c = vpeekc()) {
	    c = vgetc();
	    *Insbuffptr++ = c;
	    /*
	     * The following kludge avoids overflowing the insert buffer. 
	     */
	    if (Insbuffptr + 10 >= &Insbuff[INSERT_SIZE]) {
		int             n;

		*Insbuffptr = NUL;
		insstr(p);

		Insbuffptr = Insbuff;
		p = Insbuffptr;

		emsg("Insert buffer overflow - buffers flushed");
		sleep(2);

		n = cntllines(Filemem, Curschar);
		AppendPositionToUndobuff(Curschar->index, n);
		AppendPositionToUndoUndobuff(Curschar->index, n);
		if (endofline(Curschar)) {
		    AppendToRedobuff("a");
		    AppendToUndoUndobuff("a");
		} else {
		    AppendToRedobuff("i");
		    AppendToUndoUndobuff("i");
		}
	    }
	}
	*Insbuffptr = NUL;
	insstr(p);
    } else {
	inschar(c);
	*Insbuffptr++ = c;

	/*
	 * The following kludge avoids overflowing the insert buffer. 
	 */
	if (Insbuffptr + 10 >= &Insbuff[INSERT_SIZE]) {
	    int             n;

	    Insbuffptr = Insbuff;

	    emsg("Insert buffer overflow - buffers flushed");
	    sleep(2);

	    n = cntllines(Filemem, Curschar);
	    AppendPositionToUndobuff(Curschar->index, n);
	    AppendPositionToUndoUndobuff(Curschar->index, n);
	    if (endofline(Curschar)) {
		AppendToRedobuff("a");
		AppendToUndoUndobuff("a");
	    } else {
		AppendToRedobuff("i");
		AppendToUndoUndobuff("i");
	    }
	}
	*Insbuffptr = NUL;
    }

    updateline();
}

void
getout(r)
    int             r;
{
    windgoto(Rows - 1, 0);
    putchar('\r');
    putchar('\n');
    windexit(r);
}

void
scrolldown(nlines)
    int             nlines;
{
    LPTR           *p;
    int             done = 0;	/* total # of physical lines done */

    /* Scroll up 'nlines' lines. */
    while (nlines--) {
	if ((p = prevline(Topchar)) == NULL)
	    break;
	done += plines(p);
	*Topchar = *p;
	if (Curschar->linep == Botchar->linep->prev)
	    *Curschar = *prevline(Curschar);
    }
    s_ins(0, done);
}

void
scrollup(nlines)
    int             nlines;
{
    LPTR           *p;
    int             done = 0;	/* total # of physical lines done */
    int             pl;		/* # of plines for the current line */

    /* Scroll down 'nlines' lines. */
    while (nlines--) {
	pl = plines(Topchar);
	if ((p = nextline(Topchar)) == NULL)
	    break;
	done += pl;
	if (Curschar->linep == Topchar->linep)
	    *Curschar = *p;
	*Topchar = *p;

    }
    s_del(0, done);
}

/*
 * oneright oneleft onedown oneup 
 *
 * Move one char {right,left,down,up}.  Return TRUE when sucessful, FALSE when
 * we hit a boundary (of a line, or the file). 
 */

bool_t
oneright()
{
    set_want_col = TRUE;

    switch (inc(Curschar)) {

      case 0:
	return TRUE;

      case 1:
	dec(Curschar);		/* crossed a line, so back up */
	/* FALLTHROUGH */
      case -1:
	return FALSE;
    }

    return FALSE;		/* PARANOIA: should never reach here */
}

bool_t
oneleft()
{
    set_want_col = TRUE;

    switch (dec(Curschar)) {

      case 0:
	return TRUE;

      case 1:
	inc(Curschar);		/* crossed a line, so back up */
	/* FALLTHROUGH */
      case -1:
	return FALSE;
    }

    return FALSE;		/* PARANOIA: should never reach here */
}

void
beginline(flag)
    bool_t          flag;
{
    while (oneleft());
    if (flag) {
	while (isspace(gchar(Curschar)) && oneright());
    }
    set_want_col = TRUE;
}

bool_t
oneup(n)
{
    LPTR            p, *np;
    int             k;

    p = *Curschar;
    for (k = 0; k < n; k++) {
	/* Look for the previous line */
	if ((np = prevline(&p)) == NULL) {
	    /* If we've at least backed up a little .. */
	    if (k > 0)
		break;		/* to update the cursor, etc. */
	    else
		return FALSE;
	}
	p = *np;
    }
    *Curschar = p;

    cursupdate();		/* make sure Topchar is valid */

    /* try to advance to the column we want to be at */
    *Curschar = *coladvance(&p, Curswant);
    return TRUE;
}

bool_t
onedown(n)
{
    LPTR            p, *np;
    int             k;

    p = *Curschar;
    for (k = 0; k < n; k++) {
	/* Look for the next line */
	if ((np = nextline(&p)) == NULL) {
	    if (k > 0)
		break;
	    else
		return FALSE;
	}
	p = *np;
    }

    cursupdate();		/* make sure Topchar is valid */

    /* try to advance to the column we want to be at */
    *Curschar = *coladvance(&p, Curswant);
    return TRUE;
}
