/* input.c */
/* Copyright 1995 by Steve Kirkendall */

char id_input[] = "$Id: input.c,v 2.60 1998/10/12 18:57:22 steve Exp $";

#include "elvis.h"

/* These data types are used to store the parsing state for input mode
 * commands.  This is very simple, since most commands are only one keystroke
 * long. (The only exceptions are that INP_QUOTE is two keystrokes long, and
 * INP_HEX1/INP_HEX2 is three keystrokes long.)
 */
typedef enum {
	INP_NORMAL,	/* a normal character to insert/replace */
	INP_NEWLINE,	/* insert a newline */
	INP_QUOTE,	/* we're in the middle of a ^V sequence */
	INP_HEX1,	/* we're expecting the first of two hex digits */
	INP_HEX2,	/* we're expecting the second of two hex digits */
	INP_DIG1,	/* we're expecting the first of two digraph chars */
	INP_DIG2,	/* we're expecting the second of two digraph chars */
	INP_TAB,	/* ^I - insert a bunch of spaces to look like a tab */
	INP_ONEVI,	/* ^O - execute one vi command */
	INP_MULTIVI,	/* ESC - execute many vi commands */
	INP_BACKSPACE,	/* ^H - backspace one character */
	INP_BACKWORD,	/* ^W - backspace one word */
	INP_BACKLINE,	/* ^U - backspace one line */
	INP_SHIFTL,	/* ^D - reduce indentation */
	INP_SHIFTR,	/* ^T - increase indentation */
	INP_EXPOSE,	/* ^R/^L - redraw the screen */
	INP_PREVIOUS,	/* ^@ - insert a copy of previous text, then exit */
	INP_AGAIN,	/* ^A - insert a copy of previous text, continue */
	INP_PUT		/* ^P - insert a copy of anonymous cut buffer */
} INPCMD;
typedef struct
{
	BOOLEAN	setbottom;	/* Set bottom = cursor before drawing cursor? */
	BOOLEAN	replacing;	/* True if we're in "replace" mode */
	INPCMD	cmd;		/* the command to perform */
	CHAR	arg;		/* argument -- usually a key to insert */
	CHAR	backsp;		/* char backspaced over, or '\0' */
	CHAR	prev;		/* previously input char, or '\0' */
} INPUTINFO;


#if USE_PROTOTYPES
static void cleanup(WINDOW win, BOOLEAN del, BOOLEAN backsp, BOOLEAN yank);
static void addchar(MARK cursor, MARK top, MARK bottom, INPUTINFO *info);
static BOOLEAN tryabbr(WINDOW win, _CHAR_ nextc);
static RESULT perform(WINDOW win);
static RESULT parse(_CHAR_ key, void *info);
static ELVCURSOR shape(WINDOW win);
#endif


/* This function deletes everything between "cursor" and "bottom" of the
 * current state.  This is used, for example, when the user hits <Esc>
 * after using "cw" to change a long word into a short one.  It should be
 * called for the INP_ONEVI command with backsp=False, and before INP_MULTIVI
 * backup=True.
 */
static void cleanup(win, del, backsp, yank)
	WINDOW	win;	/* window where input took place */
	BOOLEAN	del;	/* if True, delete text after the cursor */
	BOOLEAN	backsp;	/* if True, move to the left */
	BOOLEAN	yank;	/* if True, yank input text into ". buffer */
{
	/* delete the excess in the edited region */
	if (del && markoffset(win->state->cursor) < markoffset(win->state->bottom)
	        && markoffset(win->state->top) < markoffset(win->state->bottom))
	{
		bufreplace(win->state->cursor, win->state->bottom, NULL, 0);
	}
	else
	{
		marksetoffset(win->state->bottom, markoffset(win->state->cursor));
		if (markoffset(win->state->top) > markoffset(win->state->cursor))
			marksetoffset(win->state->top, markoffset(win->state->cursor));
	}
	assert(markoffset(win->state->cursor) == markoffset(win->state->bottom));

	/* save the newly input text in the "previous input" buffer */
	if (yank)
	{
		cutyank('.', win->state->top, win->state->bottom, 'c', False);
	}

	/* move the cursor back one character, unless it is already at the
	 * start of a line.
	 */
	if (backsp && markoffset(win->state->cursor) > 0)
	{
		markaddoffset(win->state->cursor, -1);
		if (scanchar(win->state->cursor) == '\n')
		{
			markaddoffset(win->state->cursor, 1);
		}
		marksetoffset(win->state->top, markoffset(win->state->cursor));
		marksetoffset(win->state->bottom, markoffset(win->state->cursor));
	}

	/* Force the screen to be regenerated */
	if (win->di->logic == DRAW_NORMAL)
		win->di->logic = DRAW_CHANGED;
}

