[BACK]Return to emacs.c CVS log [TXT][DIR] Up to [local] / src / bin / ksh

File: [local] / src / bin / ksh / emacs.c (download)

Revision 1.90, Wed Jun 21 22:22:08 2023 UTC (11 months, 2 weeks ago) by millert
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.89: +1 -5 lines

ksh: escape control chars when displaying file name completions.
If there are multiple matches when using autocomplete, the list of
matching file names was output as-is.  However, for a single match,
control characters are escaped before the file name is displayed.
This makes the behavior more consistent by escaping control chars
in the list of matches too.  Discussed with deraadt@, OK op@

/*	$OpenBSD: emacs.c,v 1.90 2023/06/21 22:22:08 millert Exp $	*/

/*
 *  Emacs-like command line editing and history
 *
 *  created by Ron Natalie at BRL
 *  modified by Doug Kingston, Doug Gwyn, and Lou Salkind
 *  adapted to PD ksh by Eric Gisin
 *
 * partial rewrite by Marco Peereboom <marco@openbsd.org>
 * under the same license
 */

#include "config.h"
#ifdef EMACS

#include <sys/queue.h>
#include <sys/stat.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef SMALL
# include <term.h>
# include <curses.h>
#endif

#include "sh.h"
#include "edit.h"

static	Area	aedit;
#define	AEDIT	&aedit		/* area for kill ring and macro defns */

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

typedef int (*kb_func)(int);

struct	x_ftab {
	kb_func		xf_func;
	const char	*xf_name;
	short		xf_flags;
};

#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)	(!(isalnum((unsigned char)c) || \
			c == '_' || c == '$' || c & 0x80))

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

/* keybindings */
struct kb_entry {
	TAILQ_ENTRY(kb_entry)	entry;
	unsigned char		*seq;
	int			len;
	struct x_ftab		*ftab;
	void			*args;
};
TAILQ_HEAD(kb_list, kb_entry);
struct kb_list			kblist = TAILQ_HEAD_INITIALIZER(kblist);

/* { from 4.9 edit.h */
/*
 * 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 byte 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;
/* end from 4.9 edit.h } */
static	int	x_tty;		/* are we on a tty? */
static	int	x_bind_quiet;	/* be quiet when binding keys */
static int	(*x_last_command)(int);

static	char   **x_histp;	/* history position */
static	int	x_nextcmd;	/* for newline-and-next */
static	char	*xmp;		/* mark pointer */
#define	KILLSIZE	20
static	char	*killstack[KILLSIZE];
static	int	killsp, killtp;
static	int	x_literal_set;
static	int	x_arg_set;
static	char	*macro_args;
static	int	prompt_skip;
static	int	prompt_redraw;

static int	x_ins(char *);
static void	x_delete(int, int);
static int	x_bword(void);
static int	x_fword(void);
static void	x_goto(char *);
static void	x_bs(int);
static int	x_size_str(char *);
static int	x_size(int);
static void	x_zots(char *);
static void	x_zotc(int);
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 void	x_adjust(void);
static void	x_e_ungetc(int);
static int	x_e_getc(void);
static int	x_e_getu8(char *, int);
static void	x_e_putc(int);
static void	x_e_puts(const char *);
static int	x_comment(int);
static int	x_fold_case(int);
static char	*x_lastcp(void);
static void	do_complete(int, Comp_type);
static int	isu8cont(unsigned char);

