/*	$NetBSD: vi.c,v 1.21 2021/09/16 19:44:01 christos Exp $	*/

/*
 *	vi command editing
 *	written by John Rochester (initially for nsh)
 *	bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
 *
 */
#include <sys/cdefs.h>

#ifndef lint
__RCSID("$NetBSD: vi.c,v 1.21 2021/09/16 19:44:01 christos Exp $");
#endif

#include "config.h"
#ifdef VI

#include "sh.h"
#include <sys/stat.h>
#include <ctype.h>
#include "edit.h"

#define CMDLEN		1024
#define Ctrl(c)		(c&0x1f)
#define	is_wordch(c)	(letnum(c))

struct edstate {
	int	winleft;
	char	*cbuf;
	int	cbufsize;
	int	linelen;
	int	cursor;
};


static int	vi_hook	ARGS((int));
static void 	vi_reset ARGS((char *, size_t));
static int	nextstate ARGS((int));
static int	vi_insert ARGS((int));
static int	vi_cmd ARGS((int, const char *));
static int	domove ARGS((int, const char *, int));
static int	redo_insert ARGS((int));
static void	yank_range ARGS((int, int));
static int	bracktype ARGS((int));
static void	save_cbuf ARGS((void));
static void	restore_cbuf ARGS((void));
static void	edit_reset ARGS((char *, size_t));
static int	putbuf ARGS((const char *, int, int));
static void	del_range ARGS((int, int));
static int	findch ARGS((int, int, int, int));
static int	forwword ARGS((int));
static int	backword ARGS((int));
static int	endword ARGS((int));
static int	Forwword ARGS((int));
static int	Backword ARGS((int));
static int	Endword ARGS((int));
static int	grabhist ARGS((int, int));
static int	grabsearch ARGS((int, int, int, char *));
static void	redraw_line ARGS((int));
static void	refresh ARGS((int));
static int	outofwin ARGS((void));
static void	rewindow ARGS((void));
static int	newcol ARGS((int, int));
static void	display ARGS((char *, char *, int));
static void	ed_mov_opt ARGS((int, char *));
static int	expand_word ARGS((int));
static int	complete_word ARGS((int, int));
static int	print_expansions ARGS((struct edstate *, int));
static int 	char_len ARGS((int));
static void 	x_vi_zotc ARGS((int));
static void	vi_pprompt ARGS((int));
static void	vi_error ARGS((void));
static void	vi_macro_reset ARGS((void));
static int	x_vi_putbuf	ARGS((const char *, size_t));

#define C_	0x1		/* a valid command that isn't a M_, E_, U_ */
#define M_	0x2		/* movement command (h, l, etc.) */
#define E_	0x4		/* extended command (c, d, y) */
#define X_	0x8		/* long command (@, f, F, t, T, etc.) */
#define U_	0x10		/* an UN-undoable command (that isn't a M_) */
#define B_	0x20		/* bad command (^@) */
#define Z_	0x40		/* repeat count defaults to 0 (not 1) */
#define S_	0x80		/* search (/, ?) */

#define is_bad(c)	(classify[(c)&0x7f]&B_)
#define is_cmd(c)	(classify[(c)&0x7f]&(M_|E_|C_|U_))
#define is_move(c)	(classify[(c)&0x7f]&M_)
#define is_extend(c)	(classify[(c)&0x7f]&E_)
#define is_long(c)	(classify[(c)&0x7f]&X_)
#define is_undoable(c)	(!(classify[(c)&0x7f]&U_))
#define is_srch(c)	(classify[(c)&0x7f]&S_)
#define is_zerocount(c)	(classify[(c)&0x7f]&Z_)

const unsigned char	classify[128] = {
   /*       0       1       2       3       4       5       6       7        */
   /*   0   ^@     ^A      ^B      ^C      ^D      ^E      ^F      ^G        */
	    B_,     0,      0,      0,      0,      C_|U_,  C_|Z_,  0,
   /*  01   ^H     ^I      ^J      ^K      ^L      ^M      ^N      ^O        */
	    M_,     C_|Z_,  0,      0,      C_|U_,  0,      C_,     0,
   /*  02   ^P     ^Q      ^R      ^S      ^T      ^U      ^V      ^W        */
	    C_,     0,      C_|U_,  0,      0,      0,      C_,     0,
   /*  03   ^X     ^Y      ^Z      ^[      ^\      ^]      ^^      ^_        */
	    C_,     0,      0,      C_|Z_,  0,      0,      0,      0,
   /*  04  <space>  !       "       #       $       %       &       '        */
	    M_,     0,      0,      C_,     M_,     M_,     0,      0,
   /*  05   (       )       *       +       ,       -       .       /        */
	    0,      0,      C_,     C_,     M_,     C_,     0,      C_|S_,
   /*  06   0       1       2       3       4       5       6       7        */
	    M_,     0,      0,      0,      0,      0,      0,      0,
   /*  07   8       9       :       ;       <       =       >       ?        */
	    0,      0,      0,      M_,     0,      C_,     0,      C_|S_,
   /* 010   @       A       B       C       D       E       F       G        */
	    C_|X_,  C_,     M_,     C_,     C_,     M_,     M_|X_,  C_|U_|Z_,
   /* 011   H       I       J       K       L       M       N       O        */
	    0,      C_,     0,      0,      0,      0,      C_|U_,  0,
   /* 012   P       Q       R       S       T       U       V       W        */
	    C_,     0,      C_,     C_,     M_|X_,  C_,     0,      M_,
   /* 013   X       Y       Z       [       \       ]       ^       _        */
	    C_,     C_|U_,  0,      0,      C_|Z_,  0,      M_,     C_|Z_,
   /* 014   `       a       b       c       d       e       f       g        */
	    0,      C_,     M_,     E_,     E_,     M_,     M_|X_,  C_|Z_,
   /* 015   h       i       j       k       l       m       n       o        */
	    M_,     C_,     C_|U_,  C_|U_,  M_,     0,      C_|U_,  0,
   /* 016   p       q       r       s       t       u       v       w        */
	    C_,     0,      X_,     C_,     M_|X_,  C_|U_,  C_|U_|Z_,M_,
   /* 017   x       y       z       {       |       }       ~      ^?        */
	    C_,     E_|U_,  0,      0,      M_|Z_,  0,      C_,     0
};

#define MAXVICMD	3
#define SRCHLEN		40

#define INSERT		1
#define REPLACE		2

#define VNORMAL		0		/* command, insert or replace mode */
#define VARG1		1		/* digit prefix (first, eg, 5l) */
#define VEXTCMD		2		/* cmd + movement (eg, cl) */
#define VARG2		3		/* digit prefix (second, eg, 2c3l) */
#define VXCH		4		/* f, F, t, T, @ */
#define VFAIL		5		/* bad command */
#define VCMD		6		/* single char command (eg, X) */
#define VREDO		7		/* . */
#define VLIT		8		/* ^V */
#define VSEARCH		9		/* /, ? */
#define VVERSION	10		/* <ESC> ^V */