/* This function inserts/replaces a single character in a buffer, and
 * advances the cursor and (if necessary) bottom mark.
 */
static void addchar(cursor, top, bottom, info)
	MARK		cursor;	/* where to add a character */
	MARK		top;	/* start of edit bounds */
	MARK		bottom;	/* end of edit bounds */
	INPUTINFO	*info;	/* other info, including char to be inserted */
{
	MARKBUF	replaced;

	/* decide whether to insert or replace */
	replaced = *cursor;
	if (markoffset(cursor) < markoffset(bottom))
	{
		replaced.offset++;
	}
	else if (info->replacing && markoffset(cursor) < o_bufchars(markbuffer(cursor)))
	{
		if (scanchar(cursor) != '\n')
		{
			replaced.offset++;
		}
	}

	/* do it */
	bufreplace(cursor, &replaced, &info->arg, 1);

	/* we need to advance the cursor, and maybe bottom */
	markaddoffset(cursor, 1);
	if (markoffset(cursor) > markoffset(bottom))
	{
		marksetoffset(bottom, markoffset(cursor));
	}
}


/* This function attempts to expand an abbreviation at the cursor location,
 * if there is one.  If so, it deletes the short form, and pushes the long
 * form an following character back onto the type-ahead buffer.  Else it
 * returns False.
 */
static BOOLEAN tryabbr(win, nextc)
	WINDOW	win;	/* window where abbreviation may need expanding */
	_CHAR_	nextc;	/* character after the word */
{
	MARKBUF	from, to;
	CHAR	*cp, *build;
	long	slen, llen, tlen;
	CHAR	cbuf[1];


#if 0 /* this would prevent abbrs from being expanded on command lines */
	if((win->state->flags & (ELVIS_POP|ELVIS_ONCE|ELVIS_1LINE)) ==
		 (ELVIS_POP|ELVIS_ONCE|ELVIS_1LINE)) { /* nishi */
	    return False;
	}
#endif

	/* Try to do an abbreviation.  To do this, we collect
	 * characters backward to the preceding whitespace.  We
	 * go to the preceding whitespace because abbreviations
	 * can't contain whitespace; we know we'll never need
	 * more characters to recognize an abbreviation.  We
	 * collect the characters backwards just because it is
	 * easier.
	 */
	for (scanalloc(&cp, win->state->cursor), build = NULL;
	     cp && scanprev(&cp) && !isspace(*cp);
	     )
	{
		buildCHAR(&build, *cp);
	}
	scanfree(&cp);
	if (build)
	{
		cp = mapabbr(build, &slen, &llen, (BOOLEAN)(win->state->acton != NULL));
		if (cp)
		{
			/* yes, we have an abbreviation! */

			/* count the characters in the long form which appear
			 * before the first control character.
			 */
			for (tlen = 0; tlen < llen && cp[tlen] >= ' '; tlen++)
			{
			}

			/* replace the short form with the plain text part of
			 * the long form.
			 */
			cleanup(win, True, False, False);
			(void)marktmp(from, markbuffer(win->state->cursor), markoffset(win->state->cursor) - slen);
			(void)marktmp(to, markbuffer(win->state->cursor), markoffset(win->state->cursor));
			bufreplace(&from, &to, cp, tlen);
			cp += tlen;
			llen -= tlen;
			markaddoffset(&from, tlen);

			/* handle the final character (space or tab typed by
			 * the user) and any remaining characters from the
			 * long form of the abbreviation.
			 */
			if (nextc)
			{
				cbuf[0] = nextc;
				if (llen > 0)
				{
					/* stuff the long form, and the user's
					 * non-alnum character, into the queue
					 */
					mapunget(cbuf, 1, False);
					mapunget(cp, (int)llen, False);
				}
				else
				{
					/* insert the final character into the
					 * text
					 */
					bufreplace(&from, &from, cbuf, 1L);
					markaddoffset(&from, 1L);
				}
			}

			/* move cursor to where word goes */
			marksetoffset(win->state->bottom, markoffset(&from));
			marksetoffset(win->state->cursor, markoffset(&from));
			safefree(build);
			return True;
		}
		safefree(build);
	}
	return False;
}


/* This function performs an input-mode command.  Usually, this will be a
 * character to insert/replace in the text.
 */
