/*	$OpenBSD: edit.c,v 1.33 2007/08/02 10:50:25 fgsch Exp $	*/
/*	$OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $	*/
/*	$OpenBSD: emacs.c,v 1.41 2007/08/02 10:50:25 fgsch Exp $	*/
/*	$OpenBSD: vi.c,v 1.23 2006/04/10 14:38:59 jaredy Exp $	*/

#include "sh.h"

__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.144 2008/11/12 00:54:46 tg Exp $");

/* tty driver characters we are interested in */
typedef struct {
	int erase;
	int kill;
	int werase;
	int intr;
	int quit;
	int eof;
} X_chars;

X_chars edchars;

/* x_fc_glob() flags */
#define XCF_COMMAND	BIT(0)	/* Do command completion */
#define XCF_FILE	BIT(1)	/* Do file completion */
#define XCF_FULLPATH	BIT(2)	/* command completion: store full path */
#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)

static int modified;			/* buffer has been "modified" */
static char holdbuf[LINE];		/* place to hold last edit buffer */

static int x_getc(void);
static void x_putcf(int);
static bool x_mode(bool);
static int x_do_comment(char *, int, int *);
static void x_print_expansions(int, char *const *, bool);
static int x_cf_glob(int, const char *, int, int, int *, int *, char ***,
    bool *);
static int x_longest_prefix(int, char *const *);
static int x_basename(const char *, const char *);
static void x_free_words(int, char **);
static int x_escape(const char *, size_t, int (*)(const char *, size_t));
static int x_emacs(char *, size_t);
static void x_init_emacs(void);
static void x_init_prompt(void);
#ifndef MKSH_NOVI
static int x_vi(char *, size_t);
#endif

#define x_flush()	shf_flush(shl_out)
#define x_putc(c)	shf_putc((c), shl_out)

#ifdef TIOCGWINSZ
static void chkwinsz(void);
#endif

static int path_order_cmp(const void *aa, const void *bb);
static char *add_glob(const char *, int);
static void glob_table(const char *, XPtrV *, struct table *);
static void glob_path(int flags, const char *, XPtrV *, const char *);
static int x_file_glob(int, const char *, int, char ***);
static int x_command_glob(int, const char *, int, char ***);
static int x_locate_word(const char *, int, int, int *, bool *);

static int x_e_getmbc(char *);

/* +++ generic editing functions +++ */

/* Called from main */
void
x_init(void)
{
	/* set to -2 to force initial binding */
	edchars.erase = edchars.kill = edchars.intr = edchars.quit =
	    edchars.eof = -2;
	/* default value for deficient systems */
	edchars.werase = 027;	/* ^W */
#ifdef TIOCGWINSZ
	chkwinsz();
#endif
	x_init_emacs();
}

#ifdef TIOCGWINSZ
static void
chkwinsz(void)
{
	struct winsize ws;

	if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
		struct tbl *vp;

		/* Do NOT export COLUMNS/LINES.  Many applications
		 * check COLUMNS/LINES before checking ws.ws_col/row,
		 * so if the app is started with C/L in the environ
		 * and the window is then resized, the app won't
		 * see the change cause the environ doesn't change.
		 */
		if (ws.ws_col) {
			x_cols = ws.ws_col < MIN_COLS ? MIN_COLS : ws.ws_col;

			if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
				setint(vp, (long)ws.ws_col);
		}
		if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0)))
			setint(vp, (long)ws.ws_row);
	}
}
#endif

/*
 * read an edited command line
 */
int
x_read(char *buf, size_t len)
{
	int i;

	x_mode(true);
	modified = 1;
	if (Flag(FEMACS) || Flag(FGMACS))
		i = x_emacs(buf, len);
#ifndef MKSH_NOVI
	else if (Flag(FVI))
		i = x_vi(buf, len);
#endif
	else
		i = -1;		/* internal error */
	x_mode(false);
#ifdef TIOCGWINSZ
	chkwinsz();
#endif
	return i;
}

/* tty I/O */

static int
x_getc(void)
{
	char c;
	int n;

	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
		if (trap) {
			x_mode(false);
			runtraps(0);
			x_mode(true);
		}
	return ((n == 1) ? (int)(unsigned char)c : -1);
}

static void
x_putcf(int c)
{
	shf_putc(c, shl_out);
}

/* ------------------------------------------------------------------------- */
/*           Misc common code for vi/emacs				     */

/* Handle the commenting/uncommenting of a line.
 * Returns:
 *	1 if a carriage return is indicated (comment added)
 *	0 if no return (comment removed)
 *	-1 if there is an error (not enough room for comment chars)
 * If successful, *lenp contains the new length.  Note: cursor should be
 * moved to the start of the line after (un)commenting.
 */
static int
x_do_comment(char *buf, int bsize, int *lenp)
{
	int i, j, len = *lenp;

	if (len == 0)
		return 1; /* somewhat arbitrary - it's what at&t ksh does */

	/* Already commented? */
	if (buf[0] == '#') {
		bool saw_nl = false;

		for (j = 0, i = 1; i < len; i++) {
			if (!saw_nl || buf[i] != '#')
				buf[j++] = buf[i];
			saw_nl = buf[i] == '\n';
		}
		*lenp = j;
		return 0;
	} else {
		int n = 1;

		/* See if there's room for the #s - 1 per \n */
		for (i = 0; i < len; i++)
			if (buf[i] == '\n')
				n++;
		if (len + n >= bsize)
			return -1;
		/* Now add them... */
		for (i = len, j = len + n; --i >= 0; ) {
			if (buf[i] == '\n')
				buf[--j] = '#';
			buf[--j] = buf[i];
		}
		buf[0] = '#';
		*lenp += n;
		return 1;
	}
}

/* ------------------------------------------------------------------------- */
/*           Common file/command completion code for vi/emacs	             */

static void
x_print_expansions(int nwords, char * const *words, bool is_command)
{
	bool use_copy = false;
	int prefix_len;
	XPtrV l = { NULL, NULL, NULL };

	/* Check if all matches are in the same directory (in this
	 * case, we want to omit the directory name)
	 */
	if (!is_command &&
	    (prefix_len = x_longest_prefix(nwords, words)) > 0) {
		int i;

		/* Special case for 1 match (prefix is whole word) */
		if (nwords == 1)
			prefix_len = x_basename(words[0], NULL);
		/* Any (non-trailing) slashes in non-common word suffixes? */
		for (i = 0; i < nwords; i++)
			if (x_basename(words[i] + prefix_len, NULL) >
			    prefix_len)
				break;
		/* All in same directory? */
		if (i == nwords) {
			while (prefix_len > 0 && words[0][prefix_len - 1] != '/')
				prefix_len--;
			use_copy = true;
			XPinit(l, nwords + 1);
			for (i = 0; i < nwords; i++)
				XPput(l, words[i] + prefix_len);
			XPput(l, NULL);
		}
	}
	/*
	 * Enumerate expansions
	 */
	x_putc('\r');
	x_putc('\n');
	pr_list(use_copy ? (char **)XPptrv(l) : words);

	if (use_copy)
		XPfree(l); /* not x_free_words() */
}

/*
 *  Do file globbing:
 *	- appends * to (copy of) str if no globbing chars found
 *	- does expansion, checks for no match, etc.
 *	- sets *wordsp to array of matching strings
 *	- returns number of matching strings
 */
static int
x_file_glob(int flags __unused, const char *str, int slen, char ***wordsp)
{
	char *toglob, **words;
	int nwords, i, idx, escaping;
	XPtrV w;
	struct source *s, *sold;

	if (slen < 0)
		return 0;

	toglob = add_glob(str, slen);

	/* remove all escaping backward slashes */
	escaping = 0;
	for (i = 0, idx = 0; toglob[i]; i++) {
		if (toglob[i] == '\\' && !escaping) {
			escaping = 1;
			continue;
		}
		/* specially escape escaped [ for globbing */
		if (escaping && toglob[i] == '[')
			toglob[idx++] = QCHAR;

		toglob[idx] = toglob[i];
		idx++;
		if (escaping)
			escaping = 0;
	}
	toglob[idx] = '\0';

	/*
	 * Convert "foo*" (toglob) to an array of strings (words)
	 */
	sold = source;
	s = pushs(SWSTR, ATEMP);
	s->start = s->str = toglob;
	source = s;
	if (yylex(ONEWORD | LQCHAR) != LWORD) {
		source = sold;
		internal_warningf("fileglob: substitute error");
		return 0;
	}
	source = sold;
	XPinit(w, 32);
	expand(yylval.cp, &w, DOGLOB | DOTILDE | DOMARKDIRS);
	XPput(w, NULL);
	words = (char **)XPclose(w);

	for (nwords = 0; words[nwords]; nwords++)
		;
	if (nwords == 1) {
		struct stat statb;

		/* Check if globbing failed (returned glob pattern),
		 * but be careful (E.g. toglob == "ab*" when the file
		 * "ab*" exists is not an error).
		 * Also, check for empty result - happens if we tried
		 * to glob something which evaluated to an empty
		 * string (e.g., "$FOO" when there is no FOO, etc).
		 */
		if ((strcmp(words[0], toglob) == 0 &&
		    stat(words[0], &statb) < 0) ||
		    words[0][0] == '\0') {
			x_free_words(nwords, words);
			words = NULL;
			nwords = 0;
		}
	}
	afree(toglob, ATEMP);

	if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
		x_free_words(nwords, words);

	return nwords;
}

/* Data structure used in x_command_glob() */
struct path_order_info {
	char *word;
	int base;
	int path_order;
};

/* Compare routine used in x_command_glob() */
static int
path_order_cmp(const void *aa, const void *bb)
{
	const struct path_order_info *a = (const struct path_order_info *)aa;
	const struct path_order_info *b = (const struct path_order_info *)bb;
	int t;

	t = strcmp(a->word + a->base, b->word + b->base);
	return t ? t : a->path_order - b->path_order;
}

static int
x_command_glob(int flags, const char *str, int slen, char ***wordsp)
{
	char *toglob, *pat, *fpath;
	int nwords;
	XPtrV w;
	struct block *l;

	if (slen < 0)
		return 0;

	toglob = add_glob(str, slen);

	/* Convert "foo*" (toglob) to a pattern for future use */
	pat = evalstr(toglob, DOPAT | DOTILDE);
	afree(toglob, ATEMP);

	XPinit(w, 32);

	glob_table(pat, &w, &keywords);
	glob_table(pat, &w, &aliases);
	glob_table(pat, &w, &builtins);
	for (l = e->loc; l; l = l->next)
		glob_table(pat, &w, &l->funs);

	glob_path(flags, pat, &w, path);
	if ((fpath = str_val(global("FPATH"))) != null)
		glob_path(flags, pat, &w, fpath);

	nwords = XPsize(w);

	if (!nwords) {
		*wordsp = NULL;
		XPfree(w);
		return 0;
	}
	/* Sort entries */
	if (flags & XCF_FULLPATH) {
		/* Sort by basename, then path order */
		struct path_order_info *info, *last_info = NULL;
		char **words = (char **)XPptrv(w);
		int i, path_order = 0;

		info = (struct path_order_info *)
		    alloc(nwords, sizeof (struct path_order_info), ATEMP);
		for (i = 0; i < nwords; i++) {
			info[i].word = words[i];
			info[i].base = x_basename(words[i], NULL);
			if (!last_info || info[i].base != last_info->base ||
			    strncmp(words[i], last_info->word, info[i].base) != 0) {
				last_info = &info[i];
				path_order++;
			}
			info[i].path_order = path_order;
		}
		qsort(info, nwords, sizeof(struct path_order_info),
		    path_order_cmp);
		for (i = 0; i < nwords; i++)
			words[i] = info[i].word;
		afree(info, ATEMP);
	} else {
		/* Sort and remove duplicate entries */
		char **words = (char **)XPptrv(w);
		int i, j;

		qsort(words, nwords, sizeof (void *), xstrcmp);
		for (i = j = 0; i < nwords - 1; i++) {
			if (strcmp(words[i], words[i + 1]))
				words[j++] = words[i];
			else
				afree(words[i], ATEMP);
		}
		words[j++] = words[i];
		nwords = j;
		w.cur = (void **)&words[j];
	}

	XPput(w, NULL);
	*wordsp = (char **)XPclose(w);

	return nwords;
}