/* proto's for keybindings */
static int	x_abort(int);
static int	x_beg_hist(int);
static int	x_clear_screen(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_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_capitalize(int);
static int	x_fold_lower(int);
static int	x_fold_upper(int);
static int	x_set_arg(int);
static int	x_comment(int);
#ifdef DEBUG
static int	x_debug_info(int);
#endif

static const struct x_ftab x_ftab[] = {
	{ x_abort,		"abort",			0 },
	{ x_beg_hist,		"beginning-of-history",		0 },
	{ x_clear_screen,	"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_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_capitalize,	"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 },
	{ 0, 0, 0 },
#ifdef DEBUG
	{ x_debug_info,		"debug-info",			0 },
#else
	{ 0, 0, 0 },
#endif
	{ 0, 0, 0 },
};

int
isu8cont(unsigned char c)
{
	return (c & (0x80 | 0x40)) == 0x80;
}

int
x_emacs(char *buf, size_t len)
{
	struct kb_entry		*k, *kmatch = NULL;
	char			line[LINE + 1];
	int			at = 0, ntries = 0, submatch, ret;
	const char		*p;

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

	xx_cols = x_cols;
	x_col = promptlen(prompt, &p);
	prompt_skip = p - prompt;
	x_adj_ok = 1;
	prompt_redraw = 1;
	if (x_col > xx_cols)
		x_col = x_col - (x_col / xx_cols) * 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_putc('\n');
		prompt_redraw = 0;
	}

	if (x_nextcmd >= 0) {
		int off = source->line - x_nextcmd;
		if (histptr - history >= off)
			x_load_hist(histptr - off);
		x_nextcmd = -1;
	}

	x_literal_set = 0;
	x_arg = -1;
	x_last_command = NULL;
	while (1) {
		x_flush();
		if ((at = x_e_getu8(line, at)) < 0)
			return 0;
		ntries++;

		if (x_arg == -1) {
			x_arg = 1;
			x_arg_defaulted = 1;
		}

		if (x_literal_set) {
			/* literal, so insert it */
			x_literal_set = 0;
			submatch = 0;
		} else {
			submatch = 0;
			kmatch = NULL;
			TAILQ_FOREACH(k, &kblist, entry) {
				if (at > k->len)
					continue;

				if (memcmp(k->seq, line, at) == 0) {
					/* sub match */
					submatch++;
					if (k->len == at)
						kmatch = k;
				}

				/* see if we can abort search early */
				if (submatch > 1)
					break;
			}
		}

		if (submatch == 1 && kmatch) {
			if (kmatch->ftab->xf_func == x_ins_string &&
			    kmatch->args && !macro_args) {
				/* treat macro string as input */
				macro_args = kmatch->args;
				ret = KSTD;
			} else
				ret = kmatch->ftab->xf_func(line[at - 1]);
		} else {
			if (submatch)
				continue;
			if (ntries > 1) {
				ret = x_error(0); /* unmatched meta sequence */
			} else if (at > 1) {
				x_ins(line);
				ret = KSTD;
			} else {
				ret = x_insert(line[0]);
			}
		}

		switch (ret) {
		case KSTD:
			if (kmatch)
				x_last_command = kmatch->ftab->xf_func;
			else
				x_last_command = NULL;
			break;
		case KEOL:
			ret = xep - xbuf;
			return (ret);
			break;
		case KINTR:
			trapsig(SIGINT);
			x_mode(false);
			unwind(LSHELL);
			x_arg = -1;
			break;
		default:
			bi_errorf("invalid return code"); /* can't happen */
		}

		/* reset meta sequence */
		at = ntries = 0;
		if (x_arg_set)
			x_arg_set = 0; /* reset args next time around */
		else
			x_arg = -1;
	}
}

static int
x_insert(int c)
{
	char	str[2];

	/*
	 *  Should allow tab and control chars.
	 */
	if (c == 0) {
		x_e_putc(BEL);
		return KSTD;
	}
	str[0] = c;
	str[1] = '\0';
	while (x_arg--)
		x_ins(str);
	return KSTD;
}

static int
x_ins_string(int c)
{
	return x_insert(c);
}

static int
x_do_ins(const char *cp, size_t len)
{
	if (xep+len >= xend) {
		x_e_putc(BEL);
		return -1;
	}

	memmove(xcp+len, xcp, xep - xcp + 1);
	memmove(xcp, cp, len);
	xcp += len;
	xep += len;
	return 0;
}

static int
x_ins(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 */
		for (cp = xlp; cp > xcp; )
			x_bs(*--cp);
	}

	x_adj_ok = 1;
	return 0;
}

static int
x_del_back(int c)
{
	int col = xcp - xbuf;

	if (col == 0) {
		x_e_putc(BEL);
		return KSTD;
	}
	if (x_arg > col)
		x_arg = col;
	while (x_arg < col && isu8cont(xcp[-x_arg]))
		x_arg++;
	x_goto(xcp - x_arg);
	x_delete(x_arg, false);
	return KSTD;
}

static int
x_del_char(int c)
{
	int nleft = xep - xcp;

	if (!nleft) {
		x_e_putc(BEL);
		return KSTD;
	}
	if (x_arg > nleft)
		x_arg = nleft;
	while (x_arg < nleft && isu8cont(xcp[x_arg]))
		x_arg++;
	x_delete(x_arg, false);
	return KSTD;
}

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

	if (nc == 0)
		return;
	if (xmp != NULL && xmp > xcp) {
		if (xcp + nc > xmp)
			xmp = xcp;
		else
			xmp -= nc;
	}

	/*
	 * This lets us yank a word we have deleted.
	 */
	if (push)
		x_push(nc);

	xep -= nc;
	cp = xcp;
	j = 0;
	i = nc;
	while (i--) {
		j += x_size((unsigned char)*cp++);
	}
	memmove(xcp, xcp+nc, xep - xcp + 1);	/* Copies the null */
	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) {
		j = (j < i) ? j : i;
		i = j;
		while (i--)
			x_e_putc(' ');
		i = j;
		while (i--)
			x_e_putc('\b');
	}
	/*x_goto(xcp);*/
	x_adj_ok = 1;
	xlp_valid = false;
	for (cp = x_lastcp(); cp > xcp; )
		x_bs(*--cp);

	return;
}

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

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