static RESULT perform(win)
	WINDOW	win;	/* window where inputting should be done */
{
	STATE	  *state = win->state;
	INPUTINFO *ii = (INPUTINFO *)state->info;
	MARK	  cursor = state->cursor;
	MARK	  tmp;
	MARKBUF	  from, to;
	CHAR	  *cp;
	EXINFO	  xinfb;
	VIINFO	  vinfb;
	long	  col, len;
#ifdef FEATURE_COMPLETE
	CHAR	  *end;
	union
	{
		char	  partial[256];
		CHAR	  full[256];
	}	  name;
	char	  *littlecp, ch, peek;
#endif

	safeinspect();

	/* initially assume there is no matching parenthesis */
	win->match = -4;

	/* if cursor has been moved outside the top & bottom, then reset
	 * the top & bottom to match cursor
	 */
	if (markbuffer(state->top) != markbuffer(state->cursor))
	{
		marksetbuffer(state->top, markbuffer(state->cursor));
		marksetoffset(state->top, markoffset(state->cursor));
		marksetbuffer(state->bottom, markbuffer(state->cursor));
		marksetoffset(state->bottom, markoffset(state->cursor));
	}
	else if (markoffset(state->top) > markoffset(state->cursor)
	    || markoffset(state->cursor) > markoffset(state->bottom))
	{
		marksetoffset(state->top, markoffset(state->cursor));
		marksetoffset(state->bottom, markoffset(state->cursor));
	}

	/* process the keystroke */
	switch (ii->cmd)
	{
	  case INP_NORMAL:
		/* maybe try to do a digraph */
		if (ii->backsp && o_digraph)
		{
			ii->arg = digraph(ii->backsp, ii->arg);
		}

		/* If next character is non-alphanumeric, check for abbrev.
		 * (Note: Since we never expand abbreviations except in the
		 * main buffer or the ex history buffer, we can skip it if
		 * we're editing some other buffer such as regexp history.
		 * Assume only the ex history buffer has inputtab=ex.)
		 */
		if (!isalnum(ii->arg)
		 && ii->arg != '_'
		 && (state->acton == NULL || o_inputtab(markbuffer(cursor)) == 'e'))
		{
			if (tryabbr(win, ii->arg))
				break;
		}
		/* fall through... */

	  case INP_HEX1:	/* can't happen */
	  case INP_HEX2:
	  case INP_DIG1:	/* can't happen */
	  case INP_DIG2:
	  case INP_QUOTE:
		/* add the character */
		addchar(cursor, state->top, state->bottom, ii);

		/* if it wasn't whitespace, then maybe do wordwrap */
		if (!isspace(ii->arg)
		 && cursor == win->cursor
		 && win->md->wordwrap
		 && o_textwidth(markbuffer(cursor)) > 0
		 && dispmark2col(win) >= o_textwidth(markbuffer(cursor)))
		{
			/* Figure out where the current word started */
			for (scanalloc(&cp, cursor);
			     scanprev(&cp) && !isspace(*cp);
			     )
			{
			}
			if (cp)
			{
				to = *scanmark(&cp);
				markaddoffset(&to, 1L);
			}

			/* Locate the front of this line.  We won't look past
			 * back before this character.
			 */
			tmp = dispmove(win, 0L, 0L);

			/* scan backward over any whitespace */
			for (len = markoffset(&to) - markoffset(tmp);
			     len > 0 && scanprev(&cp) && isspace(*cp);
			     len--)
			{
			}
			if (cp)
			{
					from = *scanmark(&cp);
					markaddoffset(&from, 1L);
			}
			assert(cp || len <= 0);
			scanfree(&cp);

			/* We can only do the word wrap stuff if the current
			 * word isn't the line's first word.
			 */
			if (len > 0)
			{
				/* replace the whitespace with a newline */
				bufreplace(&from, &to, toCHAR("\n"), 1L);
				marksetoffset(&to, markoffset(&from) + 1);

				/* handle autoindent */
				dispindent(win, &to, -1L);
			}
		}

		/* remember digraph hints */
		ii->backsp = '\0';
		ii->prev = ii->arg;
		
		/* If the character was a parenthesis, and the showmatch
		 * option is set, then try to locate its match.
		 */
		if (o_showmatch(win) && CHARchr(toCHAR(")}]"), ii->arg))
		{
			from = *cursor;
			assert(markoffset(cursor) > 0);
			markaddoffset(cursor, -1);
			memset((char *)&vinfb, 0, sizeof vinfb);
			vinfb.command = '%';
			if (m_absolute(win, &vinfb) == RESULT_COMPLETE)
			{
				win->match = markoffset(cursor);
			}
			assert(markbuffer(cursor) == markbuffer(&from));
			*cursor = from;
		}
		break;

	  case INP_NEWLINE:
		cleanup(win, True, False, False);
		if (!tryabbr(win, '\n'))
		{
			ii->arg = '\n';
			ii->cmd = INP_NORMAL;
			perform(win);
			dispindent(win, cursor, -1);
		}
		ii->backsp = ii->prev = '\0';
		break;

	  case INP_TAB:
		if (!tryabbr(win, '\n'))
		{
			switch (o_inputtab(markbuffer(cursor)))
			{
#ifndef FEATURE_COMPLETE
			  case 'e':
			  case 'f':
			  case 'i':
#endif /* not FEATURE_COMPLETE */
			  case 't':
				/* insert a tab */
				addchar(cursor, state->top, state->bottom, ii);
				break;

			  case 's':
				/* insert enough spaces to look like a tab */
				col = dispmark2col(win);
				ii->arg = ' ';
				do
				{
					addchar(cursor, state->top, state->bottom, ii);
				} while ((++col) % o_tabstop(markbuffer(cursor)) != 0);
				break;
#ifdef FEATURE_COMPLETE
			  case 'i':
				/* try to find a matching tag name */
				cp = tagcomplete(win, cursor);

				/* If no matching tag, or already at end of
				 * complete tag, then insert a literal tab
				 * character, not a space.
				 */
				if (*cp == ' ')
					*cp = '\t';

				/* For other values, delete the trailing
				 * whitespace which would normally be appended.
				 */
				end = CHARchr(cp, ' ');
				if (end)
					*end = '\0';

				/* Now... if we have anything to add, add it */
				if (*cp)
				{
					len = CHARlen(cp);
					col = markoffset(cursor) + len;
					bufreplace(cursor, win->state->bottom, cp, len);
					marksetoffset(cursor, col);
					marksetoffset(win->state->bottom, col);
				}
				break;

			  case 'e':
				/* Assume this is an ex command line, and try
				 * to do something useful.
				 */
				tmp = markdup(cursor);
				(void)marksetline(tmp, markline(cursor));
				cp = excomplete(win, tmp, cursor);
				markfree(tmp);
				if (cp)
				{
					/* excomplete() found something useful,
					 * or returned "\t" to make the <Tab>
					 * be a literal tab character.  Copy
					 * it into the buffer.
					 */
					len = CHARlen(cp);
					col = markoffset(cursor) + len;
					bufreplace(cursor, win->state->bottom, cp, len);
					marksetoffset(cursor, col);
					marksetoffset(win->state->bottom, col);
					break;
				}
				/* else fall through to filename completion */

			  case 'f':
				/* filename completion */

				/* if at start of input, then fail */
				if (markoffset(cursor) == markoffset(state->top))
				{
					guibeep(win);
					break;
				}

				/* locate the previous character */
				tmp = markdup(cursor);
				markaddoffset(tmp, -1);

				/* if previous char can't be in name, fail */
				if (CHARchr(toCHAR(" \t\n()<>"), scanchar(tmp)))
				{
					markfree(tmp);
					guibeep(win);
					break;
				}

				/* collect the partial name into char array */
				littlecp = &name.partial[QTY(name.partial)];
				*--littlecp = '\0';
				ch = scanchar(tmp);
				do
				{
					*--littlecp = ch;
					markaddoffset(tmp, -1L);
					ch = (markoffset(tmp) >= markoffset(state->top)) ? scanchar(tmp) : '\n';
					if (markoffset(tmp) - 1 >= markoffset(state->top))
					{
						from = *tmp;
						markaddoffset(&from, -1L);
						peek = scanchar(&from);
					}
					else
						peek = 'x'; /* not backslash */
				} while (ch != '\n' && ch && ch != '\t' &&
				   (!(ch == ' ' || ch == '<' || ch == '>' || ch == '(' || ch == ')')
					|| peek == '\\'));
				markaddoffset(tmp, 1);

				/* try to expand the filename */
				littlecp = iofilename(littlecp,
					(ch == '(') ? ')' :
					o_inputtab(markbuffer(cursor)) == 'e' ? ' ' :
					'\t');
				if (!littlecp)
				{
					markfree(tmp);
					guibeep(win);
					break;
				}

				/* name found -- replace old word with expanded name */
				for (cp = name.full, col = 0;
				     (*cp++ = *littlecp++) != '\0'; /* yes, ASSIGNMENT! */
				     col++)
				{
				}
				bufreplace(tmp, win->state->bottom, name.full, col);
				marksetoffset(cursor, markoffset(tmp) + col);
				marksetoffset(win->state->bottom, markoffset(cursor));
				markfree(tmp);
#endif /* FEATURE_COMPLETE */
			}
		}
		ii->backsp = ii->prev = '\0';
		break;

	  case INP_ONEVI:
		cleanup(win, True, False, True);
		vipush(win, ELVIS_ONCE, NULL);
		ii->backsp = ii->prev = '\0';
		ii->setbottom = True;
		break;

	  case INP_MULTIVI:
		(void)tryabbr(win, '\0');
		cleanup(win, True, True, True);
		win->state->flags |= ELVIS_POP;
		ii->backsp = ii->prev = '\0';

		/* if blank line in autoindent mode, then delete whitespace */
		if (o_autoindent(markbuffer(cursor)))
		{
			for (from = *dispmove(win,0L,0L), scanalloc(&cp, &from);
			     cp && (*cp == ' ' || *cp == '\t');
			     scannext(&cp))
			{
			}
			if (cp && *cp == '\n')
			{
				to = *scanmark(&cp);
				scanfree(&cp);
				if (markoffset(&to) > markoffset(&from) &&
				    markoffset(&to) - 1 == markoffset(cursor))
				{
					bufreplace(&from, &to, NULL, 0L);
				}
			}
			else
			{
				scanfree(&cp);
			}
		}
		break;

	  case INP_BACKSPACE:
		ii->backsp = '\0';
		if (markoffset(win->state->top) < markoffset(cursor))
		{
			/* backspace within the newly typed text */
			markaddoffset(cursor, -1);
			ii->backsp = ii->prev;
		}
		else if (win->state->acton != NULL
			&& (win->state->flags & ELVIS_1LINE) != 0)
		{
			/* backspace out of an ex command line or regexp line */
			cleanup(win, True, True, True);
			win->state->flags |= ELVIS_POP;
			win->state->pop->flags &= ~(ELVIS_MORE | ELVIS_ONCE);
			if (win->state->pop->perform == _viperform)
			{
				viinitcmd((VIINFO *)win->state->pop->info);
			}
			ii->backsp = '\0';
		}
		else
		{
			/* bump into left edge of new text */
			guibeep(win);
		}
		ii->prev = '\0';
		break;

	  case INP_BACKWORD:
		if (markoffset(win->state->top) < markoffset(cursor))
		{
			/* expect to back up at least one character */
			markaddoffset(cursor, -1);
			scanalloc(&cp, cursor);

			/* if on whitespace, then back up to non-whitespace */
			while (markoffset(win->state->top) < markoffset(win->state->cursor)
			    && isspace(*cp))
			{
				markaddoffset(cursor, -1);
				scanprev(&cp);
			}

			/* back up to whitespace */
			while (markoffset(win->state->top) < markoffset(win->state->cursor)
			    && !isspace(*cp))
			{
				markaddoffset(cursor, -1);
				scanprev(&cp);
			}

			/* if we hit whitespace, then leave cursor after it */
			if (isspace(*cp))
			{
				markaddoffset(cursor, 1);
			}
			scanfree(&cp);
		}
		else
		{
			guibeep(win);
		}
		ii->backsp = ii->prev = '\0';
		break;

	  case INP_BACKLINE:
		/* find the start of this line, or if the cursor is already
		 * there, then the start of the preceding line.
		 */
		tmp = (*win->md->move)(win, cursor, 0, 0, False);
		if (markoffset(tmp) == markoffset(cursor))
		{
			tmp = (*win->md->move)(win, cursor, -1, 0, False);
		}

		/* move to either the start of the line or the top of the
		 * edited region, whichever is closer.
		 */
		if (markoffset(tmp) > markoffset(win->state->top))
		{
			marksetoffset(cursor, markoffset(tmp));
		}
		else if (markoffset(state->top) < markoffset(cursor))
		{
			marksetoffset(cursor, markoffset(state->top));
		}
		else
		{
			guibeep(win);
		}
		ii->backsp = ii->prev = '\0';
		break;

	  case INP_SHIFTL:
	  case INP_SHIFTR:
		/* build a :< or :> ex command */
		memset((char *)&xinfb, 0, sizeof xinfb);
		xinfb.defaddr = *cursor;
		xinfb.from = xinfb.to = markline(cursor);
		xinfb.fromaddr = marktmp(from, markbuffer(cursor), lowline(bufbufinfo(markbuffer(cursor)), xinfb.from));
		xinfb.toaddr = marktmp(to, markbuffer(cursor), lowline(bufbufinfo(markbuffer(cursor)), xinfb.to + 1));;
		xinfb.command = (ii->cmd == INP_SHIFTL) ? EX_SHIFTL : EX_SHIFTR;
		xinfb.multi = 1;
		xinfb.bang = True;

		/* execute the command */
		len = o_bufchars(markbuffer(cursor)) - markoffset(cursor);
		assert(len >= 0);
		(void)ex_shift(&xinfb);
		ii->backsp = ii->prev = '\0';
		assert(o_bufchars(markbuffer(cursor)) >= len);
		marksetoffset(cursor, o_bufchars(markbuffer(cursor)) - len);
		break;

	  case INP_EXPOSE:
		drawexpose(win, 0, 0, (int)(o_lines(win) - 1), (int)(o_columns(win) - 1));
		break;

	  case INP_PREVIOUS:
	  case INP_AGAIN:
	  case INP_PUT:
		cleanup(win, True, False, False);

		/* Copy the text.  Be careful not to change the "top" mark. */
		from = *state->top;
		tmp = cutput((ii->cmd == INP_PUT ? '1' : '.'),
					win, cursor, False, True, True);
		marksetoffset(state->top, markoffset(&from));

		/* if successful, tweak the "cursor" and "bottom" marks. */
		if (tmp)
		{
			marksetoffset(cursor, markoffset(tmp) + 1);
			marksetoffset(state->bottom, markoffset(cursor));
			if (ii->cmd == INP_PREVIOUS)
			{
				cleanup(win, True, True, True);
				state->flags |= ELVIS_POP;
			}
		}
		ii->backsp = ii->prev = '\0';
		break;

	}

	/* set wantcol to the cursor's current column */
	win->wantcol = dispmark2col(win);

	/* prepare for next command */
	ii->cmd = INP_NORMAL;

	return RESULT_COMPLETE;
}