#define IS_WORDC(c)	(!ctype(c, C_LEX1) && (c) != '\'' && (c) != '"' && \
			    (c) != '`' && (c) != '=' && (c) != ':')

static int
x_locate_word(const char *buf, int buflen, int pos, int *startp,
    bool *is_commandp)
{
	int start, end;

	/* Bad call?  Probably should report error */
	if (pos < 0 || pos > buflen) {
		*startp = pos;
		*is_commandp = false;
		return 0;
	}
	/* The case where pos == buflen happens to take care of itself... */

	start = pos;
	/* Keep going backwards to start of word (has effect of allowing
	 * one blank after the end of a word)
	 */
	for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
	    (start > 1 && buf[start - 2] == '\\'); start--)
		;
	/* Go forwards to end of word */
	for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
		if (buf[end] == '\\' && (end + 1) < buflen)
			end++;
	}

	if (is_commandp) {
		bool iscmd;
		int p = start - 1;

		/* Figure out if this is a command */
		while (p >= 0 && ksh_isspace(buf[p]))
			p--;
		iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
		if (iscmd) {
			/* If command has a /, path, etc. is not searched;
			 * only current directory is searched which is just
			 * like file globbing.
			 */
			for (p = start; p < end; p++)
				if (buf[p] == '/')
					break;
			iscmd = p == end;
		}
		*is_commandp = iscmd;
	}
	*startp = start;

	return end - start;
}

static int
x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
    int *endp, char ***wordsp, bool *is_commandp)
{
	int len, nwords;
	char **words = NULL;
	bool is_command;

	len = x_locate_word(buf, buflen, pos, startp, &is_command);
	if (!(flags & XCF_COMMAND))
		is_command = false;
	/* Don't do command globing on zero length strings - it takes too
	 * long and isn't very useful.  File globs are more likely to be
	 * useful, so allow these.
	 */
	if (len == 0 && is_command)
		return 0;

	nwords = is_command ?
	    x_command_glob(flags, buf + *startp, len, &words) :
	    x_file_glob(flags, buf + *startp, len, &words);
	if (nwords == 0) {
		*wordsp = NULL;
		return 0;
	}
	if (is_commandp)
		*is_commandp = is_command;
	*wordsp = words;
	*endp = *startp + len;

	return nwords;
}

/* Given a string, copy it and possibly add a '*' to the end.  The
 * new string is returned.
 */
static char *
add_glob(const char *str, int slen)
{
	char *toglob, *s;
	bool saw_slash = false;

	if (slen < 0)
		return NULL;

	strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
	toglob[slen] = '\0';

	/*
	 * If the pathname contains a wildcard (an unquoted '*',
	 * '?', or '[') or parameter expansion ('$'), or a ~username
	 * with no trailing slash, then it is globbed based on that
	 * value (i.e., without the appended '*').
	 */
	for (s = toglob; *s; s++) {
		if (*s == '\\' && s[1])
			s++;
		else if (*s == '*' || *s == '[' || *s == '?' || *s == '$'
		    || (s[1] == '(' && vstrchr("*+?@!", *s)))
			break;
		else if (*s == '/')
			saw_slash = true;
	}
	if (!*s && (*toglob != '~' || saw_slash)) {
		toglob[slen] = '*';
		toglob[slen + 1] = '\0';
	}
	return toglob;
}

/*
 * Find longest common prefix
 */
static int
x_longest_prefix(int nwords, char * const * words)
{
	int i, j, prefix_len;
	char *p;

	if (nwords <= 0)
		return 0;

	prefix_len = strlen(words[0]);
	for (i = 1; i < nwords; i++)
		for (j = 0, p = words[i]; j < prefix_len; j++)
			if (p[j] != words[0][j]) {
				prefix_len = j;
				break;
			}
	return prefix_len;
}

static void
x_free_words(int nwords, char **words)
{
	while (nwords)
		afree(words[--nwords], ATEMP);
	afree(words, ATEMP);
}

/* Return the offset of the basename of string s (which ends at se - need not
 * be null terminated).  Trailing slashes are ignored.  If s is just a slash,
 * then the offset is 0 (actually, length - 1).
 *	s		Return
 *	/etc		1
 *	/etc/		1
 *	/etc//		1
 *	/etc/fo		5
 *	foo		0
 *	///		2
 *			0
 */
static int
x_basename(const char *s, const char *se)
{
	const char *p;

	if (se == NULL)
		se = s + strlen(s);
	if (s == se)
		return 0;

	/* Skip trailing slashes */
	for (p = se - 1; p > s && *p == '/'; p--)
		;
	for (; p > s && *p != '/'; p--)
		;
	if (*p == '/' && p + 1 < se)
		p++;

	return p - s;
}

/*
 *  Apply pattern matching to a table: all table entries that match a pattern
 * are added to wp.
 */
static void
glob_table(const char *pat, XPtrV *wp, struct table *tp)
{
	struct tstate ts;
	struct tbl *te;

	ktwalk(&ts, tp);
	while ((te = ktnext(&ts)))
		if (gmatchx(te->name, pat, false)) {
			char *cp;

			strdupx(cp, te->name, ATEMP);
			XPput(*wp, cp);
		}
}

static void
glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
{
	const char *sp, *p;
	char *xp, **words;
	int staterr, pathlen, patlen, oldsize, newsize, i, j;
	XString xs;

	patlen = strlen(pat) + 1;
	sp = lpath;
	Xinit(xs, xp, patlen + 128, ATEMP);
	while (sp) {
		xp = Xstring(xs, xp);
		if (!(p = cstrchr(sp, ':')))
			p = sp + strlen(sp);
		pathlen = p - sp;
		if (pathlen) {
			/* Copy sp into xp, stuffing any MAGIC characters
			 * on the way
			 */
			const char *s = sp;

			XcheckN(xs, xp, pathlen * 2);
			while (s < p) {
				if (ISMAGIC(*s))
					*xp++ = MAGIC;
				*xp++ = *s++;
			}
			*xp++ = '/';
			pathlen++;
		}
		sp = p;
		XcheckN(xs, xp, patlen);
		memcpy(xp, pat, patlen);

		oldsize = XPsize(*wp);
		glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
		newsize = XPsize(*wp);

		/* Check that each match is executable... */
		words = (char **)XPptrv(*wp);
		for (i = j = oldsize; i < newsize; i++) {
			staterr = 0;
			if ((search_access(words[i], X_OK, &staterr) >= 0) ||
			    (staterr == EISDIR)) {
				words[j] = words[i];
				if (!(flags & XCF_FULLPATH))
					memmove(words[j], words[j] + pathlen,
					    strlen(words[j] + pathlen) + 1);
				j++;
			} else
				afree(words[i], ATEMP);
		}
		wp->cur = (void **)&words[j];

		if (!*sp++)
			break;
	}
	Xfree(xs, xp);
}

/*
 * if argument string contains any special characters, they will
 * be escaped and the result will be put into edit buffer by
 * keybinding-specific function
 */
static int
x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t))
{
	size_t add = 0, wlen = len;
	const char *ifs = str_val(local("IFS", 0));
	int rval = 0;

	while (wlen - add > 0)
		if (vstrchr("\\$()[?{}*&;#|<>\"'`", s[add]) ||
		    vstrchr(ifs, s[add])) {
			if (putbuf_func(s, add) != 0) {
				rval = -1;
				break;
			}
			putbuf_func(s[add] == '\n' ? "'" : "\\", 1);
			putbuf_func(&s[add], 1);
			if (s[add] == '\n')
				putbuf_func("'", 1);

			add++;
			wlen -= add;
			s += add;
			add = 0;
		} else
			++add;
	if (wlen > 0 && rval == 0)
		rval = putbuf_func(s, wlen);

	return (rval);
}

/* UTF-8 hack: high-level functions */

int
utf_widthadj(const char *src, const char **dst)
{
	size_t len;
	unsigned int wc;
	int width;

	if (!Flag(FUTFHACK) || (len = utf_mbtowc(&wc, src)) == (size_t)-1 ||
	    wc == 0)
		len = width = 1;
	else
		width = utf_wcwidth(wc);

	if (dst)
		*dst = src + len;
	return (width == -1 ? 2 : width);
}

int
utf_mbswidth(const char *s)
{
	size_t len;
	unsigned int wc;
	int width = 0, cw;

	if (!Flag(FUTFHACK))
		return (strlen(s));

	while (*s)
		if (((len = utf_mbtowc(&wc, s)) == (size_t)-1) ||
		    ((cw = utf_wcwidth(wc)) == -1)) {
			s++;
			width += 1;
		} else {
			s += len;
			width += cw;
		}
	return (width);
}

const char *
utf_skipcols(const char *p, int cols)
{
	int c = 0;

	while (c < cols)
		c += utf_widthadj(p, &p);
	return (p);
}

/* UTF-8 hack: low-level functions */

/* --- begin of wcwidth.c excerpt --- */
/*-
 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
 *
 * Permission to use, copy, modify, and distribute this software
 * for any purpose and without fee is hereby granted. The author
 * disclaims all warranties with regard to this software.
 */

__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $");