static int
x_mv_fword(int c)
{
	x_goto(xcp + x_fword());
	return KSTD;
}

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

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

	if (cp == xbuf) {
		x_e_putc(BEL);
		return 0;
	}
	while (x_arg--) {
		while (cp != xbuf && is_mfs(cp[-1])) {
			cp--;
			nc++;
		}
		while (cp != xbuf && !is_mfs(cp[-1])) {
			cp--;
			nc++;
		}
	}
	x_goto(cp);
	return nc;
}

static int
x_fword(void)
{
	int	nc = 0;
	char	*cp = xcp;

	if (cp == xep) {
		x_e_putc(BEL);
		return 0;
	}
	while (x_arg--) {
		while (cp != xep && is_mfs(*cp)) {
			cp++;
			nc++;
		}
		while (cp != xep && !is_mfs(*cp)) {
			cp++;
			nc++;
		}
	}
	return nc;
}

static void
x_goto(char *cp)
{
	if (cp < xbp || cp >= (xbp + x_displen)) {
		/* we are heading off screen */
		xcp = cp;
		x_adjust();
	} else if (cp < xcp) {		/* move back */
		while (cp < xcp)
			x_bs((unsigned char)*--xcp);
	} else if (cp > xcp) {		/* move forward */
		while (cp > xcp)
			x_zotc((unsigned char)*xcp++);
	}
}

static void
x_bs(int c)
{
	int i;

	i = x_size(c);
	while (i--)
		x_e_putc('\b');
}

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

static int
x_size(int c)
{
	if (c=='\t')
		return 4;	/* Kludge, tabs are always four spaces. */
	if (iscntrl(c))		/* control char */
		return 2;
	if (isu8cont(c))
		return 0;
	return 1;
}

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

	if (str > xbuf && isu8cont(*str)) {
		while (str > xbuf && isu8cont(*str))
			str--;
		x_e_putc('\b');
	}
	x_lastcp();
	while (*str && str < xlp && adj == x_adj_done)
		x_zotc(*str++);
}

static void
x_zotc(int c)
{
	if (c == '\t') {
		/*  Kludge, tabs are always four spaces.  */
		x_e_puts("    ");
	} else if (iscntrl(c)) {
		x_e_putc('^');
		x_e_putc(UNCTRL(c));
	} else
		x_e_putc(c);
}

static int
x_mv_back(int c)
{
	int col = xcp - xbuf;

	if (col == 0) {
		x_e_putc(BEL);
		return KSTD;
	}
	if (x_arg > col)
		x_arg = col;
	while (x_arg < col && isu8cont(xcp[-x_arg]))
		x_arg++;
	x_goto(xcp - x_arg);
	return KSTD;
}

static int
x_mv_forw(int c)
{
	int nleft = xep - xcp;

	if (!nleft) {
		x_e_putc(BEL);
		return KSTD;
	}
	if (x_arg > nleft)
		x_arg = nleft;
	while (x_arg < nleft && isu8cont(xcp[x_arg]))
		x_arg++;
	x_goto(xcp + x_arg);
	return KSTD;
}

static int
x_search_char_forw(int c)
{
	char *cp = xcp;

	*xep = '\0';
	c = x_e_getc();
	while (x_arg--) {
		if (c < 0 ||
		    ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL &&
		    (cp = strchr(xbuf, c)) == NULL)) {
			x_e_putc(BEL);
			return KSTD;
		}
	}
	x_goto(cp);
	return KSTD;
}

static int
x_search_char_back(int c)
{
	char *cp = xcp, *p;

	c = x_e_getc();
	for (; x_arg--; cp = p)
		for (p = cp; ; ) {
			if (p-- == xbuf)
				p = xep;
			if (c < 0 || p == cp) {
				x_e_putc(BEL);
				return KSTD;
			}
			if (*p == c)
				break;
		}
	x_goto(cp);
	return KSTD;
}

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

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

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

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

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

static int x_next_com(int c) { 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)
{
	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;

	if (hp < history || hp > histptr) {
		x_e_putc(BEL);
		return;
	}
	x_histp = hp;
	oldsize = x_size_str(xbuf);
	strlcpy(xbuf, *hp, xend - xbuf);
	xbp = xbuf;
	xep = xcp = xbuf + strlen(xbuf);
	xlp_valid = false;
	if (xep <= x_lastcp())
		x_redraw(oldsize);
	x_goto(xep);
}

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

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));
}

static kb_func
kb_find_hist_func(char c)
{
	struct kb_entry		*k;
	char			line[LINE + 1];

	line[0] = c;
	line[1] = '\0';
	TAILQ_FOREACH(k, &kblist, entry)
		if (!strcmp(k->seq, line))
			return (k->ftab->xf_func);

	return (x_insert);
}