/* This function parses a command.  This involves remembering whether we're
 * in the middle of a ^V quoted character, and also recognizing some special
 * characters.
 */
static RESULT parse(key, info)
	_CHAR_	key;	/* next keystroke */
	void	*info;	/* current parsing state */
{
	INPUTINFO *ii = (INPUTINFO *)info;
	CHAR	sym;	/* visible symbol, e.g. '^' after a ^V */

	/* maybe adjust the edit bounds */
	if (ii->setbottom)
	{
		marksetoffset(windefault->state->bottom,
			markoffset(windefault->state->cursor));
		ii->setbottom = False;
	}

	/* parse the input command */
	if (ii->cmd == INP_HEX1 || ii->cmd == INP_HEX2)
	{
		/* convert hex digit from ASCII to binary */
		if (key >= '0' && key <= '9')
		{
			key -= '0';
		}
		else if (key >= 'a' && key <= 'f')
		{
			key -= 'a' - 10;
		}
		else if (key >= 'A' && key <= 'F')
		{
			key -= 'A' - 10;
		}
		else
		{
			/* this command is invalid; prepare for next command */
			ii->cmd = INP_NORMAL;
			return RESULT_ERROR;
		}

		/* merge into arg */
		if (ii->cmd == INP_HEX1)
		{
			ii->arg = (key << 4);
			ii->cmd = INP_HEX2;
			windefault->state->mapflags |= MAP_DISABLE;
			return RESULT_MORE;
		}
		else
		{
			ii->arg |= key;
			return RESULT_COMPLETE;
		}
	}
	else if (ii->cmd == INP_DIG1 || ii->cmd == INP_DIG2)
	{
		if (ii->cmd == INP_DIG1)
		{
			ii->arg = key;
			ii->cmd = INP_DIG2;
			windefault->state->mapflags |= MAP_DISABLE;
			return RESULT_MORE;
		}
		else
		{
			ii->arg = digraph(ii->arg, key);
			return RESULT_COMPLETE;
		}
	}
	else if (ii->cmd == INP_QUOTE)
	{
		ii->arg = key;
	}
	else
	{
		ii->arg = key;
		sym = '\0';
		switch (key)
		{
		  case ELVCTRL('@'):	ii->cmd = INP_PREVIOUS;		break;
		  case ELVCTRL('A'):	ii->cmd = INP_AGAIN;		break;
		  case ELVCTRL('D'):	ii->cmd = INP_SHIFTL;		break;
		  case '\177': /* usually mapped to "visual x", else... */
		  case ELVCTRL('H'):	ii->cmd = INP_BACKSPACE;	break;
		  case ELVCTRL('I'):	ii->cmd = INP_TAB;		break;
		  case ELVCTRL('J'):	ii->cmd = INP_NEWLINE;		break;
		  case ELVCTRL('K'):	ii->cmd = INP_DIG1, sym = '+';	break;
		  case ELVCTRL('M'):	ii->cmd = INP_NEWLINE;		break;
		  case ELVCTRL('O'):	ii->cmd = INP_ONEVI;		break;
		  case ELVCTRL('P'):	ii->cmd = INP_PUT;		break;
		  case ELVCTRL('R'):	ii->cmd = INP_EXPOSE;		break;
		  case ELVCTRL('T'):	ii->cmd = INP_SHIFTR;		break;
		  case ELVCTRL('U'):	ii->cmd = INP_BACKLINE;		break;
		  case ELVCTRL('V'):	ii->cmd = INP_QUOTE, sym = '^';	break;
		  case ELVCTRL('W'):	ii->cmd = INP_BACKWORD;		break;
		  case ELVCTRL('X'):	ii->cmd = INP_HEX1, sym = '$';	break;
		  case ELVCTRL('['):	ii->cmd = INP_MULTIVI;		break;
		  default:		ii->cmd = INP_NORMAL;
		}

		/* ^V, ^X, and ^K require more keystrokes... */
		if (sym)
		{
			/* display a symbol on the screen */
			ii->arg = sym;
			addchar(windefault->state->cursor,
				windefault->state->top,
				windefault->state->bottom, ii);
			markaddoffset(windefault->state->cursor, -1);

			/* disable maps during extra character[s] */
			windefault->state->mapflags |= MAP_DISABLE;
			return RESULT_MORE;
		}
	}

	/* the command is complete */
	return RESULT_COMPLETE;
}