static char		undocbuf[CMDLEN];

static struct edstate 	*save_edstate ARGS((struct edstate *old));
static void		restore_edstate ARGS((struct edstate *old, struct edstate *new));
static void 		free_edstate ARGS((struct edstate *old));

static struct edstate	ebuf;
static struct edstate	undobuf = { 0, undocbuf, CMDLEN, 0, 0 };

static struct edstate	*es;			/* current editor state */
static struct edstate	*undo;

static char	ibuf[CMDLEN];		/* input buffer */
static int	first_insert;		/* set when starting in insert mode */
static int	saved_inslen;		/* saved inslen for first insert */
static int	inslen;			/* length of input buffer */
static int	srchlen;		/* length of current search pattern */
static char	ybuf[CMDLEN];		/* yank buffer */
static int	yanklen;		/* length of yank buffer */
static int	fsavecmd = ' ';		/* last find command */
static int	fsavech;		/* character to find */
static char	lastcmd[MAXVICMD];	/* last non-move command */
static int	lastac;			/* argcnt for lastcmd */
static int	lastsearch = ' ';	/* last search command */
static char	srchpat[SRCHLEN];	/* last search pattern */
static int	insert;			/* non-zero in insert mode */
static int	hnum;			/* position in history */
static int	ohnum;			/* history line copied (after mod) */
static int	hlast;			/* 1 past last position in history */
static int	modified;		/* buffer has been "modified" */
static int	state;

/* Information for keeping track of macros that are being expanded.
 * The format of buf is the alias contents followed by a null byte followed
 * by the name (letter) of the alias.  The end of the buffer is marked by
 * a double null.  The name of the alias is stored so recursive macros can
 * be detected.
 */
struct macro_state {
    unsigned char	*p;	/* current position in buf */
    unsigned char	*buf;	/* pointer to macro(s) being expanded */
    int			len;	/* how much data in buffer */
};
static struct macro_state macro;

enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
static enum expand_mode expanded = NONE;/* last input was expanded */

int
x_vi(buf, len)
	char	*buf;
	size_t	len;
{
	int	c;

	vi_reset(buf, len > CMDLEN ? CMDLEN : len);
	vi_pprompt(1);
	x_flush();
	while (1) {
		if (macro.p) {
			c = *macro.p++;
			/* end of current macro? */
			if (!c) {
				/* more macros left to finish? */
				if (*macro.p++)
					continue;
				/* must be the end of all the macros */
				vi_macro_reset();
				c = x_getc();
			}
		} else {
			c = x_getc();
		}
		if (c == -1)
			break;
		if (state != VLIT) {
			if (c == edchars.intr || c == edchars.quit) {
				/* pretend we got an interrupt */
				x_vi_zotc(c);
				x_flush();
				trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
				x_mode(false);
				unwind(LSHELL);
			} else if (c == edchars.eof && state != VVERSION) {
				if (es->linelen == 0) {
					x_vi_zotc(edchars.eof);
					c = -1;
					break;
				}
				continue;
			}
		}
		if (vi_hook(c))
			break;
		x_flush();
	}

	x_putc('\r'); x_putc('\n'); x_flush();

	if (c == -1 || len <= (size_t)es->linelen)
		return -1;

	if (es->cbuf != buf)
		memmove(buf, es->cbuf, es->linelen);

	buf[es->linelen++] = '\n';

	return es->linelen;
}

static int
vi_hook(ch)
	int		ch;
{
	static char	curcmd[MAXVICMD];
	static char	locpat[SRCHLEN];
	static int	cmdlen;
	static int	argc1, argc2;

	switch (state) {

	case VNORMAL:
		if (insert != 0) {
			if (ch == Ctrl('v')) {
				state = VLIT;
				ch = '^';
			}
			switch (vi_insert(ch)) {
			case -1:
				vi_error();
				state = VNORMAL;
				break;
			case 0:
				if (state == VLIT) {
					es->cursor--;
					refresh(0);
				} else
					refresh(insert != 0);
				break;
			case 1:
				return 1;
			}
		} else {
			if (ch == '\r' || ch == '\n')
				return 1;
			cmdlen = 0;
			argc1 = 0;
			if (ch >= '1' && ch <= '9') {
				argc1 = ch - '0';
				state = VARG1;
			} else {
				curcmd[cmdlen++] = ch;
				state = nextstate(ch);
				if (state == VSEARCH) {
					save_cbuf();
					es->cursor = 0;
					es->linelen = 0;
					if (ch == '/') {
						if (putbuf("/", 1, 0) != 0) {
							return -1;
						}
					} else if (putbuf("?", 1, 0) != 0)
							return -1;
					refresh(0);
				}
				if (state == VVERSION) {
					save_cbuf();
					es->cursor = 0;
					es->linelen = 0;
					putbuf(ksh_version + 4,
						strlen(ksh_version + 4), 0);
					refresh(0);
				}
			}
		}
		break;

	case VLIT:
		if (is_bad(ch)) {
			del_range(es->cursor, es->cursor + 1);
			vi_error();
		} else
			es->cbuf[es->cursor++] = ch;
		refresh(1);
		state = VNORMAL;
		break;

	case VVERSION:
		restore_cbuf();
		state = VNORMAL;
		refresh(0);
		break;

	case VARG1:
		if (isdigit(ch))
			argc1 = argc1 * 10 + ch - '0';
		else {
			curcmd[cmdlen++] = ch;
			state = nextstate(ch);
		}
		break;

	case VEXTCMD:
		argc2 = 0;
		if (ch >= '1' && ch <= '9') {
			argc2 = ch - '0';
			state = VARG2;
			return 0;
		} else {
			curcmd[cmdlen++] = ch;
			if (ch == curcmd[0])
				state = VCMD;
			else if (is_move(ch))
				state = nextstate(ch);
			else
				state = VFAIL;
		}
		break;

	case VARG2:
		if (isdigit(ch))
			argc2 = argc2 * 10 + ch - '0';
		else {
			if (argc1 == 0)
				argc1 = argc2;
			else
				argc1 *= argc2;
			curcmd[cmdlen++] = ch;
			if (ch == curcmd[0])
				state = VCMD;
			else if (is_move(ch))
				state = nextstate(ch);
			else
				state = VFAIL;
		}
		break;

	case VXCH:
		if (ch == Ctrl('['))
			state = VNORMAL;
		else {
			curcmd[cmdlen++] = ch;
			state = VCMD;
		}
		break;

	case VSEARCH:
		if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
			restore_cbuf();
			/* Repeat last search? */
			if (srchlen == 0) {
				if (!srchpat[0]) {
					vi_error();
					state = VNORMAL;
					refresh(0);
					return 0;
				}
			} else {
				locpat[srchlen] = '\0';
				(void) strlcpy(srchpat, locpat, sizeof srchpat);
			}
			state = VCMD;
		} else if (ch == edchars.erase || ch == Ctrl('h')) {
			if (srchlen != 0) {
				srchlen--;
				es->linelen -= char_len((unsigned char) locpat[srchlen]);
				es->cursor = es->linelen;
				refresh(0);
				return 0;
			}
			restore_cbuf();
			state = VNORMAL;
			refresh(0);
		} else if (ch == edchars.kill) {
			srchlen = 0;
			es->linelen = 1;
			es->cursor = 1;
			refresh(0);
			return 0;
		} else if (ch == edchars.werase) {
			int i;
			int n = srchlen;

			while (n > 0 && isspace((unsigned char)locpat[n - 1]))
				n--;
			while (n > 0 && !isspace((unsigned char)locpat[n - 1]))
				n--;
			for (i = srchlen; --i >= n; )
				es->linelen -= char_len((unsigned char) locpat[i]);
			srchlen = n;
			es->cursor = es->linelen;
			refresh(0);
			return 0;
		} else {
			if (srchlen == SRCHLEN - 1)
				vi_error();
			else {
				locpat[srchlen++] = ch;
				if ((ch & 0x80) && Flag(FVISHOW8)) {
					if (es->linelen + 2 > es->cbufsize)
						vi_error();
					es->cbuf[es->linelen++] = 'M';
					es->cbuf[es->linelen++] = '-';
					ch &= 0x7f;
				}
				if (ch < ' ' || ch == 0x7f) {
					if (es->linelen + 2 > es->cbufsize)
						vi_error();
					es->cbuf[es->linelen++] = '^';
					es->cbuf[es->linelen++] = ch ^ '@';
				} else {
					if (es->linelen >= es->cbufsize)
						vi_error();
					es->cbuf[es->linelen++] = ch;
				}
				es->cursor = es->linelen;
				refresh(0);
			}
			return 0;
		}
		break;
	}

	switch (state) {
	case VCMD:
		state = VNORMAL;
		switch (vi_cmd(argc1, curcmd)) {
		case -1:
			vi_error();
			refresh(0);
			break;
		case 0:
			if (insert != 0)
				inslen = 0;
			refresh(insert != 0);
			break;
		case 1:
			refresh(0);
			return 1;
		case 2:
			/* back from a 'v' command - don't redraw the screen */
			return 1;
		}
		break;

	case VREDO:
		state = VNORMAL;
		if (argc1 != 0)
			lastac = argc1;
		switch (vi_cmd(lastac, lastcmd)) {
		case -1:
			vi_error();
			refresh(0);
			break;
		case 0:
			if (insert != 0) {
				if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
						lastcmd[0] == 'C') {
					if (redo_insert(1) != 0)
						vi_error();
				} else {
					if (redo_insert(lastac) != 0)
						vi_error();
				}
			}
			refresh(0);
			break;
		case 1:
			refresh(0);
			return 1;
		case 2:
			/* back from a 'v' command - can't happen */
			break;
		}
		break;

	case VFAIL:
		state = VNORMAL;
		vi_error();
		break;
	}
	return 0;
}