/* 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;
	int (*f)(int);

	*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 = kb_find_hist_func(c);
		if (c == CTRL('[') || c == CTRL('@')) {
			x_e_ungetc(c);
			break;
		} else if (f == x_search_hist)
			offset = x_search(pat, 0, offset);
		else if (f == x_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 == x_insert) {
			/* add char to pattern */
			/* overflow check... */
			if (p >= &pat[sizeof(pat) - 1]) {
				x_e_putc(BEL);
				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 { /* 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_putc('\n');
			x_load_hist(hp);
			x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
			return i;
		}
	}
	x_e_putc(BEL);
	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)
{
	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);
	return KSTD;
}

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

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

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

static int
x_clear_screen(int c)
{
	x_redraw(-2);
	return KSTD;
}

/* Redraw (part of) the line.
 * A non-negative limit is the screen column up to which needs
 * redrawing. A limit of -1 redraws on a new line, while a limit
 * of -2 (attempts to) clear the screen.
 */
static void
x_redraw(int limit)
{
	int	i, j, truncate = 0;
	char	*cp;

	x_adj_ok = 0;
	if (limit == -2) {
		int cleared = 0;
#ifndef SMALL
		if (cur_term != NULL && clear_screen != NULL) {
			if (tputs(clear_screen, 1, x_putc) != ERR)
				cleared = 1;
		}
#endif
		if (!cleared)
			x_e_putc('\n');
	}
	else if (limit == -1)
		x_e_putc('\n');
	else if (limit >= 0)
		x_e_putc('\r');
	x_flush();
	if (xbp == xbuf) {
		x_col = promptlen(prompt, NULL);
		if (x_col > xx_cols)
			truncate = (x_col / xx_cols) * xx_cols;
		if (prompt_redraw)
			pprompt(prompt + prompt_skip, truncate);
	}
	if (x_col > xx_cols)
		x_col = x_col - (x_col / xx_cols) * 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
			i = limit - (xlp - xbp);

		for (j = 0; j < i && x_col < (xx_cols - 2); j++)
			x_e_putc(' ');
		i = ' ';
		if (xep > xlp) {		/* more off screen */
			if (xbp > xbuf)
				i = '*';
			else
				i = '>';
		} else if (xbp > xbuf)
			i = '<';
		x_e_putc(i);
		j++;
		while (j--)
			x_e_putc('\b');
	}
	for (cp = xlp; cp > xcp; )
		x_bs(*--cp);
	x_adj_ok = 1;
#ifdef DEBUG
	x_flush();
#endif
	return;
}

static int
x_transpose(int c)
{
	char	tmp;

	/* 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_putc(BEL);
		return KSTD;
	} else if (xcp == xep || Flag(FGMACS)) {
		if (xcp - xbuf == 1) {
			x_e_putc(BEL);
			return KSTD;
		}
		/* Gosling/Unipress emacs style: Swap two characters before the
		 * cursor, do not change cursor position
		 */
		x_bs(xcp[-1]);
		x_bs(xcp[-2]);
		x_zotc(xcp[-1]);
		x_zotc(xcp[-2]);
		tmp = xcp[-1];
		xcp[-1] = xcp[-2];
		xcp[-2] = tmp;
	} else {
		/* GNU emacs style: Swap the characters before and under the
		 * cursor, move cursor position along one.
		 */
		x_bs(xcp[-1]);
		x_zotc(xcp[0]);
		x_zotc(xcp[-1]);
		tmp = xcp[-1];
		xcp[-1] = xcp[0];
		xcp[0] = tmp;
		x_bs(xcp[0]);
		x_goto(xcp + 1);
	}
	return KSTD;
}

static int
x_literal(int c)
{
	x_literal_set = 1;
	return KSTD;
}

static int
x_kill(int c)
{
	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;
	while (x_arg < lastcol && isu8cont(xbuf[x_arg]))
		x_arg++;
	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 = str_nsave(xcp, nchars, AEDIT);
	afree(killstack[killsp], AEDIT);
	killstack[killsp] = cp;
	killsp = (killsp + 1) % KILLSIZE;
}