/* This function decides on a cursor shape */
static ELVCURSOR shape(win)
	WINDOW	win;	/* window whose shape should be returned */
{
	STATE	*state = win->state;
	INPUTINFO *info = (INPUTINFO *)state->info;
	MARK	cursor = state->cursor;

	/* if in the middle of ^V, then always CURSOR_QUOTE */
	if (info->cmd == INP_QUOTE)
	{
		state->mapflags &= ~(MAP_INPUT|MAP_COMMAND);
		return CURSOR_QUOTE;
	}
	state->mapflags |= MAP_INPUT;

	/* decide whether to insert or replace */
	if (markoffset(state->top) <= markoffset(cursor)
	 && markoffset(cursor) < markoffset(state->bottom))
	{
		if (info->setbottom)
		{
			marksetoffset(state->bottom, markoffset(cursor));
			info->setbottom = False;
			return CURSOR_INSERT;
		}
		return CURSOR_REPLACE;
	}
	else if (info->replacing && markoffset(cursor) < o_bufchars(markbuffer(cursor)))
	{
		if (scanchar(cursor) != '\n')
		{
			info->setbottom = False;
			return CURSOR_REPLACE;
		}
	}
	info->setbottom = False;
	return CURSOR_INSERT;
}


/* This function pushes a state onto the state stack, and then initializes it
 * to be either an input or replace state, with the cursor at a given location.
 * The "mode" argument can be 'R' for replace, or anything else for input.
 * The input cursor is "cursor", which should generally the result of a
 * markalloc() or markdup() function call.  Ditto for the top and bottom of
 * the edit region.
 */