static void
vi_reset(buf, len)
	char	*buf;
	size_t	len;
{
	state = VNORMAL;
	ohnum = hnum = hlast = histnum(-1) + 1;
	insert = INSERT;
	saved_inslen = inslen;
	first_insert = 1;
	inslen = 0;
	modified = 1;
	vi_macro_reset();
	edit_reset(buf, len);
}

static int
nextstate(ch)
	int	ch;
{
	if (is_extend(ch))
		return VEXTCMD;
	else if (is_srch(ch))
		return VSEARCH;
	else if (is_long(ch))
		return VXCH;
	else if (ch == '.')
		return VREDO;
	else if (ch == Ctrl('v'))
		return VVERSION;
	else if (is_cmd(ch))
		return VCMD;
	else
		return VFAIL;
}

static int
vi_insert(ch)
	int	ch;
{
	int	tcursor;

	if (ch == edchars.erase || ch == Ctrl('h')) {
		if (insert == REPLACE) {
			if (es->cursor == undo->cursor) {
				vi_error();
				return 0;
			}
			if (inslen > 0)
				inslen--;
			es->cursor--;
			if (es->cursor >= undo->linelen)
				es->linelen--;
			else
				es->cbuf[es->cursor] = undo->cbuf[es->cursor];
		} else {
			if (es->cursor == 0) {
				/* x_putc(BEL); no annoying bell here */
				return 0;
			}
			if (inslen > 0)
				inslen--;
			es->cursor--;
			es->linelen--;
			memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1],
					es->linelen - es->cursor + 1);
		}
		expanded = NONE;
		return 0;
	}
	if (ch == edchars.kill) {
		if (es->cursor != 0) {
			inslen = 0;
			memmove(es->cbuf, &es->cbuf[es->cursor],
						es->linelen - es->cursor);
			es->linelen -= es->cursor;
			es->cursor = 0;
		}
		expanded = NONE;
		return 0;
	}
	if (ch == edchars.werase) {
		if (es->cursor != 0) {
			tcursor = Backword(1);
			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
						es->linelen - es->cursor);
			es->linelen -= es->cursor - tcursor;
			if (inslen < es->cursor - tcursor)
				inslen = 0;
			else
				inslen -= es->cursor - tcursor;
			es->cursor = tcursor;
		}
		expanded = NONE;
		return 0;
	}
	/* If any chars are entered before escape, trash the saved insert
	 * buffer (if user inserts & deletes char, ibuf gets trashed and
	 * we don't want to use it)
	 */
	if (first_insert && ch != Ctrl('['))
		saved_inslen = 0;
	switch (ch) {

	case '\0':
		return -1;

	case '\r':
	case '\n':
		return 1;

	case Ctrl('['):
		expanded = NONE;
		if (first_insert) {
			first_insert = 0;
			if (inslen == 0) {
				inslen = saved_inslen;
				return redo_insert(0);
			}
			lastcmd[0] = 'a';
			lastac = 1;
		}
		if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
				lastcmd[0] == 'C')
			return redo_insert(0);
		else
			return redo_insert(lastac - 1);

	/* { Begin nonstandard vi commands */
	case Ctrl('x'):
		expand_word(0);
		break;

	case Ctrl('f'):
		complete_word(0, 0);
		break;

	case Ctrl('e'):
		print_expansions(es, 0);
		break;

	case Ctrl('i'):
		if (Flag(FVITABCOMPLETE)) {
			complete_word(0, 0);
			break;
		}
		/* FALLTHROUGH */
	/* End nonstandard vi commands } */

	default:
		if (es->linelen >= es->cbufsize - 1)
			return -1;
		ibuf[inslen++] = ch;
		if (insert == INSERT) {
			memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor],
					es->linelen - es->cursor);
			es->linelen++;
		}
		es->cbuf[es->cursor++] = ch;
		if (insert == REPLACE && es->cursor > es->linelen)
			es->linelen++;
		expanded = NONE;
	}
	return 0;
}