static int
x_yank(int c)
{
	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)
{
	int	len;
	if ((x_last_command != x_yank && x_last_command != x_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)
{
	/* x_zotc(c); */
	xlp = xep = xcp = xbp = xbuf;
	xlp_valid = true;
	*xcp = 0;
	return KINTR;
}

static int
x_error(int c)
{
	x_e_putc(BEL);
	return KSTD;
}

static char *
kb_encode(const char *s)
{
	static char		l[LINE + 1];
	int			at = 0;

	l[at] = '\0';
	while (*s) {
		if (*s == '^') {
			s++;
			if (*s >= '?')
				l[at++] = CTRL(*s);
			else {
				l[at++] = '^';
				s--;
			}
		} else
			l[at++] = *s;
		l[at] = '\0';
		s++;
	}
	return (l);
}

static char *
kb_decode(const char *s)
{
	static char		l[LINE + 1];
	unsigned int		i, at = 0;

	l[0] = '\0';
	for (i = 0; i < strlen(s); i++) {
		if (iscntrl((unsigned char)s[i])) {
			l[at++] = '^';
			l[at++] = UNCTRL(s[i]);
		} else
			l[at++] = s[i];
		l[at] = '\0';
	}

	return (l);
}

static int
kb_match(char *s)
{
	int			len = strlen(s);
	struct kb_entry		*k;

	TAILQ_FOREACH(k, &kblist, entry) {
		if (len > k->len)
			continue;

		if (memcmp(k->seq, s, len) == 0)
			return (1);
	}

	return (0);
}

static void
kb_del(struct kb_entry *k)
{
	TAILQ_REMOVE(&kblist, k, entry);
	free(k->args);
	afree(k, AEDIT);
}

static struct kb_entry *
kb_add_string(kb_func func, void *args, char *str)
{
	unsigned int		ele, count;
	struct kb_entry		*k;
	struct x_ftab		*xf = NULL;

	for (ele = 0; ele < NELEM(x_ftab); ele++)
		if (x_ftab[ele].xf_func == func) {
			xf = (struct x_ftab *)&x_ftab[ele];
			break;
		}
	if (xf == NULL)
		return (NULL);

	if (kb_match(str)) {
		if (x_bind_quiet == 0)
			bi_errorf("duplicate binding for %s", kb_decode(str));
		return (NULL);
	}
	count = strlen(str);

	k = alloc(sizeof *k + count + 1, AEDIT);
	k->seq = (unsigned char *)(k + 1);
	k->len = count;
	k->ftab = xf;
	k->args = args ? strdup(args) : NULL;

	strlcpy(k->seq, str, count + 1);

	TAILQ_INSERT_TAIL(&kblist, k, entry);

	return (k);
}

static struct kb_entry *
kb_add(kb_func func, ...)
{
	va_list			ap;
	unsigned char		ch;
	unsigned int		i;
	char			line[LINE + 1];

	va_start(ap, func);
	for (i = 0; i < sizeof(line) - 1; i++) {
		ch = va_arg(ap, unsigned int);
		if (ch == 0)
			break;
		line[i] = ch;
	}
	va_end(ap);
	line[i] = '\0';

	return (kb_add_string(func, NULL, line));
}

static void
kb_print(struct kb_entry *k)
{
	if (!(k->ftab->xf_flags & XF_NOBIND))
		shprintf("%s = %s\n",
		    kb_decode(k->seq), k->ftab->xf_name);
	else if (k->args) {
		shprintf("%s = ", kb_decode(k->seq));
		shprintf("'%s'\n", kb_decode(k->args));
	}
}

int
x_bind(const char *a1, const char *a2,
	int macro,		/* bind -m */
	int list)		/* bind -l */
{
	unsigned int		i;
	struct kb_entry		*k, *kb;
	char			in[LINE + 1];

	if (x_tty == 0) {
		bi_errorf("cannot bind, not a tty");
		return (1);
	}

	if (list) {
		/* show all function names */
		for (i = 0; i < NELEM(x_ftab); i++) {
			if (x_ftab[i].xf_name == NULL)
				continue;
			if (x_ftab[i].xf_name &&
			    !(x_ftab[i].xf_flags & XF_NOBIND))
				shprintf("%s\n", x_ftab[i].xf_name);
		}
		return (0);
	}

	if (a1 == NULL) {
		/* show all bindings */
		TAILQ_FOREACH(k, &kblist, entry)
			kb_print(k);
		return (0);
	}

	snprintf(in, sizeof in, "%s", kb_encode(a1));
	if (a2 == NULL) {
		/* print binding */
		TAILQ_FOREACH(k, &kblist, entry)
			if (!strcmp(k->seq, in)) {
				kb_print(k);
				return (0);
			}
		shprintf("%s = %s\n", kb_decode(a1), "auto-insert");
		return (0);
	}

	if (strlen(a2) == 0) {
		/* clear binding */
		TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
			if (!strcmp(k->seq, in)) {
				kb_del(k);
				break;
			}
		return (0);
	}

	/* set binding */
	if (macro) {
		/* delete old mapping */
		TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
			if (!strcmp(k->seq, in)) {
				kb_del(k);
				break;
			}
		kb_add_string(x_ins_string, kb_encode(a2), in);
		return (0);
	}

	/* set non macro binding */
	for (i = 0; i < NELEM(x_ftab); i++) {
		if (x_ftab[i].xf_name == NULL)
			continue;
		if (!strcmp(x_ftab[i].xf_name, a2)) {
			/* delete old mapping */
			TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
				if (!strcmp(k->seq, in)) {
					kb_del(k);
					break;
				}
			kb_add_string(x_ftab[i].xf_func, NULL, in);
			return (0);
		}
	}
	bi_errorf("%s: no such function", a2);
	return (1);
}

void
x_init_emacs(void)
{
	x_tty = 1;
	ainit(AEDIT);
	x_nextcmd = -1;

	TAILQ_INIT(&kblist);

	/* man page order */
	kb_add(x_abort,			CTRL('G'), 0);
	kb_add(x_mv_back,		CTRL('B'), 0);
	kb_add(x_mv_back,		CTRL('X'), CTRL('D'), 0);
	kb_add(x_mv_bword,		CTRL('['), 'b', 0);
	kb_add(x_beg_hist,		CTRL('['), '<', 0);
	kb_add(x_mv_begin,		CTRL('A'), 0);
	kb_add(x_fold_capitalize,	CTRL('['), 'C', 0);
	kb_add(x_fold_capitalize,	CTRL('['), 'c', 0);
	kb_add(x_comment,		CTRL('['), '#', 0);
	kb_add(x_complete,		CTRL('['), CTRL('['), 0);
	kb_add(x_comp_comm,		CTRL('X'), CTRL('['), 0);
	kb_add(x_comp_file,		CTRL('['), CTRL('X'), 0);
	kb_add(x_comp_list,		CTRL('I'), 0);
	kb_add(x_comp_list,		CTRL('['), '=', 0);
	kb_add(x_del_back,		CTRL('?'), 0);
	kb_add(x_del_back,		CTRL('H'), 0);
	kb_add(x_del_char,		CTRL('['), '[', '3', '~', 0); /* delete */
	kb_add(x_del_bword,		CTRL('W'), 0);
	kb_add(x_del_bword,		CTRL('['), CTRL('?'), 0);
	kb_add(x_del_bword,		CTRL('['), CTRL('H'), 0);
	kb_add(x_del_bword,		CTRL('['), 'h', 0);
	kb_add(x_del_fword,		CTRL('['), 'd', 0);
	kb_add(x_next_com,		CTRL('N'), 0);
	kb_add(x_next_com,		CTRL('X'), 'B', 0);
	kb_add(x_fold_lower,		CTRL('['), 'L', 0);
	kb_add(x_fold_lower,		CTRL('['), 'l', 0);
	kb_add(x_end_hist,		CTRL('['), '>', 0);
	kb_add(x_mv_end,		CTRL('E'), 0);
	/* how to handle: eot: ^_, underneath copied from original keybindings */
	kb_add(x_end_of_text,		CTRL('_'), 0);
	kb_add(x_eot_del,		CTRL('D'), 0);
	/* error */
	kb_add(x_xchg_point_mark,	CTRL('X'), CTRL('X'), 0);
	kb_add(x_expand,		CTRL('['), '*', 0);
	kb_add(x_mv_forw,		CTRL('F'), 0);
	kb_add(x_mv_forw,		CTRL('X'), 'C', 0);
	kb_add(x_mv_fword,		CTRL('['), 'f', 0);
	kb_add(x_goto_hist,		CTRL('['), 'g', 0);
	/* kill-line */
	kb_add(x_kill,			CTRL('K'), 0);
	kb_add(x_enumerate,		CTRL('['), '?', 0);
	kb_add(x_list_comm,		CTRL('X'), '?', 0);
	kb_add(x_list_file,		CTRL('X'), CTRL('Y'), 0);
	kb_add(x_newline,		CTRL('J'), 0);
	kb_add(x_newline,		CTRL('M'), 0);
	kb_add(x_nl_next_com,		CTRL('O'), 0);
	/* no-op */
	kb_add(x_prev_histword,		CTRL('['), '.', 0);
	kb_add(x_prev_histword,		CTRL('['), '_', 0);
	/* how to handle: quote: ^^ */
	kb_add(x_literal,		CTRL('^'), 0);
	kb_add(x_clear_screen,		CTRL('L'), 0);
	kb_add(x_search_char_back,	CTRL('['), CTRL(']'), 0);
	kb_add(x_search_char_forw,	CTRL(']'), 0);
	kb_add(x_search_hist,		CTRL('R'), 0);
	kb_add(x_set_mark,		CTRL('['), ' ', 0);
	kb_add(x_transpose,		CTRL('T'), 0);
	kb_add(x_prev_com,		CTRL('P'), 0);
	kb_add(x_prev_com,		CTRL('X'), 'A', 0);
	kb_add(x_fold_upper,		CTRL('['), 'U', 0);
	kb_add(x_fold_upper,		CTRL('['), 'u', 0);
	kb_add(x_literal,		CTRL('V'), 0);
	kb_add(x_yank,			CTRL('Y'), 0);
	kb_add(x_meta_yank,		CTRL('['), 'y', 0);
	/* man page ends here */

	/* arrow keys */
	kb_add(x_prev_com,		CTRL('['), '[', 'A', 0); /* up */
	kb_add(x_next_com,		CTRL('['), '[', 'B', 0); /* down */
	kb_add(x_mv_forw,		CTRL('['), '[', 'C', 0); /* right */
	kb_add(x_mv_back,		CTRL('['), '[', 'D', 0); /* left */
	kb_add(x_prev_com,		CTRL('['), 'O', 'A', 0); /* up */
	kb_add(x_next_com,		CTRL('['), 'O', 'B', 0); /* down */
	kb_add(x_mv_forw,		CTRL('['), 'O', 'C', 0); /* right */
	kb_add(x_mv_back,		CTRL('['), 'O', 'D', 0); /* left */

	/* more navigation keys */
	kb_add(x_mv_begin,		CTRL('['), '[', 'H', 0); /* home */
	kb_add(x_mv_end,		CTRL('['), '[', 'F', 0); /* end */
	kb_add(x_mv_begin,		CTRL('['), 'O', 'H', 0); /* home */
	kb_add(x_mv_end,		CTRL('['), 'O', 'F', 0); /* end */
	kb_add(x_mv_begin,		CTRL('['), '[', '1', '~', 0); /* home */
	kb_add(x_mv_end,		CTRL('['), '[', '4', '~', 0); /* end */
	kb_add(x_mv_begin,		CTRL('['), '[', '7', '~', 0); /* home */
	kb_add(x_mv_end,		CTRL('['), '[', '8', '~', 0); /* end */

	/* can't be bound */
	kb_add(x_set_arg,		CTRL('['), '0', 0);
	kb_add(x_set_arg,		CTRL('['), '1', 0);
	kb_add(x_set_arg,		CTRL('['), '2', 0);
	kb_add(x_set_arg,		CTRL('['), '3', 0);
	kb_add(x_set_arg,		CTRL('['), '4', 0);
	kb_add(x_set_arg,		CTRL('['), '5', 0);
	kb_add(x_set_arg,		CTRL('['), '6', 0);
	kb_add(x_set_arg,		CTRL('['), '7', 0);
	kb_add(x_set_arg,		CTRL('['), '8', 0);
	kb_add(x_set_arg,		CTRL('['), '9', 0);

	/* ctrl arrow keys */
	kb_add(x_mv_end,		CTRL('['), '[', '1', ';', '5', 'A', 0); /* ctrl up */
	kb_add(x_mv_begin,		CTRL('['), '[', '1', ';', '5', 'B', 0); /* ctrl down */
	kb_add(x_mv_fword,		CTRL('['), '[', '1', ';', '5', 'C', 0); /* ctrl right */
	kb_add(x_mv_bword,		CTRL('['), '[', '1', ';', '5', 'D', 0); /* ctrl left */
}

void
x_emacs_keys(X_chars *ec)
{
	x_bind_quiet = 1;
	if (ec->erase >= 0) {
		kb_add(x_del_back, ec->erase, 0);
		kb_add(x_del_bword, CTRL('['), ec->erase, 0);
	}
	if (ec->kill >= 0)
		kb_add(x_del_line, ec->kill, 0);
	if (ec->werase >= 0)
		kb_add(x_del_bword, ec->werase, 0);
	if (ec->intr >= 0)
		kb_add(x_abort, ec->intr, 0);
	if (ec->quit >= 0)
		kb_add(x_noop, ec->quit, 0);
	x_bind_quiet = 0;
}

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

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

	if (xmp == NULL) {
		x_e_putc(BEL);
		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)
{
	char	*tmp;

	if (xmp == NULL) {
		x_e_putc(BEL);
		return KSTD;
	}
	tmp = xmp;
	xmp = xcp;
	x_goto( tmp );
	return KSTD;
}

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

/*
 *	File/command name completion routines
 */

static int
x_comp_comm(int c)
{
	do_complete(XCF_COMMAND, CT_COMPLETE);
	return KSTD;
}
static int
x_list_comm(int c)
{
	do_complete(XCF_COMMAND, CT_LIST);
	return KSTD;
}
static int
x_complete(int c)
{
	do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
	return KSTD;
}
static int
x_enumerate(int c)
{
	do_complete(XCF_COMMAND_FILE, CT_LIST);
	return KSTD;
}
static int
x_comp_file(int c)
{
	do_complete(XCF_FILE, CT_COMPLETE);
	return KSTD;
}
static int
x_list_file(int c)
{
	do_complete(XCF_FILE, CT_LIST);
	return KSTD;
}
static int
x_comp_list(int c)
{
	do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
	return KSTD;
}
static int
x_expand(int c)
{
	char **words;
	int nwords = 0;
	int start, end;
	int is_command;
	int i;

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

	if (nwords == 0) {
		x_e_putc(BEL);
		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_putc(BEL);
			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 nwords;
	int start, end, nlen, olen;
	int is_command;
	int completed = 0;

	nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
	    &start, &end, &words, &is_command);
	/* no match */
	if (nwords == 0) {
		x_e_putc(BEL);
		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 = 1;
	}
	/* add space if single non-dir match */
	if (nwords == 1 && words[0][nlen - 1] != '/') {
		x_ins(" ");
		completed = 1;
	}

	if (type == CT_COMPLIST && !completed) {
		x_print_expansions(nwords, words, is_command);
		completed = 1;
	}

	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;
	xlp_valid = false;
	x_redraw(xx_cols);
	x_flush();
}

static int unget_char = -1;

static void
x_e_ungetc(int c)
{
	unget_char = c;
}

static int
x_e_getc(void)
{
	int c;

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

	return c;
}

static int
x_e_getu8(char *buf, int off)
{
	int	c, cc, len;

	c = x_e_getc();
	if (c == -1)
		return -1;
	buf[off++] = c;

	/*
	 * In the following, comments refer to violations of
	 * the inequality tests at the ends of the lines.
	 * See the utf8(7) manual page for details.
	 */

	if ((c & 0xf8) == 0xf0 && c < 0xf5)  /* beyond Unicode */
		len = 4;
	else if ((c & 0xf0) == 0xe0)
		len = 3;
	else if ((c & 0xe0) == 0xc0 && c > 0xc1)  /* use single byte */
		len = 2;
	else
		len = 1;

	for (; len > 1; len--) {
		cc = x_e_getc();
		if (cc == -1)
			break;
		if (isu8cont(cc) == 0 ||
		    (c == 0xe0 && len == 3 && cc < 0xa0) ||  /* use 2 bytes */
		    (c == 0xed && len == 3 && cc > 0x9f) ||  /* surrogates  */
		    (c == 0xf0 && len == 4 && cc < 0x90) ||  /* use 3 bytes */
		    (c == 0xf4 && len == 4 && cc > 0x8f)) {  /* beyond Uni. */
			x_e_ungetc(cc);
			break;
		}
		buf[off++] = cc;
	}
	buf[off] = '\0';

	return off;
}

static void
x_e_putc(int c)
{
	if (c == '\r' || c == '\n')
		x_col = 0;
	if (x_col < xx_cols) {
		x_putc(c);
		switch (c) {
		case BEL:
			break;
		case '\r':
		case '\n':
			break;
		case '\b':
			x_col--;
			break;
		default:
			if (!isu8cont(c))
				x_col++;
			break;
		}
	}
	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
		x_adjust();
}

#ifdef DEBUG
static int
x_debug_info(int c)
{
	x_flush();
	shellf("\nksh debug:\n");
	shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n",
	    x_col, xx_cols, x_displen);
	shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep);
	shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf);
	shellf("\txlp == 0x%lx\n", (long) xlp);
	shellf("\txlp == 0x%lx\n", (long) x_lastcp());
	shellf("\n");
	x_redraw(-1);
	return 0;
}
#endif

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

	while (*s && adj == x_adj_done)
		x_e_putc(*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;
	int first = 1;

	for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0)
		n = n * 10 + (c - '0');
	if (c < 0 || first) {
		x_e_putc(BEL);
		x_arg = 1;
		x_arg_defaulted = 1;
	} else {
		x_e_ungetc(c);
		x_arg = n;
		x_arg_defaulted = 0;
		x_arg_set = 1;
	}
	return KSTD;
}


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

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