void inputpush(win, flags, mode)
	WINDOW		win;	/* window that should be switched to input mode */
	ELVISSTATE	flags;	/* flags describing this state */
	_char_		mode;	/* 'R' for replace, or anything else for insert */
{
	/* push the state */
	flags |= ELVIS_REGION;
	statepush(win, flags);

	/* initialize the state */
	win->state->parse = parse;
	win->state->perform = perform;
	win->state->shape = shape;
	win->state->info = safealloc(1, sizeof (INPUTINFO));
	win->state->mapflags |= MAP_INPUT;
	if (mode == 'R')
	{
		((INPUTINFO *)win->state->info)->replacing = True;
		win->state->modename = "Replace";
	}
	else
	{
		((INPUTINFO *)win->state->info)->replacing = False;
		win->state->modename = " Input ";
	}
}

/* This function tweaks the most recent "input" or "replace" state.
 * The "mode" can be 't' to toggle "input" to "replace" or vice verse,
 * 'R' to force the mode to be "replace", or anything else to force the
 * mode to be "input".  This function is called by vi mode's perform()
 * function.
 */
void inputtoggle(win, mode)
	WINDOW	win;	/* window to be toggled */
	_char_	mode;	/* 'R' for replace, 't' to toggle, else insert */
{
	STATE	*state;

	/* find the most recent "input" or "replace" state */
	for (state = win->state; state && state->perform != perform; state = state->pop)
	{
	}
	assert(state != NULL);

	/* change the mode */
	switch (mode)
	{
	  case 't':
		((INPUTINFO *)state->info)->replacing = (BOOLEAN)!((INPUTINFO *)state->info)->replacing;
		break;

	  case 'R':
		((INPUTINFO *)state->info)->replacing = True;
		break;

	  default:
		((INPUTINFO *)state->info)->replacing = False;
	}

	if (((INPUTINFO *)state->info)->replacing)
	{
		state->modename = "Replace";
	}
	else
	{
		state->modename = " Input";
	}
}