static int
vi_cmd(argcnt, cmd)
	int		argcnt;
	const char	*cmd;
{
	int		ncursor;
	int		cur, c1, c2, c3 = 0;
	int		any;
	struct edstate	*t;

	if (argcnt == 0 && !is_zerocount(*cmd))
		argcnt = 1;

	if (is_move(*cmd)) {
		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
			if (cur == es->linelen && cur != 0)
				cur--;
			es->cursor = cur;
		} else
			return -1;
	} else {
		/* Don't save state in middle of macro.. */
		if (is_undoable(*cmd) && !macro.p) {
			undo->winleft = es->winleft;
			memmove(undo->cbuf, es->cbuf, es->linelen);
			undo->linelen = es->linelen;
			undo->cursor = es->cursor;
			lastac = argcnt;
			memmove(lastcmd, cmd, MAXVICMD);
		}
		switch (*cmd) {

		case Ctrl('l'):
		case Ctrl('r'):
			redraw_line(1);
			break;

		case '@':
			{
				static char alias[] = "_\0";
				struct tbl *ap;
				int	olen, nlen;
				char	*p, *nbuf;

				/* lookup letter in alias list... */
				alias[1] = cmd[1];
				ap = mytsearch(&aliases, alias, hash(alias));
				if (!cmd[1] || !ap || !(ap->flag & ISSET))
					return -1;
				/* check if this is a recursive call... */
				if ((p = (char *) macro.p))
					while ((p = strchr(p, '\0')) && p[1])
						if (*++p == cmd[1])
							return -1;
				/* insert alias into macro buffer */
				nlen = strlen(ap->val.s) + 1;
				olen = !macro.p ? 2
					: macro.len - (macro.p - macro.buf);
				nbuf = alloc(nlen + 1 + olen, APERM);
				memcpy(nbuf, ap->val.s, nlen);
				nbuf[nlen++] = cmd[1];
				if (macro.p) {
					memcpy(nbuf + nlen, macro.p, olen);
					afree(macro.buf, APERM);
					nlen += olen;
				} else {
					nbuf[nlen++] = '\0';
					nbuf[nlen++] = '\0';
				}
				macro.p = macro.buf = (unsigned char *) nbuf;
				macro.len = nlen;
			}
			break;

		case 'a':
			modified = 1; hnum = hlast;
			if (es->linelen != 0)
				es->cursor++;
			insert = INSERT;
			break;

		case 'A':
			modified = 1; hnum = hlast;
			del_range(0, 0);
			es->cursor = es->linelen;
			insert = INSERT;
			break;

		case 'S':
			es->cursor = domove(1, "^", 1);
			del_range(es->cursor, es->linelen);
			modified = 1; hnum = hlast;
			insert = INSERT;
			break;

		case 'Y':
			cmd = "y$";
			/* ahhhhhh... */
			/*FALLTHROUGH*/
		case 'c':
		case 'd':
		case 'y':
			if (*cmd == cmd[1]) {
				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
				c2 = es->linelen;
			} else if (!is_move(cmd[1]))
				return -1;
			else {
				if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
					return -1;
				if (*cmd == 'c' &&
						(cmd[1]=='w' || cmd[1]=='W') &&
						!isspace((unsigned char)es->cbuf[es->cursor])) {
					while (isspace((unsigned char)es->cbuf[--ncursor]))
						continue;
					ncursor++;
				}
				if (ncursor > es->cursor) {
					c1 = es->cursor;
					c2 = ncursor;
				} else {
					c1 = ncursor;
					c2 = es->cursor;
					if (cmd[1] == '%')
						c2++;
				}
			}
			if (*cmd != 'c' && c1 != c2)
				yank_range(c1, c2);
			if (*cmd != 'y') {
				del_range(c1, c2);
				es->cursor = c1;
			}
			if (*cmd == 'c') {
				modified = 1; hnum = hlast;
				insert = INSERT;
			}
			break;

		case 'p':
			modified = 1; hnum = hlast;
			if (es->linelen != 0)
				es->cursor++;
			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
				continue;
			if (es->cursor != 0)
				es->cursor--;
			if (argcnt != 0)
				return -1;
			break;

		case 'P':
			modified = 1; hnum = hlast;
			any = 0;
			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
				any = 1;
			if (any && es->cursor != 0)
				es->cursor--;
			if (argcnt != 0)
				return -1;
			break;

		case 'C':
			modified = 1; hnum = hlast;
			del_range(es->cursor, es->linelen);
			insert = INSERT;
			break;

		case 'D':
			yank_range(es->cursor, es->linelen);
			del_range(es->cursor, es->linelen);
			if (es->cursor != 0)
				es->cursor--;
			break;

		case 'g':
			if (!argcnt)
				argcnt = hlast + 1;
			/* fall through */
		case 'G':
			if (!argcnt)
				argcnt = 1;
			else
				argcnt = hlast - (source->line - argcnt);
			if (grabhist(modified, argcnt - 1) < 0)
				return -1;
			else {
				modified = 0;
				hnum = argcnt - 1;
			}
			break;

		case 'i':
			modified = 1; hnum = hlast;
			insert = INSERT;
			break;

		case 'I':
			modified = 1; hnum = hlast;
			es->cursor = domove(1, "^", 1);
			insert = INSERT;
			break;

		case 'j':
		case '+':
		case Ctrl('n'):
			if (grabhist(modified, hnum + argcnt) < 0)
				return -1;
			else {
				modified = 0;
				hnum += argcnt;
			}
			break;

		case 'k':
		case '-':
		case Ctrl('p'):
			if (grabhist(modified, hnum - argcnt) < 0)
				return -1;
			else {
				modified = 0;
				hnum -= argcnt;
			}
			break;

		case 'r':
			if (es->linelen == 0)
				return -1;
			modified = 1; hnum = hlast;
			if (cmd[1] == 0)
				vi_error();
			else
				es->cbuf[es->cursor] = cmd[1];
			break;

		case 'R':
			modified = 1; hnum = hlast;
			insert = REPLACE;
			break;

		case 's':
			if (es->linelen == 0)
				return -1;
			modified = 1; hnum = hlast;
			if (es->cursor + argcnt > es->linelen)
				argcnt = es->linelen - es->cursor;
			del_range(es->cursor, es->cursor + argcnt);
			insert = INSERT;
			break;

		case 'v':
			if (es->linelen == 0)
				return -1;
			if (!argcnt) {
				if (modified) {
					es->cbuf[es->linelen] = '\0';
					source->line++;
					histsave(source->line, es->cbuf, 1);
				} else
					argcnt = source->line + 1
						- (hlast - hnum);
			}
			shf_snprintf(es->cbuf, es->cbufsize,
					argcnt ? "%s %d" : "%s",
					"fc -e ${VISUAL:-${EDITOR:-vi}} --",
					argcnt);
			es->linelen = strlen(es->cbuf);
			return 2;

		case 'x':
			if (es->linelen == 0)
				return -1;
			modified = 1; hnum = hlast;
			if (es->cursor + argcnt > es->linelen)
				argcnt = es->linelen - es->cursor;
			yank_range(es->cursor, es->cursor + argcnt);
			del_range(es->cursor, es->cursor + argcnt);
			break;

		case 'X':
			if (es->cursor > 0) {
				modified = 1; hnum = hlast;
				if (es->cursor < argcnt)
					argcnt = es->cursor;
				yank_range(es->cursor - argcnt, es->cursor);
				del_range(es->cursor - argcnt, es->cursor);
				es->cursor -= argcnt;
			} else
				return -1;
			break;

		case 'u':
			t = es;
			es = undo;
			undo = t;
			break;

		case 'U':
			if (!modified)
				return -1;
			if (grabhist(modified, ohnum) < 0)
				return -1;
			modified = 0;
			hnum = ohnum;
			break;

		case '?':
			if (hnum == hlast)
				hnum = -1;
			/* ahhh */
			/*FALLTHROUGH*/
		case '/':
			c3 = 1;
			srchlen = 0;
			lastsearch = *cmd;
			/* fall through */
		case 'n':
		case 'N':
			if (lastsearch == ' ')
				return -1;
			if (lastsearch == '?')
				c1 = 1;
			else
				c1 = 0;
			if (*cmd == 'N')
				c1 = !c1;
			if ((c2 = grabsearch(modified, hnum,
							c1, srchpat)) < 0) {
				if (c3) {
					restore_cbuf();
					refresh(0);
				}
				return -1;
			} else {
				modified = 0;
				hnum = c2;
				ohnum = hnum;
			}
			break;
		case '_': {
			int	inspace;
			char	*p, *sp;

			if (histnum(-1) < 0)
				return -1;
			p = *histpos();
#define issp(c)		(isspace((unsigned char)(c)) || (c) == '\n')
			if (argcnt) {
				while (*p && issp(*p))
					p++;
				while (*p && --argcnt) {
					while (*p && !issp(*p))
						p++;
					while (*p && issp(*p))
						p++;
				}
				if (!*p)
					return -1;
				sp = p;
			} else {
				sp = p;
				inspace = 0;
				while (*p) {
					if (issp(*p))
						inspace = 1;
					else if (inspace) {
						inspace = 0;
						sp = p;
					}
					p++;
				}
				p = sp;
			}
			modified = 1; hnum = hlast;
			if (es->cursor != es->linelen)
				es->cursor++;
			while (*p && !issp(*p)) {
				argcnt++;
				p++;
			}
			if (putbuf(space, 1, 0) != 0)
				argcnt = -1;
			else if (putbuf(sp, argcnt, 0) != 0)
				argcnt = -1;
			if (argcnt < 0) {
				if (es->cursor != 0)
					es->cursor--;
				return -1;
			}
			insert = INSERT;
			}
			break;

		case '~': {
			char	*p;
			int	i;

			if (es->linelen == 0)
				return -1;
			for (i = 0; i < argcnt; i++) {
				p = &es->cbuf[es->cursor];
				if (islower((unsigned char)*p)) {
					modified = 1; hnum = hlast;
					*p = toupper((unsigned char)*p);
				} else if (isupper((unsigned char)*p)) {
					modified = 1; hnum = hlast;
					*p = tolower((unsigned char)*p);
				}
				if (es->cursor < es->linelen - 1)
					es->cursor++;
			}
			break;
			}

		case '#':
		    {
			int ret = x_do_comment(es->cbuf, es->cbufsize,
					    &es->linelen);
			if (ret >= 0)
				es->cursor = 0;
			return ret;
		    }

		case '=': 			/* at&t ksh */
		case Ctrl('e'):			/* Nonstandard vi/ksh */
			print_expansions(es, 1);
			break;


		case Ctrl('i'):			/* Nonstandard vi/ksh */
			if (!Flag(FVITABCOMPLETE))
				return -1;
			complete_word(1, argcnt);
			break;

		case Ctrl('['):			/* some annoying at&t ksh's */
			if (!Flag(FVIESCCOMPLETE))
				return -1;
			/*FALLTHROUGH*/
		case '\\':			/* at&t ksh */
		case Ctrl('f'):			/* Nonstandard vi/ksh */
			complete_word(1, argcnt);
			break;


		case '*':			/* at&t ksh */
		case Ctrl('x'):			/* Nonstandard vi/ksh */
			expand_word(1);
			break;
		}
		if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
			es->cursor--;
	}
	return 0;
}