int
utf_wcwidth(unsigned int c)
{
	static const struct cbset {
		unsigned short first;
		unsigned short last;
	} comb[] = {
		{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
		{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
		{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
		{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
		{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
		{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
		{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
		{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
		{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
		{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
		{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
		{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
		{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
		{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
		{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
		{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
		{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
		{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
		{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
		{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
		{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
		{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
		{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
		{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
		{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
		{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
		{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
		{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
		{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
		{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
		{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
		{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
		{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
		{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
		{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
		{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
		{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
		{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
		{ 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
		{ 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
		{ 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }
	};
	size_t min = 0, mid, max = NELEM(comb) - 1;

	/* test for 8-bit control characters */
	if (c < 32 || (c >= 0x7f && c < 0xa0))
		return (c ? -1 : 0);

	/* binary search in table of non-spacing characters */
	if (c >= comb[0].first && c <= comb[max].last)
		while (max >= min) {
			mid = (min + max) / 2;
			if (c > comb[mid].last)
				min = mid + 1;
			else if (c < comb[mid].first)
				max = mid - 1;
			else
				return (0);
		}

	/* if we arrive here, c is not a combining or C0/C1 control char */
	return ((c >= 0x1100 && (
	    c <= 0x115f || /* Hangul Jamo init. consonants */
	    c == 0x2329 || c == 0x232a ||
	    (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */
	    (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */
	    (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */
	    (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */
	    (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */
	    (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */
	    (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1);
}
/* --- end of wcwidth.c excerpt --- */

/* +++ CESU-8 multibyte and wide character conversion crafted for mksh +++ */

size_t
utf_mbtowc(unsigned int *dst, const char *src)
{
	const unsigned char *s = (const unsigned char *)src;
	unsigned int c, wc;

	if ((wc = *s++) < 0x80) {
 out:
		if (dst != NULL)
			*dst = wc;
		return (wc ? ((const char *)s - src) : 0);
	}
	if (wc < 0xC2 || wc >= 0xF0)
		/* < 0xC0: spurious second byte */
		/* < 0xC2: non-minimalistic mapping error in 2-byte seqs */
		/* > 0xEF: beyond BMP */
		goto ilseq;

	if (wc < 0xE0) {
		wc = (wc & 0x1F) << 6;
		if (((c = *s++) & 0xC0) != 0x80)
			goto ilseq;
		wc |= c & 0x3F;
		goto out;
	}

	wc = (wc & 0x0F) << 12;

	if (((c = *s++) & 0xC0) != 0x80)
		goto ilseq;
	wc |= (c & 0x3F) << 6;

	if (((c = *s++) & 0xC0) != 0x80)
		goto ilseq;
	wc |= c & 0x3F;

	/* Check for non-minimalistic mapping error in 3-byte seqs */
	if (wc >= 0x0800 && wc <= 0xFFFD)
		goto out;
 ilseq:
	return ((size_t)(-1));
}

size_t
utf_wctomb(char *dst, unsigned int wc)
{
	unsigned char *d;

	if (wc < 0x80) {
		*dst = wc;
		return (1);
	}

	d = (unsigned char *)dst;
	if (wc < 0x0800)
		*d++ = (wc >> 6) | 0xC0;
	else {
		*d++ = ((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0;
		*d++ = ((wc >> 6) & 0x3F) | 0x80;
	}
	*d++ = (wc & 0x3F) | 0x80;
	return ((char *)d - dst);
}

/* +++ emacs editing mode +++ */

static PArea AEDIT;		/* area for kill ring and macro defns */

#define	MKCTRL(x)	((x) == '?' ? 0x7F : (x) & 0x1F)	/* ASCII */
#define	UNCTRL(x)	((x) ^ 0x40)				/* ASCII */

/* values returned by keyboard functions */
#define	KSTD	0
#define	KEOL	1		/* ^M, ^J */
#define	KINTR	2		/* ^G, ^C */

struct x_ftab {
	int (*xf_func)(int c);
	const char *xf_name;
	short xf_flags;
};

struct x_defbindings {
	unsigned char xdb_func;	/* XFUNC_* */
	unsigned char xdb_tab;
	unsigned char xdb_char;
};

#define XF_ARG		1	/* command takes number prefix */
#define	XF_NOBIND	2	/* not allowed to bind to function */
#define	XF_PREFIX	4	/* function sets prefix */

/* Separator for completion */
#define	is_cfs(c)	((c) == ' ' || (c) == '\t' || (c) == '"' || (c) == '\'')
			/* Separator for motion */
#define	is_mfs(c)	(!(ksh_isalnux(c) || (c) == '$'))

#define X_NTABS		3			/* normal, meta1, meta2 */
#define X_TABSZ		256			/* size of keydef tables etc */

/* Arguments for do_complete()
 * 0 = enumerate  M-= complete as much as possible and then list
 * 1 = complete   M-Esc
 * 2 = list       M-?
 */
typedef enum {
	CT_LIST,	/* list the possible completions */
	CT_COMPLETE,	/* complete to longest prefix */
	CT_COMPLIST	/* complete and then list (if non-exact) */
} Comp_type;

/*
 * The following are used for my horizontal scrolling stuff
 */
static char *xbuf;		/* beg input buffer */
static char *xend;		/* end input buffer */
static char *xcp;		/* current position */
static char *xep;		/* current end */
static char *xbp;		/* start of visible portion of input buffer */
static char *xlp;		/* last char visible on screen */
static int x_adj_ok;
/*
 * we use x_adj_done so that functions can tell
 * whether x_adjust() has been called while they are active.
 */
static int x_adj_done;

static int xx_cols;
static int x_col;
static int x_displen;
static int x_arg;		/* general purpose arg */
static int x_arg_defaulted;	/* x_arg not explicitly set; defaulted to 1 */

static int xlp_valid;

static char **x_histp;		/* history position */
static int x_nextcmd;		/* for newline-and-next */
static char *xmp;		/* mark pointer */
static unsigned char x_last_command;
static unsigned char (*x_tab)[X_TABSZ];	/* key definition */
static char *(*x_atab)[X_TABSZ];	/* macro definitions */
static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8];
#define KILLSIZE	20
static char *killstack[KILLSIZE];
static int killsp, killtp;
static int x_curprefix;
static char *macroptr;
#ifndef MKSH_NOVI
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 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 int holdlen;		/* length of holdbuf */
#endif
static int prompt_redraw;	/* 0 if newline forced after prompt */

static int x_ins(const char *);
static void x_delete(int, int);
static int x_bword(void);
static int x_fword(int);
static void x_goto(char *);
static void x_bs3(char **);
static int x_size_str(char *);
static int x_size2(char *, char **);
static void x_zots(char *);
static void x_zotc2(int);
static void x_zotc3(char **);
static void x_load_hist(char **);
static int x_search(char *, int, int);
static int x_match(char *, char *);
static void x_redraw(int);
static void x_push(int);
static char *x_mapin(const char *, PArea);
static char *x_mapout(int);
static void x_mapout2(int, char **);
static void x_print(int, int);
static void x_adjust(void);
static void x_e_ungetc(int);
static int x_e_getc(void);
static void x_e_putc2(int);
static void x_e_putc3(const char **);
static void x_e_puts(const char *);
static int x_fold_case(int);
static char *x_lastcp(void);
static void do_complete(int, Comp_type);

static int unget_char = -1;

static int x_do_ins(const char *, size_t);
static void bind_if_not_bound(int, int, int);

#define XFUNC_abort 0
#define XFUNC_beg_hist 1
#define XFUNC_cls 2
#define XFUNC_comp_comm 3
#define XFUNC_comp_file 4
#define XFUNC_complete 5
#define XFUNC_del_back 6
#define XFUNC_del_bword 7
#define XFUNC_del_char 8
#define XFUNC_del_fword 9
#define XFUNC_del_line 10
#define XFUNC_draw_line 11
#define XFUNC_end_hist 12
#define XFUNC_end_of_text 13
#define XFUNC_enumerate 14
#define XFUNC_eot_del 15
#define XFUNC_error 16
#define XFUNC_goto_hist 17
#define XFUNC_ins_string 18
#define XFUNC_insert 19
#define XFUNC_kill 20
#define XFUNC_kill_region 21
#define XFUNC_list_comm 22
#define XFUNC_list_file 23
#define XFUNC_literal 24
#define XFUNC_meta1 25
#define XFUNC_meta2 26
#define XFUNC_meta_yank 27
#define XFUNC_mv_back 28
#define XFUNC_mv_begin 29
#define XFUNC_mv_bword 30
#define XFUNC_mv_end 31
#define XFUNC_mv_forw 32
#define XFUNC_mv_fword 33
#define XFUNC_newline 34
#define XFUNC_next_com 35
#define XFUNC_nl_next_com 36
#define XFUNC_noop 37
#define XFUNC_prev_com 38
#define XFUNC_prev_histword 39
#define XFUNC_search_char_forw 40
#define XFUNC_search_char_back 41
#define XFUNC_search_hist 42
#define XFUNC_set_mark 43
#define XFUNC_transpose 44
#define XFUNC_xchg_point_mark 45
#define XFUNC_yank 46
#define XFUNC_comp_list 47
#define XFUNC_expand 48
#define XFUNC_fold_capitalise 49
#define XFUNC_fold_lower 50
#define XFUNC_fold_upper 51
#define XFUNC_set_arg 52
#define XFUNC_comment 53
#define XFUNC_version 54
#define XFUNC_edit_line 55

/* XFUNC_* must be < 128 */

static int x_abort(int);
static int x_beg_hist(int);
static int x_cls(int);
static int x_comp_comm(int);
static int x_comp_file(int);
static int x_complete(int);
static int x_del_back(int);
static int x_del_bword(int);
static int x_del_char(int);
static int x_del_fword(int);
static int x_del_line(int);
static int x_draw_line(int);
static int x_end_hist(int);
static int x_end_of_text(int);
static int x_enumerate(int);
static int x_eot_del(int);
static int x_error(int);
static int x_goto_hist(int);
static int x_ins_string(int);
static int x_insert(int);
static int x_kill(int);
static int x_kill_region(int);
static int x_list_comm(int);
static int x_list_file(int);
static int x_literal(int);
static int x_meta1(int);
static int x_meta2(int);
static int x_meta_yank(int);
static int x_mv_back(int);
static int x_mv_begin(int);
static int x_mv_bword(int);
static int x_mv_end(int);
static int x_mv_forw(int);
static int x_mv_fword(int);
static int x_newline(int);
static int x_next_com(int);
static int x_nl_next_com(int);
static int x_noop(int);
static int x_prev_com(int);
static int x_prev_histword(int);
static int x_search_char_forw(int);
static int x_search_char_back(int);
static int x_search_hist(int);
static int x_set_mark(int);
static int x_transpose(int);
static int x_xchg_point_mark(int);
static int x_yank(int);
static int x_comp_list(int);
static int x_expand(int);
static int x_fold_capitalise(int);
static int x_fold_lower(int);
static int x_fold_upper(int);
static int x_set_arg(int);
static int x_comment(int);
static int x_version(int);
static int x_edit_line(int);

static const struct x_ftab x_ftab[] = {
	{ x_abort,		"abort",			0 },
	{ x_beg_hist,		"beginning-of-history",		0 },
	{ x_cls,		"clear-screen",			0 },
	{ x_comp_comm,		"complete-command",		0 },
	{ x_comp_file,		"complete-file",		0 },
	{ x_complete,		"complete",			0 },
	{ x_del_back,		"delete-char-backward",		XF_ARG },
	{ x_del_bword,		"delete-word-backward",		XF_ARG },
	{ x_del_char,		"delete-char-forward",		XF_ARG },
	{ x_del_fword,		"delete-word-forward",		XF_ARG },
	{ x_del_line,		"kill-line",			0 },
	{ x_draw_line,		"redraw",			0 },
	{ x_end_hist,		"end-of-history",		0 },
	{ x_end_of_text,	"eot",				0 },
	{ x_enumerate,		"list",				0 },
	{ x_eot_del,		"eot-or-delete",		XF_ARG },
	{ x_error,		"error",			0 },
	{ x_goto_hist,		"goto-history",			XF_ARG },
	{ x_ins_string,		"macro-string",			XF_NOBIND },
	{ x_insert,		"auto-insert",			XF_ARG },
	{ x_kill,		"kill-to-eol",			XF_ARG },
	{ x_kill_region,	"kill-region",			0 },
	{ x_list_comm,		"list-command",			0 },
	{ x_list_file,		"list-file",			0 },
	{ x_literal,		"quote",			0 },
	{ x_meta1,		"prefix-1",			XF_PREFIX },
	{ x_meta2,		"prefix-2",			XF_PREFIX },
	{ x_meta_yank,		"yank-pop",			0 },
	{ x_mv_back,		"backward-char",		XF_ARG },
	{ x_mv_begin,		"beginning-of-line",		0 },
	{ x_mv_bword,		"backward-word",		XF_ARG },
	{ x_mv_end,		"end-of-line",			0 },
	{ x_mv_forw,		"forward-char",			XF_ARG },
	{ x_mv_fword,		"forward-word",			XF_ARG },
	{ x_newline,		"newline",			0 },
	{ x_next_com,		"down-history",			XF_ARG },
	{ x_nl_next_com,	"newline-and-next",		0 },
	{ x_noop,		"no-op",			0 },
	{ x_prev_com,		"up-history",			XF_ARG },
	{ x_prev_histword,	"prev-hist-word",		XF_ARG },
	{ x_search_char_forw,	"search-character-forward",	XF_ARG },
	{ x_search_char_back,	"search-character-backward",	XF_ARG },
	{ x_search_hist,	"search-history",		0 },
	{ x_set_mark,		"set-mark-command",		0 },
	{ x_transpose,		"transpose-chars",		0 },
	{ x_xchg_point_mark,	"exchange-point-and-mark",	0 },
	{ x_yank,		"yank",				0 },
	{ x_comp_list,		"complete-list",		0 },
	{ x_expand,		"expand-file",			0 },
	{ x_fold_capitalise,	"capitalize-word",		XF_ARG },
	{ x_fold_lower,		"downcase-word",		XF_ARG },
	{ x_fold_upper,		"upcase-word",			XF_ARG },
	{ x_set_arg,		"set-arg",			XF_NOBIND },
	{ x_comment,		"comment",			0 },
	{ x_version,		"version",			0 },
	{ x_edit_line,		"edit-line",			XF_ARG },
	{ 0,			NULL,				0 }
};

static struct x_defbindings const x_defbindings[] = {
	{ XFUNC_del_back,		0, MKCTRL('?')	},
	{ XFUNC_del_bword,		1, MKCTRL('?')	},
	{ XFUNC_eot_del,		0, MKCTRL('D')	},
	{ XFUNC_del_back,		0, MKCTRL('H')	},
	{ XFUNC_del_bword,		1, MKCTRL('H')	},
	{ XFUNC_del_bword,		1,	  'h'	},
	{ XFUNC_mv_bword,		1,	  'b'	},
	{ XFUNC_mv_fword,		1,	  'f'	},
	{ XFUNC_del_fword,		1,	  'd'	},
	{ XFUNC_mv_back,		0, MKCTRL('B')	},
	{ XFUNC_mv_forw,		0, MKCTRL('F')	},
	{ XFUNC_search_char_forw,	0, MKCTRL(']')	},
	{ XFUNC_search_char_back,	1, MKCTRL(']')	},
	{ XFUNC_newline,		0, MKCTRL('M')	},
	{ XFUNC_newline,		0, MKCTRL('J')	},
	{ XFUNC_end_of_text,		0, MKCTRL('_')	},
	{ XFUNC_abort,			0, MKCTRL('G')	},
	{ XFUNC_prev_com,		0, MKCTRL('P')	},
	{ XFUNC_next_com,		0, MKCTRL('N')	},
	{ XFUNC_nl_next_com,		0, MKCTRL('O')	},
	{ XFUNC_search_hist,		0, MKCTRL('R')	},
	{ XFUNC_beg_hist,		1,	  '<'	},
	{ XFUNC_end_hist,		1,	  '>'	},
	{ XFUNC_goto_hist,		1,	  'g'	},
	{ XFUNC_mv_end,			0, MKCTRL('E')	},
	{ XFUNC_mv_begin,		0, MKCTRL('A')	},
	{ XFUNC_draw_line,		0, MKCTRL('L')	},
	{ XFUNC_cls,			1, MKCTRL('L')	},
	{ XFUNC_meta1,			0, MKCTRL('[')	},
	{ XFUNC_meta2,			0, MKCTRL('X')	},
	{ XFUNC_kill,			0, MKCTRL('K')	},
	{ XFUNC_yank,			0, MKCTRL('Y')	},
	{ XFUNC_meta_yank,		1,	  'y'	},
	{ XFUNC_literal,		0, MKCTRL('^')	},
	{ XFUNC_comment,		1,	  '#'	},
	{ XFUNC_transpose,		0, MKCTRL('T')	},
	{ XFUNC_complete,		1, MKCTRL('[')	},
	{ XFUNC_comp_list,		0, MKCTRL('I')	},
	{ XFUNC_comp_list,		1,	  '='	},
	{ XFUNC_enumerate,		1,	  '?'	},
	{ XFUNC_expand,			1,	  '*'	},
	{ XFUNC_comp_file,		1, MKCTRL('X')	},
	{ XFUNC_comp_comm,		2, MKCTRL('[')	},
	{ XFUNC_list_comm,		2,	  '?'	},
	{ XFUNC_list_file,		2, MKCTRL('Y')	},
	{ XFUNC_set_mark,		1,	  ' '	},
	{ XFUNC_kill_region,		0, MKCTRL('W')	},
	{ XFUNC_xchg_point_mark,	2, MKCTRL('X')	},
	{ XFUNC_literal,		0, MKCTRL('V')	},
	{ XFUNC_version,		1, MKCTRL('V')	},
	{ XFUNC_prev_histword,		1,	  '.'	},
	{ XFUNC_prev_histword,		1,	  '_'	},
	{ XFUNC_set_arg,		1,	  '0'	},
	{ XFUNC_set_arg,		1,	  '1'	},
	{ XFUNC_set_arg,		1,	  '2'	},
	{ XFUNC_set_arg,		1,	  '3'	},
	{ XFUNC_set_arg,		1,	  '4'	},
	{ XFUNC_set_arg,		1,	  '5'	},
	{ XFUNC_set_arg,		1,	  '6'	},
	{ XFUNC_set_arg,		1,	  '7'	},
	{ XFUNC_set_arg,		1,	  '8'	},
	{ XFUNC_set_arg,		1,	  '9'	},
	{ XFUNC_fold_upper,		1,	  'U'	},
	{ XFUNC_fold_upper,		1,	  'u'	},
	{ XFUNC_fold_lower,		1,	  'L'	},
	{ XFUNC_fold_lower,		1,	  'l'	},
	{ XFUNC_fold_capitalise,	1,	  'C'	},
	{ XFUNC_fold_capitalise,	1,	  'c'	},
	/* These for ansi arrow keys: arguablely shouldn't be here by
	 * default, but its simpler/faster/smaller than using termcap
	 * entries.
	 */
	{ XFUNC_meta2,			1,	  '['	},
	{ XFUNC_meta2,			1,	  'O'	},
	{ XFUNC_prev_com,		2,	  'A'	},
	{ XFUNC_next_com,		2,	  'B'	},
	{ XFUNC_mv_forw,		2,	  'C'	},
	{ XFUNC_mv_back,		2,	  'D'	},
	{ XFUNC_mv_begin | 0x80,	2,	  '1'	},
	{ XFUNC_mv_begin | 0x80,	2,	  '7'	},
	{ XFUNC_mv_begin,		2,	  'H'	},
	{ XFUNC_mv_end | 0x80,		2,	  '4'	},
	{ XFUNC_mv_end | 0x80,		2,	  '8'	},
	{ XFUNC_mv_end,			2,	  'F'	},
	{ XFUNC_del_char | 0x80,	2,	  '3'	},
	/* more non-standard ones */
	{ XFUNC_edit_line,		2,	  'e'	}
};

#ifdef MKSH_SMALL
static void x_modified(void);
static void
x_modified(void)
{
	if (!modified) {
		x_histp = histptr + 1;
		modified = 1;
	}
}
#else
#define x_modified() do {			\
	if (!modified) {			\
		x_histp = histptr + 1;		\
		modified = 1;			\
	}					\
} while (/* CONSTCOND */ 0)
#endif

static int
x_e_getmbc(char *sbuf)
{
	int c, pos = 0;
	unsigned char *buf = (unsigned char *)sbuf;

	memset(buf, 0, 4);
	buf[pos++] = c = x_e_getc();
	if (c == -1)
		return (-1);
	if (Flag(FUTFHACK)) {
		if ((buf[0] >= 0xC2) && (buf[0] < 0xF0)) {
			c = x_e_getc();
			if (c == -1)
				return (-1);
			if ((c & 0xC0) != 0x80) {
				x_e_ungetc(c);
				return (1);
			}
			buf[pos++] = c;
		}
		if ((buf[0] >= 0xE0) && (buf[0] < 0xF0)) {
			/* XXX x_e_ungetc is one-octet only */
			buf[pos++] = c = x_e_getc();
			if (c == -1)
				return (-1);
		}
	}
	return (pos);
}

static void
x_init_prompt(void)
{
	x_col = promptlen(prompt);
	x_adj_ok = 1;
	prompt_redraw = 1;
	if (x_col > xx_cols)
		x_col %= xx_cols;
	x_displen = xx_cols - 2 - x_col;
	x_adj_done = 0;

	pprompt(prompt, 0);
	if (x_displen < 1) {
		x_col = 0;
		x_displen = xx_cols - 2;
		x_e_putc2('\n');
		prompt_redraw = 0;
	}
}

static int
x_emacs(char *buf, size_t len)
{
	int c, i;
	unsigned char f;

	xbp = xbuf = buf; xend = buf + len;
	xlp = xcp = xep = buf;
	*xcp = 0;
	xlp_valid = true;
	xmp = NULL;
	x_curprefix = 0;
	macroptr = NULL;
	x_histp = histptr + 1;
	x_last_command = XFUNC_error;

	xx_cols = x_cols;
	x_init_prompt();

	if (x_nextcmd >= 0) {
		int off = source->line - x_nextcmd;
		if (histptr - history >= off)
			x_load_hist(histptr - off);
		x_nextcmd = -1;
	}
	while (1) {
		x_flush();
		if ((c = x_e_getc()) < 0)
			return 0;

		f = x_curprefix == -1 ? XFUNC_insert :
		    x_tab[x_curprefix][c];
		if (f & 0x80) {
			f &= 0x7F;
			if ((i = x_e_getc()) != '~')
				x_e_ungetc(i);
		}

		if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
		    x_last_command != XFUNC_set_arg) {
			x_arg = 1;
			x_arg_defaulted = 1;
		}
		i = c | (x_curprefix << 8);
		x_curprefix = 0;
		switch ((*x_ftab[f].xf_func)(i)) {
		case KSTD:
			if (!(x_ftab[f].xf_flags & XF_PREFIX))
				x_last_command = f;
			break;
		case KEOL:
			i = xep - xbuf;
			return i;
		case KINTR:	/* special case for interrupt */
			trapsig(SIGINT);
			x_mode(false);
			unwind(LSHELL);
		}
		/* ad-hoc hack for fixing the cursor position */
		x_goto(xcp);
	}
}

static int
x_insert(int c)
{
	static int left = 0, pos, save_arg;
	static char str[4];

	/*
	 *  Should allow tab and control chars.
	 */
	if (c == 0) {
 invmbs:
		left = 0;
		x_e_putc2(7);
		return KSTD;
	}
	if (Flag(FUTFHACK)) {
		if (((c & 0xC0) == 0x80) && left) {
			str[pos++] = c;
			if (!--left) {
				str[pos] = '\0';
				x_arg = save_arg;
				while (x_arg--)
					x_ins(str);
			}
			return (KSTD);
		}
		if (left) {
			if (x_curprefix == -1) {
				/* flush invalid multibyte */
				str[pos] = '\0';
				while (save_arg--)
					x_ins(str);
			}
		}
		if ((c >= 0xC2) && (c < 0xE0))
			left = 1;
		else if ((c >= 0xE0) && (c < 0xF0))
			left = 2;
		else if (c > 0x7F)
			goto invmbs;
		else
			left = 0;
		if (left) {
			save_arg = x_arg;
			pos = 1;
			str[0] = c;
			return (KSTD);
		}
	}
	left = 0;
	str[0] = c;
	str[1] = '\0';
	while (x_arg--)
		x_ins(str);
	return KSTD;
}

static int
x_ins_string(int c)
{
	if (macroptr) {
		x_e_putc2(7);
		return KSTD;
	}
	macroptr = x_atab[c >> 8][c & 255];
	if (macroptr && !*macroptr) {
		/* XXX bell? */
		macroptr = NULL;
	}
	return KSTD;
}

static int
x_do_ins(const char *cp, size_t len)
{
	if (xep + len >= xend) {
		x_e_putc2(7);
		return -1;
	}
	memmove(xcp + len, xcp, xep - xcp + 1);
	memmove(xcp, cp, len);
	xcp += len;
	xep += len;
	x_modified();
	return 0;
}

static int
x_ins(const char *s)
{
	char *cp = xcp;
	int adj = x_adj_done;

	if (x_do_ins(s, strlen(s)) < 0)
		return -1;
	/*
	 * x_zots() may result in a call to x_adjust()
	 * we want xcp to reflect the new position.
	 */
	xlp_valid = false;
	x_lastcp();
	x_adj_ok = (xcp >= xlp);
	x_zots(cp);
	if (adj == x_adj_done) {	/* has x_adjust() been called? */
		/* no */
		cp = xlp;
		while (cp > xcp)
			x_bs3(&cp);
	}
	if (xlp == xep - 1)
		x_redraw(xx_cols);
	x_adj_ok = 1;
	return 0;
}

static int
x_del_back(int c __unused)
{
	int i = 0;

	if (xcp == xbuf) {
		x_e_putc2(7);
		return (KSTD);
	}
	do {
		x_goto(xcp - 1);
	} while ((++i < x_arg) && (xcp != xbuf));
	x_delete(i, false);
	return KSTD;
}

static int
x_del_char(int c __unused)
{
	char *cp, *cp2;
	int i = 0;

	cp = xcp;
	while (i < x_arg) {
		utf_ptradjx(cp, cp2);
		if (cp2 > xep)
			break;
		cp = cp2;
		i++;
	}

	if (!i) {
		x_e_putc2(7);
		return KSTD;
	}
	x_delete(i, false);
	return KSTD;
}

/* Delete nc chars to the right of the cursor (including cursor position) */
static void
x_delete(int nc, int push)
{
	int i, nb, nw;
	char *cp;

	if (nc == 0)
		return;

	nw = 0;
	cp = xcp;
	for (i = 0; i < nc; ++i) {
		char *cp2;
		int j;

		j = x_size2(cp, &cp2);
		if (cp2 > xep)
			break;
		cp = cp2;
		nw += j;
	}
	nb = cp - xcp;
	/* nc = i; */

	if (xmp != NULL && xmp > xcp) {
		if (xcp + nb > xmp)
			xmp = xcp;
		else
			xmp -= nb;
	}
	/*
	 * This lets us yank a word we have deleted.
	 */
	if (push)
		x_push(nb);

	xep -= nb;
	memmove(xcp, xcp + nb, xep - xcp + 1);	/* Copies the NUL */
	x_adj_ok = 0;			/* don't redraw */
	xlp_valid = false;
	x_zots(xcp);
	/*
	 * if we are already filling the line,
	 * there is no need to ' ','\b'.
	 * But if we must, make sure we do the minimum.
	 */
	if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
		nw = i = (nw < i) ? nw : i;
		while (i--)
			x_e_putc2(' ');
		if (x_col == xx_cols - 2) {
			x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' ');
			++nw;
		}
		while (nw--)
			x_e_putc2('\b');
	}
	/*x_goto(xcp);*/
	x_adj_ok = 1;
	xlp_valid = false;
	cp = x_lastcp();
	while (cp > xcp)
		x_bs3(&cp);

	x_modified();
	return;
}

static int
x_del_bword(int c __unused)
{
	x_delete(x_bword(), true);
	return KSTD;
}

static int
x_mv_bword(int c __unused)
{
	x_bword();
	return KSTD;
}

static int
x_mv_fword(int c __unused)
{
	x_fword(1);
	return KSTD;
}

static int
x_del_fword(int c __unused)
{
	x_delete(x_fword(0), true);
	return KSTD;
}

static int
x_bword(void)
{
	int nc = 0, nb = 0;
	char *cp = xcp;

	if (cp == xbuf) {
		x_e_putc2(7);
		return 0;
	}
	while (x_arg--) {
		while (cp != xbuf && is_mfs(cp[-1])) {
			cp--;
			nb++;
		}
		while (cp != xbuf && !is_mfs(cp[-1])) {
			cp--;
			nb++;
		}
	}
	x_goto(cp);
	for (cp = xcp; cp < (xcp + nb); ++nc)
		utf_ptradjx(cp, cp);
	return nc;
}

static int
x_fword(int move)
{
	int nc = 0;
	char *cp = xcp, *cp2;

	if (cp == xep) {
		x_e_putc2(7);
		return 0;
	}
	while (x_arg--) {
		while (cp != xep && is_mfs(*cp))
			cp++;
		while (cp != xep && !is_mfs(*cp))
			cp++;
	}
	for (cp2 = xcp; cp2 < cp; ++nc)
		utf_ptradjx(cp2, cp2);
	if (move)
		x_goto(cp);
	return nc;
}

static void
x_goto(char *cp)
{
	if (Flag(FUTFHACK))
		while ((cp > xbuf) && ((*cp & 0xC0) == 0x80))
			--cp;
	if (cp < xbp || cp >= utf_skipcols(xbp, x_displen)) {
		/* we are heading off screen */
		xcp = cp;
		x_adjust();
	} else if (cp < xcp) {		/* move back */
		while (cp < xcp)
			x_bs3(&xcp);
	} else if (cp > xcp) {		/* move forward */
		while (cp > xcp)
			x_zotc3(&xcp);
	}
}

static void
x_bs3(char **p)
{
	int i;

	(*p)--;
	if (Flag(FUTFHACK))
		while (((unsigned char)**p & 0xC0) == 0x80)
			(*p)--;

	i = x_size2(*p, NULL);
	while (i--)
		x_e_putc2('\b');
}

static int
x_size_str(char *cp)
{
	int size = 0;
	while (*cp)
		size += x_size2(cp, &cp);
	return size;
}

static int
x_size2(char *cp, char **dcp)
{
	int c = *(unsigned char *)cp;

	if (Flag(FUTFHACK) && (c > 0x7F))
		return (utf_widthadj(cp, (const char **)dcp));
	if (dcp)
		*dcp = cp + 1;
	if (c == '\t')
		return 4;	/* Kludge, tabs are always four spaces. */
	if (c < ' ' || c == 0x7f)
		return 2;	/* control unsigned char */
	return 1;
}

static void
x_zots(char *str)
{
	int adj = x_adj_done;

	x_lastcp();
	while (*str && str < xlp && adj == x_adj_done)
		x_zotc3(&str);
}

static void
x_zotc2(int c)
{
	if (c == '\t') {
		/*  Kludge, tabs are always four spaces.  */
		x_e_puts("    ");
	} else if (c < ' ' || c == 0x7f) {
		x_e_putc2('^');
		x_e_putc2(UNCTRL(c));
	} else
		x_e_putc2(c);
}

static void
x_zotc3(char **cp)
{
	unsigned char c = **(unsigned char **)cp;

	if (c == 0xC2 && Flag(FUTFHACK)) {
		unsigned char c2 = ((unsigned char *)*cp)[1];

		if (c2 >= 0x80 && c2 < 0xA0) {
			c = c2;
			(*cp)++;
		}
	}
	if (c == '\t') {
		/*  Kludge, tabs are always four spaces.  */
		x_e_puts("    ");
		(*cp)++;
	} else if (c < ' ' || (c >= 0x7F && c < 0xA0)) {
		x_e_putc2('^');
		x_e_putc2(UNCTRL(c));
		(*cp)++;
	} else
		x_e_putc3((const char **)cp);
}

static int
x_mv_back(int c __unused)
{
	if (xcp == xbuf) {
		x_e_putc2(7);
		return KSTD;
	}
	while (x_arg--) {
		x_goto(xcp - 1);
		if (xcp == xbuf)
			break;
	}
	return KSTD;
}

static int
x_mv_forw(int c __unused)
{
	char *cp = xcp, *cp2;

	if (xcp == xep) {
		x_e_putc2(7);
		return KSTD;
	}
	while (x_arg--) {
		utf_ptradjx(cp, cp2);
		if (cp2 > xep)
			break;
		cp = cp2;
	}
	x_goto(cp);
	return KSTD;
}

static int
x_search_char_forw(int c __unused)
{
	char *cp = xcp;
	char tmp[4];

	*xep = '\0';
	if (x_e_getmbc(tmp) < 0) {
		x_e_putc2(7);
		return KSTD;
	}
	while (x_arg--) {
		if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL &&
		    (cp = strstr(xbuf, tmp)) == NULL) {
			x_e_putc2(7);
			return KSTD;
		}
	}
	x_goto(cp);
	return KSTD;
}

static int
x_search_char_back(int c __unused)
{
	char *cp = xcp, *p, tmp[4];
	bool b;

	if (x_e_getmbc(tmp) < 0) {
		x_e_putc2(7);
		return KSTD;
	}
	for (; x_arg--; cp = p)
		for (p = cp; ; ) {
			if (p-- == xbuf)
				p = xep;
			if (p == cp) {
				x_e_putc2(7);
				return KSTD;
			}
			if ((tmp[1] && ((p+1) > xep)) ||
			    (tmp[2] && ((p+2) > xep)))
				continue;
			b = true;
			if (*p != tmp[0])
				b = false;
			if (b && tmp[1] && p[1] != tmp[1])
				b = false;
			if (b && tmp[2] && p[2] != tmp[2])
				b = false;
			if (b)
				break;
		}
	x_goto(cp);
	return KSTD;
}

static int
x_newline(int c __unused)
{
	x_e_putc2('\r');
	x_e_putc2('\n');
	x_flush();
	*xep++ = '\n';
	return KEOL;
}

static int
x_end_of_text(int c __unused)
{
	x_zotc2(edchars.eof);
	x_putc('\r');
	x_putc('\n');
	x_flush();
	return KEOL;
}

static int
x_beg_hist(int c __unused)
{
	x_load_hist(history);
	return KSTD;
}

static int
x_end_hist(int c __unused)
{
	x_load_hist(histptr);
	return KSTD;
}

static int
x_prev_com(int c __unused)
{
	x_load_hist(x_histp - x_arg);
	return KSTD;
}

static int
x_next_com(int c __unused)
{
	x_load_hist(x_histp + x_arg);
	return KSTD;
}

/* Goto a particular history number obtained from argument.
 * If no argument is given history 1 is probably not what you
 * want so we'll simply go to the oldest one.
 */
static int
x_goto_hist(int c __unused)
{
	if (x_arg_defaulted)
		x_load_hist(history);
	else
		x_load_hist(histptr + x_arg - source->line);
	return KSTD;
}

static void
x_load_hist(char **hp)
{
	int oldsize;
	char *sp = NULL;

	if (hp == histptr + 1) {
		sp = holdbuf;
		modified = 0;
	} else if (hp < history || hp > histptr) {
		x_e_putc2(7);
		return;
	}
	if (sp == NULL)
		sp = *hp;
	x_histp = hp;
	oldsize = x_size_str(xbuf);
	if (modified)
		strlcpy(holdbuf, xbuf, sizeof (holdbuf));
	strlcpy(xbuf, sp, xend - xbuf);
	xbp = xbuf;
	xep = xcp = xbuf + strlen(xbuf);
	xlp_valid = false;
	if (xep <= x_lastcp()) {
		x_redraw(oldsize);
	}
	x_goto(xep);
	modified = 0;
}

static int
x_nl_next_com(int c __unused)
{
	x_nextcmd = source->line - (histptr - x_histp) + 1;
	return (x_newline('\n'));
}

static int
x_eot_del(int c)
{
	if (xep == xbuf && x_arg_defaulted)
		return (x_end_of_text(c));
	else
		return (x_del_char(c));
}

/* reverse incremental history search */
static int
x_search_hist(int c)
{
	int offset = -1;	/* offset of match in xbuf, else -1 */
	char pat[256 + 1];	/* pattern buffer */
	char *p = pat;
	unsigned char f;

	*p = '\0';
	while (1) {
		if (offset < 0) {
			x_e_puts("\nI-search: ");
			x_e_puts(pat);
		}
		x_flush();
		if ((c = x_e_getc()) < 0)
			return KSTD;
		f = x_tab[0][c];
		if (c == MKCTRL('[')) {
			if ((f & 0x7F) == XFUNC_meta1) {
				if ((c = x_e_getc()) < 0)
					return KSTD;
				f = x_tab[1][c] & 0x7F;
				if (f == XFUNC_meta1 || f == XFUNC_meta2)
					x_meta1(MKCTRL('['));
				x_e_ungetc(c);
			}
			break;
		}
		if (f & 0x80) {
			f &= 0x7F;
			if ((c = x_e_getc()) != '~')
				x_e_ungetc(c);
		}
		if (f == XFUNC_search_hist)
			offset = x_search(pat, 0, offset);
		else if (f == XFUNC_del_back) {
			if (p == pat) {
				offset = -1;
				break;
			}
			if (p > pat)
				*--p = '\0';
			if (p == pat)
				offset = -1;
			else
				offset = x_search(pat, 1, offset);
			continue;
		} else if (f == XFUNC_insert) {
			/* add char to pattern */
			/* overflow check... */
			if (p >= &pat[sizeof(pat) - 1]) {
				x_e_putc2(7);
				continue;
			}
			*p++ = c, *p = '\0';
			if (offset >= 0) {
				/* already have partial match */
				offset = x_match(xbuf, pat);
				if (offset >= 0) {
					x_goto(xbuf + offset + (p - pat) -
					    (*pat == '^'));
					continue;
				}
			}
			offset = x_search(pat, 0, offset);
		} else if (f == XFUNC_abort) {
			if (offset >= 0)
				x_load_hist(histptr + 1);
			break;
		} else { /* other command */
			x_e_ungetc(c);
			break;
		}
	}
	if (offset < 0)
		x_redraw(-1);
	return KSTD;
}

/* search backward from current line */
static int
x_search(char *pat, int sameline, int offset)
{
	char **hp;
	int i;

	for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) {
		i = x_match(*hp, pat);
		if (i >= 0) {
			if (offset < 0)
				x_e_putc2('\n');
			x_load_hist(hp);
			x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
			return i;
		}
	}
	x_e_putc2(7);
	x_histp = histptr;
	return -1;
}

/* return position of first match of pattern in string, else -1 */
static int
x_match(char *str, char *pat)
{
	if (*pat == '^') {
		return (strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1;
	} else {
		char *q = strstr(str, pat);
		return (q == NULL) ? -1 : q - str;
	}
}

static int
x_del_line(int c __unused)
{
	int i, j;

	*xep = 0;
	i = xep - xbuf;
	j = x_size_str(xbuf);
	xcp = xbuf;
	x_push(i);
	xlp = xbp = xep = xbuf;
	xlp_valid = true;
	*xcp = 0;
	xmp = NULL;
	x_redraw(j);
	x_modified();
	return KSTD;
}

static int
x_mv_end(int c __unused)
{
	x_goto(xep);
	return KSTD;
}

static int
x_mv_begin(int c __unused)
{
	x_goto(xbuf);
	return KSTD;
}

static int
x_draw_line(int c __unused)
{
	x_redraw(-1);
	return KSTD;
}

static int
x_cls(int c __unused)
{
/* in later versions we might use libtermcap for this */
#ifndef MKSH_CLS_STRING
#define MKSH_CLS_STRING	"\033[;H\033[J"
#endif
	shf_fprintf(shl_out, MKSH_CLS_STRING);
	x_putc('\r');
	x_init_prompt();
	x_redraw(0);
	return (KSTD);
}

/* Redraw (part of) the line.  If limit is < 0, the everything is redrawn
 * on a NEW line, otherwise limit is the screen column up to which needs
 * redrawing.
 */
static void
x_redraw(int limit)
{
	int i, j, x_trunc = 0;
	char *cp;

	x_adj_ok = 0;
	if (limit == -1)
		x_e_putc2('\n');
	else
		x_e_putc2('\r');
	x_flush();
	if (xbp == xbuf) {
		x_col = promptlen(prompt);
		if (x_col > xx_cols)
			x_trunc = (x_col / xx_cols) * xx_cols;
		if (prompt_redraw)
			pprompt(prompt, x_trunc);
	}
	if (x_col > xx_cols)
		x_col %= xx_cols;
	x_displen = xx_cols - 2 - x_col;
	if (x_displen < 1) {
		x_col = 0;
		x_displen = xx_cols - 2;
	}
	xlp_valid = false;
	x_lastcp();
	x_zots(xbp);
	if (xbp != xbuf || xep > xlp)
		limit = xx_cols;
	if (limit >= 0) {
		if (xep > xlp)
			i = 0;			/* we fill the line */
		else {
			char *cpl = xbp;

			i = limit;
			while (cpl < xlp)
				i -= x_size2(cpl, &cpl);
		}

		j = 0;
		while ((j < i) || (x_col < (xx_cols - 2))) {
			if (!(x_col < (xx_cols - 2)))
				break;
			x_e_putc2(' ');
			j++;
		}
		i = ' ';
		if (xep > xlp) {		/* more off screen */
			if (xbp > xbuf)
				i = '*';
			else
				i = '>';
		} else if (xbp > xbuf)
			i = '<';
		x_e_putc2(i);
		j++;
		while (j--)
			x_e_putc2('\b');
	}
	cp = xlp;
	while (cp > xcp)
		x_bs3(&cp);
	x_adj_ok = 1;
	return;
}

static int
x_transpose(int c __unused)
{
	unsigned int tmpa, tmpb;

	/* What transpose is meant to do seems to be up for debate. This
	 * is a general summary of the options; the text is abcd with the
	 * upper case character or underscore indicating the cursor position:
	 *     Who			Before	After  Before	After
	 *     at&t ksh in emacs mode:	abCd	abdC   abcd_	(bell)
	 *     at&t ksh in gmacs mode:	abCd	baCd   abcd_	abdc_
	 *     gnu emacs:		abCd	acbD   abcd_	abdc_
	 * Pdksh currently goes with GNU behavior since I believe this is the
	 * most common version of emacs, unless in gmacs mode, in which case
	 * it does the at&t ksh gmacs mode.
	 * This should really be broken up into 3 functions so users can bind
	 * to the one they want.
	 */
	if (xcp == xbuf) {
		x_e_putc2(7);
		return KSTD;
	} else if (xcp == xep || Flag(FGMACS)) {
		if (xcp - xbuf == 1) {
			x_e_putc2(7);
			return KSTD;
		}
		/* Gosling/Unipress emacs style: Swap two characters before the
		 * cursor, do not change cursor position
		 */
		x_bs3(&xcp);
		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
			x_e_putc2(7);
			return KSTD;
		}
		x_bs3(&xcp);
		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
			x_e_putc2(7);
			return KSTD;
		}
		utf_wctomb(xcp, tmpa);
		x_zotc3(&xcp);
		utf_wctomb(xcp, tmpb);
		x_zotc3(&xcp);
	} else {
		/* GNU emacs style: Swap the characters before and under the
		 * cursor, move cursor position along one.
		 */
		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
			x_e_putc2(7);
			return KSTD;
		}
		x_bs3(&xcp);
		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
			x_e_putc2(7);
			return KSTD;
		}
		utf_wctomb(xcp, tmpa);
		x_zotc3(&xcp);
		utf_wctomb(xcp, tmpb);
		x_zotc3(&xcp);
	}
	x_modified();
	return KSTD;
}

static int
x_literal(int c __unused)
{
	x_curprefix = -1;
	return KSTD;
}

static int
x_meta1(int c __unused)
{
	x_curprefix = 1;
	return KSTD;
}

static int
x_meta2(int c __unused)
{
	x_curprefix = 2;
	return KSTD;
}

static int
x_kill(int c __unused)
{
	int col = xcp - xbuf;
	int lastcol = xep - xbuf;
	int ndel;

	if (x_arg_defaulted)
		x_arg = lastcol;
	else if (x_arg > lastcol)
		x_arg = lastcol;
	ndel = x_arg - col;
	if (ndel < 0) {
		x_goto(xbuf + x_arg);
		ndel = -ndel;
	}
	x_delete(ndel, true);
	return KSTD;
}

static void
x_push(int nchars)
{
	char *cp;

	strndupx(cp, xcp, nchars, AEDIT);
	if (killstack[killsp])
		afree(killstack[killsp], AEDIT);
	killstack[killsp] = cp;
	killsp = (killsp + 1) % KILLSIZE;
}

static int
x_yank(int c __unused)
{
	if (killsp == 0)
		killtp = KILLSIZE;
	else
		killtp = killsp;
	killtp--;
	if (killstack[killtp] == 0) {
		x_e_puts("\nnothing to yank");
		x_redraw(-1);
		return KSTD;
	}
	xmp = xcp;
	x_ins(killstack[killtp]);
	return KSTD;
}

static int
x_meta_yank(int c __unused)
{
	int len;

	if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
	    killstack[killtp] == 0) {
		killtp = killsp;
		x_e_puts("\nyank something first");
		x_redraw(-1);
		return KSTD;
	}
	len = strlen(killstack[killtp]);
	x_goto(xcp - len);
	x_delete(len, false);
	do {
		if (killtp == 0)
			killtp = KILLSIZE - 1;
		else
			killtp--;
	} while (killstack[killtp] == 0);
	x_ins(killstack[killtp]);
	return KSTD;
}

static int
x_abort(int c __unused)
{
	/* x_zotc(c); */
	xlp = xep = xcp = xbp = xbuf;
	xlp_valid = true;
	*xcp = 0;
	x_modified();
	return KINTR;
}

static int
x_error(int c __unused)
{
	x_e_putc2(7);
	return KSTD;
}

static char *
x_mapin(const char *cp, PArea ap)
{
	char *new, *op;

	strdupx(new, cp, ap);
	op = new;
	while (*cp) {
		/* XXX -- should handle \^ escape? */
		if (*cp == '^') {
			cp++;
			if (*cp >= '?')	/* includes '?'; ASCII */
				*op++ = MKCTRL(*cp);
			else {
				*op++ = '^';
				cp--;
			}
		} else
			*op++ = *cp;
		cp++;
	}
	*op = '\0';

	return new;
}

static void
x_mapout2(int c, char **buf)
{
	char *p = *buf;

	if (c < ' ' || c == 0x7f) {
		*p++ = '^';
		*p++ = UNCTRL(c);
	} else
		*p++ = c;
	*p = 0;
	*buf = p;
}

static char *
x_mapout(int c)
{
	static char buf[8];
	char *bp = buf;

	x_mapout2(c, &bp);
	return (buf);
}

static void
x_print(int prefix, int key)
{
	int f = x_tab[prefix][key];

	if (prefix)
		/* prefix == 1 || prefix == 2 */
		shf_puts(x_mapout(prefix == 1 ?
		    MKCTRL('[') : MKCTRL('X')), shl_stdout);
	shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : "");
	if ((f & 0x7F) != XFUNC_ins_string)
		shprintf("%s\n", x_ftab[f & 0x7F].xf_name);
	else
		shprintf("'%s'\n", x_atab[prefix][key]);
}

int
x_bind(const char *a1, const char *a2,
    int macro,			/* bind -m */
    int list)			/* bind -l */
{
	unsigned char f;
	int prefix, key;
	char *sp = NULL, *m1, *m2;
	bool hastilde;

	if (x_tab == NULL) {
		bi_errorf("cannot bind, not a tty");
		return (1);
	}
	/* List function names */
	if (list) {
		for (f = 0; f < NELEM(x_ftab); f++)
			if (x_ftab[f].xf_name &&
			    !(x_ftab[f].xf_flags & XF_NOBIND))
				shprintf("%s\n", x_ftab[f].xf_name);
		return (0);
	}
	if (a1 == NULL) {
		for (prefix = 0; prefix < X_NTABS; prefix++)
			for (key = 0; key < X_TABSZ; key++) {
				f = x_tab[prefix][key] & 0x7F;
				if (f == XFUNC_insert || f == XFUNC_error ||
				    (macro && f != XFUNC_ins_string))
					continue;
				x_print(prefix, key);
			}
		return (0);
	}
	m2 = m1 = x_mapin(a1, ATEMP);
	prefix = key = 0;
	for (;; m1++) {
		key = (unsigned char)*m1;
		f = x_tab[prefix][key] & 0x7F;
		if (f == XFUNC_meta1)
			prefix = 1;
		else if (f == XFUNC_meta2)
			prefix = 2;
		else
			break;
	}
	if (*++m1 && ((*m1 != '~') || *(m1+1))) {
		char msg[256] = "key sequence '";
		const char *c = a1;
		m1 = msg + strlen(msg);
		while (*c && m1 < (msg + sizeof (msg) - 3))
			x_mapout2(*c++, &m1);
		bi_errorf("%s' too long", msg);
		return (1);
	}
	hastilde = *m1;
	afree(m2, ATEMP);

	if (a2 == NULL) {
		x_print(prefix, key);
		return (0);
	}
	if (*a2 == 0)
		f = XFUNC_insert;
	else if (!macro) {
		for (f = 0; f < NELEM(x_ftab); f++)
			if (x_ftab[f].xf_name &&
			    strcmp(x_ftab[f].xf_name, a2) == 0)
				break;
		if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
			bi_errorf("%s: no such function", a2);
			return (1);
		}
	} else {
		f = XFUNC_ins_string;
		sp = x_mapin(a2, AEDIT);
	}

	if ((x_tab[prefix][key] & 0x7F) == XFUNC_ins_string &&
	    x_atab[prefix][key])
		afree(x_atab[prefix][key], AEDIT);
	x_tab[prefix][key] = f | (hastilde ? 0x80 : 0);
	x_atab[prefix][key] = sp;

	/* Track what the user has bound so x_mode(true) won't toast things */
	if (f == XFUNC_insert)
		x_bound[(prefix * X_TABSZ + key) / 8] &=
		    ~(1 << ((prefix * X_TABSZ + key) % 8));
	else
		x_bound[(prefix * X_TABSZ + key) / 8] |=
		    (1 << ((prefix * X_TABSZ + key) % 8));

	return (0);
}

static void
x_init_emacs(void)
{
	int i, j;

	AEDIT = anew();
	x_nextcmd = -1;

	x_tab = alloc(X_NTABS, sizeof (*x_tab), AEDIT);
	for (j = 0; j < X_TABSZ; j++)
		x_tab[0][j] = XFUNC_insert;
	for (i = 1; i < X_NTABS; i++)
		for (j = 0; j < X_TABSZ; j++)
			x_tab[i][j] = XFUNC_error;
	for (i = 0; i < (int)NELEM(x_defbindings); i++)
		x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
		    = x_defbindings[i].xdb_func;

	x_atab = alloc(X_NTABS, sizeof (*x_atab), AEDIT);
	for (i = 1; i < X_NTABS; i++)
		for (j = 0; j < X_TABSZ; j++)
			x_atab[i][j] = NULL;
}

static void
bind_if_not_bound(int p, int k, int func)
{
	/* Has user already bound this key?  If so, don't override it */
	if (x_bound[((p) * X_TABSZ + (k)) / 8] &
	    (1 << (((p) * X_TABSZ + (k)) % 8)))
		return;

	x_tab[p][k] = func;
}

static int
x_set_mark(int c __unused)
{
	xmp = xcp;
	return KSTD;
}

static int
x_kill_region(int c __unused)
{
	int rsize;
	char *xr;

	if (xmp == NULL) {
		x_e_putc2(7);
		return KSTD;
	}
	if (xmp > xcp) {
		rsize = xmp - xcp;
		xr = xcp;
	} else {
		rsize = xcp - xmp;
		xr = xmp;
	}
	x_goto(xr);
	x_delete(rsize, true);
	xmp = xr;
	return KSTD;
}

static int
x_xchg_point_mark(int c __unused)
{
	char *tmp;

	if (xmp == NULL) {
		x_e_putc2(7);
		return KSTD;
	}
	tmp = xmp;
	xmp = xcp;
	x_goto(tmp);
	return KSTD;
}

static int
x_noop(int c __unused)
{
	return KSTD;
}

/*
 *	File/command name completion routines
 */
static int
x_comp_comm(int c __unused)
{
	do_complete(XCF_COMMAND, CT_COMPLETE);
	return KSTD;
}

static int
x_list_comm(int c __unused)
{
	do_complete(XCF_COMMAND, CT_LIST);
	return KSTD;
}

static int
x_complete(int c __unused)
{
	do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
	return KSTD;
}

static int
x_enumerate(int c __unused)
{
	do_complete(XCF_COMMAND_FILE, CT_LIST);
	return KSTD;
}

static int
x_comp_file(int c __unused)
{
	do_complete(XCF_FILE, CT_COMPLETE);
	return KSTD;
}

static int
x_list_file(int c __unused)
{
	do_complete(XCF_FILE, CT_LIST);
	return KSTD;
}

static int
x_comp_list(int c __unused)
{
	do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
	return KSTD;
}

static int
x_expand(int c __unused)
{
	char **words;
	int start, end, nwords, i;
	bool is_command;

	nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
	    &start, &end, &words, &is_command);

	if (nwords == 0) {
		x_e_putc2(7);
		return KSTD;
	}
	x_goto(xbuf + start);
	x_delete(end - start, false);
	for (i = 0; i < nwords;) {
		if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
		    (++i < nwords && x_ins(" ") < 0)) {
			x_e_putc2(7);
			return KSTD;
		}
	}
	x_adjust();

	return KSTD;
}

/* type == 0 for list, 1 for complete and 2 for complete-list */
static void
do_complete(int flags,	/* XCF_{COMMAND,FILE,COMMAND_FILE} */
    Comp_type type)
{
	char **words;
	int start, end, nlen, olen, nwords;
	bool is_command, completed = false;

	nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
	    &start, &end, &words, &is_command);
	/* no match */
	if (nwords == 0) {
		x_e_putc2(7);
		return;
	}
	if (type == CT_LIST) {
		x_print_expansions(nwords, words, is_command);
		x_redraw(0);
		x_free_words(nwords, words);
		return;
	}
	olen = end - start;
	nlen = x_longest_prefix(nwords, words);
	/* complete */
	if (nwords == 1 || nlen > olen) {
		x_goto(xbuf + start);
		x_delete(olen, false);
		x_escape(words[0], nlen, x_do_ins);
		x_adjust();
		completed = true;
	}
	/* add space if single non-dir match */
	if (nwords == 1 && words[0][nlen - 1] != '/') {
		x_ins(" ");
		completed = true;
	}
	if (type == CT_COMPLIST && !completed) {
		x_print_expansions(nwords, words, is_command);
		completed = true;
	}
	if (completed)
		x_redraw(0);

	x_free_words(nwords, words);
}

/* NAME:
 *	x_adjust - redraw the line adjusting starting point etc.
 *
 * DESCRIPTION:
 *	This function is called when we have exceeded the bounds
 *	of the edit window.  It increments x_adj_done so that
 *	functions like x_ins and x_delete know that we have been
 *	called and can skip the x_bs() stuff which has already
 *	been done by x_redraw.
 *
 * RETURN VALUE:
 *	None
 */
static void
x_adjust(void)
{
	x_adj_done++;			/* flag the fact that we were called. */
	/*
	 * we had a problem if the prompt length > xx_cols / 2
	 */
	if ((xbp = xcp - (x_displen / 2)) < xbuf)
		xbp = xbuf;
	if (Flag(FUTFHACK))
		while ((xbp > xbuf) && ((*xbp & 0xC0) == 0x80))
			--xbp;
	xlp_valid = false;
	x_redraw(xx_cols);
	x_flush();
}

static void
x_e_ungetc(int c)
{
	unget_char = c < 0 ? -1 : (c & 255);
}

static int
x_e_getc(void)
{
	int c;

	if (unget_char >= 0) {
		c = unget_char;
		unget_char = -1;
	} else if (macroptr) {
		c = (unsigned char)*macroptr++;
		if (!*macroptr)
			macroptr = NULL;
	} else
		c = x_getc();

	return (c);
}

static void
x_e_putc2(int c)
{
	int width = 1;

	if (c == '\r' || c == '\n')
		x_col = 0;
	if (x_col < xx_cols) {
		if (Flag(FUTFHACK) && (c > 0x7F)) {
			char utf_tmp[3];
			size_t x;

			if (c < 0xA0)
				c = 0xFFFD;
			x = utf_wctomb(utf_tmp, c);
			x_putc(utf_tmp[0]);
			if (x > 1)
				x_putc(utf_tmp[1]);
			if (x > 2)
				x_putc(utf_tmp[2]);
			width = utf_wcwidth(c);
		} else
			x_putc(c);
		switch (c) {
		case 7:
			break;
		case '\r':
		case '\n':
			break;
		case '\b':
			x_col--;
			break;
		default:
			x_col += width;
			break;
		}
	}
	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
		x_adjust();
}

static void
x_e_putc3(const char **cp)
{
	int width = 1, c = **(const unsigned char **)cp;

	if (c == '\r' || c == '\n')
		x_col = 0;
	if (x_col < xx_cols) {
		if (Flag(FUTFHACK) && (c > 0x7F)) {
			char *cp2;

			width = utf_widthadj(*cp, (const char **)&cp2);
			while (*cp < cp2)
				x_putcf(*(*cp)++);
		} else {
			(*cp)++;
			x_putc(c);
		}
		switch (c) {
		case 7:
			break;
		case '\r':
		case '\n':
			break;
		case '\b':
			x_col--;
			break;
		default:
			x_col += width;
			break;
		}
	}
	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
		x_adjust();
}

static void
x_e_puts(const char *s)
{
	int adj = x_adj_done;

	while (*s && adj == x_adj_done)
		x_e_putc3(&s);
}

/* NAME:
 *	x_set_arg - set an arg value for next function
 *
 * DESCRIPTION:
 *	This is a simple implementation of M-[0-9].
 *
 * RETURN VALUE:
 *	KSTD
 */
static int
x_set_arg(int c)
{
	int n = 0, first = 1;

	c &= 255;	/* strip command prefix */
	for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
		n = n * 10 + (c - '0');
	if (c < 0 || first) {
		x_e_putc2(7);
		x_arg = 1;
		x_arg_defaulted = 1;
	} else {
		x_e_ungetc(c);
		x_arg = n;
		x_arg_defaulted = 0;
	}
	return KSTD;
}

/* Comment or uncomment the current line. */
static int
x_comment(int c __unused)
{
	int oldsize = x_size_str(xbuf);
	int len = xep - xbuf;
	int ret = x_do_comment(xbuf, xend - xbuf, &len);

	if (ret < 0)
		x_e_putc2(7);
	else {
		x_modified();
		xep = xbuf + len;
		*xep = '\0';
		xcp = xbp = xbuf;
		x_redraw(oldsize);
		if (ret > 0)
			return x_newline('\n');
	}
	return KSTD;
}

static int
x_version(int c __unused)
{
	char *o_xbuf = xbuf, *o_xend = xend;
	char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
	int vlen, lim = x_lastcp() - xbp;
	char *v;

	strdupx(v, KSH_VERSION, ATEMP);

	xbuf = xbp = xcp = v;
	xend = xep = v + (vlen = strlen(v));
	x_redraw(lim);
	x_flush();

	c = x_e_getc();
	xbuf = o_xbuf;
	xend = o_xend;
	xbp = o_xbp;
	xep = o_xep;
	xcp = o_xcp;
	x_redraw(vlen);

	if (c < 0)
		return KSTD;
	/* This is what at&t ksh seems to do...  Very bizarre */
	if (c != ' ')
		x_e_ungetc(c);

	afree(v, ATEMP);
	return KSTD;
}

static int
x_edit_line(int c __unused)
{
	if (x_arg_defaulted) {
		if (xep == xbuf) {
			x_e_putc2(7);
			return (KSTD);
		}
		if (modified) {
			*xep = '\0';
			histsave(&source->line, xbuf, true, true);
			x_arg = 0;
		} else
			x_arg = source->line - (histptr - x_histp);
	}
	if (x_arg)
		shf_snprintf(xbuf, xend - xbuf, "%s %d",
		    "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
	else
		strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
	xep = xbuf + strlen(xbuf);
	return (x_newline('\n'));
}

/* NAME:
 *	x_prev_histword - recover word from prev command
 *
 * DESCRIPTION:
 *	This function recovers the last word from the previous
 *	command and inserts it into the current edit line.  If a
 *	numeric arg is supplied then the n'th word from the
 *	start of the previous command is used.
 *	As a side effect, trashes the mark in order to achieve
 *	being called in a repeatable fashion.
 *
 *	Bound to M-.
 *
 * RETURN VALUE:
 *	KSTD
 */
static int
x_prev_histword(int c __unused)
{
	char *rcp, *cp;
	char **xhp;
	int m;

	if (xmp && modified > 1)
		x_kill_region(0);
	m = modified ? modified : 1;
	xhp = histptr - (m - 1);
	if ((xhp < history) || !(cp = *xhp)) {
		x_e_putc2(7);
		x_modified();
		return (KSTD);
	}
	x_set_mark(0);
	if (x_arg_defaulted) {
		rcp = &cp[strlen(cp) - 1];
		/*
		 * ignore white-space after the last word
		 */
		while (rcp > cp && is_cfs(*rcp))
			rcp--;
		while (rcp > cp && !is_cfs(*rcp))
			rcp--;
		if (is_cfs(*rcp))
			rcp++;
		x_ins(rcp);
	} else {
		char ch;

		rcp = cp;
		/*
		 * ignore white-space at start of line
		 */
		while (*rcp && is_cfs(*rcp))
			rcp++;
		while (x_arg-- > 1) {
			while (*rcp && !is_cfs(*rcp))
				rcp++;
			while (*rcp && is_cfs(*rcp))
				rcp++;
		}
		cp = rcp;
		while (*rcp && !is_cfs(*rcp))
			rcp++;
		ch = *rcp;
		*rcp = '\0';
		x_ins(cp);
		*rcp = ch;
	}
	modified = m + 1;
	return KSTD;
}

/* Uppercase N(1) words */
static int
x_fold_upper(int c __unused)
{
	return x_fold_case('U');
}

/* Lowercase N(1) words */
static int
x_fold_lower(int c __unused)
{
	return x_fold_case('L');
}

/* Lowercase N(1) words */
static int
x_fold_capitalise(int c __unused)
{
	return x_fold_case('C');
}

/* NAME:
 *	x_fold_case - convert word to UPPER/lower/Capital case
 *
 * DESCRIPTION:
 *	This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
 *	to UPPER case, lower case or Capitalise words.
 *
 * RETURN VALUE:
 *	None
 */
static int
x_fold_case(int c)
{
	char *cp = xcp;

	if (cp == xep) {
		x_e_putc2(7);
		return KSTD;
	}
	while (x_arg--) {
		/*
		 * first skip over any white-space
		 */
		while (cp != xep && is_mfs(*cp))
			cp++;
		/*
		 * do the first char on its own since it may be
		 * a different action than for the rest.
		 */
		if (cp != xep) {
			if (c == 'L')		/* lowercase */
				*cp = ksh_tolower(*cp);
			else			/* uppercase, capitalise */
				*cp = ksh_toupper(*cp);
			cp++;
		}
		/*
		 * now for the rest of the word
		 */
		while (cp != xep && !is_mfs(*cp)) {
			if (c == 'U')		/* uppercase */
				*cp = ksh_toupper(*cp);
			else			/* lowercase, capitalise */
				*cp = ksh_tolower(*cp);
			cp++;
		}
	}
	x_goto(cp);
	x_modified();
	return KSTD;
}

/* NAME:
 *	x_lastcp - last visible char
 *
 * SYNOPSIS:
 *	x_lastcp()
 *
 * DESCRIPTION:
 *	This function returns a pointer to that  char in the
 *	edit buffer that will be the last displayed on the
 *	screen.  The sequence:
 *
 *	cp = x_lastcp();
 *	while (cp > xcp)
 *		x_bs3(&cp);
 *
 *	Will position the cursor correctly on the screen.
 *
 * RETURN VALUE:
 *	cp or NULL
 */
static char *
x_lastcp(void)
{
	if (!xlp_valid) {
		int i = 0, j;
		char *xlp2;

		xlp = xbp;
		while (xlp < xep) {
			j = x_size2(xlp, &xlp2);
			if ((i + j) > x_displen)
				break;
			i += j;
			xlp = xlp2;
		}
	}
	xlp_valid = true;
	return (xlp);
}

static bool
x_mode(bool onoff)
{
	static bool x_cur_mode;
	bool prev;

	if (x_cur_mode == onoff)
		return x_cur_mode;
	prev = x_cur_mode;
	x_cur_mode = onoff;

	if (onoff) {
		struct termios cb;

		cb = tty_state;

		edchars.erase = cb.c_cc[VERASE];
		edchars.kill = cb.c_cc[VKILL];
		edchars.intr = cb.c_cc[VINTR];
		edchars.quit = cb.c_cc[VQUIT];
		edchars.eof = cb.c_cc[VEOF];
#ifdef VWERASE
		edchars.werase = cb.c_cc[VWERASE];
#endif
		cb.c_iflag &= ~(INLCR | ICRNL);
		cb.c_lflag &= ~(ISIG | ICANON | ECHO);
#ifdef VLNEXT
		/* osf/1 processes lnext when ~icanon */
		cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
#endif
		/* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
#ifdef VDISCARD
		cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
#endif
		cb.c_cc[VTIME] = 0;
		cb.c_cc[VMIN] = 1;

		tcsetattr(tty_fd, TCSADRAIN, &cb);

		/* Convert unset values to internal 'unset' value */
		if (edchars.erase == _POSIX_VDISABLE)
			edchars.erase = -1;
		if (edchars.kill == _POSIX_VDISABLE)
			edchars.kill = -1;
		if (edchars.intr == _POSIX_VDISABLE)
			edchars.intr = -1;
		if (edchars.quit == _POSIX_VDISABLE)
			edchars.quit = -1;
		if (edchars.eof == _POSIX_VDISABLE)
			edchars.eof = -1;
		if (edchars.werase == _POSIX_VDISABLE)
			edchars.werase = -1;

		if (edchars.erase >= 0) {
			bind_if_not_bound(0, edchars.erase, XFUNC_del_back);
			bind_if_not_bound(1, edchars.erase, XFUNC_del_bword);
		}
		if (edchars.kill >= 0)
			bind_if_not_bound(0, edchars.kill, XFUNC_del_line);
		if (edchars.werase >= 0)
			bind_if_not_bound(0, edchars.werase, XFUNC_del_bword);
		if (edchars.intr >= 0)
			bind_if_not_bound(0, edchars.intr, XFUNC_abort);
		if (edchars.quit >= 0)
			bind_if_not_bound(0, edchars.quit, XFUNC_noop);
	} else
		tcsetattr(tty_fd, TCSADRAIN, &tty_state);

	return prev;
}

#ifndef MKSH_NOVI
/* +++ vi editing mode +++ */

#define Ctrl(c)		(c&0x1f)

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

static int	vi_hook(int);
static int	nextstate(int);
static int	vi_insert(int);
static int	vi_cmd(int, const char *);
static int	domove(int, const char *, int);
static int	redo_insert(int);
static void	yank_range(int, int);
static int	bracktype(int);
static void	save_cbuf(void);
static void	restore_cbuf(void);
static int	putbuf(const char *, int, int);
static void	del_range(int, int);
static int	findch(int, int, int, int);
static int	forwword(int);
static int	backword(int);
static int	endword(int);
static int	Forwword(int);
static int	Backword(int);
static int	Endword(int);
static int	grabhist(int, int);
static int	grabsearch(int, int, int, char *);
static void	redraw_line(int);
static void	refresh(int);
static int	outofwin(void);
static void	rewindow(void);
static int	newcol(int, int);
static void	display(char *, char *, int);
static void	ed_mov_opt(int, char *);
static int	expand_word(int);
static int	complete_word(int, int);
static int	print_expansions(struct edstate *, int);
#define char_len(c)	((c) < ' ' || (c) == 0x7F ? 2 : 1)
static void	x_vi_zotc(int);
static void	vi_error(void);
static void	vi_macro_reset(void);
static int	x_vi_putbuf(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[LINE];

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

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

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

static char	ibuf[LINE];		/* 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[LINE];		/* 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	state;

/* Information for keeping track of macros that are being expanded.
 * The format of buf is the alias contents followed by a NUL byte followed
 * by the name (letter) of the alias.  The end of the buffer is marked by
 * a double NUL.  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 */

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

	state = VNORMAL;
	ohnum = hnum = hlast = histnum(-1) + 1;
	insert = INSERT;
	saved_inslen = inslen;
	first_insert = 1;
	inslen = 0;
	vi_macro_reset();

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

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

	cur_col = promptlen(prompt);
	prompt_trunc = (cur_col / x_cols) * x_cols;
	cur_col -= prompt_trunc;

	pprompt(prompt, 0);
	if (cur_col > x_cols - 3 - MIN_EDIT_SPACE) {
		prompt_redraw = cur_col = 0;
		x_putc('\n');
	} else
		prompt_redraw = 1;
	pwidth = cur_col;

	if (!wbuf_len || wbuf_len != x_cols - 3) {
		wbuf_len = x_cols - 3;
		wbuf[0] = aresize(wbuf[0], 1, wbuf_len, APERM);
		wbuf[1] = aresize(wbuf[1], 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;

	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 || (ssize_t)len <= 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(int ch)
{
	static char curcmd[MAXVICMD], locpat[SRCHLEN];
	static int cmdlen, 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,
					     strlen(KSH_VERSION), 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 (ksh_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 (ksh_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 && ksh_isspace(locpat[n - 1]))
				n--;
			while (n > 0 && !ksh_isspace(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 < ' ' || 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 int
nextstate(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(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)
				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(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 = ktsearch(&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(1, 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... */
		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') &&
				    !ksh_isspace(es->cbuf[es->cursor])) {
					do {
						--ncursor;
					} while (ksh_isspace(es->cbuf[ncursor]));
					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)
				;
			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;
			/* FALLTHRU */
		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 {
				int n;

				if (es->cursor + argcnt > es->linelen)
					return -1;
				for (n = 0; n < argcnt; ++n)
					es->cbuf[es->cursor + n] = cmd[1];
				es->cursor += n - 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 (!argcnt) {
				if (es->linelen == 0)
					return -1;
				if (modified) {
					es->cbuf[es->linelen] = '\0';
					histsave(&source->line, es->cbuf, true,
					    true);
				} else
					argcnt = source->line + 1
					    - (hlast - hnum);
			}
			if (argcnt)
				shf_snprintf(es->cbuf, es->cbufsize, "%s %d",
				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
				    argcnt);
			else
				strlcpy(es->cbuf,
				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
				    es->cbufsize);
			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 */
		case '/':
			c3 = 1;
			srchlen = 0;
			lastsearch = *cmd;
			/* FALLTHRU */
		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)		(ksh_isspace(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(" ", 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 (ksh_islower(*p)) {
						modified = 1;
						hnum = hlast;
						*p = ksh_toupper(*p);
					} else if (ksh_isupper(*p)) {
						modified = 1;
						hnum = hlast;
						*p = ksh_tolower(*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 kshs */
			if (!Flag(FVIESCCOMPLETE))
				return -1;
		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(int argcnt, const char *cmd, int sub)
{
	int bcount, i = 0, t;
	int ncursor = 0;

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

	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 &&
		    ksh_isspace(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(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(int a, int b)
{
	yanklen = b - a;
	if (yanklen != 0)
		memmove(ybuf, &es->cbuf[a], yanklen);
}

static int
bracktype(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 void
save_cbuf(void)
{
	memmove(holdbuf, es->cbuf, es->linelen);
	holdlen = es->linelen;
	holdbuf[holdlen] = '\0';
}

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

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

	new = alloc(1, sizeof (struct edstate), APERM);
	new->cbuf = alloc(1, 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(struct edstate *new, struct edstate *old)
{
	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(struct edstate *old)
{
	afree(old->cbuf, APERM);
	afree(old, APERM);
}

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

static int
putbuf(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(int a, int b)
{
	if (es->linelen != b)
		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
	es->linelen -= b - a;
}

static int
findch(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(int argcnt)
{
	int ncursor;

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

static int
backword(int argcnt)
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor > 0 && argcnt--) {
		while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor]))
			;
		if (ncursor > 0) {
			if (ksh_isalnux(es->cbuf[ncursor]))
				while (--ncursor >= 0 &&
				    ksh_isalnux(es->cbuf[ncursor]))
					;
			else
				while (--ncursor >= 0 &&
				    !ksh_isalnux(es->cbuf[ncursor]) &&
				    !ksh_isspace(es->cbuf[ncursor]))
					;
			ncursor++;
		}
	}
	return ncursor;
}

static int
endword(int argcnt)
{
	int	ncursor;

	ncursor = es->cursor;
	while (ncursor < es->linelen && argcnt--) {
		while (++ncursor < es->linelen - 1 &&
		    ksh_isspace(es->cbuf[ncursor]))
			;
		if (ncursor < es->linelen - 1) {
			if (ksh_isalnux(es->cbuf[ncursor]))
				while (++ncursor < es->linelen &&
				    ksh_isalnux(es->cbuf[ncursor]))
					;
			else
				while (++ncursor < es->linelen &&
				    !ksh_isalnux(es->cbuf[ncursor]) &&
				    !ksh_isspace(es->cbuf[ncursor]))
					;
			ncursor--;
		}
	}
	return ncursor;
}

static int
Forwword(int argcnt)
{
	int ncursor;

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

static int
Backword(int argcnt)
{
	int	ncursor;

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

static int
Endword(int argcnt)
{
	int	ncursor;

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

static int
grabhist(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_warningf("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(int save, int start, int 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 strcmp be strncmp? */
		if (start != 0 && fwd && strcmp(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(int newl)
{
	(void)memset(wbuf[win], ' ', wbuf_len);
	if (newl) {
		x_putc('\r');
		x_putc('\n');
	}
	if (prompt_redraw)
		pprompt(prompt, prompt_trunc);
	cur_col = pwidth;
	morec = ' ';
}

static void
refresh(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(void)
{
	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(void)
{
	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(int ch, int col)
{
	if (ch == '\t')
		return (col | 7) + 1;
	return col + char_len(ch);
}

static void
display(char *wb1, char *wb2, int leftside)
{
	unsigned char ch;
	char *twb1, *twb2, mc;
	int cur, col, cnt;
	int ncol = 0;
	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 (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(int col, char *wb)
{
	if (col < cur_col) {
		if (col + 1 < cur_col - col) {
			x_putc('\r');
			if (prompt_redraw)
				pprompt(prompt, prompt_trunc);
			cur_col = pwidth;
			while (cur_col++ < col)
				x_putcf(*wb++);
		} else {
			while (cur_col-- > col)
				x_putc('\b');
		}
	} else {
		wb = &wb[cur_col - pwidth];
		while (cur_col++ < col)
			x_putcf(*wb++);
	}
	cur_col = col;
}


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

	/* Undo previous expansion */
	if (cmd == 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, NULL);
	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(" ", 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(int cmd, int count)
{
	static struct edstate *buf;
	int rval, nwords, start, end, match_len;
	char **words;
	char *match;
	bool is_command, is_unique;

	/* Undo previous completion */
	if (cmd == 0 && expanded == COMPLETE && buf) {
		print_expansions(buf, 0);
		expanded = PRINT;
		return 0;
	}
	if (cmd == 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], NULL);
			/* If more than one possible match, use full path */
			for (i = 0; i < nwords; i++)
				if (i != count &&
				    strcmp(words[i] + x_basename(words[i],
				    NULL), match) == 0) {
					match = words[count];
					break;
				}
		} else
			match = words[count];
		match_len = strlen(match);
		is_unique = true;
		/* 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 && match[match_len - 1] != '/')
			rval = putbuf(" ", 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(struct edstate *est, int cmd __unused)
{
	int start, end, nwords;
	char **words;
	bool is_command;

	nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH,
	    est->cbuf, est->linelen, est->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;
}

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

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

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