/* This function sets the edit boundaries of an "input" state.  If there
 * is no input state on the state stack, then this function will push one.
 * This function is used to implement the <c> operator, among other things.
 */
void inputchange(win, from, to, linemd)
	WINDOW	win;	/* window to be affected */
	MARK	from;	/* new start of edit bounds */
	MARK	to;	/* new end of edit bounds */
	BOOLEAN	linemd;	/* replace old text with a new line? */
{
	MARKBUF	tmp;
	CHAR	ch;
	BOOLEAN	ctrl_o = False;	/* JohnW 19/06/96 */

	assert(markbuffer(from) == markbuffer(win->state->cursor));
	assert(markbuffer(from) == markbuffer(to));
	assert(markoffset(from) <= markoffset(to));

	/* Was this command issued via <Control-O> from input mode?
	 * If not, then we'll need to push one.
	 */
	if (!win->state->pop)
	{
		inputpush(win, 0, 'i');
	}
	else
	{
		ctrl_o = True;
	}

	/* replace the last char with '$', if there is a last char
	 * and it is on the same line.  If it is on a different line,
	 * then delete the old text.  If from==to, then do nothing.
	 */
	if (markoffset(from) == markoffset(to))
	{
		/* do nothing */
	}
	else if (ctrl_o || /* JohnW 19/06/96 */
		markoffset(to) > markoffset(dispmove(win, 0, INFINITY)))
	{
		/* delete the old text */
		if (linemd)
		{
			if (o_autoindent(markbuffer(from)))
			{
				for (ch = scanchar(from);
				     markoffset(from) < markoffset(to) && (ch == ' ' || ch == '\t');
				     markaddoffset(from, 1), ch = scanchar(from))
				{
				}
			}
			bufreplace(from, to, toCHAR("\n"), 1);
		}
		else
		{
			bufreplace(from, to, NULL, 0);
		}
		marksetoffset(to, markoffset(from));
	}
	else
	{
		/* replace the last character with a '$' */
		tmp = *to;
		tmp.offset--;
		bufreplace(&tmp, to, toCHAR("$"), 1);
	}

	/* set the edit boundaries and the cursor */
	marksetbuffer(win->state->top, markbuffer(from));
	marksetbuffer(win->state->bottom, markbuffer(to));
	marksetoffset(win->state->top, markoffset(from));
	marksetoffset(win->state->bottom, markoffset(to));
	marksetoffset(win->state->cursor, markoffset(from));
}


/* This function is called by statekey() when the user hits <Enter>, before
 * calling the stratum's enter() function.  This function deletes extra
 * characters after the cursor, and adjusts the endpoints of the edited
 * region to make them be whole lines.
 */
void inputbeforeenter(win)
	WINDOW	win;	/* window where <Enter> was just pressed */
{
	/* Make sure "win->state->bottom" includes the cursor position */
	if (markoffset(win->state->bottom) < markoffset(win->state->cursor))
	{
		marksetoffset(win->state->bottom, markoffset(win->state->cursor));
	}

	/* Delete stuff from after the cursor */
	cleanup(win, True, False, False);

	/* Attempt to expand an abbreviation at the end of the line */
	(void)tryabbr(win, '\0');

	/* adjust the endpoints of the edited area to be whole lines */
	marksetoffset(win->state->top,
		markoffset((*dmnormal.move)(win, win->state->top, 0, 0, False)));
	marksetoffset(win->state->bottom,
		markoffset((*dmnormal.move)(win, win->state->bottom, 0, INFINITY, False)));
}