static int
domove(argcnt, cmd, sub)
	int	argcnt;
	const char *cmd;
	int	sub;
{
	int	bcount, UNINITIALIZED(i), t;
	int	UNINITIALIZED(ncursor);

	switch (*cmd) {

	case 'b':
		if (!sub && es->cursor == 0)
			return -1;
		ncursor = backword(argcnt);
		break;

	case 'B':
		if (!sub && es->cursor == 0)
			return -1;
		ncursor = Backword(argcnt);
		break;

	case 'e':
		if (!sub && es->cursor + 1 >= es->linelen)
			return -1;
		ncursor = endword(argcnt);
		if (sub && ncursor < es->linelen)
			ncursor++;
		break;

	case 'E':
		if (!sub && es->cursor + 1 >= es->linelen)
			return -1;
		ncursor = Endword(argcnt);
		if (sub && ncursor < es->linelen)
			ncursor++;
		break;

	case 'f':
	case 'F':
	case 't':
	case 'T':
		fsavecmd = *cmd;
		fsavech = cmd[1];
		/* drop through */
		/*FALLTHROUGH*/
	case ',':
	case ';':
		if (fsavecmd == ' ')
			return -1;
		i = fsavecmd == 'f' || fsavecmd == 'F';
		t = fsavecmd > 'a';
		if (*cmd == ',')
			t = !t;
		if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
			return -1;
		if (sub && t)
			ncursor++;
		break;

	case 'h':
	case Ctrl('h'):
		if (!sub && es->cursor == 0)
			return -1;
		ncursor = es->cursor - argcnt;
		if (ncursor < 0)
			ncursor = 0;
		break;

	case ' ':
	case 'l':
		if (!sub && es->cursor + 1 >= es->linelen)
			return -1;
		if (es->linelen != 0) {
			ncursor = es->cursor + argcnt;
			if (ncursor > es->linelen)
				ncursor = es->linelen;
		}
		break;

	case 'w':
		if (!sub && es->cursor + 1 >= es->linelen)
			return -1;
		ncursor = forwword(argcnt);
		break;

	case 'W':
		if (!sub && es->cursor + 1 >= es->linelen)
			return -1;
		ncursor = Forwword(argcnt);
		break;

	case '0':
		ncursor = 0;
		break;

	case '^':
		ncursor = 0;
		while (ncursor < es->linelen - 1 && isspace((unsigned char)es->cbuf[ncursor]))
			ncursor++;
		break;

	case '|':
		ncursor = argcnt;
		if (ncursor > es->linelen)
			ncursor = es->linelen;
		if (ncursor)
			ncursor--;
		break;

	case '$':
		if (es->linelen != 0)
			ncursor = es->linelen;
		else
			ncursor = 0;
		break;

	case '%':
		ncursor = es->cursor;
		while (ncursor < es->linelen &&
				(i = bracktype(es->cbuf[ncursor])) == 0)
			ncursor++;
		if (ncursor == es->linelen)
			return -1;
		bcount = 1;
		do {
			if (i > 0) {
				if (++ncursor >= es->linelen)
					return -1;
			} else {
				if (--ncursor < 0)
					return -1;
			}
			t = bracktype(es->cbuf[ncursor]);
			if (t == i)
				bcount++;
			else if (t == -i)
				bcount--;
		} while (bcount != 0);
		if (sub && i > 0)
			ncursor++;
		break;

	default:
		return -1;
	}
	return ncursor;
}

