① currently: ((cond) ? true : false) but (!!(cond)) and casting to bool, the latter only if stdbool.h, would also work – which performs best on (and across) all supported systems?
		
			
				
	
	
		
			5341 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			5341 lines
		
	
	
		
			104 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*	$OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $	*/
 | 
						|
/*	$OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $	*/
 | 
						|
/*	$OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $	*/
 | 
						|
/*	$OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $	*/
 | 
						|
 | 
						|
/*-
 | 
						|
 * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011
 | 
						|
 *	Thorsten Glaser <tg@mirbsd.org>
 | 
						|
 *
 | 
						|
 * Provided that these terms and disclaimer and all copyright notices
 | 
						|
 * are retained or reproduced in an accompanying document, permission
 | 
						|
 * is granted to deal in this work without restriction, including un-
 | 
						|
 * limited rights to use, publicly perform, distribute, sell, modify,
 | 
						|
 * merge, give away, or sublicence.
 | 
						|
 *
 | 
						|
 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
 | 
						|
 * the utmost extent permitted by applicable law, neither express nor
 | 
						|
 * implied; without malicious intent or gross negligence. In no event
 | 
						|
 * may a licensor, author or contributor be held liable for indirect,
 | 
						|
 * direct, other damage, loss, or other issues arising in any way out
 | 
						|
 * of dealing in the work, even if advised of the possibility of such
 | 
						|
 * damage or existence of a defect, except proven that it results out
 | 
						|
 * of said person's immediate fault when using the work as intended.
 | 
						|
 */
 | 
						|
 | 
						|
#include "sh.h"
 | 
						|
 | 
						|
__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.205 2011/03/07 20:30:35 tg Exp $");
 | 
						|
 | 
						|
/*
 | 
						|
 * in later versions we might use libtermcap for this, but since external
 | 
						|
 * dependencies are problematic, this has not yet been decided on; another
 | 
						|
 * good string is "\033c" except on hardware terminals like the DEC VT420
 | 
						|
 * which do a full power cycle then...
 | 
						|
 */
 | 
						|
#ifndef MKSH_CLS_STRING
 | 
						|
#define MKSH_CLS_STRING		"\033[;H\033[J"
 | 
						|
#endif
 | 
						|
#ifndef MKSH_CLRTOEOL_STRING
 | 
						|
#define MKSH_CLRTOEOL_STRING	"\033[K"
 | 
						|
#endif
 | 
						|
 | 
						|
/* tty driver characters we are interested in */
 | 
						|
typedef struct {
 | 
						|
	int erase;
 | 
						|
	int kill;
 | 
						|
	int werase;
 | 
						|
	int intr;
 | 
						|
	int quit;
 | 
						|
	int eof;
 | 
						|
} X_chars;
 | 
						|
 | 
						|
static 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)
 | 
						|
#define XCF_IS_COMMAND	BIT(3)	/* return flag: is command */
 | 
						|
#define XCF_IS_VARSUB	BIT(4)	/* return flag: is $FOO substitution */
 | 
						|
#define XCF_IS_EXTGLOB	BIT(5)	/* return flag: is foo* expansion */
 | 
						|
 | 
						|
static char editmode;
 | 
						|
static int xx_cols;			/* for Emacs mode */
 | 
						|
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 ***);
 | 
						|
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);
 | 
						|
#if !MKSH_S_NOVI
 | 
						|
static int x_vi(char *, size_t);
 | 
						|
#endif
 | 
						|
 | 
						|
#define x_flush()	shf_flush(shl_out)
 | 
						|
#ifdef MKSH_SMALL
 | 
						|
#define x_putc(c)	x_putcf(c)
 | 
						|
#else
 | 
						|
#define x_putc(c)	shf_putc((c), shl_out)
 | 
						|
#endif
 | 
						|
 | 
						|
static int path_order_cmp(const void *aa, const void *bb);
 | 
						|
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, char *, char ***);
 | 
						|
static int x_command_glob(int, char *, char ***);
 | 
						|
static int x_locate_word(const char *, int, int, int *, bool *);
 | 
						|
 | 
						|
static int x_e_getmbc(char *);
 | 
						|
static int x_e_rebuildline(const char *);
 | 
						|
 | 
						|
/* +++ generic editing functions +++ */
 | 
						|
 | 
						|
/* Called from main */
 | 
						|
void
 | 
						|
x_init(void)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * Set edchars to -2 to force initial binding, except
 | 
						|
	 * we need default values for some deficient systems…
 | 
						|
	 */
 | 
						|
	edchars.erase = edchars.kill = edchars.intr = edchars.quit =
 | 
						|
	    edchars.eof = -2;
 | 
						|
	/* ^W */
 | 
						|
	edchars.werase = 027;
 | 
						|
	x_init_emacs();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * 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);
 | 
						|
#if !MKSH_S_NOVI
 | 
						|
	else if (Flag(FVI))
 | 
						|
		i = x_vi(buf, len);
 | 
						|
#endif
 | 
						|
	else
 | 
						|
		/* internal error */
 | 
						|
		i = -1;
 | 
						|
	editmode = 0;
 | 
						|
	x_mode(false);
 | 
						|
	return (i);
 | 
						|
}
 | 
						|
 | 
						|
/* tty I/O */
 | 
						|
 | 
						|
static int
 | 
						|