/* 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.
 *
 *      Bound to M-.
 *
 * RETURN VALUE:
 *      KSTD
 */

static int
x_prev_histword(int c)
{
	char *rcp;
	char *cp;

	cp = *histptr;
	if (!cp)
		x_e_putc(BEL);
	else 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 {
		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++;
		c = *rcp;
		*rcp = '\0';
		x_ins(cp);
		*rcp = c;
	}
	return KSTD;
}

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

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

/* Lowercase N(1) words */
static int
x_fold_capitalize(int c)
{
	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 Capitalize words.
 *
 * RETURN VALUE:
 *      None
 */

static int
x_fold_case(int c)
{
	char *cp = xcp;

	if (cp == xep) {
		x_e_putc(BEL);
		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 */
				if (isupper((unsigned char)*cp))
					*cp = tolower((unsigned char)*cp);
			} else {		/* uppercase, capitalize */
				if (islower((unsigned char)*cp))
					*cp = toupper((unsigned char)*cp);
			}
			cp++;
		}
		/*
		 * now for the rest of the word
		 */
		while (cp != xep && !is_mfs(*cp)) {
			if (c == 'U') {		/* uppercase */
				if (islower((unsigned char)*cp))
					*cp = toupper((unsigned char)*cp);
			} else {		/* lowercase, capitalize */
				if (isupper((unsigned char)*cp))
					*cp = tolower((unsigned char)*cp);
			}
			cp++;
		}
	}
	x_goto(cp);
	return KSTD;
}

/* NAME:
 *      x_lastcp - last visible byte
 *
 * SYNOPSIS:
 *      x_lastcp()
 *
 * DESCRIPTION:
 *      This function returns a pointer to that byte in the
 *      edit buffer that will be the last displayed on the
 *      screen.  The sequence:
 *
 *      for (cp = x_lastcp(); cp > xcp; cp)
 *        x_bs(*--cp);
 *
 *      Will position the cursor correctly on the screen.
 *
 * RETURN VALUE:
 *      cp or NULL
 */

static char *
x_lastcp(void)
{
	char *rcp;
	int i;

	if (!xlp_valid) {
		for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
			i += x_size((unsigned char)*rcp);
		xlp = rcp;
	}
	xlp_valid = true;
	return (xlp);
}

#endif /* EMACS */