static int
redo_insert(count)
	int	count;
{
	while (count-- > 0)
		if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
			return -1;
	if (es->cursor > 0)
		es->cursor--;
	insert = 0;
	return 0;
}

static void
yank_range(a, b)
	int	a, b;
{
	yanklen = b - a;
	if (yanklen != 0)
		memmove(ybuf, &es->cbuf[a], yanklen);
}

static int
bracktype(ch)
	int	ch;
{
	switch (ch) {

	case '(':
		return 1;

	case '[':
		return 2;

	case '{':
		return 3;

	case ')':
		return -1;

	case ']':
		return -2;

	case '}':
		return -3;

	default:
		return 0;
	}
}

/*
 *	Non user interface editor routines below here
 */

static int	cur_col;		/* current column on line */
static int	pwidth;			/* width of prompt */
static int	prompt_trunc;		/* how much of prompt to truncate */
static int	prompt_skip;		/* how much of prompt to skip */
static int	winwidth;		/* width of window */
static char	*wbuf[2];		/* window buffers */
static int	wbuf_len;		/* length of window buffers (x_cols-3)*/
static int	win;			/* window buffer in use */
static char	morec;			/* more character at right of window */
static int	lastref;		/* argument to last refresh() */
static char	holdbuf[CMDLEN];	/* place to hold last edit buffer */
static int	holdlen;		/* length of holdbuf */

static void
save_cbuf()
{
	memmove(holdbuf, es->cbuf, es->linelen);
	holdlen = es->linelen;
	holdbuf[holdlen] = '\0';
}

static void
restore_cbuf()
{
	es->cursor = 0;
	es->linelen = holdlen;
	memmove(es->cbuf, holdbuf, holdlen);
}

/* return a new edstate */
static struct edstate *
save_edstate(old)
	struct edstate *old;
{
	struct edstate *new;

	new = (struct edstate *)alloc(sizeof(struct edstate), APERM);
	new->cbuf = alloc(old->cbufsize, APERM);
	memcpy(new->cbuf, old->cbuf, old->linelen);
	new->cbufsize = old->cbufsize;
	new->linelen = old->linelen;
	new->cursor = old->cursor;
	new->winleft = old->winleft;
	return new;
}

static void
restore_edstate(new, old)
	struct edstate *old, *new;
{
	memcpy(new->cbuf, old->cbuf, old->linelen);
	new->linelen = old->linelen;
	new->cursor = old->cursor;
	new->winleft = old->winleft;
	free_edstate(old);
}

static void
free_edstate(old)
	struct edstate *old;
{
	afree(old->cbuf, APERM);
	afree((char *)old, APERM);
}



static void
edit_reset(buf, len)
	char	*buf;
	size_t	len;
{
	const char *p;

	es = &ebuf;
	es->cbuf = buf;
	es->cbufsize = len;
	undo = &undobuf;
	undo->cbufsize = len;

	es->linelen = undo->linelen = 0;
	es->cursor = undo->cursor = 0;
	es->winleft = undo->winleft = 0;

	cur_col = pwidth = promptlen(prompt, &p);
	prompt_skip = p - prompt;
	if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
		cur_col = x_cols - 3 - MIN_EDIT_SPACE;
		prompt_trunc = pwidth - cur_col;
		pwidth -= prompt_trunc;
	} else
		prompt_trunc = 0;
	if (!wbuf_len || wbuf_len != x_cols - 3) {
		wbuf_len = x_cols - 3;
		wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
		wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
	}
	(void) memset(wbuf[0], ' ', wbuf_len);
	(void) memset(wbuf[1], ' ', wbuf_len);
	winwidth = x_cols - pwidth - 3;
	win = 0;
	morec = ' ';
	lastref = 1;
	holdlen = 0;
}

/*
 * this is used for calling x_escape() in complete_word()
 */
static int
x_vi_putbuf(s, len)
	const char *s;
	size_t len;
{
	return putbuf(s, len, 0);
}

static int
putbuf(buf, len, repl)
	const char *buf;
	int	len;
	int	repl;
{
	if (len == 0)
		return 0;
	if (repl) {
		if (es->cursor + len >= es->cbufsize)
			return -1;
		if (es->cursor + len > es->linelen)
			es->linelen = es->cursor + len;
	} else {
		if (es->linelen + len >= es->cbufsize)
			return -1;
		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
			es->linelen - es->cursor);
		es->linelen += len;
	}
	memmove(&es->cbuf[es->cursor], buf, len);
	es->cursor += len;
	return 0;
}

static void
del_range(a, b)
	int	a, b;
{
	if (es->linelen != b)
		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
	es->linelen -= b - a;
}

static int
findch(ch, cnt, forw, incl)
	int	ch;
	int	cnt;
	int	forw;
	int	incl;
{
	int	ncursor;

	if (es->linelen == 0)
		return -1;
	ncursor = es->cursor;
	while (cnt--) {
		do {
			if (forw) {
				if (++ncursor == es->linelen)
					return -1;
			} else {
				if (--ncursor < 0)
					return -1;
			}
		} while (es->cbuf[ncursor] != ch);
	}
	if (!incl) {
		if (forw)
			ncursor--;
		else
			ncursor++;
	}
	return ncursor;
}

static int
forwword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor < es->linelen && argcnt--) {
		if (is_wordch(es->cbuf[ncursor]))
			while (ncursor < es->linelen &&
			    is_wordch(es->cbuf[ncursor]))
				ncursor++;
		else if (!isspace((unsigned char)es->cbuf[ncursor]))
			while (ncursor < es->linelen &&
			    !is_wordch(es->cbuf[ncursor]) &&
			    !isspace((unsigned char)es->cbuf[ncursor]))
				ncursor++;
		while (ncursor < es->linelen &&
		    isspace((unsigned char)es->cbuf[ncursor]))
			ncursor++;
	}
	return ncursor;
}