x_getc(void)
 | 
						|
{
 | 
						|
	char c;
 | 
						|
	ssize_t n;
 | 
						|
 | 
						|
	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
 | 
						|
		if (trap) {
 | 
						|
			x_mode(false);
 | 
						|
			runtraps(0);
 | 
						|
#ifdef SIGWINCH
 | 
						|
			if (got_winch) {
 | 
						|
				change_winsz();
 | 
						|
				if (x_cols != xx_cols && editmode == 1) {
 | 
						|
					/* redraw line in Emacs mode */
 | 
						|
					xx_cols = x_cols;
 | 
						|
					x_e_rebuildline(MKSH_CLRTOEOL_STRING);
 | 
						|
				}
 | 
						|
			}
 | 
						|
#endif
 | 
						|
			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)
 | 
						|
		/* somewhat arbitrary - it's what AT&T ksh does */
 | 
						|
		return (1);
 | 
						|
 | 
						|
	/* 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)
 | 
						|
		/* not x_free_words() */
 | 
						|
		XPfree(l);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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 MKSH_A_UNUSED, char *toglob, char ***wordsp)
 | 
						|
{
 | 
						|
	char **words;
 | 
						|
	int nwords, i, idx;
 | 
						|
	bool escaping;
 | 
						|
	XPtrV w;
 | 
						|
	struct source *s, *sold;
 | 
						|
 | 
						|
	/* remove all escaping backward slashes */
 | 
						|
	escaping = false;
 | 
						|
	for (i = 0, idx = 0; toglob[i]; i++) {
 | 
						|
		if (toglob[i] == '\\' && !escaping) {
 | 
						|
			escaping = true;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		/* specially escape escaped [ or $ or ` for globbing */
 | 
						|
		if (escaping && (toglob[i] == '[' ||
 | 
						|
		    toglob[i] == '$' || toglob[i] == '`'))
 | 
						|
			toglob[idx++] = QCHAR;
 | 
						|
 | 
						|
		toglob[idx] = toglob[i];
 | 
						|
		idx++;
 | 
						|
		if (escaping)
 | 
						|
			escaping = false;
 | 
						|
	}
 | 
						|
	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("%s: %s", "fileglob", "bad substitution");
 | 
						|
		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;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	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, char *toglob, char ***wordsp)
 | 
						|
{
 | 
						|
	char *pat, *fpath;
 | 
						|
	int nwords;
 | 
						|
	XPtrV w;
 | 
						|
	struct block *l;
 | 
						|
 | 
						|
	/* Convert "foo*" (toglob) to a pattern for future use */
 | 
						|
	pat = evalstr(toglob, DOPAT | DOTILDE);
 | 
						|
 | 
						|
	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 *)
 | 
						|
		    alloc2(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 *flagsp, const char *buf, int buflen, int pos, int *startp,
 | 
						|
    int *endp, char ***wordsp)
 | 
						|
{
 | 
						|
	int len, nwords = 0;
 | 
						|
	char **words = NULL;
 | 
						|
	bool is_command;
 | 
						|
 | 
						|
	len = x_locate_word(buf, buflen, pos, startp, &is_command);
 | 
						|
	if (!(*flagsp & 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);
 | 
						|
 | 
						|
	if (len >= 0) {
 | 
						|
		char *toglob, *s;
 | 
						|
		bool saw_slash = false, saw_dollar = false, saw_glob = false;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Given a string, copy it and possibly add a '*' to the end.
 | 
						|
		 */
 | 
						|
 | 
						|
		strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
 | 
						|
		toglob[len] = '\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 == '$') {
 | 
						|
				/*
 | 
						|
				 * Do not append a space after the value
 | 
						|
				 * if expanding a parameter substitution
 | 
						|
				 * as in: “cat $HOME/.ss↹” (LP: #710539)
 | 
						|
				 */
 | 
						|
				saw_dollar = true;
 | 
						|
			} else if (*s == '?' || *s == '*' || *s == '[' ||
 | 
						|
			    /* ?() *() +() @() !() but two already checked */
 | 
						|
			    (s[1] == '(' /*)*/ &&
 | 
						|
			    (*s == '+' || *s == '@' || *s == '!'))) {
 | 
						|
				/* just expand based on the extglob */
 | 
						|
				saw_glob = true;
 | 
						|
			} else if (*s == '/')
 | 
						|
				saw_slash = true;
 | 
						|
		}
 | 
						|
		if (saw_glob) {
 | 
						|
			/*
 | 
						|
			 * do not append a glob, we already have a
 | 
						|
			 * glob or extglob; it works even if this is
 | 
						|
			 * a parameter expansion as we have a glob
 | 
						|
			 */
 | 
						|
			*flagsp |= XCF_IS_EXTGLOB;
 | 
						|
		} else if (saw_dollar) {
 | 
						|
			/* do not append a glob, nor later a space */
 | 
						|
			*flagsp |= XCF_IS_VARSUB;
 | 
						|
		} else if (*toglob != '~' || saw_slash) {
 | 
						|
			/* append a glob, this is not just a tilde */
 | 
						|
			toglob[len] = '*';
 | 
						|
			toglob[len + 1] = '\0';
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Expand (glob) it now.
 | 
						|
		 */
 | 
						|
 | 
						|
		nwords = is_command ?
 | 
						|
		    x_command_glob(*flagsp, toglob, &words) :
 | 
						|
		    x_file_glob(*flagsp, toglob, &words);
 | 
						|
		afree(toglob, ATEMP);
 | 
						|
	}
 | 
						|
	if (nwords == 0) {
 | 
						|
		*wordsp = NULL;
 | 
						|
		return (0);
 | 
						|
	}
 | 
						|
	*flagsp |= XCF_IS_COMMAND;
 | 
						|
	*wordsp = words;
 | 
						|
	*endp = *startp + len;
 | 
						|
 | 
						|
	return (nwords);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * 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 = lpath, *p;
 | 
						|
	char *xp, **words;
 | 
						|
	size_t pathlen, patlen, oldsize, newsize, i, j;
 | 
						|
	int staterr;
 | 
						|
	XString xs;
 | 
						|
 | 
						|
	patlen = strlen(pat);
 | 
						|
	checkoktoadd(patlen, 129 + X_EXTRA);
 | 
						|
	++patlen;
 | 
						|
	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);
 | 
						|
		/* mark dirs */
 | 
						|
		glob_str(Xstring(xs, xp), wp, 1);
 | 
						|
		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);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* +++ emacs editing mode +++ */
 | 
						|
 | 
						|
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 */
 | 
						|
 | 
						|
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) == '$' || ((c) & 0x80)))
 | 
						|
 | 
						|
#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 x_col;
 | 
						|
static int x_displen;
 | 
						|
static int x_arg;		/* general purpose arg */
 | 
						|
static bool 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 */
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static char *(*x_atab)[X_TABSZ];	/* macro definitions */
 | 
						|
#endif
 | 
						|
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;
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static char *macroptr = NULL;	/* bind key macro active? */
 | 
						|
#endif
 | 
						|
#if !MKSH_S_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);
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static int x_search_dir(int);
 | 
						|
#endif
 | 
						|
static int x_match(char *, char *);
 | 
						|
static void x_redraw(int);
 | 
						|
static void x_push(int);
 | 
						|
static char *x_mapin(const char *, Area *)
 | 
						|
    MKSH_A_NONNULL((nonnull (1)));
 | 
						|
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 *);
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static int x_fold_case(int);
 | 
						|
#endif
 | 
						|
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);
 | 
						|
 | 
						|