static int
backword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor > 0 && argcnt--) {
		while (--ncursor > 0 && isspace((unsigned char)es->cbuf[ncursor]))
			continue;
		if (ncursor > 0) {
			if (is_wordch(es->cbuf[ncursor]))
				while (--ncursor >= 0 &&
				   is_wordch(es->cbuf[ncursor]))
					continue;
			else
				while (--ncursor >= 0 &&
				   !is_wordch(es->cbuf[ncursor]) &&
				   !isspace((unsigned char)es->cbuf[ncursor]))
					continue;
			ncursor++;
		}
	}
	return ncursor;
}

static int
endword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor < es->linelen && argcnt--) {
		while (++ncursor < es->linelen - 1 &&
		    isspace((unsigned char)es->cbuf[ncursor]))
			continue;
		if (ncursor < es->linelen - 1) {
			if (is_wordch(es->cbuf[ncursor]))
				while (++ncursor < es->linelen &&
				    is_wordch(es->cbuf[ncursor]))
					continue;
			else
				while (++ncursor < es->linelen &&
				   !is_wordch(es->cbuf[ncursor]) &&
				   !isspace((unsigned char)es->cbuf[ncursor]))
					continue;
			ncursor--;
		}
	}
	return ncursor;
}

static int
Forwword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor < es->linelen && argcnt--) {
		while (ncursor < es->linelen &&
		    !isspace((unsigned char)es->cbuf[ncursor]))
			ncursor++;
		while (ncursor < es->linelen &&
		    isspace((unsigned char)es->cbuf[ncursor]))
			ncursor++;
	}
	return ncursor;
}

static int
Backword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor > 0 && argcnt--) {
		while (--ncursor >= 0 && isspace((unsigned char)es->cbuf[ncursor]))
			continue;
		while (ncursor >= 0 && !isspace((unsigned char)es->cbuf[ncursor]))
			ncursor--;
		ncursor++;
	}
	return ncursor;
}

static int
Endword(argcnt)
	int	argcnt;
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor < es->linelen - 1 && argcnt--) {
		while (++ncursor < es->linelen - 1 &&
		    isspace((unsigned char)es->cbuf[ncursor]))
			continue;
		if (ncursor < es->linelen - 1) {
			while (++ncursor < es->linelen &&
			    !isspace((unsigned char)es->cbuf[ncursor]))
				continue;
			ncursor--;
		}
	}
	return ncursor;
}

static int
grabhist(save, n)
	int	save;
	int	n;
{
	char	*hptr;

	if (n < 0 || n > hlast)
		return -1;
	if (n == hlast) {
		restore_cbuf();
		ohnum = n;
		return 0;
	}
	(void) histnum(n);
	if ((hptr = *histpos()) == NULL) {
		internal_errorf(0, "grabhist: bad history array");
		return -1;
	}
	if (save)
		save_cbuf();
	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
		es->linelen = es->cbufsize - 1;
	memmove(es->cbuf, hptr, es->linelen);
	es->cursor = 0;
	ohnum = n;
	return 0;
}

static int
grabsearch(save, start, fwd, pat)
	int	save, start, fwd;
	char	*pat;
{
	char	*hptr;
	int	hist;
	int	anchored;

	if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
		return -1;
	if (fwd)
		start++;
	else
		start--;
	anchored = *pat == '^' ? (++pat, 1) : 0;
	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
		/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
		/* XXX should FILECMP be strncmp? */
		if (start != 0 && fwd && FILECMP(holdbuf, pat) >= 0) {
			restore_cbuf();
			return 0;
		} else
			return -1;
	}
	if (save)
		save_cbuf();
	histnum(hist);
	hptr = *histpos();
	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
		es->linelen = es->cbufsize - 1;
	memmove(es->cbuf, hptr, es->linelen);
	es->cursor = 0;
	return hist;
}

static void
redraw_line(newlinex)
	int newlinex;
{
	(void) memset(wbuf[win], ' ', wbuf_len);
	if (newlinex) {
		x_putc('\r');
		x_putc('\n');
	}
	vi_pprompt(0);
	cur_col = pwidth;
	morec = ' ';
}

static void
refresh(leftside)
	int		leftside;
{
	if (leftside < 0)
		leftside = lastref;
	else
		lastref = leftside;
	if (outofwin())
		rewindow();
	display(wbuf[1 - win], wbuf[win], leftside);
	win = 1 - win;
}

static int
outofwin()
{
	int	cur, col;

	if (es->cursor < es->winleft)
		return 1;
	col = 0;
	cur = es->winleft;
	while (cur < es->cursor)
		col = newcol((unsigned char) es->cbuf[cur++], col);
	if (col >= winwidth)
		return 1;
	return 0;
}

static void
rewindow()
{
	int		tcur, tcol;
	int		holdcur1, holdcol1;
	int		holdcur2, holdcol2;

	holdcur1 = holdcur2 = tcur = 0;
	holdcol1 = holdcol2 = tcol = 0;
	while (tcur < es->cursor) {
		if (tcol - holdcol2 > winwidth / 2) {
			holdcur1 = holdcur2;
			holdcol1 = holdcol2;
			holdcur2 = tcur;
			holdcol2 = tcol;
		}
		tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
	}
	while (tcol - holdcol1 > winwidth / 2)
		holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
				  holdcol1);
	es->winleft = holdcur1;
}

static int
newcol(ch, col)
	int	ch, col;
{
	if (ch == '\t')
		return (col | 7) + 1;
	return col + char_len(ch);
}

static void
display(wb1, wb2, leftside)
	char	*wb1, *wb2;
	int	leftside;
{
	unsigned char ch;
	char	*twb1, *twb2, mc;
	int	cur, col, cnt;
	int	UNINITIALIZED(ncol);
	int	moreright;

	col = 0;
	cur = es->winleft;
	moreright = 0;
	twb1 = wb1;
	while (col < winwidth && cur < es->linelen) {
		if (cur == es->cursor && leftside)
			ncol = col + pwidth;
		if ((ch = es->cbuf[cur]) == '\t') {
			do {
				*twb1++ = ' ';
			} while (++col < winwidth && (col & 7) != 0);
		} else {
			if ((ch & 0x80) && Flag(FVISHOW8)) {
				*twb1++ = 'M';
				if (++col < winwidth) {
					*twb1++ = '-';
					col++;
				}
				ch &= 0x7f;
			}
			if (col < winwidth) {
				if (ch < ' ' || ch == 0x7f) {
					*twb1++ = '^';
					if (++col < winwidth) {
						*twb1++ = ch ^ '@';
						col++;
					}
				} else {
					*twb1++ = ch;
					col++;
				}
			}
		}
		if (cur == es->cursor && !leftside)
			ncol = col + pwidth - 1;
		cur++;
	}
	if (cur == es->cursor)
		ncol = col + pwidth;
	if (col < winwidth) {
		while (col < winwidth) {
			*twb1++ = ' ';
			col++;
		}
	} else
		moreright++;
	*twb1 = ' ';

	col = pwidth;
	cnt = winwidth;
	twb1 = wb1;
	twb2 = wb2;
	while (cnt--) {
		if (*twb1 != *twb2) {
			if (cur_col != col)
				ed_mov_opt(col, wb1);
			x_putc(*twb1);
			cur_col++;
		}
		twb1++;
		twb2++;
		col++;
	}
	if (es->winleft > 0 && moreright)
		/* POSIX says to use * for this but that is a globbing
		 * character and may confuse people; + is more innocuous
		 */
		mc = '+';
	else if (es->winleft > 0)
		mc = '<';
	else if (moreright)
		mc = '>';
	else
		mc = ' ';
	if (mc != morec) {
		ed_mov_opt(pwidth + winwidth + 1, wb1);
		x_putc(mc);
		cur_col++;
		morec = mc;
	}
	if (cur_col != ncol)
		ed_mov_opt(ncol, wb1);
}

static void
ed_mov_opt(col, wb)
	int	col;
	char	*wb;
{
	if (col < cur_col) {
		if (col + 1 < cur_col - col) {
			x_putc('\r');
			vi_pprompt(0);
			cur_col = pwidth;
			while (cur_col++ < col)
				x_putc(*wb++);
		} else {
			while (cur_col-- > col)
				x_putc('\b');
		}
	} else {
		wb = &wb[cur_col - pwidth];
		while (cur_col++ < col)
			x_putc(*wb++);
	}
	cur_col = col;
}


/* replace word with all expansions (ie, expand word*) */
static int
expand_word(commandx)
	int commandx;
{
	static struct edstate *buf;
	int rval = 0;
	int nwords;
	int start, end;
	char **words;
	int i;

	/* Undo previous expansion */
	if (commandx == 0 && expanded == EXPAND && buf) {
		restore_edstate(es, buf);
		buf = 0;
		expanded = NONE;
		return 0;
	}
	if (buf) {
		free_edstate(buf);
		buf = 0;
	}

	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
		es->cbuf, es->linelen, es->cursor,
		&start, &end, &words, (int *) 0);
	if (nwords == 0) {
		vi_error();
		return -1;
	}

	buf = save_edstate(es);
	expanded = EXPAND;
	del_range(start, end);
	es->cursor = start;
	for (i = 0; i < nwords; ) {
		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
			rval = -1;
			break;
		}
		if (++i < nwords && putbuf(space, 1, 0) != 0) {
			rval = -1;
			break;
		}
	}
	i = buf->cursor - end;
	if (rval == 0 && i > 0)
		es->cursor += i;
	modified = 1; hnum = hlast;
	insert = INSERT;
	lastac = 0;
	refresh(0);
	return rval;
}

static int
complete_word(commandx, count)
	int commandx;
	int count;
{
	static struct edstate *buf;
	int rval = 0;
	int nwords;
	int start, end;
	char **words;
	char *match;
	int match_len;
	int is_unique;
	int is_command;

	/* Undo previous completion */
	if (commandx == 0 && expanded == COMPLETE && buf) {
		print_expansions(buf, 0);
		expanded = PRINT;
		return 0;
	}
	if (commandx == 0 && expanded == PRINT && buf) {
		restore_edstate(es, buf);
		buf = 0;
		expanded = NONE;
		return 0;
	}
	if (buf) {
		free_edstate(buf);
		buf = 0;
	}

	/* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
	 * was done this way.
	 */
	nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
		es->cbuf, es->linelen, es->cursor,
		&start, &end, &words, &is_command);
	if (nwords == 0) {
		vi_error();
		return -1;
	}
	if (count) {
		int i;

		count--;
		if (count >= nwords) {
			vi_error();
			x_print_expansions(nwords, words, is_command);
			x_free_words(nwords, words);
			redraw_line(0);
			return -1;
		}
		/*
		 * Expand the count'th word to its basename
		 */
		if (is_command) {
			match = words[count]
				+ x_basename(words[count], (char *) 0);
			/* If more than one possible match, use full path */
			for (i = 0; i < nwords; i++)
				if (i != count &&
				    FILECMP(words[i]
					    + x_basename(words[i], (char *) 0),
					    match) == 0)
				{
					match = words[count];
					break;
				}
		} else
			match = words[count];
		match_len = strlen(match);
		is_unique = 1;
		/* expanded = PRINT;	next call undo */
	} else {
		match = words[0];
		match_len = x_longest_prefix(nwords, words);
		expanded = COMPLETE;	/* next call will list completions */
		is_unique = nwords == 1;
	}

	buf = save_edstate(es);
	del_range(start, end);
	es->cursor = start;

	/* escape all shell-sensitive characters and put the result into
	 * command buffer */
	rval = x_escape(match, match_len, x_vi_putbuf);

	if (rval == 0 && is_unique) {
		/* If exact match, don't undo.  Allows directory completions
		 * to be used (ie, complete the next portion of the path).
		 */
		expanded = NONE;

		/* If not a directory, add a space to the end... */
		if (match_len > 0 && !ISDIRSEP(match[match_len - 1]))
			rval = putbuf(space, 1, 0);
	}
	x_free_words(nwords, words);

	modified = 1; hnum = hlast;
	insert = INSERT;
	lastac = 0;	 /* prevent this from being redone... */
	refresh(0);

	return rval;
}

static int
print_expansions(ex, commandx)
	struct edstate *ex;
	int	commandx;
{
	int nwords;
	int start, end;
	char **words;
	int is_command;

	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
		ex->cbuf, ex->linelen, ex->cursor,
		&start, &end, &words, &is_command);
	if (nwords == 0) {
		vi_error();
		return -1;
	}
	x_print_expansions(nwords, words, is_command);
	x_free_words(nwords, words);
	redraw_line(0);
	return 0;
}

/* How long is char when displayed (not counting tabs) */
static int
char_len(c)
	int c;
{
	int len = 1;

	if ((c & 0x80) && Flag(FVISHOW8)) {
		len += 2;
		c &= 0x7f;
	}
	if (c < ' ' || c == 0x7f)
		len++;
	return len;
}

/* Similar to x_zotc(emacs.c), but no tab weirdness */
static void
x_vi_zotc(c)
	int c;
{
	if (Flag(FVISHOW8) && (c & 0x80)) {
		x_puts("M-");
		c &= 0x7f;
	}
	if (c < ' ' || c == 0x7f) {
		x_putc('^');
		c ^= '@';
	}
	x_putc(c);
}

static void
vi_pprompt(full)
	int full;
{
	pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
}

static void
vi_error()
{
	/* Beem out of any macros as soon as an error occurs */
	vi_macro_reset();
	x_putc(BEL);
	x_flush();
}

static void
vi_macro_reset()
{
	if (macro.p) {
		afree(macro.buf, APERM);
		memset((char *) &macro, 0, sizeof(macro));
	}
}

#endif	/* VI */