enum emacs_funcs {
 | 
						|
#define EMACSFN_ENUMS
 | 
						|
#include "emacsfn.h"
 | 
						|
	XFUNC_MAX
 | 
						|
};
 | 
						|
 | 
						|
#define EMACSFN_DEFNS
 | 
						|
#include "emacsfn.h"
 | 
						|
 | 
						|
static const struct x_ftab x_ftab[] = {
 | 
						|
#define EMACSFN_ITEMS
 | 
						|
#include "emacsfn.h"
 | 
						|
	{ 0, NULL, 0 }
 | 
						|
};
 | 
						|
 | 
						|
static struct x_defbindings const x_defbindings[] = {
 | 
						|
	{ XFUNC_del_back,		0, CTRL('?')	},
 | 
						|
	{ XFUNC_del_bword,		1, CTRL('?')	},
 | 
						|
	{ XFUNC_eot_del,		0, CTRL('D')	},
 | 
						|
	{ XFUNC_del_back,		0, CTRL('H')	},
 | 
						|
	{ XFUNC_del_bword,		1, CTRL('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, CTRL('B')	},
 | 
						|
	{ XFUNC_mv_forw,		0, CTRL('F')	},
 | 
						|
	{ XFUNC_search_char_forw,	0, CTRL(']')	},
 | 
						|
	{ XFUNC_search_char_back,	1, CTRL(']')	},
 | 
						|
	{ XFUNC_newline,		0, CTRL('M')	},
 | 
						|
	{ XFUNC_newline,		0, CTRL('J')	},
 | 
						|
	{ XFUNC_end_of_text,		0, CTRL('_')	},
 | 
						|
	{ XFUNC_abort,			0, CTRL('G')	},
 | 
						|
	{ XFUNC_prev_com,		0, CTRL('P')	},
 | 
						|
	{ XFUNC_next_com,		0, CTRL('N')	},
 | 
						|
	{ XFUNC_nl_next_com,		0, CTRL('O')	},
 | 
						|
	{ XFUNC_search_hist,		0, CTRL('R')	},
 | 
						|
	{ XFUNC_beg_hist,		1,	'<'	},
 | 
						|
	{ XFUNC_end_hist,		1,	'>'	},
 | 
						|
	{ XFUNC_goto_hist,		1,	'g'	},
 | 
						|
	{ XFUNC_mv_end,			0, CTRL('E')	},
 | 
						|
	{ XFUNC_mv_begin,		0, CTRL('A')	},
 | 
						|
	{ XFUNC_draw_line,		0, CTRL('L')	},
 | 
						|
	{ XFUNC_cls,			1, CTRL('L')	},
 | 
						|
	{ XFUNC_meta1,			0, CTRL('[')	},
 | 
						|
	{ XFUNC_meta2,			0, CTRL('X')	},
 | 
						|
	{ XFUNC_kill,			0, CTRL('K')	},
 | 
						|
	{ XFUNC_yank,			0, CTRL('Y')	},
 | 
						|
	{ XFUNC_meta_yank,		1,	'y'	},
 | 
						|
	{ XFUNC_literal,		0, CTRL('^')	},
 | 
						|
	{ XFUNC_comment,		1,	'#'	},
 | 
						|
	{ XFUNC_transpose,		0, CTRL('T')	},
 | 
						|
	{ XFUNC_complete,		1, CTRL('[')	},
 | 
						|
	{ XFUNC_comp_list,		0, CTRL('I')	},
 | 
						|
	{ XFUNC_comp_list,		1,	'='	},
 | 
						|
	{ XFUNC_enumerate,		1,	'?'	},
 | 
						|
	{ XFUNC_expand,			1,	'*'	},
 | 
						|
	{ XFUNC_comp_file,		1, CTRL('X')	},
 | 
						|
	{ XFUNC_comp_comm,		2, CTRL('[')	},
 | 
						|
	{ XFUNC_list_comm,		2,	'?'	},
 | 
						|
	{ XFUNC_list_file,		2, CTRL('Y')	},
 | 
						|
	{ XFUNC_set_mark,		1,	' '	},
 | 
						|
	{ XFUNC_kill_region,		0, CTRL('W')	},
 | 
						|
	{ XFUNC_xchg_point_mark,	2, CTRL('X')	},
 | 
						|
	{ XFUNC_literal,		0, CTRL('V')	},
 | 
						|
	{ XFUNC_version,		1, CTRL('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'	},
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	{ 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'	},
 | 
						|
#endif
 | 
						|
	/*
 | 
						|
	 * 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'	},
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	{ XFUNC_vt_hack,		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'	},
 | 
						|
	{ XFUNC_search_hist_up | 0x80,	2,	'5'	},
 | 
						|
	{ XFUNC_search_hist_dn | 0x80,	2,	'6'	},
 | 
						|
	/* more non-standard ones */
 | 
						|
	{ XFUNC_edit_line,		2,	'e'	}
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
#ifdef MKSH_SMALL
 | 
						|
static void x_modified(void);
 | 
						|
static void
 | 
						|
x_modified(void)
 | 
						|
{
 | 
						|
	if (!modified) {
 | 
						|
		x_histp = histptr + 1;
 | 
						|
		modified = 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
#define XFUNC_VALUE(f) (f)
 | 
						|
#else
 | 
						|
#define x_modified() do {			\
 | 
						|
	if (!modified) {			\
 | 
						|
		x_histp = histptr + 1;		\
 | 
						|
		modified = 1;			\
 | 
						|
	}					\
 | 
						|
} while (/* CONSTCOND */ 0)
 | 
						|
#define XFUNC_VALUE(f) (f & 0x7F)
 | 
						|
#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 (UTFMODE) {
 | 
						|
		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;
 | 
						|
	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;
 | 
						|
	}
 | 
						|
	editmode = 1;
 | 
						|
	while (1) {
 | 
						|
		x_flush();
 | 
						|
		if ((c = x_e_getc()) < 0)
 | 
						|
			return (0);
 | 
						|
 | 
						|
		f = x_curprefix == -1 ? XFUNC_insert :
 | 
						|
		    x_tab[x_curprefix][c];
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
		if (f & 0x80) {
 | 
						|
			f &= 0x7F;
 | 
						|
			if ((i = x_e_getc()) != '~')
 | 
						|
				x_e_ungetc(i);
 | 
						|
		}
 | 
						|
 | 
						|
		/* avoid bind key macro recursion */
 | 
						|
		if (macroptr && f == XFUNC_ins_string)
 | 
						|
			f = XFUNC_insert;
 | 
						|
#endif
 | 
						|
 | 
						|
		if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
 | 
						|
		    x_last_command != XFUNC_set_arg) {
 | 
						|
			x_arg = 1;
 | 
						|
			x_arg_defaulted = true;
 | 
						|
		}
 | 
						|
		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 (UTFMODE) {
 | 
						|
		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);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static int
 | 
						|
x_ins_string(int c)
 | 
						|
{
 | 
						|
	macroptr = x_atab[c >> 8][c & 255];
 | 
						|
	/*
 | 
						|
	 * we no longer need to bother checking if macroptr is
 | 
						|
	 * not NULL but first char is NUL; x_e_getc() does it
 | 
						|
	 */
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
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);
 | 
						|
	/* has x_adjust() been called? */
 | 
						|
	if (adj == x_adj_done) {
 | 
						|
		/* 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 MKSH_A_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 MKSH_A_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;
 | 
						|
	/* Copies the NUL */
 | 
						|
	memmove(xcp, xcp + nb, xep - xcp + 1);
 | 
						|
	/* don't redraw */
 | 
						|
	x_adj_ok = 0;
 | 
						|
	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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_delete(x_bword(), true);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_mv_bword(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_bword();
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_mv_fword(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_fword(1);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_del_fword(int c MKSH_A_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)
 | 
						|
		cp += utf_ptradj(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)
 | 
						|
		cp2 += utf_ptradj(cp2);
 | 
						|
	if (move)
 | 
						|
		x_goto(cp);
 | 
						|
	return (nc);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
x_goto(char *cp)
 | 
						|
{
 | 
						|
	if (UTFMODE)
 | 
						|
		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 (UTFMODE)
 | 
						|
		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 (UTFMODE && (c > 0x7F))
 | 
						|
		return (utf_widthadj(cp, (const char **)dcp));
 | 
						|
	if (dcp)
 | 
						|
		*dcp = cp + 1;
 | 
						|
	if (c == '\t')
 | 
						|
		/* Kludge, tabs are always four spaces. */
 | 
						|
		return (4);
 | 
						|
	if (c < ' ' || c == 0x7f)
 | 
						|
		/* control unsigned char */
 | 
						|
		return (2);
 | 
						|
	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 == '\t') {
 | 
						|
		/* Kludge, tabs are always four spaces. */
 | 
						|
		x_e_puts("    ");
 | 
						|
		(*cp)++;
 | 
						|
	} else if (c < ' ' || c == 0x7f) {
 | 
						|
		x_e_putc2('^');
 | 
						|
		x_e_putc2(UNCTRL(c));
 | 
						|
		(*cp)++;
 | 
						|
	} else
 | 
						|
		x_e_putc3((const char **)cp);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_mv_back(int c MKSH_A_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 MKSH_A_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 MKSH_A_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 MKSH_A_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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_e_putc2('\r');
 | 
						|
	x_e_putc2('\n');
 | 
						|
	x_flush();
 | 
						|
	*xep++ = '\n';
 | 
						|
	return (KEOL);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_end_of_text(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_zotc2(edchars.eof);
 | 
						|
	x_putc('\r');
 | 
						|
	x_putc('\n');
 | 
						|
	x_flush();
 | 
						|
	return (KEOL);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_beg_hist(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_load_hist(history);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_end_hist(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_load_hist(histptr);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_prev_com(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_load_hist(x_histp - x_arg);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_next_com(int c MKSH_A_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 MKSH_A_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 MKSH_A_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 == CTRL('[')) {
 | 
						|
			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(CTRL('['));
 | 
						|
				x_e_ungetc(c);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		}
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
		if (f & 0x80) {
 | 
						|
			f &= 0x7F;
 | 
						|
			if ((c = x_e_getc()) != '~')
 | 
						|
				x_e_ungetc(c);
 | 
						|
		}
 | 
						|
#endif
 | 
						|
		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);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
/* anchored search up from current line */
 | 
						|
static int
 | 
						|
x_search_hist_up(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (x_search_dir(-1));
 | 
						|
}
 | 
						|
 | 
						|
/* anchored search down from current line */
 | 
						|
static int
 | 
						|
x_search_hist_dn(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (x_search_dir(1));
 | 
						|
}
 | 
						|
 | 
						|
/* anchored search in the indicated direction */
 | 
						|
static int
 | 
						|
x_search_dir(int search_dir /* should've been bool */)
 | 
						|
{
 | 
						|
	char **hp = x_histp + search_dir;
 | 
						|
	size_t curs = xcp - xbuf;
 | 
						|
 | 
						|
	while (histptr >= hp && hp >= history) {
 | 
						|
		if (strncmp(xbuf, *hp, curs) == 0) {
 | 
						|
			x_load_hist(hp);
 | 
						|
			x_goto(xbuf + curs);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		hp += search_dir;
 | 
						|
	}
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/* 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 MKSH_A_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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_goto(xep);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_mv_begin(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_goto(xbuf);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_draw_line(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_redraw(-1);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_e_rebuildline(const char *clrstr)
 | 
						|
{
 | 
						|
	shf_puts(clrstr, shl_out);
 | 
						|
	x_adjust();
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_cls(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (x_e_rebuildline(MKSH_CLS_STRING));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * 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)
 | 
						|
			/* we fill the line */
 | 
						|
			i = 0;
 | 
						|
		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 MKSH_A_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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_curprefix = -1;
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_meta1(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_curprefix = 1;
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_meta2(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_curprefix = 2;
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_kill(int c MKSH_A_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 MKSH_A_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 MKSH_A_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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	/* x_zotc(c); */
 | 
						|
	xlp = xep = xcp = xbp = xbuf;
 | 
						|
	xlp_valid = true;
 | 
						|
	*xcp = 0;
 | 
						|
	x_modified();
 | 
						|
	return (KINTR);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_error(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	x_e_putc2(7);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
/* special VT100 style key sequence hack */
 | 
						|
static int
 | 
						|
x_vt_hack(int c)
 | 
						|
{
 | 
						|
	/* we only support PF2-'1' for now */
 | 
						|
	if (c != (2 << 8 | '1'))
 | 
						|
		return (x_error(c));
 | 
						|
 | 
						|
	/* what's the next character? */
 | 
						|
	switch ((c = x_e_getc())) {
 | 
						|
	case '~':
 | 
						|
		x_arg = 1;
 | 
						|
		x_arg_defaulted = true;
 | 
						|
		return (x_mv_begin(0));
 | 
						|
	case ';':
 | 
						|
		/* "interesting" sequence detected */
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		goto unwind_err;
 | 
						|
	}
 | 
						|
 | 
						|
	/* XXX x_e_ungetc is one-octet only */
 | 
						|
	if ((c = x_e_getc()) != '5' && c != '3')
 | 
						|
		goto unwind_err;
 | 
						|
 | 
						|
	/*-
 | 
						|
	 * At this point, we have read the following octets so far:
 | 
						|
	 * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
 | 
						|
	 * - 1 (vt_hack)
 | 
						|
	 * - ;
 | 
						|
	 * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
 | 
						|
	 * We can now accept one more octet designating the key.
 | 
						|
	 */
 | 
						|
 | 
						|
	switch ((c = x_e_getc())) {
 | 
						|
	case 'C':
 | 
						|
		return (x_mv_fword(c));
 | 
						|
	case 'D':
 | 
						|
		return (x_mv_bword(c));
 | 
						|
	}
 | 
						|
 | 
						|
 unwind_err:
 | 
						|
	x_e_ungetc(c);
 | 
						|
	return (x_error(c));
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static char *
 | 
						|
x_mapin(const char *cp, Area *ap)
 | 
						|
{
 | 
						|
	char *news, *op;
 | 
						|
 | 
						|
	/* for clang's static analyser, the nonnull attribute isn't enough */
 | 
						|
	mkssert(cp != NULL);
 | 
						|
 | 
						|
	strdupx(news, cp, ap);
 | 
						|
	op = news;
 | 
						|
	while (*cp) {
 | 
						|
		/* XXX -- should handle \^ escape? */
 | 
						|
		if (*cp == '^') {
 | 
						|
			cp++;
 | 
						|
			if (*cp >= '?')
 | 
						|
				/* includes '?'; ASCII */
 | 
						|
				*op++ = CTRL(*cp);
 | 
						|
			else {
 | 
						|
				*op++ = '^';
 | 
						|
				cp--;
 | 
						|
			}
 | 
						|
		} else
 | 
						|
			*op++ = *cp;
 | 
						|
		cp++;
 | 
						|
	}
 | 
						|
	*op = '\0';
 | 
						|
 | 
						|
	return (news);
 | 
						|
}
 | 
						|
 | 
						|
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 ?
 | 
						|
		    CTRL('[') : CTRL('X')), shl_stdout);
 | 
						|
#ifdef MKSH_SMALL
 | 
						|
	shprintf("%s = ", x_mapout(key));
 | 
						|
#else
 | 
						|
	shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : "");
 | 
						|
	if (XFUNC_VALUE(f) != XFUNC_ins_string)
 | 
						|
#endif
 | 
						|
		shprintf("%s\n", x_ftab[XFUNC_VALUE(f)].xf_name);
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	else
 | 
						|
		shprintf("'%s'\n", x_atab[prefix][key]);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
int
 | 
						|
x_bind(const char *a1, const char *a2,
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
    /* bind -m */
 | 
						|
    bool macro,
 | 
						|
#endif
 | 
						|
    /* bind -l */
 | 
						|
    bool list)
 | 
						|
{
 | 
						|
	unsigned char f;
 | 
						|
	int prefix, key;
 | 
						|
	char *m1, *m2;
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	char *sp = NULL;
 | 
						|
	bool hastilde;
 | 
						|
#endif
 | 
						|
 | 
						|
	if (x_tab == NULL) {
 | 
						|
		bi_errorf("can't 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 = XFUNC_VALUE(x_tab[prefix][key]);
 | 
						|
				if (f == XFUNC_insert || f == XFUNC_error
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
				    || (macro && f != XFUNC_ins_string)
 | 
						|
#endif
 | 
						|
				    )
 | 
						|
					continue;
 | 
						|
				x_print(prefix, key);
 | 
						|
			}
 | 
						|
		return (0);
 | 
						|
	}
 | 
						|
	m2 = m1 = x_mapin(a1, ATEMP);
 | 
						|
	prefix = 0;
 | 
						|
	for (;; m1++) {
 | 
						|
		key = (unsigned char)*m1;
 | 
						|
		f = XFUNC_VALUE(x_tab[prefix][key]);
 | 
						|
		if (f == XFUNC_meta1)
 | 
						|
			prefix = 1;
 | 
						|
		else if (f == XFUNC_meta2)
 | 
						|
			prefix = 2;
 | 
						|
		else
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	if (*++m1
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	    && ((*m1 != '~') || *(m1 + 1))
 | 
						|
#endif
 | 
						|
	    ) {
 | 
						|
		char msg[256];
 | 
						|
		const char *c = a1;
 | 
						|
		m1 = msg;
 | 
						|
		while (*c && m1 < (msg + sizeof(msg) - 3))
 | 
						|
			x_mapout2(*c++, &m1);
 | 
						|
		bi_errorf("%s: %s", "too long key sequence", msg);
 | 
						|
		return (1);
 | 
						|
	}
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	hastilde = tobool(*m1);
 | 
						|
#endif
 | 
						|
	afree(m2, ATEMP);
 | 
						|
 | 
						|
	if (a2 == NULL) {
 | 
						|
		x_print(prefix, key);
 | 
						|
		return (0);
 | 
						|
	}
 | 
						|
	if (*a2 == 0) {
 | 
						|
		f = XFUNC_insert;
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	} else if (macro) {
 | 
						|
		f = XFUNC_ins_string;
 | 
						|
		sp = x_mapin(a2, AEDIT);
 | 
						|
#endif
 | 
						|
	} else {
 | 
						|
		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: %s %s", a2, "no such", T_function);
 | 
						|
			return (1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string &&
 | 
						|
	    x_atab[prefix][key])
 | 
						|
		afree(x_atab[prefix][key], AEDIT);
 | 
						|
#endif
 | 
						|
	x_tab[prefix][key] = f
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	    | (hastilde ? 0x80 : 0)
 | 
						|
#endif
 | 
						|
	    ;
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	x_atab[prefix][key] = sp;
 | 
						|
#endif
 | 
						|
 | 
						|
	/* 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;
 | 
						|
 | 
						|
	ainit(AEDIT);
 | 
						|
	x_nextcmd = -1;
 | 
						|
 | 
						|
	x_tab = alloc2(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;
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	x_atab = alloc2(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;
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	xmp = xcp;
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_kill_region(int c MKSH_A_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 MKSH_A_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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *	File/command name completion routines
 | 
						|
 */
 | 
						|
static int
 | 
						|
x_comp_comm(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_COMMAND, CT_COMPLETE);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_list_comm(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_COMMAND, CT_LIST);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_complete(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_enumerate(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_COMMAND_FILE, CT_LIST);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_comp_file(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_FILE, CT_COMPLETE);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_list_file(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_FILE, CT_LIST);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_comp_list(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
x_expand(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	char **words;
 | 
						|
	int start, end, nwords, i;
 | 
						|
 | 
						|
	i = XCF_FILE;
 | 
						|
	nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
 | 
						|
	    &start, &end, &words);
 | 
						|
 | 
						|
	if (nwords == 0) {
 | 
						|
		x_e_putc2(7);
 | 
						|
		return (KSTD);
 | 
						|
	}
 | 
						|
	x_goto(xbuf + start);
 | 
						|
	x_delete(end - start, false);
 | 
						|
 | 
						|
	i = 0;
 | 
						|
	while (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);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
do_complete(
 | 
						|
    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
 | 
						|
    int flags,
 | 
						|
    /* 0 for list, 1 for complete and 2 for complete-list */
 | 
						|
    Comp_type type)
 | 
						|
{
 | 
						|
	char **words;
 | 
						|
	int start, end, nlen, olen, nwords;
 | 
						|
	bool completed = false;
 | 
						|
 | 
						|
	nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
 | 
						|
	    &start, &end, &words);
 | 
						|
	/* no match */
 | 
						|
	if (nwords == 0) {
 | 
						|
		x_e_putc2(7);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (type == CT_LIST) {
 | 
						|
		x_print_expansions(nwords, words, flags & XCF_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 and not parameter substitution */
 | 
						|
	if (nwords == 1 && words[0][nlen - 1] != '/' &&
 | 
						|
	    !(flags & XCF_IS_VARSUB)) {
 | 
						|
		x_ins(" ");
 | 
						|
		completed = true;
 | 
						|
	}
 | 
						|
	if (type == CT_COMPLIST && !completed) {
 | 
						|
		x_print_expansions(nwords, words, flags & XCF_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)
 | 
						|
{
 | 
						|
	/* flag the fact that we were called. */
 | 
						|
	x_adj_done++;
 | 
						|
	/*
 | 
						|
	 * we had a problem if the prompt length > xx_cols / 2
 | 
						|
	 */
 | 
						|
	if ((xbp = xcp - (x_displen / 2)) < xbuf)
 | 
						|
		xbp = xbuf;
 | 
						|
	if (UTFMODE)
 | 
						|
		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;
 | 
						|
		return (c);
 | 
						|
	}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
	if (macroptr) {
 | 
						|
		if ((c = (unsigned char)*macroptr++))
 | 
						|
			return (c);
 | 
						|
		macroptr = NULL;
 | 
						|
	}
 | 
						|
#endif
 | 
						|
 | 
						|
	return (x_getc());
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
x_e_putc2(int c)
 | 
						|
{
 | 
						|
	int width = 1;
 | 
						|
 | 
						|
	if (c == '\r' || c == '\n')
 | 
						|
		x_col = 0;
 | 
						|
	if (x_col < xx_cols) {
 | 
						|
		if (UTFMODE && (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 (UTFMODE && (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;
 | 
						|
	bool first = true;
 | 
						|
 | 
						|
	/* strip command prefix */
 | 
						|
	c &= 255;
 | 
						|
	while (c >= 0 && ksh_isdigit(c)) {
 | 
						|
		n = n * 10 + (c - '0');
 | 
						|
		c = x_e_getc();
 | 
						|
		first = false;
 | 
						|
	}
 | 
						|
	if (c < 0 || first) {
 | 
						|
		x_e_putc2(7);
 | 
						|
		x_arg = 1;
 | 
						|
		x_arg_defaulted = true;
 | 
						|
	} else {
 | 
						|
		x_e_ungetc(c);
 | 
						|
		x_arg = n;
 | 
						|
		x_arg_defaulted = false;
 | 
						|
	}
 | 
						|
	return (KSTD);
 | 
						|
}
 | 
						|
 | 
						|
/* Comment or uncomment the current line. */
 | 
						|
static int
 | 
						|
x_comment(int c MKSH_A_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 MKSH_A_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);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
static int
 | 
						|
x_edit_line(int c MKSH_A_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'));
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/*-
 | 
						|
 * 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 MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	char *rcp, *cp;
 | 
						|
	char **xhp;
 | 
						|
	int m = 1;
 | 
						|
	/* -1 = defaulted; 0+ = argument */
 | 
						|
	static int last_arg = -1;
 | 
						|
 | 
						|
	if (x_last_command == XFUNC_prev_histword) {
 | 
						|
		if (xmp && modified > 1)
 | 
						|
			x_kill_region(0);
 | 
						|
		if (modified)
 | 
						|
			m = modified;
 | 
						|
	} else
 | 
						|
		last_arg = x_arg_defaulted ? -1 : x_arg;
 | 
						|
	xhp = histptr - (m - 1);
 | 
						|
	if ((xhp < history) || !(cp = *xhp)) {
 | 
						|
		x_e_putc2(7);
 | 
						|
		x_modified();
 | 
						|
		return (KSTD);
 | 
						|
	}
 | 
						|
	x_set_mark(0);
 | 
						|
	if ((x_arg = last_arg) == -1) {
 | 
						|
		/* 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 {
 | 
						|
		/* not x_arg_defaulted */
 | 
						|
		char ch;
 | 
						|
 | 
						|
		rcp = cp;
 | 
						|
		/*
 | 
						|
		 * ignore white-space at start of line
 | 
						|
		 */
 | 
						|
		while (*rcp && is_cfs(*rcp))
 | 
						|
			rcp++;
 | 
						|
		while (x_arg-- > 0) {
 | 
						|
			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);
 | 
						|
}
 | 
						|
 | 
						|
#ifndef MKSH_SMALL
 | 
						|
/* Uppercase N(1) words */
 | 
						|
static int
 | 
						|
x_fold_upper(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (x_fold_case('U'));
 | 
						|
}
 | 
						|
 | 
						|
/* Lowercase N(1) words */
 | 
						|
static int
 | 
						|
x_fold_lower(int c MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	return (x_fold_case('L'));
 | 
						|
}
 | 
						|
 | 
						|
/* Lowercase N(1) words */
 | 
						|
static int
 | 
						|
x_fold_capitalise(int c MKSH_A_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);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/*-
 | 
						|
 * 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);
 | 
						|
#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
 | 
						|
		/* OSF/1 processes lnext when ~icanon */
 | 
						|
		cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
 | 
						|
#endif
 | 
						|
		/* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */
 | 
						|
#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
 | 
						|
		cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
 | 
						|
#endif
 | 
						|
		cb.c_cc[VTIME] = 0;
 | 
						|
		cb.c_cc[VMIN] = 1;
 | 
						|
 | 
						|
		tcsetattr(tty_fd, TCSADRAIN, &cb);
 | 
						|
 | 
						|
#ifdef _POSIX_VDISABLE
 | 
						|
		/* 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;
 | 
						|
#endif
 | 
						|
 | 
						|
		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);
 | 
						|
}
 | 
						|
 | 
						|
#if !MKSH_S_NOVI
 | 
						|
/* +++ vi editing mode +++ */
 | 
						|
 | 
						|
#define Ctrl(c)		(c&0x1f)
 | 
						|
 | 
						|
struct edstate {
 | 
						|
	char *cbuf;
 | 
						|
	int winleft;
 | 
						|
	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_)
 | 
						|
 | 
						|
static 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,
 | 
						|
/* 1	^H	^I	^J	^K	^L	^M	^N	^O	*/
 | 
						|
	M_,	C_|Z_,	0,	0,	C_|U_,	0,	C_,	0,
 | 
						|
/* 2	^P	^Q	^R	^S	^T	^U	^V	^W	*/
 | 
						|
	C_,	0,	C_|U_,	0,	0,	0,	C_,	0,
 | 
						|
/* 3	^X	^Y	^Z	^[	^\	^]	^^	^_	*/
 | 
						|
	C_,	0,	0,	C_|Z_,	0,	0,	0,	0,
 | 
						|
/* 4	<space>	!	"	#	$	%	&	'	*/
 | 
						|
	M_,	0,	0,	C_,	M_,	M_,	0,	0,
 | 
						|
/* 5	(	)	*	+	,	-	.	/	*/
 | 
						|
	0,	0,	C_,	C_,	M_,	C_,	0,	C_|S_,
 | 
						|
/* 6	0	1	2	3	4	5	6	7	*/
 | 
						|
	M_,	0,	0,	0,	0,	0,	0,	0,
 | 
						|
/* 7	8	9	:	;	<	=	>	?	*/
 | 
						|
	0,	0,	0,	M_,	0,	C_,	0,	C_|S_,
 | 
						|
/* 8	@	A	B	C	D	E	F	G	*/
 | 
						|
	C_|X_,	C_,	M_,	C_,	C_,	M_,	M_|X_,	C_|U_|Z_,
 | 
						|
/* 9	H	I	J	K	L	M	N	O	*/
 | 
						|
	0,	C_,	0,	0,	0,	0,	C_|U_,	0,
 | 
						|
/* A	P	Q	R	S	T	U	V	W	*/
 | 
						|
	C_,	0,	C_,	C_,	M_|X_,	C_,	0,	M_,
 | 
						|
/* B	X	Y	Z	[	\	]	^	_	*/
 | 
						|
	C_,	C_|U_,	0,	0,	C_|Z_,	0,	M_,	C_|Z_,
 | 
						|
/* C	`	a	b	c	d	e	f	g	*/
 | 
						|
	0,	C_,	M_,	E_,	E_,	M_,	M_|X_,	C_|Z_,
 | 
						|
/* D	h	i	j	k	l	m	n	o	*/
 | 
						|
	M_,	C_,	C_|U_,	C_|U_,	M_,	0,	C_|U_,	0,
 | 
						|
/* E	p	q	r	s	t	u	v	w	*/
 | 
						|
	C_,	0,	X_,	C_,	M_|X_,	C_|U_,	C_|U_|Z_, M_,
 | 
						|
/* F	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 *news);
 | 
						|
static void		free_edstate(struct edstate *old);
 | 
						|
 | 
						|
static struct edstate	ebuf;
 | 
						|
static struct edstate	undobuf = { undocbuf, 0, 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], wbuf_len, APERM);
 | 
						|
		wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
 | 
						|
	}
 | 
						|
	(void)memset(wbuf[0], ' ', wbuf_len);
 | 
						|
	(void)memset(wbuf[1], ' ', wbuf_len);
 | 
						|
	winwidth = x_cols - pwidth - 3;
 | 
						|
	win = 0;
 | 
						|
	morec = ' ';
 | 
						|
	lastref = 1;
 | 
						|
	holdlen = 0;
 | 
						|
 | 
						|
	editmode = 2;
 | 
						|
	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';
 | 
						|
				memcpy(srchpat, locpat, srchlen + 1);
 | 
						|
			}
 | 
						|
			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, n = srchlen;
 | 
						|
			struct edstate new_es, *save_es;
 | 
						|
 | 
						|
			new_es.cursor = n;
 | 
						|
			new_es.cbuf = locpat;
 | 
						|
 | 
						|
			save_es = es;
 | 
						|
			es = &new_es;
 | 
						|
			n = backword(1);
 | 
						|
			es = save_es;
 | 
						|
 | 
						|
			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;
 | 
						|
				size_t 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 = strnul(p)) && 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);
 | 
						|
				/*
 | 
						|
				 * at this point, it's fairly reasonable that
 | 
						|
				 * nlen + olen + 2 doesn't overflow
 | 
						|
				 */
 | 
						|
				nbuf = alloc(nlen + 1 + olen, APERM);
 | 
						|
				memcpy(nbuf, ap->val.s, nlen);
 | 
						|
				nbuf[nlen++] = cmd[1];
 | 
						|
				if (macro.p) {
 | 
						|
					memcpy(nbuf + nlen, macro.p, olen);
 | 
						|
					afree(macro.buf, APERM);
 | 
						|
					nlen += olen;
 | 
						|
				} else {
 | 
						|
					nbuf[nlen++] = '\0';
 | 
						|
					nbuf[nlen++] = '\0';
 | 
						|
				}
 | 
						|
				macro.p = macro.buf = (unsigned char *)nbuf;
 | 
						|
				macro.len = nlen;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case 'a':
 | 
						|
			modified = 1;
 | 
						|
			hnum = hlast;
 | 
						|
			if (es->linelen != 0)
 | 
						|
				es->cursor++;
 | 
						|
			insert = INSERT;
 | 
						|
			break;
 | 
						|
 | 
						|
		case 'A':
 | 
						|
			modified = 1;
 | 
						|
			hnum = hlast;
 | 
						|
			del_range(0, 0);
 | 
						|
			es->cursor = es->linelen;
 | 
						|
			insert = INSERT;
 | 
						|
			break;
 | 
						|
 | 
						|
		case 'S':
 | 
						|
			es->cursor = domove(1, "^", 1);
 | 
						|
			del_range(es->cursor, es->linelen);
 | 
						|
			modified = 1;
 | 
						|
			hnum = hlast;
 | 
						|
			insert = INSERT;
 | 
						|
			break;
 | 
						|
 | 
						|
		case 'Y':
 | 
						|
			cmd = "y$";
 | 
						|
			/* ahhhhhh... */
 | 
						|
		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;
 | 
						|
			/* FALLTHROUGH */
 | 
						|
		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;
 | 
						|
			/* FALLTHROUGH */
 | 
						|
		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);
 | 
						|
			}
 | 
						|
 | 
						|
		/* AT&T ksh */
 | 
						|
		case '=':
 | 
						|
		/* Nonstandard vi/ksh */
 | 
						|
		case Ctrl('e'):
 | 
						|
			print_expansions(es, 1);
 | 
						|
			break;
 | 
						|
 | 
						|
 | 
						|
		/* Nonstandard vi/ksh */
 | 
						|
		case Ctrl('i'):
 | 
						|
			if (!Flag(FVITABCOMPLETE))
 | 
						|
				return (-1);
 | 
						|
			complete_word(1, argcnt);
 | 
						|
			break;
 | 
						|
 | 
						|
		/* some annoying AT&T kshs */
 | 
						|
		case Ctrl('['):
 | 
						|
			if (!Flag(FVIESCCOMPLETE))
 | 
						|
				return (-1);
 | 
						|
		/* AT&T ksh */
 | 
						|
		case '\\':
 | 
						|
		/* Nonstandard vi/ksh */
 | 
						|
		case Ctrl('f'):
 | 
						|
			complete_word(1, argcnt);
 | 
						|
			break;
 | 
						|
 | 
						|
 | 
						|
		/* AT&T ksh */
 | 
						|
		case '*':
 | 
						|
		/* Nonstandard vi/ksh */
 | 
						|
		case Ctrl('x'):
 | 
						|
			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 *news;
 | 
						|
 | 
						|
	news = alloc(sizeof(struct edstate), APERM);
 | 
						|
	news->cbuf = alloc(old->cbufsize, APERM);
 | 
						|
	memcpy(news->cbuf, old->cbuf, old->linelen);
 | 
						|
	news->cbufsize = old->cbufsize;
 | 
						|
	news->linelen = old->linelen;
 | 
						|
	news->cursor = old->cursor;
 | 
						|
	news->winleft = old->winleft;
 | 
						|
	return (news);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
restore_edstate(struct edstate *news, struct edstate *old)
 | 
						|
{
 | 
						|
	memcpy(news->cbuf, old->cbuf, old->linelen);
 | 
						|
	news->linelen = old->linelen;
 | 
						|
	news->cursor = old->cursor;
 | 
						|
	news->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("%s: %s", "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, nwords, start, end, i;
 | 
						|
	char **words;
 | 
						|
 | 
						|
	/* 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;
 | 
						|
	}
 | 
						|
 | 
						|
	i = XCF_COMMAND_FILE | XCF_FULLPATH;
 | 
						|
	nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
 | 
						|
	    &start, &end, &words);
 | 
						|
	if (nwords == 0) {
 | 
						|
		vi_error();
 | 
						|
		return (-1);
 | 
						|
	}
 | 
						|
 | 
						|
	buf = save_edstate(es);
 | 
						|
	expanded = EXPAND;
 | 
						|
	del_range(start, end);
 | 
						|
	es->cursor = start;
 | 
						|
	i = 0;
 | 
						|
	while (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, flags;
 | 
						|
	char **words;
 | 
						|
	char *match;
 | 
						|
	bool 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.
 | 
						|
	 */
 | 
						|
	flags = XCF_COMMAND_FILE;
 | 
						|
	if (count)
 | 
						|
		flags |= XCF_FULLPATH;
 | 
						|
	nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
 | 
						|
	    &start, &end, &words);
 | 
						|
	if (nwords == 0) {
 | 
						|
		vi_error();
 | 
						|
		return (-1);
 | 
						|
	}
 | 
						|
	if (count) {
 | 
						|
		int i;
 | 
						|
 | 
						|
		count--;
 | 
						|
		if (count >= nwords) {
 | 
						|
			vi_error();
 | 
						|
			x_print_expansions(nwords, words,
 | 
						|
			    flags & XCF_IS_COMMAND);
 | 
						|
			x_free_words(nwords, words);
 | 
						|
			redraw_line(0);
 | 
						|
			return (-1);
 | 
						|
		}
 | 
						|
		/*
 | 
						|
		 * Expand the count'th word to its basename
 | 
						|
		 */
 | 
						|
		if (flags & XCF_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);
 | 
						|
		/* next call will list completions */
 | 
						|
		expanded = COMPLETE;
 | 
						|
		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;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * append a space if this is not a directory or the
 | 
						|
		 * result of a parameter substitution
 | 
						|
		 */
 | 
						|
		if (match_len > 0 && match[match_len - 1] != '/' &&
 | 
						|
		    !(flags & XCF_IS_VARSUB))
 | 
						|
			rval = putbuf(" ", 1, 0);
 | 
						|
	}
 | 
						|
	x_free_words(nwords, words);
 | 
						|
 | 
						|
	modified = 1;
 | 
						|
	hnum = hlast;
 | 
						|
	insert = INSERT;
 | 
						|
	/* prevent this from being redone... */
 | 
						|
	lastac = 0;
 | 
						|
	refresh(0);
 | 
						|
 | 
						|
	return (rval);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
 | 
						|
{
 | 
						|
	int start, end, nwords, i;
 | 
						|
	char **words;
 | 
						|
 | 
						|
	i = XCF_COMMAND_FILE | XCF_FULLPATH;
 | 
						|
	nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor,
 | 
						|
	    &start, &end, &words);
 | 
						|
	if (nwords == 0) {
 | 
						|
		vi_error();
 | 
						|
		return (-1);
 | 
						|
	}
 | 
						|
	x_print_expansions(nwords, words, i & XCF_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 *)¯o, 0, sizeof(macro));
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif /* !MKSH_S_NOVI */
 |