1/*	$OpenBSD: edit.c,v 1.41 2015/09/01 13:12:31 tedu Exp $	*/
2/*	$OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $	*/
3/*	$OpenBSD: emacs.c,v 1.52 2015/09/10 22:48:58 nicm Exp $	*/
4/*	$OpenBSD: vi.c,v 1.30 2015/09/10 22:48:58 nicm Exp $	*/
5
6/*-
7 * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
8 *		 2011, 2012, 2013, 2014, 2015, 2016
9 *	mirabilos <m@mirbsd.org>
10 *
11 * Provided that these terms and disclaimer and all copyright notices
12 * are retained or reproduced in an accompanying document, permission
13 * is granted to deal in this work without restriction, including un-
14 * limited rights to use, publicly perform, distribute, sell, modify,
15 * merge, give away, or sublicence.
16 *
17 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
18 * the utmost extent permitted by applicable law, neither express nor
19 * implied; without malicious intent or gross negligence. In no event
20 * may a licensor, author or contributor be held liable for indirect,
21 * direct, other damage, loss, or other issues arising in any way out
22 * of dealing in the work, even if advised of the possibility of such
23 * damage or existence of a defect, except proven that it results out
24 * of said person's immediate fault when using the work as intended.
25 */
26
27#include "sh.h"
28
29#ifndef MKSH_NO_CMDLINE_EDITING
30
31__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.312 2016/11/11 23:48:28 tg Exp $");
32
33/*
34 * in later versions we might use libtermcap for this, but since external
35 * dependencies are problematic, this has not yet been decided on; another
36 * good string is "\033c" except on hardware terminals like the DEC VT420
37 * which do a full power cycle then...
38 */
39#ifndef MKSH_CLS_STRING
40#define MKSH_CLS_STRING		"\033[;H\033[J"
41#endif
42
43/* tty driver characters we are interested in */
44#define EDCHAR_DISABLED	0xFFFFU
45#define EDCHAR_INITIAL	0xFFFEU
46static struct {
47	unsigned short erase;
48	unsigned short kill;
49	unsigned short werase;
50	unsigned short intr;
51	unsigned short quit;
52	unsigned short eof;
53} edchars;
54
55#define isched(x,e) ((unsigned short)(unsigned char)(x) == (e))
56#define isedchar(x) (!((x) & ~0xFF))
57#ifndef _POSIX_VDISABLE
58#define toedchar(x) ((unsigned short)(unsigned char)(x))
59#else
60#define toedchar(x) (((_POSIX_VDISABLE != -1) && ((x) == _POSIX_VDISABLE)) ? \
61			((unsigned short)EDCHAR_DISABLED) : \
62			((unsigned short)(unsigned char)(x)))
63#endif
64
65/* x_cf_glob() flags */
66#define XCF_COMMAND	BIT(0)	/* Do command completion */
67#define XCF_FILE	BIT(1)	/* Do file completion */
68#define XCF_FULLPATH	BIT(2)	/* command completion: store full path */
69#define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE)
70#define XCF_IS_COMMAND	BIT(3)	/* return flag: is command */
71#define XCF_IS_NOSPACE	BIT(4)	/* return flag: do not append a space */
72
73static char editmode;
74static int xx_cols;			/* for Emacs mode */
75static int modified;			/* buffer has been "modified" */
76static char *holdbufp;			/* place to hold last edit buffer */
77
78/* 0=dumb 1=tmux (for now) */
79static bool x_term_mode;
80
81static void x_adjust(void);
82static int x_getc(void);
83static void x_putcf(int);
84static void x_modified(void);
85static void x_mode(bool);
86static int x_do_comment(char *, ssize_t, ssize_t *);
87static void x_print_expansions(int, char * const *, bool);
88static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***);
89static size_t x_longest_prefix(int, char * const *);
90static void x_glob_hlp_add_qchar(char *);
91static char *x_glob_hlp_tilde_and_rem_qchar(char *, bool);
92static size_t x_basename(const char *, const char *);
93static void x_free_words(int, char **);
94static int x_escape(const char *, size_t, int (*)(const char *, size_t));
95static int x_emacs(char *);
96static void x_init_prompt(bool);
97#if !MKSH_S_NOVI
98static int x_vi(char *);
99#endif
100
101#define x_flush()	shf_flush(shl_out)
102#if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST)
103#define x_putc(c)	x_putcf(c)
104#else
105#define x_putc(c)	shf_putc((c), shl_out)
106#endif
107
108static int path_order_cmp(const void *, const void *);
109static void glob_table(const char *, XPtrV *, struct table *);
110static void glob_path(int, const char *, XPtrV *, const char *);
111static int x_file_glob(int *, char *, char ***);
112static int x_command_glob(int, char *, char ***);
113static int x_locate_word(const char *, int, int, int *, bool *);
114
115static int x_e_getmbc(char *);
116
117/* +++ generic editing functions +++ */
118
119/*
120 * read an edited command line
121 */
122int
123x_read(char *buf)
124{
125	int i;
126
127	x_mode(true);
128	modified = 1;
129	if (Flag(FEMACS) || Flag(FGMACS))
130		i = x_emacs(buf);
131#if !MKSH_S_NOVI
132	else if (Flag(FVI))
133		i = x_vi(buf);
134#endif
135	else
136		/* internal error */
137		i = -1;
138	editmode = 0;
139	x_mode(false);
140	return (i);
141}
142
143/* tty I/O */
144
145static int
146x_getc(void)
147{
148	char c;
149	ssize_t n;
150
151	while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
152		if (trap) {
153			x_mode(false);
154			runtraps(0);
155#ifdef SIGWINCH
156			if (got_winch) {
157				change_winsz();
158				if (x_cols != xx_cols && editmode == 1) {
159					/* redraw line in Emacs mode */
160					xx_cols = x_cols;
161					x_init_prompt(false);
162					x_adjust();
163				}
164			}
165#endif
166			x_mode(true);
167		}
168	return ((n == 1) ? (int)(unsigned char)c : -1);
169}
170
171static void
172x_putcf(int c)
173{
174	shf_putc(c, shl_out);
175}
176
177/*********************************
178 * Misc common code for vi/emacs *
179 *********************************/
180
181/*-
182 * Handle the commenting/uncommenting of a line.
183 * Returns:
184 *	1 if a carriage return is indicated (comment added)
185 *	0 if no return (comment removed)
186 *	-1 if there is an error (not enough room for comment chars)
187 * If successful, *lenp contains the new length. Note: cursor should be
188 * moved to the start of the line after (un)commenting.
189 */
190static int
191x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp)
192{
193	ssize_t i, j, len = *lenp;
194
195	if (len == 0)
196		/* somewhat arbitrary - it's what AT&T ksh does */
197		return (1);
198
199	/* Already commented? */
200	if (buf[0] == '#') {
201		bool saw_nl = false;
202
203		for (j = 0, i = 1; i < len; i++) {
204			if (!saw_nl || buf[i] != '#')
205				buf[j++] = buf[i];
206			saw_nl = buf[i] == '\n';
207		}
208		*lenp = j;
209		return (0);
210	} else {
211		int n = 1;
212
213		/* See if there's room for the #s - 1 per \n */
214		for (i = 0; i < len; i++)
215			if (buf[i] == '\n')
216				n++;
217		if (len + n >= bsize)
218			return (-1);
219		/* Now add them... */
220		for (i = len, j = len + n; --i >= 0; ) {
221			if (buf[i] == '\n')
222				buf[--j] = '#';
223			buf[--j] = buf[i];
224		}
225		buf[0] = '#';
226		*lenp += n;
227		return (1);
228	}
229}
230
231/****************************************************
232 * Common file/command completion code for vi/emacs *
233 ****************************************************/
234
235static void
236x_print_expansions(int nwords, char * const *words, bool is_command)
237{
238	bool use_copy = false;
239	size_t prefix_len;
240	XPtrV l = { NULL, 0, 0 };
241	struct columnise_opts co;
242
243	/*
244	 * Check if all matches are in the same directory (in this
245	 * case, we want to omit the directory name)
246	 */
247	if (!is_command &&
248	    (prefix_len = x_longest_prefix(nwords, words)) > 0) {
249		int i;
250
251		/* Special case for 1 match (prefix is whole word) */
252		if (nwords == 1)
253			prefix_len = x_basename(words[0], NULL);
254		/* Any (non-trailing) slashes in non-common word suffixes? */
255		for (i = 0; i < nwords; i++)
256			if (x_basename(words[i] + prefix_len, NULL) >
257			    prefix_len)
258				break;
259		/* All in same directory? */
260		if (i == nwords) {
261			while (prefix_len > 0 &&
262			    !mksh_cdirsep(words[0][prefix_len - 1]))
263				prefix_len--;
264			use_copy = true;
265			XPinit(l, nwords + 1);
266			for (i = 0; i < nwords; i++)
267				XPput(l, words[i] + prefix_len);
268			XPput(l, NULL);
269		}
270	}
271	/*
272	 * Enumerate expansions
273	 */
274	x_putc('\r');
275	x_putc('\n');
276	co.shf = shl_out;
277	co.linesep = '\n';
278	co.do_last = true;
279	co.prefcol = false;
280	pr_list(&co, use_copy ? (char **)XPptrv(l) : words);
281
282	if (use_copy)
283		/* not x_free_words() */
284		XPfree(l);
285}
286
287/*
288 * Convert backslash-escaped string to QCHAR-escaped
289 * string useful for globbing; loses QCHAR unless it
290 * can squeeze in, eg. by previous loss of backslash
291 */
292static void
293x_glob_hlp_add_qchar(char *cp)
294{
295	char ch, *dp = cp;
296	bool escaping = false;
297
298	while ((ch = *cp++)) {
299		if (ch == '\\' && !escaping) {
300			escaping = true;
301			continue;
302		}
303		if (escaping || (ch == QCHAR && (cp - dp) > 1)) {
304			/*
305			 * empirically made list of chars to escape
306			 * for globbing as well as QCHAR itself
307			 */
308			switch (ch) {
309			case QCHAR:
310			case '$':
311			case '*':
312			case '?':
313			case '[':
314			case '\\':
315			case '`':
316				*dp++ = QCHAR;
317				break;
318			}
319			escaping = false;
320		}
321		*dp++ = ch;
322	}
323	*dp = '\0';
324}
325
326/*
327 * Run tilde expansion on argument string, return the result
328 * after unescaping; if the flag is set, the original string
329 * is freed if changed and assumed backslash-escaped, if not
330 * it is assumed QCHAR-escaped
331 */
332static char *
333x_glob_hlp_tilde_and_rem_qchar(char *s, bool magic_flag)
334{
335	char ch, *cp, *dp;
336
337	/*
338	 * On the string, check whether we have a tilde expansion,
339	 * and if so, discern "~foo/bar" and "~/baz" from "~blah";
340	 * if we have a directory part (the former), try to expand
341	 */
342	if (*s == '~' && (cp = mksh_sdirsep(s)) != NULL) {
343		/* ok, so split into "~foo"/"bar" or "~"/"baz" */
344		*cp++ = 0;
345		/* try to expand the tilde */
346		if (!(dp = do_tilde(s + 1))) {
347			/* nope, revert damage */
348			*--cp = '/';
349		} else {
350			/* ok, expand and replace */
351			cp = shf_smprintf(Tf_sSs, dp, cp);
352			if (magic_flag)
353				afree(s, ATEMP);
354			s = cp;
355		}
356	}
357
358	/* ... convert it from backslash-escaped via QCHAR-escaped... */
359	if (magic_flag)
360		x_glob_hlp_add_qchar(s);
361	/* ... to unescaped, for comparison with the matches */
362	cp = dp = s;
363
364	while ((ch = *cp++)) {
365		if (ch == QCHAR && !(ch = *cp++))
366			break;
367		*dp++ = ch;
368	}
369	*dp = '\0';
370
371	return (s);
372}
373
374/**
375 * Do file globbing:
376 *	- does expansion, checks for no match, etc.
377 *	- sets *wordsp to array of matching strings
378 *	- returns number of matching strings
379 */
380static int
381x_file_glob(int *flagsp, char *toglob, char ***wordsp)
382{
383	char **words, *cp;
384	int nwords;
385	XPtrV w;
386	struct source *s, *sold;
387
388	/* remove all escaping backward slashes */
389	x_glob_hlp_add_qchar(toglob);
390
391	/*
392	 * Convert "foo*" (toglob) to an array of strings (words)
393	 */
394	sold = source;
395	s = pushs(SWSTR, ATEMP);
396	s->start = s->str = toglob;
397	source = s;
398	if (yylex(ONEWORD | LQCHAR) != LWORD) {
399		source = sold;
400		internal_warningf(Tfg_badsubst);
401		return (0);
402	}
403	source = sold;
404	afree(s, ATEMP);
405	XPinit(w, 32);
406	cp = yylval.cp;
407	while (*cp == CHAR || *cp == QCHAR)
408		cp += 2;
409	nwords = DOGLOB | DOTILDE | DOMARKDIRS;
410	if (*cp != EOS) {
411		/* probably a $FOO expansion */
412		*flagsp |= XCF_IS_NOSPACE;
413		/* this always results in at most one match */
414		nwords = 0;
415	}
416	expand(yylval.cp, &w, nwords);
417	XPput(w, NULL);
418	words = (char **)XPclose(w);
419
420	for (nwords = 0; words[nwords]; nwords++)
421		;
422	if (nwords == 1) {
423		struct stat statb;
424
425		/* Expand any tilde and drop all QCHAR for comparison */
426		toglob = x_glob_hlp_tilde_and_rem_qchar(toglob, false);
427
428		/*
429		 * Check if globbing failed (returned glob pattern),
430		 * but be careful (e.g. toglob == "ab*" when the file
431		 * "ab*" exists is not an error).
432		 * Also, check for empty result - happens if we tried
433		 * to glob something which evaluated to an empty
434		 * string (e.g., "$FOO" when there is no FOO, etc).
435		 */
436		if ((strcmp(words[0], toglob) == 0 &&
437		    stat(words[0], &statb) < 0) ||
438		    words[0][0] == '\0') {
439			x_free_words(nwords, words);
440			words = NULL;
441			nwords = 0;
442		}
443	}
444
445	if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
446		x_free_words(nwords, words);
447
448	return (nwords);
449}
450
451/* Data structure used in x_command_glob() */
452struct path_order_info {
453	char *word;
454	size_t base;
455	size_t path_order;
456};
457
458/* Compare routine used in x_command_glob() */
459static int
460path_order_cmp(const void *aa, const void *bb)
461{
462	const struct path_order_info *a = (const struct path_order_info *)aa;
463	const struct path_order_info *b = (const struct path_order_info *)bb;
464	int t;
465
466	if ((t = strcmp(a->word + a->base, b->word + b->base)))
467		return (t);
468	if (a->path_order > b->path_order)
469		return (1);
470	if (a->path_order < b->path_order)
471		return (-1);
472	return (0);
473}
474
475static int
476x_command_glob(int flags, char *toglob, char ***wordsp)
477{
478	char *pat, *fpath;
479	size_t nwords;
480	XPtrV w;
481	struct block *l;
482
483	/* Convert "foo*" (toglob) to a pattern for future use */
484	pat = evalstr(toglob, DOPAT | DOTILDE);
485
486	XPinit(w, 32);
487
488	glob_table(pat, &w, &keywords);
489	glob_table(pat, &w, &aliases);
490	glob_table(pat, &w, &builtins);
491	for (l = e->loc; l; l = l->next)
492		glob_table(pat, &w, &l->funs);
493
494	glob_path(flags, pat, &w, path);
495	if ((fpath = str_val(global(TFPATH))) != null)
496		glob_path(flags, pat, &w, fpath);
497
498	nwords = XPsize(w);
499
500	if (!nwords) {
501		*wordsp = NULL;
502		XPfree(w);
503		return (0);
504	}
505	/* Sort entries */
506	if (flags & XCF_FULLPATH) {
507		/* Sort by basename, then path order */
508		struct path_order_info *info, *last_info = NULL;
509		char **words = (char **)XPptrv(w);
510		size_t i, path_order = 0;
511
512		info = (struct path_order_info *)
513		    alloc2(nwords, sizeof(struct path_order_info), ATEMP);
514		for (i = 0; i < nwords; i++) {
515			info[i].word = words[i];
516			info[i].base = x_basename(words[i], NULL);
517			if (!last_info || info[i].base != last_info->base ||
518			    strncmp(words[i], last_info->word, info[i].base) != 0) {
519				last_info = &info[i];
520				path_order++;
521			}
522			info[i].path_order = path_order;
523		}
524		qsort(info, nwords, sizeof(struct path_order_info),
525		    path_order_cmp);
526		for (i = 0; i < nwords; i++)
527			words[i] = info[i].word;
528		afree(info, ATEMP);
529	} else {
530		/* Sort and remove duplicate entries */
531		char **words = (char **)XPptrv(w);
532		size_t i, j;
533
534		qsort(words, nwords, sizeof(void *), xstrcmp);
535		for (i = j = 0; i < nwords - 1; i++) {
536			if (strcmp(words[i], words[i + 1]))
537				words[j++] = words[i];
538			else
539				afree(words[i], ATEMP);
540		}
541		words[j++] = words[i];
542		w.len = nwords = j;
543	}
544
545	XPput(w, NULL);
546	*wordsp = (char **)XPclose(w);
547
548	return (nwords);
549}
550
551#define IS_WORDC(c)	(!ctype(c, C_LEX1) && (c) != '\'' && (c) != '"' && \
552			    (c) != '`' && (c) != '=' && (c) != ':')
553
554static int
555x_locate_word(const char *buf, int buflen, int pos, int *startp,
556    bool *is_commandp)
557{
558	int start, end;
559
560	/* Bad call? Probably should report error */
561	if (pos < 0 || pos > buflen) {
562		*startp = pos;
563		*is_commandp = false;
564		return (0);
565	}
566	/* The case where pos == buflen happens to take care of itself... */
567
568	start = pos;
569	/*
570	 * Keep going backwards to start of word (has effect of allowing
571	 * one blank after the end of a word)
572	 */
573	for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
574	    (start > 1 && buf[start - 2] == '\\'); start--)
575		;
576	/* Go forwards to end of word */
577	for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
578		if (buf[end] == '\\' && (end + 1) < buflen)
579			end++;
580	}
581
582	if (is_commandp) {
583		bool iscmd;
584		int p = start - 1;
585
586		/* Figure out if this is a command */
587		while (p >= 0 && ksh_isspace(buf[p]))
588			p--;
589		iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
590		if (iscmd) {
591			/*
592			 * If command has a /, path, etc. is not searched;
593			 * only current directory is searched which is just
594			 * like file globbing.
595			 */
596			for (p = start; p < end; p++)
597				if (mksh_cdirsep(buf[p]))
598					break;
599			iscmd = p == end;
600		}
601		*is_commandp = iscmd;
602	}
603	*startp = start;
604
605	return (end - start);
606}
607
608static int
609x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
610    int *endp, char ***wordsp)
611{
612	int len, nwords = 0;
613	char **words = NULL;
614	bool is_command;
615
616	len = x_locate_word(buf, buflen, pos, startp, &is_command);
617	if (!((*flagsp) & XCF_COMMAND))
618		is_command = false;
619	/*
620	 * Don't do command globing on zero length strings - it takes too
621	 * long and isn't very useful. File globs are more likely to be
622	 * useful, so allow these.
623	 */
624	if (len == 0 && is_command)
625		return (0);
626
627	if (len >= 0) {
628		char *toglob, *s;
629
630		/*
631		 * Given a string, copy it and possibly add a '*' to the end.
632		 */
633
634		strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
635		toglob[len] = '\0';
636
637		/*
638		 * If the pathname contains a wildcard (an unquoted '*',
639		 * '?', or '[') or an extglob, then it is globbed based
640		 * on that value (i.e., without the appended '*'). Same
641		 * for parameter substitutions (as in “cat $HOME/.ss↹”)
642		 * without appending a trailing space (LP: #710539), as
643		 * well as for “~foo” (but not “~foo/”).
644		 */
645		for (s = toglob; *s; s++) {
646			if (*s == '\\' && s[1])
647				s++;
648			else if (*s == '?' || *s == '*' || *s == '[' ||
649			    *s == '$' ||
650			    /* ?() *() +() @() !() but two already checked */
651			    (s[1] == '(' /*)*/ &&
652			    (*s == '+' || *s == '@' || *s == '!'))) {
653				/*
654				 * just expand based on the extglob
655				 * or parameter
656				 */
657				goto dont_add_glob;
658			}
659		}
660
661		if (*toglob == '~' && !mksh_vdirsep(toglob)) {
662			/* neither for '~foo' (but '~foo/bar') */
663			*flagsp |= XCF_IS_NOSPACE;
664			goto dont_add_glob;
665		}
666
667		/* append a glob */
668		toglob[len] = '*';
669		toglob[len + 1] = '\0';
670 dont_add_glob:
671		/*
672		 * Expand (glob) it now.
673		 */
674
675		nwords = is_command ?
676		    x_command_glob(*flagsp, toglob, &words) :
677		    x_file_glob(flagsp, toglob, &words);
678		afree(toglob, ATEMP);
679	}
680	if (nwords == 0) {
681		*wordsp = NULL;
682		return (0);
683	}
684	if (is_command)
685		*flagsp |= XCF_IS_COMMAND;
686	*wordsp = words;
687	*endp = *startp + len;
688
689	return (nwords);
690}
691
692/*
693 * Find longest common prefix
694 */
695static size_t
696x_longest_prefix(int nwords, char * const * words)
697{
698	int i;
699	size_t j, prefix_len;
700	char *p;
701
702	if (nwords <= 0)
703		return (0);
704
705	prefix_len = strlen(words[0]);
706	for (i = 1; i < nwords; i++)
707		for (j = 0, p = words[i]; j < prefix_len; j++)
708			if (p[j] != words[0][j]) {
709				prefix_len = j;
710				break;
711			}
712	/* false for nwords==1 as 0 = words[0][prefix_len] then */
713	if (UTFMODE && prefix_len && (words[0][prefix_len] & 0xC0) == 0x80)
714		while (prefix_len && (words[0][prefix_len] & 0xC0) != 0xC0)
715			--prefix_len;
716	return (prefix_len);
717}
718
719static void
720x_free_words(int nwords, char **words)
721{
722	while (nwords)
723		afree(words[--nwords], ATEMP);
724	afree(words, ATEMP);
725}
726
727/*-
728 * Return the offset of the basename of string s (which ends at se - need not
729 * be null terminated). Trailing slashes are ignored. If s is just a slash,
730 * then the offset is 0 (actually, length - 1).
731 *	s		Return
732 *	/etc		1
733 *	/etc/		1
734 *	/etc//		1
735 *	/etc/fo		5
736 *	foo		0
737 *	///		2
738 *			0
739 */
740static size_t
741x_basename(const char *s, const char *se)
742{
743	const char *p;
744
745	if (se == NULL)
746		se = s + strlen(s);
747	if (s == se)
748		return (0);
749
750	/* skip trailing directory separators */
751	p = se - 1;
752	while (p > s && mksh_cdirsep(*p))
753		--p;
754	/* drop last component */
755	while (p > s && !mksh_cdirsep(*p))
756		--p;
757	if (mksh_cdirsep(*p) && p + 1 < se)
758		++p;
759
760	return (p - s);
761}
762
763/*
764 * Apply pattern matching to a table: all table entries that match a pattern
765 * are added to wp.
766 */
767static void
768glob_table(const char *pat, XPtrV *wp, struct table *tp)
769{
770	struct tstate ts;
771	struct tbl *te;
772
773	ktwalk(&ts, tp);
774	while ((te = ktnext(&ts)))
775		if (gmatchx(te->name, pat, false)) {
776			char *cp;
777
778			strdupx(cp, te->name, ATEMP);
779			XPput(*wp, cp);
780		}
781}
782
783static void
784glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
785{
786	const char *sp = lpath, *p;
787	char *xp, **words;
788	size_t pathlen, patlen, oldsize, newsize, i, j;
789	XString xs;
790
791	patlen = strlen(pat);
792	checkoktoadd(patlen, 129 + X_EXTRA);
793	++patlen;
794	Xinit(xs, xp, patlen + 128, ATEMP);
795	while (sp) {
796		xp = Xstring(xs, xp);
797		if (!(p = cstrchr(sp, MKSH_PATHSEPC)))
798			p = sp + strlen(sp);
799		pathlen = p - sp;
800		if (pathlen) {
801			/*
802			 * Copy sp into xp, stuffing any MAGIC characters
803			 * on the way
804			 */
805			const char *s = sp;
806
807			XcheckN(xs, xp, pathlen * 2);
808			while (s < p) {
809				if (ISMAGIC(*s))
810					*xp++ = MAGIC;
811				*xp++ = *s++;
812			}
813			*xp++ = '/';
814			pathlen++;
815		}
816		sp = p;
817		XcheckN(xs, xp, patlen);
818		memcpy(xp, pat, patlen);
819
820		oldsize = XPsize(*wp);
821		/* mark dirs */
822		glob_str(Xstring(xs, xp), wp, true);
823		newsize = XPsize(*wp);
824
825		/* Check that each match is executable... */
826		words = (char **)XPptrv(*wp);
827		for (i = j = oldsize; i < newsize; i++) {
828			if (ksh_access(words[i], X_OK) == 0) {
829				words[j] = words[i];
830				if (!(flags & XCF_FULLPATH))
831					memmove(words[j], words[j] + pathlen,
832					    strlen(words[j] + pathlen) + 1);
833				j++;
834			} else
835				afree(words[i], ATEMP);
836		}
837		wp->len = j;
838
839		if (!*sp++)
840			break;
841	}
842	Xfree(xs, xp);
843}
844
845/*
846 * if argument string contains any special characters, they will
847 * be escaped and the result will be put into edit buffer by
848 * keybinding-specific function
849 */
850static int
851x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t))
852{
853	size_t add = 0, wlen = len;
854	int rval = 0;
855
856	while (wlen - add > 0)
857		if (vstrchr("\"#$&'()*:;<=>?[\\`{|}", s[add]) ||
858		    ctype(s[add], C_IFS)) {
859			if (putbuf_func(s, add) != 0) {
860				rval = -1;
861				break;
862			}
863			putbuf_func(s[add] == '\n' ? "'" : "\\", 1);
864			putbuf_func(&s[add], 1);
865			if (s[add] == '\n')
866				putbuf_func("'", 1);
867
868			add++;
869			wlen -= add;
870			s += add;
871			add = 0;
872		} else
873			++add;
874	if (wlen > 0 && rval == 0)
875		rval = putbuf_func(s, wlen);
876
877	return (rval);
878}
879
880
881/* +++ emacs editing mode +++ */
882
883static	Area	aedit;
884#define	AEDIT	&aedit		/* area for kill ring and macro defns */
885
886/* values returned by keyboard functions */
887#define	KSTD	0
888#define	KEOL	1		/* ^M, ^J */
889#define	KINTR	2		/* ^G, ^C */
890
891struct x_ftab {
892	int (*xf_func)(int c);
893	const char *xf_name;
894	short xf_flags;
895};
896
897struct x_defbindings {
898	unsigned char xdb_func;	/* XFUNC_* */
899	unsigned char xdb_tab;
900	unsigned char xdb_char;
901};
902
903#define XF_ARG		1	/* command takes number prefix */
904#define	XF_NOBIND	2	/* not allowed to bind to function */
905#define	XF_PREFIX	4	/* function sets prefix */
906
907/* Separator for completion */
908#define	is_cfs(c)	((c) == ' ' || (c) == '\t' || (c) == '"' || (c) == '\'')
909/* Separator for motion */
910#define	is_mfs(c)	(!(ksh_isalnux(c) || (c) == '$' || ((c) & 0x80)))
911
912#define X_NTABS		4			/* normal, meta1, meta2, pc */
913#define X_TABSZ		256			/* size of keydef tables etc */
914
915/*-
916 * Arguments for do_complete()
917 * 0 = enumerate	M-=	complete as much as possible and then list
918 * 1 = complete		M-Esc
919 * 2 = list		M-?
920 */
921typedef enum {
922	CT_LIST,	/* list the possible completions */
923	CT_COMPLETE,	/* complete to longest prefix */
924	CT_COMPLIST	/* complete and then list (if non-exact) */
925} Comp_type;
926
927/*
928 * The following are used for my horizontal scrolling stuff
929 */
930static char *xbuf;		/* beg input buffer */
931static char *xend;		/* end input buffer */
932static char *xcp;		/* current position */
933static char *xep;		/* current end */
934static char *xbp;		/* start of visible portion of input buffer */
935static char *xlp;		/* last char visible on screen */
936static bool x_adj_ok;
937/*
938 * we use x_adj_done so that functions can tell
939 * whether x_adjust() has been called while they are active.
940 */
941static int x_adj_done;		/* is incremented by x_adjust() */
942
943static int x_displen;
944static int x_arg;		/* general purpose arg */
945static bool x_arg_defaulted;	/* x_arg not explicitly set; defaulted to 1 */
946
947static bool xlp_valid;		/* lastvis pointer was recalculated */
948
949static char **x_histp;		/* history position */
950static int x_nextcmd;		/* for newline-and-next */
951static char **x_histncp;	/* saved x_histp for " */
952static char *xmp;		/* mark pointer */
953static unsigned char x_last_command;
954static unsigned char (*x_tab)[X_TABSZ];	/* key definition */
955#ifndef MKSH_SMALL
956static char *(*x_atab)[X_TABSZ];	/* macro definitions */
957#endif
958static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8];
959#define KILLSIZE	20
960static char *killstack[KILLSIZE];
961static int killsp, killtp;
962static int x_curprefix;
963#ifndef MKSH_SMALL
964static char *macroptr;		/* bind key macro active? */
965#endif
966#if !MKSH_S_NOVI
967static int winwidth;		/* width of window */
968static char *wbuf[2];		/* window buffers */
969static int wbuf_len;		/* length of window buffers (x_cols - 3) */
970static int win;			/* window buffer in use */
971static char morec;		/* more character at right of window */
972static int lastref;		/* argument to last refresh() */
973static int holdlen;		/* length of holdbuf */
974#endif
975static int pwidth;		/* width of prompt */
976static int prompt_trunc;	/* how much of prompt to truncate or -1 */
977static int x_col;		/* current column on line */
978
979static int x_ins(const char *);
980static void x_delete(size_t, bool);
981static size_t x_bword(void);
982static size_t x_fword(bool);
983static void x_goto(char *);
984static char *x_bs0(char *, char *) MKSH_A_PURE;
985static void x_bs3(char **);
986static int x_size2(char *, char **);
987static void x_zots(char *);
988static void x_zotc3(char **);
989static void x_load_hist(char **);
990static int x_search(char *, int, int);
991#ifndef MKSH_SMALL
992static int x_search_dir(int);
993#endif
994static int x_match(char *, char *);
995static void x_redraw(int);
996static void x_push(size_t);
997static char *x_mapin(const char *, Area *);
998static char *x_mapout(int);
999static void x_mapout2(int, char **);
1000static void x_print(int, int);
1001static void x_e_ungetc(int);
1002static int x_e_getc(void);
1003static void x_e_putc2(int);
1004static void x_e_putc3(const char **);
1005static void x_e_puts(const char *);
1006#ifndef MKSH_SMALL
1007static int x_fold_case(int);
1008#endif
1009static char *x_lastcp(void);
1010static void x_lastpos(void);
1011static void do_complete(int, Comp_type);
1012static size_t x_nb2nc(size_t) MKSH_A_PURE;
1013
1014static int unget_char = -1;
1015
1016static int x_do_ins(const char *, size_t);
1017static void bind_if_not_bound(int, int, int);
1018
1019enum emacs_funcs {
1020#define EMACSFN_ENUMS
1021#include "emacsfn.h"
1022	XFUNC_MAX
1023};
1024
1025#define EMACSFN_DEFNS
1026#include "emacsfn.h"
1027
1028static const struct x_ftab x_ftab[] = {
1029#define EMACSFN_ITEMS
1030#include "emacsfn.h"
1031};
1032
1033static struct x_defbindings const x_defbindings[] = {
1034	{ XFUNC_del_back,		0, CTRL('?')	},
1035	{ XFUNC_del_bword,		1, CTRL('?')	},
1036	{ XFUNC_eot_del,		0, CTRL('D')	},
1037	{ XFUNC_del_back,		0, CTRL('H')	},
1038	{ XFUNC_del_bword,		1, CTRL('H')	},
1039	{ XFUNC_del_bword,		1,	'h'	},
1040	{ XFUNC_mv_bword,		1,	'b'	},
1041	{ XFUNC_mv_fword,		1,	'f'	},
1042	{ XFUNC_del_fword,		1,	'd'	},
1043	{ XFUNC_mv_back,		0, CTRL('B')	},
1044	{ XFUNC_mv_forw,		0, CTRL('F')	},
1045	{ XFUNC_search_char_forw,	0, CTRL(']')	},
1046	{ XFUNC_search_char_back,	1, CTRL(']')	},
1047	{ XFUNC_newline,		0, CTRL('M')	},
1048	{ XFUNC_newline,		0, CTRL('J')	},
1049	{ XFUNC_end_of_text,		0, CTRL('_')	},
1050	{ XFUNC_abort,			0, CTRL('G')	},
1051	{ XFUNC_prev_com,		0, CTRL('P')	},
1052	{ XFUNC_next_com,		0, CTRL('N')	},
1053	{ XFUNC_nl_next_com,		0, CTRL('O')	},
1054	{ XFUNC_search_hist,		0, CTRL('R')	},
1055	{ XFUNC_beg_hist,		1,	'<'	},
1056	{ XFUNC_end_hist,		1,	'>'	},
1057	{ XFUNC_goto_hist,		1,	'g'	},
1058	{ XFUNC_mv_end,			0, CTRL('E')	},
1059	{ XFUNC_mv_beg,			0, CTRL('A')	},
1060	{ XFUNC_draw_line,		0, CTRL('L')	},
1061	{ XFUNC_cls,			1, CTRL('L')	},
1062	{ XFUNC_meta1,			0, CTRL('[')	},
1063	{ XFUNC_meta2,			0, CTRL('X')	},
1064	{ XFUNC_kill,			0, CTRL('K')	},
1065	{ XFUNC_yank,			0, CTRL('Y')	},
1066	{ XFUNC_meta_yank,		1,	'y'	},
1067	{ XFUNC_literal,		0, CTRL('^')	},
1068	{ XFUNC_comment,		1,	'#'	},
1069	{ XFUNC_transpose,		0, CTRL('T')	},
1070	{ XFUNC_complete,		1, CTRL('[')	},
1071	{ XFUNC_comp_list,		0, CTRL('I')	},
1072	{ XFUNC_comp_list,		1,	'='	},
1073	{ XFUNC_enumerate,		1,	'?'	},
1074	{ XFUNC_expand,			1,	'*'	},
1075	{ XFUNC_comp_file,		1, CTRL('X')	},
1076	{ XFUNC_comp_comm,		2, CTRL('[')	},
1077	{ XFUNC_list_comm,		2,	'?'	},
1078	{ XFUNC_list_file,		2, CTRL('Y')	},
1079	{ XFUNC_set_mark,		1,	' '	},
1080	{ XFUNC_kill_region,		0, CTRL('W')	},
1081	{ XFUNC_xchg_point_mark,	2, CTRL('X')	},
1082	{ XFUNC_literal,		0, CTRL('V')	},
1083	{ XFUNC_version,		1, CTRL('V')	},
1084	{ XFUNC_prev_histword,		1,	'.'	},
1085	{ XFUNC_prev_histword,		1,	'_'	},
1086	{ XFUNC_set_arg,		1,	'0'	},
1087	{ XFUNC_set_arg,		1,	'1'	},
1088	{ XFUNC_set_arg,		1,	'2'	},
1089	{ XFUNC_set_arg,		1,	'3'	},
1090	{ XFUNC_set_arg,		1,	'4'	},
1091	{ XFUNC_set_arg,		1,	'5'	},
1092	{ XFUNC_set_arg,		1,	'6'	},
1093	{ XFUNC_set_arg,		1,	'7'	},
1094	{ XFUNC_set_arg,		1,	'8'	},
1095	{ XFUNC_set_arg,		1,	'9'	},
1096#ifndef MKSH_SMALL
1097	{ XFUNC_fold_upper,		1,	'U'	},
1098	{ XFUNC_fold_upper,		1,	'u'	},
1099	{ XFUNC_fold_lower,		1,	'L'	},
1100	{ XFUNC_fold_lower,		1,	'l'	},
1101	{ XFUNC_fold_capitalise,	1,	'C'	},
1102	{ XFUNC_fold_capitalise,	1,	'c'	},
1103#endif
1104	/*
1105	 * These for ANSI arrow keys: arguablely shouldn't be here by
1106	 * default, but its simpler/faster/smaller than using termcap
1107	 * entries.
1108	 */
1109	{ XFUNC_meta2,			1,	'['	},
1110	{ XFUNC_meta2,			1,	'O'	},
1111	{ XFUNC_prev_com,		2,	'A'	},
1112	{ XFUNC_next_com,		2,	'B'	},
1113	{ XFUNC_mv_forw,		2,	'C'	},
1114	{ XFUNC_mv_back,		2,	'D'	},
1115#ifndef MKSH_SMALL
1116	{ XFUNC_vt_hack,		2,	'1'	},
1117	{ XFUNC_mv_beg | 0x80,		2,	'7'	},
1118	{ XFUNC_mv_beg,			2,	'H'	},
1119	{ XFUNC_mv_end | 0x80,		2,	'4'	},
1120	{ XFUNC_mv_end | 0x80,		2,	'8'	},
1121	{ XFUNC_mv_end,			2,	'F'	},
1122	{ XFUNC_del_char | 0x80,	2,	'3'	},
1123	{ XFUNC_del_char,		2,	'P'	},
1124	{ XFUNC_search_hist_up | 0x80,	2,	'5'	},
1125	{ XFUNC_search_hist_dn | 0x80,	2,	'6'	},
1126#endif
1127	/* PC scancodes */
1128#if !defined(MKSH_SMALL) || defined(__OS2__)
1129	{ XFUNC_meta3,			0,	0	},
1130	{ XFUNC_mv_beg,			3,	71	},
1131	{ XFUNC_prev_com,		3,	72	},
1132#ifndef MKSH_SMALL
1133	{ XFUNC_search_hist_up,		3,	73	},
1134#endif
1135	{ XFUNC_mv_back,		3,	75	},
1136	{ XFUNC_mv_forw,		3,	77	},
1137	{ XFUNC_mv_end,			3,	79	},
1138	{ XFUNC_next_com,		3,	80	},
1139#ifndef MKSH_SMALL
1140	{ XFUNC_search_hist_dn,		3,	81	},
1141#endif
1142	{ XFUNC_del_char,		3,	83	},
1143#endif
1144#ifndef MKSH_SMALL
1145	/* more non-standard ones */
1146	{ XFUNC_eval_region,		1, CTRL('E')	},
1147	{ XFUNC_edit_line,		2,	'e'	}
1148#endif
1149};
1150
1151static size_t
1152x_nb2nc(size_t nb)
1153{
1154	char *cp;
1155	size_t nc = 0;
1156
1157	for (cp = xcp; cp < (xcp + nb); ++nc)
1158		cp += utf_ptradj(cp);
1159	return (nc);
1160}
1161
1162static void
1163x_modified(void)
1164{
1165	if (!modified) {
1166		x_histp = histptr + 1;
1167		modified = 1;
1168	}
1169}
1170
1171#ifdef MKSH_SMALL
1172#define XFUNC_VALUE(f) (f)
1173#else
1174#define XFUNC_VALUE(f) (f & 0x7F)
1175#endif
1176
1177static int
1178x_e_getmbc(char *sbuf)
1179{
1180	int c, pos = 0;
1181	unsigned char *buf = (unsigned char *)sbuf;
1182
1183	memset(buf, 0, 4);
1184	buf[pos++] = c = x_e_getc();
1185	if (c == -1)
1186		return (-1);
1187	if (UTFMODE) {
1188		if ((buf[0] >= 0xC2) && (buf[0] < 0xF0)) {
1189			c = x_e_getc();
1190			if (c == -1)
1191				return (-1);
1192			if ((c & 0xC0) != 0x80) {
1193				x_e_ungetc(c);
1194				return (1);
1195			}
1196			buf[pos++] = c;
1197		}
1198		if ((buf[0] >= 0xE0) && (buf[0] < 0xF0)) {
1199			/* XXX x_e_ungetc is one-octet only */
1200			buf[pos++] = c = x_e_getc();
1201			if (c == -1)
1202				return (-1);
1203		}
1204	}
1205	return (pos);
1206}
1207
1208/*
1209 * minimum required space to work with on a line - if the prompt
1210 * leaves less space than this on a line, the prompt is truncated
1211 */
1212#define MIN_EDIT_SPACE	7
1213
1214static void
1215x_init_prompt(bool doprint)
1216{
1217	prompt_trunc = pprompt(prompt, doprint ? 0 : -1);
1218	pwidth = prompt_trunc % x_cols;
1219	prompt_trunc -= pwidth;
1220	if ((mksh_uari_t)pwidth > ((mksh_uari_t)x_cols - 3 - MIN_EDIT_SPACE)) {
1221		/* force newline after prompt */
1222		prompt_trunc = -1;
1223		pwidth = 0;
1224		if (doprint)
1225			x_e_putc2('\n');
1226	}
1227}
1228
1229static int
1230x_emacs(char *buf)
1231{
1232	int c, i;
1233	unsigned char f;
1234
1235	xbp = xbuf = buf;
1236	xend = buf + LINE;
1237	xlp = xcp = xep = buf;
1238	*xcp = 0;
1239	xlp_valid = true;
1240	xmp = NULL;
1241	x_curprefix = 0;
1242	x_histp = histptr + 1;
1243	x_last_command = XFUNC_error;
1244
1245	x_init_prompt(true);
1246	x_displen = (xx_cols = x_cols) - 2 - (x_col = pwidth);
1247	x_adj_done = 0;
1248	x_adj_ok = true;
1249
1250	x_histncp = NULL;
1251	if (x_nextcmd >= 0) {
1252		int off = source->line - x_nextcmd;
1253		if (histptr - history >= off) {
1254			x_load_hist(histptr - off);
1255			x_histncp = x_histp;
1256		}
1257		x_nextcmd = -1;
1258	}
1259	editmode = 1;
1260	while (/* CONSTCOND */ 1) {
1261		x_flush();
1262		if ((c = x_e_getc()) < 0)
1263			return (0);
1264
1265		f = x_curprefix == -1 ? XFUNC_insert :
1266		    x_tab[x_curprefix][c];
1267#ifndef MKSH_SMALL
1268		if (f & 0x80) {
1269			f &= 0x7F;
1270			if ((i = x_e_getc()) != '~')
1271				x_e_ungetc(i);
1272		}
1273
1274		/* avoid bind key macro recursion */
1275		if (macroptr && f == XFUNC_ins_string)
1276			f = XFUNC_insert;
1277#endif
1278
1279		if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
1280		    x_last_command != XFUNC_set_arg) {
1281			x_arg = 1;
1282			x_arg_defaulted = true;
1283		}
1284		i = c | (x_curprefix << 8);
1285		x_curprefix = 0;
1286		switch ((*x_ftab[f].xf_func)(i)) {
1287		case KSTD:
1288			if (!(x_ftab[f].xf_flags & XF_PREFIX))
1289				x_last_command = f;
1290			break;
1291		case KEOL:
1292			i = xep - xbuf;
1293			return (i);
1294		case KINTR:
1295			/* special case for interrupt */
1296			trapsig(SIGINT);
1297			x_mode(false);
1298			unwind(LSHELL);
1299		}
1300		/* ad-hoc hack for fixing the cursor position */
1301		x_goto(xcp);
1302	}
1303}
1304
1305static int
1306x_insert(int c)
1307{
1308	static int left, pos, save_arg;
1309	static char str[4];
1310
1311	/*
1312	 * Should allow tab and control chars.
1313	 */
1314	if (c == 0) {
1315 invmbs:
1316		left = 0;
1317		x_e_putc2(7);
1318		return (KSTD);
1319	}
1320	if (UTFMODE) {
1321		if (((c & 0xC0) == 0x80) && left) {
1322			str[pos++] = c;
1323			if (!--left) {
1324				str[pos] = '\0';
1325				x_arg = save_arg;
1326				while (x_arg--)
1327					x_ins(str);
1328			}
1329			return (KSTD);
1330		}
1331		if (left) {
1332			if (x_curprefix == -1) {
1333				/* flush invalid multibyte */
1334				str[pos] = '\0';
1335				while (save_arg--)
1336					x_ins(str);
1337			}
1338		}
1339		if ((c >= 0xC2) && (c < 0xE0))
1340			left = 1;
1341		else if ((c >= 0xE0) && (c < 0xF0))
1342			left = 2;
1343		else if (c > 0x7F)
1344			goto invmbs;
1345		else
1346			left = 0;
1347		if (left) {
1348			save_arg = x_arg;
1349			pos = 1;
1350			str[0] = c;
1351			return (KSTD);
1352		}
1353	}
1354	left = 0;
1355	str[0] = c;
1356	str[1] = '\0';
1357	while (x_arg--)
1358		x_ins(str);
1359	return (KSTD);
1360}
1361
1362#ifndef MKSH_SMALL
1363static int
1364x_ins_string(int c)
1365{
1366	macroptr = x_atab[c >> 8][c & 255];
1367	/*
1368	 * we no longer need to bother checking if macroptr is
1369	 * not NULL but first char is NUL; x_e_getc() does it
1370	 */
1371	return (KSTD);
1372}
1373#endif
1374
1375static int
1376x_do_ins(const char *cp, size_t len)
1377{
1378	if (xep + len >= xend) {
1379		x_e_putc2(7);
1380		return (-1);
1381	}
1382	memmove(xcp + len, xcp, xep - xcp + 1);
1383	memmove(xcp, cp, len);
1384	xcp += len;
1385	xep += len;
1386	x_modified();
1387	return (0);
1388}
1389
1390static int
1391x_ins(const char *s)
1392{
1393	char *cp = xcp;
1394	int adj = x_adj_done;
1395
1396	if (x_do_ins(s, strlen(s)) < 0)
1397		return (-1);
1398	/*
1399	 * x_zots() may result in a call to x_adjust()
1400	 * we want xcp to reflect the new position.
1401	 */
1402	xlp_valid = false;
1403	x_lastcp();
1404	x_adj_ok = tobool(xcp >= xlp);
1405	x_zots(cp);
1406	if (adj == x_adj_done)
1407		/* x_adjust() has not been called */
1408		x_lastpos();
1409	x_adj_ok = true;
1410	return (0);
1411}
1412
1413static int
1414x_del_back(int c MKSH_A_UNUSED)
1415{
1416	ssize_t i = 0;
1417
1418	if (xcp == xbuf) {
1419		x_e_putc2(7);
1420		return (KSTD);
1421	}
1422	do {
1423		x_goto(xcp - 1);
1424	} while ((++i < x_arg) && (xcp != xbuf));
1425	x_delete(i, false);
1426	return (KSTD);
1427}
1428
1429static int
1430x_del_char(int c MKSH_A_UNUSED)
1431{
1432	char *cp, *cp2;
1433	size_t i = 0;
1434
1435	cp = xcp;
1436	while (i < (size_t)x_arg) {
1437		utf_ptradjx(cp, cp2);
1438		if (cp2 > xep)
1439			break;
1440		cp = cp2;
1441		i++;
1442	}
1443
1444	if (!i) {
1445		x_e_putc2(7);
1446		return (KSTD);
1447	}
1448	x_delete(i, false);
1449	return (KSTD);
1450}
1451
1452/* Delete nc chars to the right of the cursor (including cursor position) */
1453static void
1454x_delete(size_t nc, bool push)
1455{
1456	size_t i, nb, nw;
1457	char *cp;
1458
1459	if (nc == 0)
1460		return;
1461
1462	nw = 0;
1463	cp = xcp;
1464	for (i = 0; i < nc; ++i) {
1465		char *cp2;
1466		int j;
1467
1468		j = x_size2(cp, &cp2);
1469		if (cp2 > xep)
1470			break;
1471		cp = cp2;
1472		nw += j;
1473	}
1474	nb = cp - xcp;
1475	/* nc = i; */
1476
1477	if (xmp != NULL && xmp > xcp) {
1478		if (xcp + nb > xmp)
1479			xmp = xcp;
1480		else
1481			xmp -= nb;
1482	}
1483	/*
1484	 * This lets us yank a word we have deleted.
1485	 */
1486	if (push)
1487		x_push(nb);
1488
1489	xep -= nb;
1490	/* Copies the NUL */
1491	memmove(xcp, xcp + nb, xep - xcp + 1);
1492	/* don't redraw */
1493	x_adj_ok = false;
1494	xlp_valid = false;
1495	x_zots(xcp);
1496	/*
1497	 * if we are already filling the line,
1498	 * there is no need to ' ', '\b'.
1499	 * But if we must, make sure we do the minimum.
1500	 */
1501	if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
1502		nw = i = (nw < i) ? nw : i;
1503		while (i--)
1504			x_e_putc2(' ');
1505		if (x_col == xx_cols - 2) {
1506			x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' ');
1507			++nw;
1508		}
1509		while (nw--)
1510			x_e_putc2('\b');
1511	}
1512	/*x_goto(xcp);*/
1513	x_adj_ok = true;
1514	xlp_valid = false;
1515	x_lastpos();
1516	x_modified();
1517	return;
1518}
1519
1520static int
1521x_del_bword(int c MKSH_A_UNUSED)
1522{
1523	x_delete(x_bword(), true);
1524	return (KSTD);
1525}
1526
1527static int
1528x_mv_bword(int c MKSH_A_UNUSED)
1529{
1530	x_bword();
1531	return (KSTD);
1532}
1533
1534static int
1535x_mv_fword(int c MKSH_A_UNUSED)
1536{
1537	x_fword(true);
1538	return (KSTD);
1539}
1540
1541static int
1542x_del_fword(int c MKSH_A_UNUSED)
1543{
1544	x_delete(x_fword(false), true);
1545	return (KSTD);
1546}
1547
1548static size_t
1549x_bword(void)
1550{
1551	size_t nb = 0;
1552	char *cp = xcp;
1553
1554	if (cp == xbuf) {
1555		x_e_putc2(7);
1556		return (0);
1557	}
1558	while (x_arg--) {
1559		while (cp != xbuf && is_mfs(cp[-1])) {
1560			cp--;
1561			nb++;
1562		}
1563		while (cp != xbuf && !is_mfs(cp[-1])) {
1564			cp--;
1565			nb++;
1566		}
1567	}
1568	x_goto(cp);
1569	return (x_nb2nc(nb));
1570}
1571
1572static size_t
1573x_fword(bool move)
1574{
1575	size_t nc;
1576	char *cp = xcp;
1577
1578	if (cp == xep) {
1579		x_e_putc2(7);
1580		return (0);
1581	}
1582	while (x_arg--) {
1583		while (cp != xep && is_mfs(*cp))
1584			cp++;
1585		while (cp != xep && !is_mfs(*cp))
1586			cp++;
1587	}
1588	nc = x_nb2nc(cp - xcp);
1589	if (move)
1590		x_goto(cp);
1591	return (nc);
1592}
1593
1594static void
1595x_goto(char *cp)
1596{
1597	cp = cp >= xep ? xep : x_bs0(cp, xbuf);
1598	if (cp < xbp || cp >= utf_skipcols(xbp, x_displen, NULL)) {
1599		/* we are heading off screen */
1600		xcp = cp;
1601		x_adjust();
1602	} else if (cp < xcp) {
1603		/* move back */
1604		while (cp < xcp)
1605			x_bs3(&xcp);
1606	} else if (cp > xcp) {
1607		/* move forward */
1608		while (cp > xcp)
1609			x_zotc3(&xcp);
1610	}
1611}
1612
1613static char *
1614x_bs0(char *cp, char *lower_bound)
1615{
1616	if (UTFMODE)
1617		while ((!lower_bound || (cp > lower_bound)) &&
1618		    ((*(unsigned char *)cp & 0xC0) == 0x80))
1619			--cp;
1620	return (cp);
1621}
1622
1623static void
1624x_bs3(char **p)
1625{
1626	int i;
1627
1628	*p = x_bs0((*p) - 1, NULL);
1629	i = x_size2(*p, NULL);
1630	while (i--)
1631		x_e_putc2('\b');
1632}
1633
1634static int
1635x_size2(char *cp, char **dcp)
1636{
1637	uint8_t c = *(unsigned char *)cp;
1638
1639	if (UTFMODE && (c > 0x7F))
1640		return (utf_widthadj(cp, (const char **)dcp));
1641	if (dcp)
1642		*dcp = cp + 1;
1643	if (c == '\t')
1644		/* Kludge, tabs are always four spaces. */
1645		return (4);
1646	if (ISCTRL(c) && /* but not C1 */ c < 0x80)
1647		/* control unsigned char */
1648		return (2);
1649	return (1);
1650}
1651
1652static void
1653x_zots(char *str)
1654{
1655	int adj = x_adj_done;
1656
1657	x_lastcp();
1658	while (*str && str < xlp && x_col < xx_cols && adj == x_adj_done)
1659		x_zotc3(&str);
1660}
1661
1662static void
1663x_zotc3(char **cp)
1664{
1665	unsigned char c = **(unsigned char **)cp;
1666
1667	if (c == '\t') {
1668		/* Kludge, tabs are always four spaces. */
1669		x_e_puts(T4spaces);
1670		(*cp)++;
1671	} else if (ISCTRL(c) && /* but not C1 */ c < 0x80) {
1672		x_e_putc2('^');
1673		x_e_putc2(UNCTRL(c));
1674		(*cp)++;
1675	} else
1676		x_e_putc3((const char **)cp);
1677}
1678
1679static int
1680x_mv_back(int c MKSH_A_UNUSED)
1681{
1682	if (xcp == xbuf) {
1683		x_e_putc2(7);
1684		return (KSTD);
1685	}
1686	while (x_arg--) {
1687		x_goto(xcp - 1);
1688		if (xcp == xbuf)
1689			break;
1690	}
1691	return (KSTD);
1692}
1693
1694static int
1695x_mv_forw(int c MKSH_A_UNUSED)
1696{
1697	char *cp = xcp, *cp2;
1698
1699	if (xcp == xep) {
1700		x_e_putc2(7);
1701		return (KSTD);
1702	}
1703	while (x_arg--) {
1704		utf_ptradjx(cp, cp2);
1705		if (cp2 > xep)
1706			break;
1707		cp = cp2;
1708	}
1709	x_goto(cp);
1710	return (KSTD);
1711}
1712
1713static int
1714x_search_char_forw(int c MKSH_A_UNUSED)
1715{
1716	char *cp = xcp;
1717	char tmp[4];
1718
1719	*xep = '\0';
1720	if (x_e_getmbc(tmp) < 0) {
1721		x_e_putc2(7);
1722		return (KSTD);
1723	}
1724	while (x_arg--) {
1725		if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL &&
1726		    (cp = strstr(xbuf, tmp)) == NULL) {
1727			x_e_putc2(7);
1728			return (KSTD);
1729		}
1730	}
1731	x_goto(cp);
1732	return (KSTD);
1733}
1734
1735static int
1736x_search_char_back(int c MKSH_A_UNUSED)
1737{
1738	char *cp = xcp, *p, tmp[4];
1739	bool b;
1740
1741	if (x_e_getmbc(tmp) < 0) {
1742		x_e_putc2(7);
1743		return (KSTD);
1744	}
1745	for (; x_arg--; cp = p)
1746		for (p = cp; ; ) {
1747			if (p-- == xbuf)
1748				p = xep;
1749			if (p == cp) {
1750				x_e_putc2(7);
1751				return (KSTD);
1752			}
1753			if ((tmp[1] && ((p+1) > xep)) ||
1754			    (tmp[2] && ((p+2) > xep)))
1755				continue;
1756			b = true;
1757			if (*p != tmp[0])
1758				b = false;
1759			if (b && tmp[1] && p[1] != tmp[1])
1760				b = false;
1761			if (b && tmp[2] && p[2] != tmp[2])
1762				b = false;
1763			if (b)
1764				break;
1765		}
1766	x_goto(cp);
1767	return (KSTD);
1768}
1769
1770static int
1771x_newline(int c MKSH_A_UNUSED)
1772{
1773	x_e_putc2('\r');
1774	x_e_putc2('\n');
1775	x_flush();
1776	*xep++ = '\n';
1777	return (KEOL);
1778}
1779
1780static int
1781x_end_of_text(int c MKSH_A_UNUSED)
1782{
1783	unsigned char tmp;
1784	char *cp = (void *)&tmp;
1785
1786	tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof :
1787	    (unsigned char)CTRL('D');
1788	x_zotc3(&cp);
1789	x_putc('\r');
1790	x_putc('\n');
1791	x_flush();
1792	return (KEOL);
1793}
1794
1795static int
1796x_beg_hist(int c MKSH_A_UNUSED)
1797{
1798	x_load_hist(history);
1799	return (KSTD);
1800}
1801
1802static int
1803x_end_hist(int c MKSH_A_UNUSED)
1804{
1805	x_load_hist(histptr);
1806	return (KSTD);
1807}
1808
1809static int
1810x_prev_com(int c MKSH_A_UNUSED)
1811{
1812	x_load_hist(x_histp - x_arg);
1813	return (KSTD);
1814}
1815
1816static int
1817x_next_com(int c MKSH_A_UNUSED)
1818{
1819	x_load_hist(x_histp + x_arg);
1820	return (KSTD);
1821}
1822
1823/*
1824 * Goto a particular history number obtained from argument.
1825 * If no argument is given history 1 is probably not what you
1826 * want so we'll simply go to the oldest one.
1827 */
1828static int
1829x_goto_hist(int c MKSH_A_UNUSED)
1830{
1831	if (x_arg_defaulted)
1832		x_load_hist(history);
1833	else
1834		x_load_hist(histptr + x_arg - source->line);
1835	return (KSTD);
1836}
1837
1838static void
1839x_load_hist(char **hp)
1840{
1841	char *sp = NULL;
1842
1843	if (hp == histptr + 1) {
1844		sp = holdbufp;
1845		modified = 0;
1846	} else if (hp < history || hp > histptr) {
1847		x_e_putc2(7);
1848		return;
1849	}
1850	if (sp == NULL)
1851		sp = *hp;
1852	x_histp = hp;
1853	if (modified)
1854		strlcpy(holdbufp, xbuf, LINE);
1855	strlcpy(xbuf, sp, xend - xbuf);
1856	xbp = xbuf;
1857	xep = xcp = xbuf + strlen(xbuf);
1858	x_adjust();
1859	modified = 0;
1860}
1861
1862static int
1863x_nl_next_com(int c MKSH_A_UNUSED)
1864{
1865	if (!x_histncp || (x_histp != x_histncp && x_histp != histptr + 1))
1866		/* fresh start of ^O */
1867		x_histncp = x_histp;
1868	x_nextcmd = source->line - (histptr - x_histncp) + 1;
1869	return (x_newline('\n'));
1870}
1871
1872static int
1873x_eot_del(int c)
1874{
1875	if (xep == xbuf && x_arg_defaulted)
1876		return (x_end_of_text(c));
1877	else
1878		return (x_del_char(c));
1879}
1880
1881/* reverse incremental history search */
1882static int
1883x_search_hist(int c)
1884{
1885	int offset = -1;	/* offset of match in xbuf, else -1 */
1886	char pat[80 + 1];	/* pattern buffer */
1887	char *p = pat;
1888	unsigned char f;
1889
1890	*p = '\0';
1891	while (/* CONSTCOND */ 1) {
1892		if (offset < 0) {
1893			x_e_puts("\nI-search: ");
1894			x_e_puts(pat);
1895		}
1896		x_flush();
1897		if ((c = x_e_getc()) < 0)
1898			return (KSTD);
1899		f = x_tab[0][c];
1900		if (c == CTRL('[')) {
1901			if ((f & 0x7F) == XFUNC_meta1) {
1902				if ((c = x_e_getc()) < 0)
1903					return (KSTD);
1904				f = x_tab[1][c] & 0x7F;
1905				if (f == XFUNC_meta1 || f == XFUNC_meta2)
1906					x_meta1(CTRL('['));
1907				x_e_ungetc(c);
1908			}
1909			break;
1910		}
1911#ifndef MKSH_SMALL
1912		if (f & 0x80) {
1913			f &= 0x7F;
1914			if ((c = x_e_getc()) != '~')
1915				x_e_ungetc(c);
1916		}
1917#endif
1918		if (f == XFUNC_search_hist)
1919			offset = x_search(pat, 0, offset);
1920		else if (f == XFUNC_del_back) {
1921			if (p == pat) {
1922				offset = -1;
1923				break;
1924			}
1925			if (p > pat)
1926				*--p = '\0';
1927			if (p == pat)
1928				offset = -1;
1929			else
1930				offset = x_search(pat, 1, offset);
1931			continue;
1932		} else if (f == XFUNC_insert) {
1933			/* add char to pattern */
1934			/* overflow check... */
1935			if ((size_t)(p - pat) >= sizeof(pat) - 1) {
1936				x_e_putc2(7);
1937				continue;
1938			}
1939			*p++ = c, *p = '\0';
1940			if (offset >= 0) {
1941				/* already have partial match */
1942				offset = x_match(xbuf, pat);
1943				if (offset >= 0) {
1944					x_goto(xbuf + offset + (p - pat) -
1945					    (*pat == '^'));
1946					continue;
1947				}
1948			}
1949			offset = x_search(pat, 0, offset);
1950		} else if (f == XFUNC_abort) {
1951			if (offset >= 0)
1952				x_load_hist(histptr + 1);
1953			break;
1954		} else {
1955			/* other command */
1956			x_e_ungetc(c);
1957			break;
1958		}
1959	}
1960	if (offset < 0)
1961		x_redraw('\n');
1962	return (KSTD);
1963}
1964
1965/* search backward from current line */
1966static int
1967x_search(char *pat, int sameline, int offset)
1968{
1969	char **hp;
1970	int i;
1971
1972	for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) {
1973		i = x_match(*hp, pat);
1974		if (i >= 0) {
1975			if (offset < 0)
1976				x_e_putc2('\n');
1977			x_load_hist(hp);
1978			x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
1979			return (i);
1980		}
1981	}
1982	x_e_putc2(7);
1983	x_histp = histptr;
1984	return (-1);
1985}
1986
1987#ifndef MKSH_SMALL
1988/* anchored search up from current line */
1989static int
1990x_search_hist_up(int c MKSH_A_UNUSED)
1991{
1992	return (x_search_dir(-1));
1993}
1994
1995/* anchored search down from current line */
1996static int
1997x_search_hist_dn(int c MKSH_A_UNUSED)
1998{
1999	return (x_search_dir(1));
2000}
2001
2002/* anchored search in the indicated direction */
2003static int
2004x_search_dir(int search_dir /* should've been bool */)
2005{
2006	char **hp = x_histp + search_dir;
2007	size_t curs = xcp - xbuf;
2008
2009	while (histptr >= hp && hp >= history) {
2010		if (strncmp(xbuf, *hp, curs) == 0) {
2011			x_load_hist(hp);
2012			x_goto(xbuf + curs);
2013			break;
2014		}
2015		hp += search_dir;
2016	}
2017	return (KSTD);
2018}
2019#endif
2020
2021/* return position of first match of pattern in string, else -1 */
2022static int
2023x_match(char *str, char *pat)
2024{
2025	if (*pat == '^') {
2026		return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1);
2027	} else {
2028		char *q = strstr(str, pat);
2029		return ((q == NULL) ? -1 : q - str);
2030	}
2031}
2032
2033static int
2034x_del_line(int c MKSH_A_UNUSED)
2035{
2036	*xep = 0;
2037	x_push(xep - (xcp = xbuf));
2038	xlp = xbp = xep = xbuf;
2039	xlp_valid = true;
2040	*xcp = 0;
2041	xmp = NULL;
2042	x_redraw('\r');
2043	x_modified();
2044	return (KSTD);
2045}
2046
2047static int
2048x_mv_end(int c MKSH_A_UNUSED)
2049{
2050	x_goto(xep);
2051	return (KSTD);
2052}
2053
2054static int
2055x_mv_beg(int c MKSH_A_UNUSED)
2056{
2057	x_goto(xbuf);
2058	return (KSTD);
2059}
2060
2061static int
2062x_draw_line(int c MKSH_A_UNUSED)
2063{
2064	x_redraw('\n');
2065	return (KSTD);
2066}
2067
2068static int
2069x_cls(int c MKSH_A_UNUSED)
2070{
2071	shf_puts(MKSH_CLS_STRING, shl_out);
2072	x_redraw(0);
2073	return (KSTD);
2074}
2075
2076/*
2077 * clear line from x_col (current cursor position) to xx_cols - 2,
2078 * then output lastch, then go back to x_col; if lastch is space,
2079 * clear with termcap instead of spaces, or not if line_was_cleared;
2080 * lastch MUST be an ASCII character with wcwidth(lastch) == 1
2081 */
2082static void
2083x_clrtoeol(int lastch, bool line_was_cleared)
2084{
2085	int col;
2086
2087	if (lastch == ' ' && !line_was_cleared && x_term_mode == 1) {
2088		shf_puts("\033[K", shl_out);
2089		line_was_cleared = true;
2090	}
2091	if (lastch == ' ' && line_was_cleared)
2092		return;
2093
2094	col = x_col;
2095	while (col < (xx_cols - 2)) {
2096		x_putc(' ');
2097		++col;
2098	}
2099	x_putc(lastch);
2100	++col;
2101	while (col > x_col) {
2102		x_putc('\b');
2103		--col;
2104	}
2105}
2106
2107/* output the prompt, assuming a line has just been started */
2108static void
2109x_pprompt(void)
2110{
2111	if (prompt_trunc != -1)
2112		pprompt(prompt, prompt_trunc);
2113	x_col = pwidth;
2114}
2115
2116/* output CR, then redraw the line, clearing to EOL if needed (cr ≠ 0, LF) */
2117static void
2118x_redraw(int cr)
2119{
2120	int lch;
2121
2122	x_adj_ok = false;
2123	/* clear the line */
2124	x_e_putc2(cr ? cr : '\r');
2125	x_flush();
2126	/* display the prompt */
2127	if (xbp == xbuf)
2128		x_pprompt();
2129	x_displen = xx_cols - 2 - x_col;
2130	/* display the line content */
2131	xlp_valid = false;
2132	x_zots(xbp);
2133	/* check whether there is more off-screen */
2134	lch = xep > xlp ? (xbp > xbuf ? '*' : '>') : (xbp > xbuf) ? '<' : ' ';
2135	/* clear the rest of the line */
2136	x_clrtoeol(lch, !cr || cr == '\n');
2137	/* go back to actual cursor position */
2138	x_lastpos();
2139	x_adj_ok = true;
2140}
2141
2142static int
2143x_transpose(int c MKSH_A_UNUSED)
2144{
2145	unsigned int tmpa, tmpb;
2146
2147	/*-
2148	 * What transpose is meant to do seems to be up for debate. This
2149	 * is a general summary of the options; the text is abcd with the
2150	 * upper case character or underscore indicating the cursor position:
2151	 *	Who			Before	After	Before	After
2152	 *	AT&T ksh in emacs mode:	abCd	abdC	abcd_	(bell)
2153	 *	AT&T ksh in gmacs mode:	abCd	baCd	abcd_	abdc_
2154	 *	gnu emacs:		abCd	acbD	abcd_	abdc_
2155	 * Pdksh currently goes with GNU behavior since I believe this is the
2156	 * most common version of emacs, unless in gmacs mode, in which case
2157	 * it does the AT&T ksh gmacs mode.
2158	 * This should really be broken up into 3 functions so users can bind
2159	 * to the one they want.
2160	 */
2161	if (xcp == xbuf) {
2162		x_e_putc2(7);
2163		return (KSTD);
2164	} else if (xcp == xep || Flag(FGMACS)) {
2165		if (xcp - xbuf == 1) {
2166			x_e_putc2(7);
2167			return (KSTD);
2168		}
2169		/*
2170		 * Gosling/Unipress emacs style: Swap two characters before
2171		 * the cursor, do not change cursor position
2172		 */
2173		x_bs3(&xcp);
2174		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
2175			x_e_putc2(7);
2176			return (KSTD);
2177		}
2178		x_bs3(&xcp);
2179		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
2180			x_e_putc2(7);
2181			return (KSTD);
2182		}
2183		utf_wctomb(xcp, tmpa);
2184		x_zotc3(&xcp);
2185		utf_wctomb(xcp, tmpb);
2186		x_zotc3(&xcp);
2187	} else {
2188		/*
2189		 * GNU emacs style: Swap the characters before and under the
2190		 * cursor, move cursor position along one.
2191		 */
2192		if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
2193			x_e_putc2(7);
2194			return (KSTD);
2195		}
2196		x_bs3(&xcp);
2197		if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) {
2198			x_e_putc2(7);
2199			return (KSTD);
2200		}
2201		utf_wctomb(xcp, tmpa);
2202		x_zotc3(&xcp);
2203		utf_wctomb(xcp, tmpb);
2204		x_zotc3(&xcp);
2205	}
2206	x_modified();
2207	return (KSTD);
2208}
2209
2210static int
2211x_literal(int c MKSH_A_UNUSED)
2212{
2213	x_curprefix = -1;
2214	return (KSTD);
2215}
2216
2217static int
2218x_meta1(int c MKSH_A_UNUSED)
2219{
2220	x_curprefix = 1;
2221	return (KSTD);
2222}
2223
2224static int
2225x_meta2(int c MKSH_A_UNUSED)
2226{
2227	x_curprefix = 2;
2228	return (KSTD);
2229}
2230
2231static int
2232x_meta3(int c MKSH_A_UNUSED)
2233{
2234	x_curprefix = 3;
2235	return (KSTD);
2236}
2237
2238static int
2239x_kill(int c MKSH_A_UNUSED)
2240{
2241	size_t col = xcp - xbuf;
2242	size_t lastcol = xep - xbuf;
2243	size_t ndel, narg;
2244
2245	if (x_arg_defaulted || (narg = x_arg) > lastcol)
2246		narg = lastcol;
2247	if (narg < col) {
2248		x_goto(xbuf + narg);
2249		ndel = col - narg;
2250	} else
2251		ndel = narg - col;
2252	x_delete(x_nb2nc(ndel), true);
2253	return (KSTD);
2254}
2255
2256static void
2257x_push(size_t nchars)
2258{
2259	afree(killstack[killsp], AEDIT);
2260	strndupx(killstack[killsp], xcp, nchars, AEDIT);
2261	killsp = (killsp + 1) % KILLSIZE;
2262}
2263
2264static int
2265x_yank(int c MKSH_A_UNUSED)
2266{
2267	if (killsp == 0)
2268		killtp = KILLSIZE;
2269	else
2270		killtp = killsp;
2271	killtp--;
2272	if (killstack[killtp] == 0) {
2273		x_e_puts("\nnothing to yank");
2274		x_redraw('\n');
2275		return (KSTD);
2276	}
2277	xmp = xcp;
2278	x_ins(killstack[killtp]);
2279	return (KSTD);
2280}
2281
2282static int
2283x_meta_yank(int c MKSH_A_UNUSED)
2284{
2285	size_t len;
2286
2287	if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
2288	    killstack[killtp] == 0) {
2289		killtp = killsp;
2290		x_e_puts("\nyank something first");
2291		x_redraw('\n');
2292		return (KSTD);
2293	}
2294	len = strlen(killstack[killtp]);
2295	x_goto(xcp - len);
2296	x_delete(x_nb2nc(len), false);
2297	do {
2298		if (killtp == 0)
2299			killtp = KILLSIZE - 1;
2300		else
2301			killtp--;
2302	} while (killstack[killtp] == 0);
2303	x_ins(killstack[killtp]);
2304	return (KSTD);
2305}
2306
2307static int
2308x_abort(int c MKSH_A_UNUSED)
2309{
2310	/* x_zotc(c); */
2311	xlp = xep = xcp = xbp = xbuf;
2312	xlp_valid = true;
2313	*xcp = 0;
2314	x_modified();
2315	return (KINTR);
2316}
2317
2318static int
2319x_error(int c MKSH_A_UNUSED)
2320{
2321	x_e_putc2(7);
2322	return (KSTD);
2323}
2324
2325#ifndef MKSH_SMALL
2326/* special VT100 style key sequence hack */
2327static int
2328x_vt_hack(int c)
2329{
2330	/* we only support PF2-'1' for now */
2331	if (c != (2 << 8 | '1'))
2332		return (x_error(c));
2333
2334	/* what's the next character? */
2335	switch ((c = x_e_getc())) {
2336	case '~':
2337		x_arg = 1;
2338		x_arg_defaulted = true;
2339		return (x_mv_beg(0));
2340	case ';':
2341		/* "interesting" sequence detected */
2342		break;
2343	default:
2344		goto unwind_err;
2345	}
2346
2347	/* XXX x_e_ungetc is one-octet only */
2348	if ((c = x_e_getc()) != '5' && c != '3')
2349		goto unwind_err;
2350
2351	/*-
2352	 * At this point, we have read the following octets so far:
2353	 * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
2354	 * - 1 (vt_hack)
2355	 * - ;
2356	 * - 5 (Ctrl key combiner) or 3 (Alt key combiner)
2357	 * We can now accept one more octet designating the key.
2358	 */
2359
2360	switch ((c = x_e_getc())) {
2361	case 'C':
2362		return (x_mv_fword(c));
2363	case 'D':
2364		return (x_mv_bword(c));
2365	}
2366
2367 unwind_err:
2368	x_e_ungetc(c);
2369	return (x_error(c));
2370}
2371#endif
2372
2373static char *
2374x_mapin(const char *cp, Area *ap)
2375{
2376	char *news, *op;
2377
2378	strdupx(news, cp, ap);
2379	op = news;
2380	while (*cp) {
2381		/* XXX -- should handle \^ escape? */
2382		if (*cp == '^') {
2383			cp++;
2384			/*XXX or ^^ escape? this is ugly. */
2385			if (*cp >= '?')
2386				/* includes '?'; ASCII */
2387				*op++ = CTRL(*cp);
2388			else {
2389				*op++ = '^';
2390				cp--;
2391			}
2392		} else
2393			*op++ = *cp;
2394		cp++;
2395	}
2396	*op = '\0';
2397
2398	return (news);
2399}
2400
2401static void
2402x_mapout2(int c, char **buf)
2403{
2404	char *p = *buf;
2405
2406	if (ISCTRL(c)) {
2407		*p++ = '^';
2408		*p++ = UNCTRL(c);
2409	} else
2410		*p++ = c;
2411	*p = 0;
2412	*buf = p;
2413}
2414
2415static char *
2416x_mapout(int c)
2417{
2418	static char buf[8];
2419	char *bp = buf;
2420
2421	x_mapout2(c, &bp);
2422	return (buf);
2423}
2424
2425static void
2426x_print(int prefix, int key)
2427{
2428	int f = x_tab[prefix][key];
2429
2430	if (prefix)
2431		/* prefix == 1 || prefix == 2 */
2432		shf_puts(x_mapout(prefix == 1 ? CTRL('[') :
2433		    prefix == 2 ? CTRL('X') : 0), shl_stdout);
2434#ifdef MKSH_SMALL
2435	shprintf("%s = ", x_mapout(key));
2436#else
2437	shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : "");
2438	if (XFUNC_VALUE(f) != XFUNC_ins_string)
2439#endif
2440		shprintf(Tf_sN, x_ftab[XFUNC_VALUE(f)].xf_name);
2441#ifndef MKSH_SMALL
2442	else
2443		shprintf("'%s'\n", x_atab[prefix][key]);
2444#endif
2445}
2446
2447int
2448x_bind(const char *a1, const char *a2,
2449#ifndef MKSH_SMALL
2450    /* bind -m */
2451    bool macro,
2452#endif
2453    /* bind -l */
2454    bool list)
2455{
2456	unsigned char f;
2457	int prefix, key;
2458	char *m1, *m2;
2459#ifndef MKSH_SMALL
2460	char *sp = NULL;
2461	bool hastilde;
2462#endif
2463
2464	if (x_tab == NULL) {
2465		bi_errorf("can't bind, not a tty");
2466		return (1);
2467	}
2468	/* List function names */
2469	if (list) {
2470		for (f = 0; f < NELEM(x_ftab); f++)
2471			if (!(x_ftab[f].xf_flags & XF_NOBIND))
2472				shprintf(Tf_sN, x_ftab[f].xf_name);
2473		return (0);
2474	}
2475	if (a1 == NULL) {
2476		for (prefix = 0; prefix < X_NTABS; prefix++)
2477			for (key = 0; key < X_TABSZ; key++) {
2478				f = XFUNC_VALUE(x_tab[prefix][key]);
2479				if (f == XFUNC_insert || f == XFUNC_error
2480#ifndef MKSH_SMALL
2481				    || (macro && f != XFUNC_ins_string)
2482#endif
2483				    )
2484					continue;
2485				x_print(prefix, key);
2486			}
2487		return (0);
2488	}
2489	m2 = m1 = x_mapin(a1, ATEMP);
2490	prefix = 0;
2491	for (;; m1++) {
2492		key = (unsigned char)*m1;
2493		f = XFUNC_VALUE(x_tab[prefix][key]);
2494		if (f == XFUNC_meta1)
2495			prefix = 1;
2496		else if (f == XFUNC_meta2)
2497			prefix = 2;
2498		else if (f == XFUNC_meta3)
2499			prefix = 3;
2500		else
2501			break;
2502	}
2503	if (*++m1
2504#ifndef MKSH_SMALL
2505	    && ((*m1 != '~') || *(m1 + 1))
2506#endif
2507	    ) {
2508		char msg[256];
2509		const char *c = a1;
2510		m1 = msg;
2511		while (*c && (size_t)(m1 - msg) < sizeof(msg) - 3)
2512			x_mapout2(*c++, &m1);
2513		bi_errorf("too long key sequence: %s", msg);
2514		return (1);
2515	}
2516#ifndef MKSH_SMALL
2517	hastilde = tobool(*m1);
2518#endif
2519	afree(m2, ATEMP);
2520
2521	if (a2 == NULL) {
2522		x_print(prefix, key);
2523		return (0);
2524	}
2525	if (*a2 == 0) {
2526		f = XFUNC_insert;
2527#ifndef MKSH_SMALL
2528	} else if (macro) {
2529		f = XFUNC_ins_string;
2530		sp = x_mapin(a2, AEDIT);
2531#endif
2532	} else {
2533		for (f = 0; f < NELEM(x_ftab); f++)
2534			if (!strcmp(x_ftab[f].xf_name, a2))
2535				break;
2536		if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
2537			bi_errorf("%s: no such function", a2);
2538			return (1);
2539		}
2540	}
2541
2542#ifndef MKSH_SMALL
2543	if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string &&
2544	    x_atab[prefix][key])
2545		afree(x_atab[prefix][key], AEDIT);
2546#endif
2547	x_tab[prefix][key] = f
2548#ifndef MKSH_SMALL
2549	    | (hastilde ? 0x80 : 0)
2550#endif
2551	    ;
2552#ifndef MKSH_SMALL
2553	x_atab[prefix][key] = sp;
2554#endif
2555
2556	/* Track what the user has bound so x_mode(true) won't toast things */
2557	if (f == XFUNC_insert)
2558		x_bound[(prefix * X_TABSZ + key) / 8] &=
2559		    ~(1 << ((prefix * X_TABSZ + key) % 8));
2560	else
2561		x_bound[(prefix * X_TABSZ + key) / 8] |=
2562		    (1 << ((prefix * X_TABSZ + key) % 8));
2563
2564	return (0);
2565}
2566
2567static void
2568bind_if_not_bound(int p, int k, int func)
2569{
2570	int t;
2571
2572	/*
2573	 * Has user already bound this key?
2574	 * If so, do not override it.
2575	 */
2576	t = p * X_TABSZ + k;
2577	if (x_bound[t >> 3] & (1 << (t & 7)))
2578		return;
2579
2580	x_tab[p][k] = func;
2581}
2582
2583static int
2584x_set_mark(int c MKSH_A_UNUSED)
2585{
2586	xmp = xcp;
2587	return (KSTD);
2588}
2589
2590static int
2591x_kill_region(int c MKSH_A_UNUSED)
2592{
2593	size_t rsize;
2594	char *xr;
2595
2596	if (xmp == NULL) {
2597		x_e_putc2(7);
2598		return (KSTD);
2599	}
2600	if (xmp > xcp) {
2601		rsize = xmp - xcp;
2602		xr = xcp;
2603	} else {
2604		rsize = xcp - xmp;
2605		xr = xmp;
2606	}
2607	x_goto(xr);
2608	x_delete(x_nb2nc(rsize), true);
2609	xmp = xr;
2610	return (KSTD);
2611}
2612
2613static int
2614x_xchg_point_mark(int c MKSH_A_UNUSED)
2615{
2616	char *tmp;
2617
2618	if (xmp == NULL) {
2619		x_e_putc2(7);
2620		return (KSTD);
2621	}
2622	tmp = xmp;
2623	xmp = xcp;
2624	x_goto(tmp);
2625	return (KSTD);
2626}
2627
2628static int
2629x_noop(int c MKSH_A_UNUSED)
2630{
2631	return (KSTD);
2632}
2633
2634/*
2635 *	File/command name completion routines
2636 */
2637static int
2638x_comp_comm(int c MKSH_A_UNUSED)
2639{
2640	do_complete(XCF_COMMAND, CT_COMPLETE);
2641	return (KSTD);
2642}
2643
2644static int
2645x_list_comm(int c MKSH_A_UNUSED)
2646{
2647	do_complete(XCF_COMMAND, CT_LIST);
2648	return (KSTD);
2649}
2650
2651static int
2652x_complete(int c MKSH_A_UNUSED)
2653{
2654	do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
2655	return (KSTD);
2656}
2657
2658static int
2659x_enumerate(int c MKSH_A_UNUSED)
2660{
2661	do_complete(XCF_COMMAND_FILE, CT_LIST);
2662	return (KSTD);
2663}
2664
2665static int
2666x_comp_file(int c MKSH_A_UNUSED)
2667{
2668	do_complete(XCF_FILE, CT_COMPLETE);
2669	return (KSTD);
2670}
2671
2672static int
2673x_list_file(int c MKSH_A_UNUSED)
2674{
2675	do_complete(XCF_FILE, CT_LIST);
2676	return (KSTD);
2677}
2678
2679static int
2680x_comp_list(int c MKSH_A_UNUSED)
2681{
2682	do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
2683	return (KSTD);
2684}
2685
2686static int
2687x_expand(int c MKSH_A_UNUSED)
2688{
2689	char **words;
2690	int start, end, nwords, i;
2691
2692	i = XCF_FILE;
2693	nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
2694	    &start, &end, &words);
2695
2696	if (nwords == 0) {
2697		x_e_putc2(7);
2698		return (KSTD);
2699	}
2700	x_goto(xbuf + start);
2701	x_delete(x_nb2nc(end - start), false);
2702
2703	i = 0;
2704	while (i < nwords) {
2705		if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
2706		    (++i < nwords && x_ins(T1space) < 0)) {
2707			x_e_putc2(7);
2708			return (KSTD);
2709		}
2710	}
2711	x_adjust();
2712
2713	return (KSTD);
2714}
2715
2716static void
2717do_complete(
2718    /* XCF_{COMMAND,FILE,COMMAND_FILE} */
2719    int flags,
2720    /* 0 for list, 1 for complete and 2 for complete-list */
2721    Comp_type type)
2722{
2723	char **words;
2724	int start, end, nlen, olen, nwords;
2725	bool completed;
2726
2727	nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
2728	    &start, &end, &words);
2729	/* no match */
2730	if (nwords == 0) {
2731		x_e_putc2(7);
2732		return;
2733	}
2734	if (type == CT_LIST) {
2735		x_print_expansions(nwords, words,
2736		    tobool(flags & XCF_IS_COMMAND));
2737		x_redraw(0);
2738		x_free_words(nwords, words);
2739		return;
2740	}
2741	olen = end - start;
2742	nlen = x_longest_prefix(nwords, words);
2743	if (nwords == 1) {
2744		/*
2745		 * always complete single matches;
2746		 * any expansion of parameter substitution
2747		 * is always at most one result, too
2748		 */
2749		completed = true;
2750	} else {
2751		char *unescaped;
2752
2753		/* make a copy of the original string part */
2754		strndupx(unescaped, xbuf + start, olen, ATEMP);
2755
2756		/* expand any tilde and unescape the string for comparison */
2757		unescaped = x_glob_hlp_tilde_and_rem_qchar(unescaped, true);
2758
2759		/*
2760		 * match iff entire original string is part of the
2761		 * longest prefix, implying the latter is at least
2762		 * the same size (after unescaping)
2763		 */
2764		completed = !strncmp(words[0], unescaped, strlen(unescaped));
2765
2766		afree(unescaped, ATEMP);
2767	}
2768	if (type == CT_COMPLIST && nwords > 1) {
2769		/*
2770		 * print expansions, since we didn't get back
2771		 * just a single match
2772		 */
2773		x_print_expansions(nwords, words,
2774		    tobool(flags & XCF_IS_COMMAND));
2775	}
2776	if (completed) {
2777		/* expand on the command line */
2778		xmp = NULL;
2779		xcp = xbuf + start;
2780		xep -= olen;
2781		memmove(xcp, xcp + olen, xep - xcp + 1);
2782		x_escape(words[0], nlen, x_do_ins);
2783	}
2784	x_adjust();
2785	/*
2786	 * append a space if this is a single non-directory match
2787	 * and not a parameter or homedir substitution
2788	 */
2789	if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1]) &&
2790	    !(flags & XCF_IS_NOSPACE)) {
2791		x_ins(T1space);
2792	}
2793
2794	x_free_words(nwords, words);
2795}
2796
2797/*-
2798 * NAME:
2799 *	x_adjust - redraw the line adjusting starting point etc.
2800 *
2801 * DESCRIPTION:
2802 *	This function is called when we have exceeded the bounds
2803 *	of the edit window. It increments x_adj_done so that
2804 *	functions like x_ins and x_delete know that we have been
2805 *	called and can skip the x_bs() stuff which has already
2806 *	been done by x_redraw.
2807 *
2808 * RETURN VALUE:
2809 *	None
2810 */
2811static void
2812x_adjust(void)
2813{
2814	int col_left, n;
2815
2816	/* flag the fact that we were called */
2817	x_adj_done++;
2818
2819	/*
2820	 * calculate the amount of columns we need to "go back"
2821	 * from xcp to set xbp to (but never < xbuf) to 2/3 of
2822	 * the display width; take care of pwidth though
2823	 */
2824	if ((col_left = xx_cols * 2 / 3) < MIN_EDIT_SPACE) {
2825		/*
2826		 * cowardly refuse to do anything
2827		 * if the available space is too small;
2828		 * fall back to dumb pdksh code
2829		 */
2830		if ((xbp = xcp - (x_displen / 2)) < xbuf)
2831			xbp = xbuf;
2832		/* elide UTF-8 fixup as penalty */
2833		goto x_adjust_out;
2834	}
2835
2836	/* fix up xbp to just past a character end first */
2837	xbp = xcp >= xep ? xep : x_bs0(xcp, xbuf);
2838	/* walk backwards */
2839	while (xbp > xbuf && col_left > 0) {
2840		xbp = x_bs0(xbp - 1, xbuf);
2841		col_left -= (n = x_size2(xbp, NULL));
2842	}
2843	/* check if we hit the prompt */
2844	if (xbp == xbuf && xcp != xbuf && col_left >= 0 && col_left < pwidth) {
2845		/* so we did; force scrolling occurs */
2846		xbp += utf_ptradj(xbp);
2847	}
2848
2849 x_adjust_out:
2850	xlp_valid = false;
2851	x_redraw('\r');
2852	x_flush();
2853}
2854
2855static void
2856x_e_ungetc(int c)
2857{
2858	unget_char = c < 0 ? -1 : (c & 255);
2859}
2860
2861static int
2862x_e_getc(void)
2863{
2864	int c;
2865
2866	if (unget_char >= 0) {
2867		c = unget_char;
2868		unget_char = -1;
2869		return (c);
2870	}
2871
2872#ifndef MKSH_SMALL
2873	if (macroptr) {
2874		if ((c = (unsigned char)*macroptr++))
2875			return (c);
2876		macroptr = NULL;
2877	}
2878#endif
2879
2880	return (x_getc());
2881}
2882
2883static void
2884x_e_putc2(int c)
2885{
2886	int width = 1;
2887
2888	if (c == '\r' || c == '\n')
2889		x_col = 0;
2890	if (x_col < xx_cols) {
2891		if (UTFMODE && (c > 0x7F)) {
2892			char utf_tmp[3];
2893			size_t x;
2894
2895			if (c < 0xA0)
2896				c = 0xFFFD;
2897			x = utf_wctomb(utf_tmp, c);
2898			x_putc(utf_tmp[0]);
2899			if (x > 1)
2900				x_putc(utf_tmp[1]);
2901			if (x > 2)
2902				x_putc(utf_tmp[2]);
2903			width = utf_wcwidth(c);
2904		} else
2905			x_putc(c);
2906		switch (c) {
2907		case 7:
2908			break;
2909		case '\r':
2910		case '\n':
2911			break;
2912		case '\b':
2913			x_col--;
2914			break;
2915		default:
2916			x_col += width;
2917			break;
2918		}
2919	}
2920	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
2921		x_adjust();
2922}
2923
2924static void
2925x_e_putc3(const char **cp)
2926{
2927	int width = 1, c = **(const unsigned char **)cp;
2928
2929	if (c == '\r' || c == '\n')
2930		x_col = 0;
2931	if (x_col < xx_cols) {
2932		if (UTFMODE && (c > 0x7F)) {
2933			char *cp2;
2934
2935			width = utf_widthadj(*cp, (const char **)&cp2);
2936			while (*cp < cp2)
2937				x_putcf(*(*cp)++);
2938		} else {
2939			(*cp)++;
2940			x_putc(c);
2941		}
2942		switch (c) {
2943		case 7:
2944			break;
2945		case '\r':
2946		case '\n':
2947			break;
2948		case '\b':
2949			x_col--;
2950			break;
2951		default:
2952			x_col += width;
2953			break;
2954		}
2955	}
2956	if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
2957		x_adjust();
2958}
2959
2960static void
2961x_e_puts(const char *s)
2962{
2963	int adj = x_adj_done;
2964
2965	while (*s && adj == x_adj_done)
2966		x_e_putc3(&s);
2967}
2968
2969/*-
2970 * NAME:
2971 *	x_set_arg - set an arg value for next function
2972 *
2973 * DESCRIPTION:
2974 *	This is a simple implementation of M-[0-9].
2975 *
2976 * RETURN VALUE:
2977 *	KSTD
2978 */
2979static int
2980x_set_arg(int c)
2981{
2982	unsigned int n = 0;
2983	bool first = true;
2984
2985	/* strip command prefix */
2986	c &= 255;
2987	while (c >= 0 && ksh_isdigit(c)) {
2988		n = n * 10 + ksh_numdig(c);
2989		if (n > LINE)
2990			/* upper bound for repeat */
2991			goto x_set_arg_too_big;
2992		c = x_e_getc();
2993		first = false;
2994	}
2995	if (c < 0 || first) {
2996 x_set_arg_too_big:
2997		x_e_putc2(7);
2998		x_arg = 1;
2999		x_arg_defaulted = true;
3000	} else {
3001		x_e_ungetc(c);
3002		x_arg = n;
3003		x_arg_defaulted = false;
3004	}
3005	return (KSTD);
3006}
3007
3008/* Comment or uncomment the current line. */
3009static int
3010x_comment(int c MKSH_A_UNUSED)
3011{
3012	ssize_t len = xep - xbuf;
3013	int ret = x_do_comment(xbuf, xend - xbuf, &len);
3014
3015	if (ret < 0)
3016		x_e_putc2(7);
3017	else {
3018		x_modified();
3019		xep = xbuf + len;
3020		*xep = '\0';
3021		xcp = xbp = xbuf;
3022		x_redraw('\r');
3023		if (ret > 0)
3024			return (x_newline('\n'));
3025	}
3026	return (KSTD);
3027}
3028
3029static int
3030x_version(int c MKSH_A_UNUSED)
3031{
3032	char *o_xbuf = xbuf, *o_xend = xend;
3033	char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
3034	char *v;
3035
3036	strdupx(v, KSH_VERSION, ATEMP);
3037
3038	xbuf = xbp = xcp = v;
3039	xend = xep = v + strlen(v);
3040	x_redraw('\r');
3041	x_flush();
3042
3043	c = x_e_getc();
3044	xbuf = o_xbuf;
3045	xend = o_xend;
3046	xbp = o_xbp;
3047	xep = o_xep;
3048	xcp = o_xcp;
3049	x_redraw('\r');
3050
3051	if (c < 0)
3052		return (KSTD);
3053	/* This is what AT&T ksh seems to do... Very bizarre */
3054	if (c != ' ')
3055		x_e_ungetc(c);
3056
3057	afree(v, ATEMP);
3058	return (KSTD);
3059}
3060
3061#ifndef MKSH_SMALL
3062static int
3063x_edit_line(int c MKSH_A_UNUSED)
3064{
3065	if (x_arg_defaulted) {
3066		if (xep == xbuf) {
3067			x_e_putc2(7);
3068			return (KSTD);
3069		}
3070		if (modified) {
3071			*xep = '\0';
3072			histsave(&source->line, xbuf, HIST_STORE, true);
3073			x_arg = 0;
3074		} else
3075			x_arg = source->line - (histptr - x_histp);
3076	}
3077	if (x_arg)
3078		shf_snprintf(xbuf, xend - xbuf, Tf_sd,
3079		    "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg);
3080	else
3081		strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf);
3082	xep = xbuf + strlen(xbuf);
3083	return (x_newline('\n'));
3084}
3085#endif
3086
3087/*-
3088 * NAME:
3089 *	x_prev_histword - recover word from prev command
3090 *
3091 * DESCRIPTION:
3092 *	This function recovers the last word from the previous
3093 *	command and inserts it into the current edit line. If a
3094 *	numeric arg is supplied then the n'th word from the
3095 *	start of the previous command is used.
3096 *	As a side effect, trashes the mark in order to achieve
3097 *	being called in a repeatable fashion.
3098 *
3099 *	Bound to M-.
3100 *
3101 * RETURN VALUE:
3102 *	KSTD
3103 */
3104static int
3105x_prev_histword(int c MKSH_A_UNUSED)
3106{
3107	char *rcp, *cp;
3108	char **xhp;
3109	int m = 1;
3110	/* -1 = defaulted; 0+ = argument */
3111	static int last_arg = -1;
3112
3113	if (x_last_command == XFUNC_prev_histword) {
3114		if (xmp && modified > 1)
3115			x_kill_region(0);
3116		if (modified)
3117			m = modified;
3118	} else
3119		last_arg = x_arg_defaulted ? -1 : x_arg;
3120	xhp = histptr - (m - 1);
3121	if ((xhp < history) || !(cp = *xhp)) {
3122		x_e_putc2(7);
3123		x_modified();
3124		return (KSTD);
3125	}
3126	x_set_mark(0);
3127	if ((x_arg = last_arg) == -1) {
3128		/* x_arg_defaulted */
3129
3130		rcp = &cp[strlen(cp) - 1];
3131		/*
3132		 * ignore white-space after the last word
3133		 */
3134		while (rcp > cp && is_cfs(*rcp))
3135			rcp--;
3136		while (rcp > cp && !is_cfs(*rcp))
3137			rcp--;
3138		if (is_cfs(*rcp))
3139			rcp++;
3140		x_ins(rcp);
3141	} else {
3142		/* not x_arg_defaulted */
3143		char ch;
3144
3145		rcp = cp;
3146		/*
3147		 * ignore white-space at start of line
3148		 */
3149		while (*rcp && is_cfs(*rcp))
3150			rcp++;
3151		while (x_arg-- > 0) {
3152			while (*rcp && !is_cfs(*rcp))
3153				rcp++;
3154			while (*rcp && is_cfs(*rcp))
3155				rcp++;
3156		}
3157		cp = rcp;
3158		while (*rcp && !is_cfs(*rcp))
3159			rcp++;
3160		ch = *rcp;
3161		*rcp = '\0';
3162		x_ins(cp);
3163		*rcp = ch;
3164	}
3165	modified = m + 1;
3166	return (KSTD);
3167}
3168
3169#ifndef MKSH_SMALL
3170/* Uppercase N(1) words */
3171static int
3172x_fold_upper(int c MKSH_A_UNUSED)
3173{
3174	return (x_fold_case('U'));
3175}
3176
3177/* Lowercase N(1) words */
3178static int
3179x_fold_lower(int c MKSH_A_UNUSED)
3180{
3181	return (x_fold_case('L'));
3182}
3183
3184/* Titlecase N(1) words */
3185static int
3186x_fold_capitalise(int c MKSH_A_UNUSED)
3187{
3188	return (x_fold_case('C'));
3189}
3190
3191/*-
3192 * NAME:
3193 *	x_fold_case - convert word to UPPER/lower/Capital case
3194 *
3195 * DESCRIPTION:
3196 *	This function is used to implement M-U/M-u, M-L/M-l, M-C/M-c
3197 *	to UPPER CASE, lower case or Capitalise Words.
3198 *
3199 * RETURN VALUE:
3200 *	None
3201 */
3202static int
3203x_fold_case(int c)
3204{
3205	char *cp = xcp;
3206
3207	if (cp == xep) {
3208		x_e_putc2(7);
3209		return (KSTD);
3210	}
3211	while (x_arg--) {
3212		/*
3213		 * first skip over any white-space
3214		 */
3215		while (cp != xep && is_mfs(*cp))
3216			cp++;
3217		/*
3218		 * do the first char on its own since it may be
3219		 * a different action than for the rest.
3220		 */
3221		if (cp != xep) {
3222			if (c == 'L')
3223				/* lowercase */
3224				*cp = ksh_tolower(*cp);
3225			else
3226				/* uppercase, capitalise */
3227				*cp = ksh_toupper(*cp);
3228			cp++;
3229		}
3230		/*
3231		 * now for the rest of the word
3232		 */
3233		while (cp != xep && !is_mfs(*cp)) {
3234			if (c == 'U')
3235				/* uppercase */
3236				*cp = ksh_toupper(*cp);
3237			else
3238				/* lowercase, capitalise */
3239				*cp = ksh_tolower(*cp);
3240			cp++;
3241		}
3242	}
3243	x_goto(cp);
3244	x_modified();
3245	return (KSTD);
3246}
3247#endif
3248
3249/*-
3250 * NAME:
3251 *	x_lastcp - last visible char
3252 *
3253 * DESCRIPTION:
3254 *	This function returns a pointer to that char in the
3255 *	edit buffer that will be the last displayed on the
3256 *	screen.
3257 */
3258static char *
3259x_lastcp(void)
3260{
3261	if (!xlp_valid) {
3262		int i = 0, j;
3263		char *xlp2;
3264
3265		xlp = xbp;
3266		while (xlp < xep) {
3267			j = x_size2(xlp, &xlp2);
3268			if ((i + j) > x_displen)
3269				break;
3270			i += j;
3271			xlp = xlp2;
3272		}
3273	}
3274	xlp_valid = true;
3275	return (xlp);
3276}
3277
3278/* correctly position the cursor on the screen from end of visible area */
3279static void
3280x_lastpos(void)
3281{
3282	char *cp = x_lastcp();
3283
3284	while (cp > xcp)
3285		x_bs3(&cp);
3286}
3287
3288static void
3289x_mode(bool onoff)
3290{
3291	static bool x_cur_mode;
3292
3293	if (x_cur_mode == onoff)
3294		return;
3295	x_cur_mode = onoff;
3296
3297	if (onoff) {
3298		x_mkraw(tty_fd, NULL, false);
3299
3300		edchars.erase = toedchar(tty_state.c_cc[VERASE]);
3301		edchars.kill = toedchar(tty_state.c_cc[VKILL]);
3302		edchars.intr = toedchar(tty_state.c_cc[VINTR]);
3303		edchars.quit = toedchar(tty_state.c_cc[VQUIT]);
3304		edchars.eof = toedchar(tty_state.c_cc[VEOF]);
3305#ifdef VWERASE
3306		edchars.werase = toedchar(tty_state.c_cc[VWERASE]);
3307#else
3308		edchars.werase = 0;
3309#endif
3310
3311		if (!edchars.erase)
3312			edchars.erase = CTRL('H');
3313		if (!edchars.kill)
3314			edchars.kill = CTRL('U');
3315		if (!edchars.intr)
3316			edchars.intr = CTRL('C');
3317		if (!edchars.quit)
3318			edchars.quit = CTRL('\\');
3319		if (!edchars.eof)
3320			edchars.eof = CTRL('D');
3321		if (!edchars.werase)
3322			edchars.werase = CTRL('W');
3323
3324		if (isedchar(edchars.erase)) {
3325			bind_if_not_bound(0, edchars.erase, XFUNC_del_back);
3326			bind_if_not_bound(1, edchars.erase, XFUNC_del_bword);
3327		}
3328		if (isedchar(edchars.kill))
3329			bind_if_not_bound(0, edchars.kill, XFUNC_del_line);
3330		if (isedchar(edchars.werase))
3331			bind_if_not_bound(0, edchars.werase, XFUNC_del_bword);
3332		if (isedchar(edchars.intr))
3333			bind_if_not_bound(0, edchars.intr, XFUNC_abort);
3334		if (isedchar(edchars.quit))
3335			bind_if_not_bound(0, edchars.quit, XFUNC_noop);
3336	} else
3337		mksh_tcset(tty_fd, &tty_state);
3338}
3339
3340#if !MKSH_S_NOVI
3341/* +++ vi editing mode +++ */
3342
3343struct edstate {
3344	char *cbuf;
3345	ssize_t winleft;
3346	ssize_t cbufsize;
3347	ssize_t linelen;
3348	ssize_t cursor;
3349};
3350
3351static int vi_hook(int);
3352static int nextstate(int);
3353static int vi_insert(int);
3354static int vi_cmd(int, const char *);
3355static int domove(int, const char *, int);
3356static int redo_insert(int);
3357static void yank_range(int, int);
3358static int bracktype(int);
3359static void save_cbuf(void);
3360static void restore_cbuf(void);
3361static int putbuf(const char *, ssize_t, bool);
3362static void del_range(int, int);
3363static int findch(int, int, bool, bool) MKSH_A_PURE;
3364static int forwword(int);
3365static int backword(int);
3366static int endword(int);
3367static int Forwword(int);
3368static int Backword(int);
3369static int Endword(int);
3370static int grabhist(int, int);
3371static int grabsearch(int, int, int, const char *);
3372static void redraw_line(bool);
3373static void refresh(int);
3374static int outofwin(void);
3375static void rewindow(void);
3376static int newcol(unsigned char, int);
3377static void display(char *, char *, int);
3378static void ed_mov_opt(int, char *);
3379static int expand_word(int);
3380static int complete_word(int, int);
3381static int print_expansions(struct edstate *, int);
3382#define char_len(c)	((ISCTRL((unsigned char)c) && \
3383			/* but not C1 */ (unsigned char)c < 0x80) ? 2 : 1)
3384static void x_vi_zotc(int);
3385static void vi_error(void);
3386static void vi_macro_reset(void);
3387static int x_vi_putbuf(const char *, size_t);
3388
3389#define vC	0x01		/* a valid command that isn't a vM, vE, vU */
3390#define vM	0x02		/* movement command (h, l, etc.) */
3391#define vE	0x04		/* extended command (c, d, y) */
3392#define vX	0x08		/* long command (@, f, F, t, T, etc.) */
3393#define vU	0x10		/* an UN-undoable command (that isn't a vM) */
3394#define vB	0x20		/* bad command (^@) */
3395#define vZ	0x40		/* repeat count defaults to 0 (not 1) */
3396#define vS	0x80		/* search (/, ?) */
3397
3398#define is_bad(c)	(classify[(c)&0x7f]&vB)
3399#define is_cmd(c)	(classify[(c)&0x7f]&(vM|vE|vC|vU))
3400#define is_move(c)	(classify[(c)&0x7f]&vM)
3401#define is_extend(c)	(classify[(c)&0x7f]&vE)
3402#define is_long(c)	(classify[(c)&0x7f]&vX)
3403#define is_undoable(c)	(!(classify[(c)&0x7f]&vU))
3404#define is_srch(c)	(classify[(c)&0x7f]&vS)
3405#define is_zerocount(c)	(classify[(c)&0x7f]&vZ)
3406
3407static const unsigned char classify[128] = {
3408/*	 0	1	2	3	4	5	6	7	*/
3409/* 0	^@	^A	^B	^C	^D	^E	^F	^G	*/
3410	vB,	0,	0,	0,	0,	vC|vU,	vC|vZ,	0,
3411/* 1	^H	^I	^J	^K	^L	^M	^N	^O	*/
3412	vM,	vC|vZ,	0,	0,	vC|vU,	0,	vC,	0,
3413/* 2	^P	^Q	^R	^S	^T	^U	^V	^W	*/
3414	vC,	0,	vC|vU,	0,	0,	0,	vC,	0,
3415/* 3	^X	^Y	^Z	^[	^\	^]	^^	^_	*/
3416	vC,	0,	0,	vC|vZ,	0,	0,	0,	0,
3417/* 4	<space>	!	"	#	$	%	&	'	*/
3418	vM,	0,	0,	vC,	vM,	vM,	0,	0,
3419/* 5	(	)	*	+	,	-	.	/	*/
3420	0,	0,	vC,	vC,	vM,	vC,	0,	vC|vS,
3421/* 6	0	1	2	3	4	5	6	7	*/
3422	vM,	0,	0,	0,	0,	0,	0,	0,
3423/* 7	8	9	:	;	<	=	>	?	*/
3424	0,	0,	0,	vM,	0,	vC,	0,	vC|vS,
3425/* 8	@	A	B	C	D	E	F	G	*/
3426	vC|vX,	vC,	vM,	vC,	vC,	vM,	vM|vX,	vC|vU|vZ,
3427/* 9	H	I	J	K	L	M	N	O	*/
3428	0,	vC,	0,	0,	0,	0,	vC|vU,	vU,
3429/* A	P	Q	R	S	T	U	V	W	*/
3430	vC,	0,	vC,	vC,	vM|vX,	vC,	0,	vM,
3431/* B	X	Y	Z	[	\	]	^	_	*/
3432	vC,	vC|vU,	0,	vU,	vC|vZ,	0,	vM,	vC|vZ,
3433/* C	`	a	b	c	d	e	f	g	*/
3434	0,	vC,	vM,	vE,	vE,	vM,	vM|vX,	vC|vZ,
3435/* D	h	i	j	k	l	m	n	o	*/
3436	vM,	vC,	vC|vU,	vC|vU,	vM,	0,	vC|vU,	0,
3437/* E	p	q	r	s	t	u	v	w	*/
3438	vC,	0,	vX,	vC,	vM|vX,	vC|vU,	vC|vU|vZ, vM,
3439/* F	x	y	z	{	|	}	~	^?	*/
3440	vC,	vE|vU,	0,	0,	vM|vZ,	0,	vC,	0
3441};
3442
3443#define MAXVICMD	3
3444#define SRCHLEN		40
3445
3446#define INSERT		1
3447#define REPLACE		2
3448
3449#define VNORMAL		0		/* command, insert or replace mode */
3450#define VARG1		1		/* digit prefix (first, eg, 5l) */
3451#define VEXTCMD		2		/* cmd + movement (eg, cl) */
3452#define VARG2		3		/* digit prefix (second, eg, 2c3l) */
3453#define VXCH		4		/* f, F, t, T, @ */
3454#define VFAIL		5		/* bad command */
3455#define VCMD		6		/* single char command (eg, X) */
3456#define VREDO		7		/* . */
3457#define VLIT		8		/* ^V */
3458#define VSEARCH		9		/* /, ? */
3459#define VVERSION	10		/* <ESC> ^V */
3460#define VPREFIX2	11		/* ^[[ and ^[O in insert mode */
3461
3462static struct edstate	*save_edstate(struct edstate *old);
3463static void		restore_edstate(struct edstate *old, struct edstate *news);
3464static void		free_edstate(struct edstate *old);
3465
3466static struct edstate	ebuf;
3467static struct edstate	undobuf;
3468
3469static struct edstate	*es;		/* current editor state */
3470static struct edstate	*undo;
3471
3472static char *ibuf;			/* input buffer */
3473static bool first_insert;		/* set when starting in insert mode */
3474static int saved_inslen;		/* saved inslen for first insert */
3475static int inslen;			/* length of input buffer */
3476static int srchlen;			/* length of current search pattern */
3477static char *ybuf;			/* yank buffer */
3478static int yanklen;			/* length of yank buffer */
3479static int fsavecmd = ' ';		/* last find command */
3480static int fsavech;			/* character to find */
3481static char lastcmd[MAXVICMD];		/* last non-move command */
3482static int lastac;			/* argcnt for lastcmd */
3483static int lastsearch = ' ';		/* last search command */
3484static char srchpat[SRCHLEN];		/* last search pattern */
3485static int insert;			/* <>0 in insert mode */
3486static int hnum;			/* position in history */
3487static int ohnum;			/* history line copied (after mod) */
3488static int hlast;			/* 1 past last position in history */
3489static int state;
3490
3491/*
3492 * Information for keeping track of macros that are being expanded.
3493 * The format of buf is the alias contents followed by a NUL byte followed
3494 * by the name (letter) of the alias. The end of the buffer is marked by
3495 * a double NUL. The name of the alias is stored so recursive macros can
3496 * be detected.
3497 */
3498struct macro_state {
3499	unsigned char *p;	/* current position in buf */
3500	unsigned char *buf;	/* pointer to macro(s) being expanded */
3501	size_t len;		/* how much data in buffer */
3502};
3503static struct macro_state macro;
3504
3505/* last input was expanded */
3506static enum expand_mode {
3507	NONE = 0, EXPAND, COMPLETE, PRINT
3508} expanded;
3509
3510static int
3511x_vi(char *buf)
3512{
3513	int c;
3514
3515	state = VNORMAL;
3516	ohnum = hnum = hlast = histnum(-1) + 1;
3517	insert = INSERT;
3518	saved_inslen = inslen;
3519	first_insert = true;
3520	inslen = 0;
3521	vi_macro_reset();
3522
3523	ebuf.cbuf = buf;
3524	if (undobuf.cbuf == NULL) {
3525		ibuf = alloc(LINE, AEDIT);
3526		ybuf = alloc(LINE, AEDIT);
3527		undobuf.cbuf = alloc(LINE, AEDIT);
3528	}
3529	undobuf.cbufsize = ebuf.cbufsize = LINE;
3530	undobuf.linelen = ebuf.linelen = 0;
3531	undobuf.cursor = ebuf.cursor = 0;
3532	undobuf.winleft = ebuf.winleft = 0;
3533	es = &ebuf;
3534	undo = &undobuf;
3535
3536	x_init_prompt(true);
3537	x_col = pwidth;
3538
3539	if (wbuf_len != x_cols - 3 && ((wbuf_len = x_cols - 3))) {
3540		wbuf[0] = aresize(wbuf[0], wbuf_len, AEDIT);
3541		wbuf[1] = aresize(wbuf[1], wbuf_len, AEDIT);
3542	}
3543	if (wbuf_len) {
3544		memset(wbuf[0], ' ', wbuf_len);
3545		memset(wbuf[1], ' ', wbuf_len);
3546	}
3547	winwidth = x_cols - pwidth - 3;
3548	win = 0;
3549	morec = ' ';
3550	lastref = 1;
3551	holdlen = 0;
3552
3553	editmode = 2;
3554	x_flush();
3555	while (/* CONSTCOND */ 1) {
3556		if (macro.p) {
3557			c = (unsigned char)*macro.p++;
3558			/* end of current macro? */
3559			if (!c) {
3560				/* more macros left to finish? */
3561				if (*macro.p++)
3562					continue;
3563				/* must be the end of all the macros */
3564				vi_macro_reset();
3565				c = x_getc();
3566			}
3567		} else
3568			c = x_getc();
3569
3570		if (c == -1)
3571			break;
3572		if (state != VLIT) {
3573			if (isched(c, edchars.intr) ||
3574			    isched(c, edchars.quit)) {
3575				/* pretend we got an interrupt */
3576				x_vi_zotc(c);
3577				x_flush();
3578				trapsig(isched(c, edchars.intr) ?
3579				    SIGINT : SIGQUIT);
3580				x_mode(false);
3581				unwind(LSHELL);
3582			} else if (isched(c, edchars.eof) &&
3583			    state != VVERSION) {
3584				if (es->linelen == 0) {
3585					x_vi_zotc(c);
3586					c = -1;
3587					break;
3588				}
3589				continue;
3590			}
3591		}
3592		if (vi_hook(c))
3593			break;
3594		x_flush();
3595	}
3596
3597	x_putc('\r');
3598	x_putc('\n');
3599	x_flush();
3600
3601	if (c == -1 || (ssize_t)LINE <= es->linelen)
3602		return (-1);
3603
3604	if (es->cbuf != buf)
3605		memcpy(buf, es->cbuf, es->linelen);
3606
3607	buf[es->linelen++] = '\n';
3608
3609	return (es->linelen);
3610}
3611
3612static int
3613vi_hook(int ch)
3614{
3615	static char curcmd[MAXVICMD], locpat[SRCHLEN];
3616	static int cmdlen, argc1, argc2;
3617
3618	switch (state) {
3619
3620	case VNORMAL:
3621		/* PC scancodes */
3622		if (!ch) switch (cmdlen = 0, (ch = x_getc())) {
3623		case 71: ch = '0'; goto pseudo_vi_command;
3624		case 72: ch = 'k'; goto pseudo_vi_command;
3625		case 73: ch = 'A'; goto vi_xfunc_search_up;
3626		case 75: ch = 'h'; goto pseudo_vi_command;
3627		case 77: ch = 'l'; goto pseudo_vi_command;
3628		case 79: ch = '$'; goto pseudo_vi_command;
3629		case 80: ch = 'j'; goto pseudo_vi_command;
3630		case 83: ch = 'x'; goto pseudo_vi_command;
3631		default: ch = 0; goto vi_insert_failed;
3632		}
3633		if (insert != 0) {
3634			if (ch == CTRL('v')) {
3635				state = VLIT;
3636				ch = '^';
3637			}
3638			switch (vi_insert(ch)) {
3639			case -1:
3640 vi_insert_failed:
3641				vi_error();
3642				state = VNORMAL;
3643				break;
3644			case 0:
3645				if (state == VLIT) {
3646					es->cursor--;
3647					refresh(0);
3648				} else
3649					refresh(insert != 0);
3650				break;
3651			case 1:
3652				return (1);
3653			}
3654		} else {
3655			if (ch == '\r' || ch == '\n')
3656				return (1);
3657			cmdlen = 0;
3658			argc1 = 0;
3659			if (ch >= ord('1') && ch <= ord('9')) {
3660				argc1 = ksh_numdig(ch);
3661				state = VARG1;
3662			} else {
3663 pseudo_vi_command:
3664				curcmd[cmdlen++] = ch;
3665				state = nextstate(ch);
3666				if (state == VSEARCH) {
3667					save_cbuf();
3668					es->cursor = 0;
3669					es->linelen = 0;
3670					if (putbuf(ch == '/' ? "/" : "?", 1,
3671					    false) != 0)
3672						return (-1);
3673					refresh(0);
3674				}
3675				if (state == VVERSION) {
3676					save_cbuf();
3677					es->cursor = 0;
3678					es->linelen = 0;
3679					putbuf(KSH_VERSION,
3680					    strlen(KSH_VERSION), false);
3681					refresh(0);
3682				}
3683			}
3684		}
3685		break;
3686
3687	case VLIT:
3688		if (is_bad(ch)) {
3689			del_range(es->cursor, es->cursor + 1);
3690			vi_error();
3691		} else
3692			es->cbuf[es->cursor++] = ch;
3693		refresh(1);
3694		state = VNORMAL;
3695		break;
3696
3697	case VVERSION:
3698		restore_cbuf();
3699		state = VNORMAL;
3700		refresh(0);
3701		break;
3702
3703	case VARG1:
3704		if (ksh_isdigit(ch))
3705			argc1 = argc1 * 10 + ksh_numdig(ch);
3706		else {
3707			curcmd[cmdlen++] = ch;
3708			state = nextstate(ch);
3709		}
3710		break;
3711
3712	case VEXTCMD:
3713		argc2 = 0;
3714		if (ch >= ord('1') && ch <= ord('9')) {
3715			argc2 = ksh_numdig(ch);
3716			state = VARG2;
3717			return (0);
3718		} else {
3719			curcmd[cmdlen++] = ch;
3720			if (ch == curcmd[0])
3721				state = VCMD;
3722			else if (is_move(ch))
3723				state = nextstate(ch);
3724			else
3725				state = VFAIL;
3726		}
3727		break;
3728
3729	case VARG2:
3730		if (ksh_isdigit(ch))
3731			argc2 = argc2 * 10 + ksh_numdig(ch);
3732		else {
3733			if (argc1 == 0)
3734				argc1 = argc2;
3735			else
3736				argc1 *= argc2;
3737			curcmd[cmdlen++] = ch;
3738			if (ch == curcmd[0])
3739				state = VCMD;
3740			else if (is_move(ch))
3741				state = nextstate(ch);
3742			else
3743				state = VFAIL;
3744		}
3745		break;
3746
3747	case VXCH:
3748		if (ch == CTRL('['))
3749			state = VNORMAL;
3750		else {
3751			curcmd[cmdlen++] = ch;
3752			state = VCMD;
3753		}
3754		break;
3755
3756	case VSEARCH:
3757		if (ch == '\r' || ch == '\n' /*|| ch == CTRL('[')*/ ) {
3758			restore_cbuf();
3759			/* Repeat last search? */
3760			if (srchlen == 0) {
3761				if (!srchpat[0]) {
3762					vi_error();
3763					state = VNORMAL;
3764					refresh(0);
3765					return (0);
3766				}
3767			} else {
3768				locpat[srchlen] = '\0';
3769				memcpy(srchpat, locpat, srchlen + 1);
3770			}
3771			state = VCMD;
3772		} else if (isched(ch, edchars.erase) || ch == CTRL('h')) {
3773			if (srchlen != 0) {
3774				srchlen--;
3775				es->linelen -= char_len(locpat[srchlen]);
3776				es->cursor = es->linelen;
3777				refresh(0);
3778				return (0);
3779			}
3780			restore_cbuf();
3781			state = VNORMAL;
3782			refresh(0);
3783		} else if (isched(ch, edchars.kill)) {
3784			srchlen = 0;
3785			es->linelen = 1;
3786			es->cursor = 1;
3787			refresh(0);
3788			return (0);
3789		} else if (isched(ch, edchars.werase)) {
3790			unsigned int i, n;
3791			struct edstate new_es, *save_es;
3792
3793			new_es.cursor = srchlen;
3794			new_es.cbuf = locpat;
3795
3796			save_es = es;
3797			es = &new_es;
3798			n = backword(1);
3799			es = save_es;
3800
3801			i = (unsigned)srchlen;
3802			while (--i >= n)
3803				es->linelen -= char_len(locpat[i]);
3804			srchlen = (int)n;
3805			es->cursor = es->linelen;
3806			refresh(0);
3807			return (0);
3808		} else {
3809			if (srchlen == SRCHLEN - 1)
3810				vi_error();
3811			else {
3812				locpat[srchlen++] = ch;
3813				if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
3814					if ((size_t)es->linelen + 2 >
3815					    (size_t)es->cbufsize)
3816						vi_error();
3817					es->cbuf[es->linelen++] = '^';
3818					es->cbuf[es->linelen++] = UNCTRL(ch);
3819				} else {
3820					if (es->linelen >= es->cbufsize)
3821						vi_error();
3822					es->cbuf[es->linelen++] = ch;
3823				}
3824				es->cursor = es->linelen;
3825				refresh(0);
3826			}
3827			return (0);
3828		}
3829		break;
3830
3831	case VPREFIX2:
3832 vi_xfunc_search_up:
3833		state = VFAIL;
3834		switch (ch) {
3835		case 'A':
3836			/* the cursor may not be at the BOL */
3837			if (!es->cursor)
3838				break;
3839			/* nor further in the line than we can search for */
3840			if ((size_t)es->cursor >= sizeof(srchpat) - 1)
3841				es->cursor = sizeof(srchpat) - 2;
3842			/* anchor the search pattern */
3843			srchpat[0] = '^';
3844			/* take the current line up to the cursor */
3845			memmove(srchpat + 1, es->cbuf, es->cursor);
3846			srchpat[es->cursor + 1] = '\0';
3847			/* set a magic flag */
3848			argc1 = 2 + (int)es->cursor;
3849			/* and emulate a backwards history search */
3850			lastsearch = '/';
3851			*curcmd = 'n';
3852			goto pseudo_VCMD;
3853		}
3854		break;
3855	}
3856
3857	switch (state) {
3858	case VCMD:
3859 pseudo_VCMD:
3860		state = VNORMAL;
3861		switch (vi_cmd(argc1, curcmd)) {
3862		case -1:
3863			vi_error();
3864			refresh(0);
3865			break;
3866		case 0:
3867			if (insert != 0)
3868				inslen = 0;
3869			refresh(insert != 0);
3870			break;
3871		case 1:
3872			refresh(0);
3873			return (1);
3874		case 2:
3875			/* back from a 'v' command - don't redraw the screen */
3876			return (1);
3877		}
3878		break;
3879
3880	case VREDO:
3881		state = VNORMAL;
3882		if (argc1 != 0)
3883			lastac = argc1;
3884		switch (vi_cmd(lastac, lastcmd)) {
3885		case -1:
3886			vi_error();
3887			refresh(0);
3888			break;
3889		case 0:
3890			if (insert != 0) {
3891				if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
3892				    lastcmd[0] == 'C') {
3893					if (redo_insert(1) != 0)
3894						vi_error();
3895				} else {
3896					if (redo_insert(lastac) != 0)
3897						vi_error();
3898				}
3899			}
3900			refresh(0);
3901			break;
3902		case 1:
3903			refresh(0);
3904			return (1);
3905		case 2:
3906			/* back from a 'v' command - can't happen */
3907			break;
3908		}
3909		break;
3910
3911	case VFAIL:
3912		state = VNORMAL;
3913		vi_error();
3914		break;
3915	}
3916	return (0);
3917}
3918
3919static int
3920nextstate(int ch)
3921{
3922	if (is_extend(ch))
3923		return (VEXTCMD);
3924	else if (is_srch(ch))
3925		return (VSEARCH);
3926	else if (is_long(ch))
3927		return (VXCH);
3928	else if (ch == '.')
3929		return (VREDO);
3930	else if (ch == CTRL('v'))
3931		return (VVERSION);
3932	else if (is_cmd(ch))
3933		return (VCMD);
3934	else
3935		return (VFAIL);
3936}
3937
3938static int
3939vi_insert(int ch)
3940{
3941	int tcursor;
3942
3943	if (isched(ch, edchars.erase) || ch == CTRL('h')) {
3944		if (insert == REPLACE) {
3945			if (es->cursor == undo->cursor) {
3946				vi_error();
3947				return (0);
3948			}
3949			if (inslen > 0)
3950				inslen--;
3951			es->cursor--;
3952			if (es->cursor >= undo->linelen)
3953				es->linelen--;
3954			else
3955				es->cbuf[es->cursor] = undo->cbuf[es->cursor];
3956		} else {
3957			if (es->cursor == 0)
3958				return (0);
3959			if (inslen > 0)
3960				inslen--;
3961			es->cursor--;
3962			es->linelen--;
3963			memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor + 1],
3964			    es->linelen - es->cursor + 1);
3965		}
3966		expanded = NONE;
3967		return (0);
3968	}
3969	if (isched(ch, edchars.kill)) {
3970		if (es->cursor != 0) {
3971			inslen = 0;
3972			memmove(es->cbuf, &es->cbuf[es->cursor],
3973			    es->linelen - es->cursor);
3974			es->linelen -= es->cursor;
3975			es->cursor = 0;
3976		}
3977		expanded = NONE;
3978		return (0);
3979	}
3980	if (isched(ch, edchars.werase)) {
3981		if (es->cursor != 0) {
3982			tcursor = backword(1);
3983			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
3984			    es->linelen - es->cursor);
3985			es->linelen -= es->cursor - tcursor;
3986			if (inslen < es->cursor - tcursor)
3987				inslen = 0;
3988			else
3989				inslen -= es->cursor - tcursor;
3990			es->cursor = tcursor;
3991		}
3992		expanded = NONE;
3993		return (0);
3994	}
3995	/*
3996	 * If any chars are entered before escape, trash the saved insert
3997	 * buffer (if user inserts & deletes char, ibuf gets trashed and
3998	 * we don't want to use it)
3999	 */
4000	if (first_insert && ch != CTRL('['))
4001		saved_inslen = 0;
4002	switch (ch) {
4003	case '\0':
4004		return (-1);
4005
4006	case '\r':
4007	case '\n':
4008		return (1);
4009
4010	case CTRL('['):
4011		expanded = NONE;
4012		if (first_insert) {
4013			first_insert = false;
4014			if (inslen == 0) {
4015				inslen = saved_inslen;
4016				return (redo_insert(0));
4017			}
4018			lastcmd[0] = 'a';
4019			lastac = 1;
4020		}
4021		if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
4022		    lastcmd[0] == 'C')
4023			return (redo_insert(0));
4024		else
4025			return (redo_insert(lastac - 1));
4026
4027	/* { start nonstandard vi commands */
4028	case CTRL('x'):
4029		expand_word(0);
4030		break;
4031
4032	case CTRL('f'):
4033		complete_word(0, 0);
4034		break;
4035
4036	case CTRL('e'):
4037		print_expansions(es, 0);
4038		break;
4039
4040	case CTRL('i'):
4041		if (Flag(FVITABCOMPLETE)) {
4042			complete_word(0, 0);
4043			break;
4044		}
4045		/* FALLTHROUGH */
4046	/* end nonstandard vi commands } */
4047
4048	default:
4049		if (es->linelen >= es->cbufsize - 1)
4050			return (-1);
4051		ibuf[inslen++] = ch;
4052		if (insert == INSERT) {
4053			memmove(&es->cbuf[es->cursor + 1], &es->cbuf[es->cursor],
4054			    es->linelen - es->cursor);
4055			es->linelen++;
4056		}
4057		es->cbuf[es->cursor++] = ch;
4058		if (insert == REPLACE && es->cursor > es->linelen)
4059			es->linelen++;
4060		expanded = NONE;
4061	}
4062	return (0);
4063}
4064
4065static int
4066vi_cmd(int argcnt, const char *cmd)
4067{
4068	int ncursor;
4069	int cur, c1, c2, c3 = 0;
4070	int any;
4071	struct edstate *t;
4072
4073	if (argcnt == 0 && !is_zerocount(*cmd))
4074		argcnt = 1;
4075
4076	if (is_move(*cmd)) {
4077		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
4078			if (cur == es->linelen && cur != 0)
4079				cur--;
4080			es->cursor = cur;
4081		} else
4082			return (-1);
4083	} else {
4084		/* Don't save state in middle of macro.. */
4085		if (is_undoable(*cmd) && !macro.p) {
4086			undo->winleft = es->winleft;
4087			memmove(undo->cbuf, es->cbuf, es->linelen);
4088			undo->linelen = es->linelen;
4089			undo->cursor = es->cursor;
4090			lastac = argcnt;
4091			memmove(lastcmd, cmd, MAXVICMD);
4092		}
4093		switch (*cmd) {
4094
4095		case CTRL('l'):
4096		case CTRL('r'):
4097			redraw_line(true);
4098			break;
4099
4100		case '@':
4101			{
4102				static char alias[] = "_\0";
4103				struct tbl *ap;
4104				size_t olen, nlen;
4105				char *p, *nbuf;
4106
4107				/* lookup letter in alias list... */
4108				alias[1] = cmd[1];
4109				ap = ktsearch(&aliases, alias, hash(alias));
4110				if (!cmd[1] || !ap || !(ap->flag & ISSET))
4111					return (-1);
4112				/* check if this is a recursive call... */
4113				if ((p = (char *)macro.p))
4114					while ((p = strnul(p)) && p[1])
4115						if (*++p == cmd[1])
4116							return (-1);
4117				/* insert alias into macro buffer */
4118				nlen = strlen(ap->val.s) + 1;
4119				olen = !macro.p ? 2 :
4120				    macro.len - (macro.p - macro.buf);
4121				/*
4122				 * at this point, it's fairly reasonable that
4123				 * nlen + olen + 2 doesn't overflow
4124				 */
4125				nbuf = alloc(nlen + 1 + olen, AEDIT);
4126				memcpy(nbuf, ap->val.s, nlen);
4127				nbuf[nlen++] = cmd[1];
4128				if (macro.p) {
4129					memcpy(nbuf + nlen, macro.p, olen);
4130					afree(macro.buf, AEDIT);
4131					nlen += olen;
4132				} else {
4133					nbuf[nlen++] = '\0';
4134					nbuf[nlen++] = '\0';
4135				}
4136				macro.p = macro.buf = (unsigned char *)nbuf;
4137				macro.len = nlen;
4138			}
4139			break;
4140
4141		case 'a':
4142			modified = 1;
4143			hnum = hlast;
4144			if (es->linelen != 0)
4145				es->cursor++;
4146			insert = INSERT;
4147			break;
4148
4149		case 'A':
4150			modified = 1;
4151			hnum = hlast;
4152			del_range(0, 0);
4153			es->cursor = es->linelen;
4154			insert = INSERT;
4155			break;
4156
4157		case 'S':
4158			es->cursor = domove(1, "^", 1);
4159			del_range(es->cursor, es->linelen);
4160			modified = 1;
4161			hnum = hlast;
4162			insert = INSERT;
4163			break;
4164
4165		case 'Y':
4166			cmd = "y$";
4167			/* ahhhhhh... */
4168
4169			/* FALLTHROUGH */
4170		case 'c':
4171		case 'd':
4172		case 'y':
4173			if (*cmd == cmd[1]) {
4174				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
4175				c2 = es->linelen;
4176			} else if (!is_move(cmd[1]))
4177				return (-1);
4178			else {
4179				if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
4180					return (-1);
4181				if (*cmd == 'c' &&
4182				    (cmd[1] == 'w' || cmd[1] == 'W') &&
4183				    !ksh_isspace(es->cbuf[es->cursor])) {
4184					do {
4185						--ncursor;
4186					} while (ksh_isspace(es->cbuf[ncursor]));
4187					ncursor++;
4188				}
4189				if (ncursor > es->cursor) {
4190					c1 = es->cursor;
4191					c2 = ncursor;
4192				} else {
4193					c1 = ncursor;
4194					c2 = es->cursor;
4195					if (cmd[1] == '%')
4196						c2++;
4197				}
4198			}
4199			if (*cmd != 'c' && c1 != c2)
4200				yank_range(c1, c2);
4201			if (*cmd != 'y') {
4202				del_range(c1, c2);
4203				es->cursor = c1;
4204			}
4205			if (*cmd == 'c') {
4206				modified = 1;
4207				hnum = hlast;
4208				insert = INSERT;
4209			}
4210			break;
4211
4212		case 'p':
4213			modified = 1;
4214			hnum = hlast;
4215			if (es->linelen != 0)
4216				es->cursor++;
4217			while (putbuf(ybuf, yanklen, false) == 0 &&
4218			    --argcnt > 0)
4219				;
4220			if (es->cursor != 0)
4221				es->cursor--;
4222			if (argcnt != 0)
4223				return (-1);
4224			break;
4225
4226		case 'P':
4227			modified = 1;
4228			hnum = hlast;
4229			any = 0;
4230			while (putbuf(ybuf, yanklen, false) == 0 &&
4231			    --argcnt > 0)
4232				any = 1;
4233			if (any && es->cursor != 0)
4234				es->cursor--;
4235			if (argcnt != 0)
4236				return (-1);
4237			break;
4238
4239		case 'C':
4240			modified = 1;
4241			hnum = hlast;
4242			del_range(es->cursor, es->linelen);
4243			insert = INSERT;
4244			break;
4245
4246		case 'D':
4247			yank_range(es->cursor, es->linelen);
4248			del_range(es->cursor, es->linelen);
4249			if (es->cursor != 0)
4250				es->cursor--;
4251			break;
4252
4253		case 'g':
4254			if (!argcnt)
4255				argcnt = hlast;
4256			/* FALLTHROUGH */
4257		case 'G':
4258			if (!argcnt)
4259				argcnt = 1;
4260			else
4261				argcnt = hlast - (source->line - argcnt);
4262			if (grabhist(modified, argcnt - 1) < 0)
4263				return (-1);
4264			else {
4265				modified = 0;
4266				hnum = argcnt - 1;
4267			}
4268			break;
4269
4270		case 'i':
4271			modified = 1;
4272			hnum = hlast;
4273			insert = INSERT;
4274			break;
4275
4276		case 'I':
4277			modified = 1;
4278			hnum = hlast;
4279			es->cursor = domove(1, "^", 1);
4280			insert = INSERT;
4281			break;
4282
4283		case 'j':
4284		case '+':
4285		case CTRL('n'):
4286			if (grabhist(modified, hnum + argcnt) < 0)
4287				return (-1);
4288			else {
4289				modified = 0;
4290				hnum += argcnt;
4291			}
4292			break;
4293
4294		case 'k':
4295		case '-':
4296		case CTRL('p'):
4297			if (grabhist(modified, hnum - argcnt) < 0)
4298				return (-1);
4299			else {
4300				modified = 0;
4301				hnum -= argcnt;
4302			}
4303			break;
4304
4305		case 'r':
4306			if (es->linelen == 0)
4307				return (-1);
4308			modified = 1;
4309			hnum = hlast;
4310			if (cmd[1] == 0)
4311				vi_error();
4312			else {
4313				int n;
4314
4315				if (es->cursor + argcnt > es->linelen)
4316					return (-1);
4317				for (n = 0; n < argcnt; ++n)
4318					es->cbuf[es->cursor + n] = cmd[1];
4319				es->cursor += n - 1;
4320			}
4321			break;
4322
4323		case 'R':
4324			modified = 1;
4325			hnum = hlast;
4326			insert = REPLACE;
4327			break;
4328
4329		case 's':
4330			if (es->linelen == 0)
4331				return (-1);
4332			modified = 1;
4333			hnum = hlast;
4334			if (es->cursor + argcnt > es->linelen)
4335				argcnt = es->linelen - es->cursor;
4336			del_range(es->cursor, es->cursor + argcnt);
4337			insert = INSERT;
4338			break;
4339
4340		case 'v':
4341			if (!argcnt) {
4342				if (es->linelen == 0)
4343					return (-1);
4344				if (modified) {
4345					es->cbuf[es->linelen] = '\0';
4346					histsave(&source->line, es->cbuf,
4347					    HIST_STORE, true);
4348				} else
4349					argcnt = source->line + 1 -
4350					    (hlast - hnum);
4351			}
4352			if (argcnt)
4353				shf_snprintf(es->cbuf, es->cbufsize, Tf_sd,
4354				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
4355				    argcnt);
4356			else
4357				strlcpy(es->cbuf,
4358				    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
4359				    es->cbufsize);
4360			es->linelen = strlen(es->cbuf);
4361			return (2);
4362
4363		case 'x':
4364			if (es->linelen == 0)
4365				return (-1);
4366			modified = 1;
4367			hnum = hlast;
4368			if (es->cursor + argcnt > es->linelen)
4369				argcnt = es->linelen - es->cursor;
4370			yank_range(es->cursor, es->cursor + argcnt);
4371			del_range(es->cursor, es->cursor + argcnt);
4372			break;
4373
4374		case 'X':
4375			if (es->cursor > 0) {
4376				modified = 1;
4377				hnum = hlast;
4378				if (es->cursor < argcnt)
4379					argcnt = es->cursor;
4380				yank_range(es->cursor - argcnt, es->cursor);
4381				del_range(es->cursor - argcnt, es->cursor);
4382				es->cursor -= argcnt;
4383			} else
4384				return (-1);
4385			break;
4386
4387		case 'u':
4388			t = es;
4389			es = undo;
4390			undo = t;
4391			break;
4392
4393		case 'U':
4394			if (!modified)
4395				return (-1);
4396			if (grabhist(modified, ohnum) < 0)
4397				return (-1);
4398			modified = 0;
4399			hnum = ohnum;
4400			break;
4401
4402		case '?':
4403			if (hnum == hlast)
4404				hnum = -1;
4405			/* ahhh */
4406
4407			/* FALLTHROUGH */
4408		case '/':
4409			c3 = 1;
4410			srchlen = 0;
4411			lastsearch = *cmd;
4412			/* FALLTHROUGH */
4413		case 'n':
4414		case 'N':
4415			if (lastsearch == ' ')
4416				return (-1);
4417			if (lastsearch == '?')
4418				c1 = 1;
4419			else
4420				c1 = 0;
4421			if (*cmd == 'N')
4422				c1 = !c1;
4423			if ((c2 = grabsearch(modified, hnum,
4424			    c1, srchpat)) < 0) {
4425				if (c3) {
4426					restore_cbuf();
4427					refresh(0);
4428				}
4429				return (-1);
4430			} else {
4431				modified = 0;
4432				hnum = c2;
4433				ohnum = hnum;
4434			}
4435			if (argcnt >= 2) {
4436				/* flag from cursor-up command */
4437				es->cursor = argcnt - 2;
4438				return (0);
4439			}
4440			break;
4441		case '_':
4442			{
4443				bool inspace;
4444				char *p, *sp;
4445
4446				if (histnum(-1) < 0)
4447					return (-1);
4448				p = *histpos();
4449#define issp(c)		(ksh_isspace(c) || (c) == '\n')
4450				if (argcnt) {
4451					while (*p && issp(*p))
4452						p++;
4453					while (*p && --argcnt) {
4454						while (*p && !issp(*p))
4455							p++;
4456						while (*p && issp(*p))
4457							p++;
4458					}
4459					if (!*p)
4460						return (-1);
4461					sp = p;
4462				} else {
4463					sp = p;
4464					inspace = false;
4465					while (*p) {
4466						if (issp(*p))
4467							inspace = true;
4468						else if (inspace) {
4469							inspace = false;
4470							sp = p;
4471						}
4472						p++;
4473					}
4474					p = sp;
4475				}
4476				modified = 1;
4477				hnum = hlast;
4478				if (es->cursor != es->linelen)
4479					es->cursor++;
4480				while (*p && !issp(*p)) {
4481					argcnt++;
4482					p++;
4483				}
4484				if (putbuf(T1space, 1, false) != 0 ||
4485				    putbuf(sp, argcnt, false) != 0) {
4486					if (es->cursor != 0)
4487						es->cursor--;
4488					return (-1);
4489				}
4490				insert = INSERT;
4491			}
4492			break;
4493
4494		case '~':
4495			{
4496				char *p;
4497				int i;
4498
4499				if (es->linelen == 0)
4500					return (-1);
4501				for (i = 0; i < argcnt; i++) {
4502					p = &es->cbuf[es->cursor];
4503					if (ksh_islower(*p)) {
4504						modified = 1;
4505						hnum = hlast;
4506						*p = ksh_toupper(*p);
4507					} else if (ksh_isupper(*p)) {
4508						modified = 1;
4509						hnum = hlast;
4510						*p = ksh_tolower(*p);
4511					}
4512					if (es->cursor < es->linelen - 1)
4513						es->cursor++;
4514				}
4515				break;
4516			}
4517
4518		case '#':
4519			{
4520				int ret = x_do_comment(es->cbuf, es->cbufsize,
4521				    &es->linelen);
4522				if (ret >= 0)
4523					es->cursor = 0;
4524				return (ret);
4525			}
4526
4527		/* AT&T ksh */
4528		case '=':
4529		/* Nonstandard vi/ksh */
4530		case CTRL('e'):
4531			print_expansions(es, 1);
4532			break;
4533
4534
4535		/* Nonstandard vi/ksh */
4536		case CTRL('i'):
4537			if (!Flag(FVITABCOMPLETE))
4538				return (-1);
4539			complete_word(1, argcnt);
4540			break;
4541
4542		/* some annoying AT&T kshs */
4543		case CTRL('['):
4544			if (!Flag(FVIESCCOMPLETE))
4545				return (-1);
4546			/* FALLTHROUGH */
4547		/* AT&T ksh */
4548		case '\\':
4549		/* Nonstandard vi/ksh */
4550		case CTRL('f'):
4551			complete_word(1, argcnt);
4552			break;
4553
4554
4555		/* AT&T ksh */
4556		case '*':
4557		/* Nonstandard vi/ksh */
4558		case CTRL('x'):
4559			expand_word(1);
4560			break;
4561
4562
4563		/* mksh: cursor movement */
4564		case '[':
4565		case 'O':
4566			state = VPREFIX2;
4567			if (es->linelen != 0)
4568				es->cursor++;
4569			insert = INSERT;
4570			return (0);
4571		}
4572		if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
4573			es->cursor--;
4574	}
4575	return (0);
4576}
4577
4578static int
4579domove(int argcnt, const char *cmd, int sub)
4580{
4581	int ncursor = 0, i = 0, t;
4582	unsigned int bcount;
4583
4584	switch (*cmd) {
4585	case 'b':
4586		if (!sub && es->cursor == 0)
4587			return (-1);
4588		ncursor = backword(argcnt);
4589		break;
4590
4591	case 'B':
4592		if (!sub && es->cursor == 0)
4593			return (-1);
4594		ncursor = Backword(argcnt);
4595		break;
4596
4597	case 'e':
4598		if (!sub && es->cursor + 1 >= es->linelen)
4599			return (-1);
4600		ncursor = endword(argcnt);
4601		if (sub && ncursor < es->linelen)
4602			ncursor++;
4603		break;
4604
4605	case 'E':
4606		if (!sub && es->cursor + 1 >= es->linelen)
4607			return (-1);
4608		ncursor = Endword(argcnt);
4609		if (sub && ncursor < es->linelen)
4610			ncursor++;
4611		break;
4612
4613	case 'f':
4614	case 'F':
4615	case 't':
4616	case 'T':
4617		fsavecmd = *cmd;
4618		fsavech = cmd[1];
4619		/* FALLTHROUGH */
4620	case ',':
4621	case ';':
4622		if (fsavecmd == ' ')
4623			return (-1);
4624		i = fsavecmd == 'f' || fsavecmd == 'F';
4625		t = fsavecmd > 'a';
4626		if (*cmd == ',')
4627			t = !t;
4628		if ((ncursor = findch(fsavech, argcnt, tobool(t),
4629		    tobool(i))) < 0)
4630			return (-1);
4631		if (sub && t)
4632			ncursor++;
4633		break;
4634
4635	case 'h':
4636	case CTRL('h'):
4637		if (!sub && es->cursor == 0)
4638			return (-1);
4639		ncursor = es->cursor - argcnt;
4640		if (ncursor < 0)
4641			ncursor = 0;
4642		break;
4643
4644	case ' ':
4645	case 'l':
4646		if (!sub && es->cursor + 1 >= es->linelen)
4647			return (-1);
4648		if (es->linelen != 0) {
4649			ncursor = es->cursor + argcnt;
4650			if (ncursor > es->linelen)
4651				ncursor = es->linelen;
4652		}
4653		break;
4654
4655	case 'w':
4656		if (!sub && es->cursor + 1 >= es->linelen)
4657			return (-1);
4658		ncursor = forwword(argcnt);
4659		break;
4660
4661	case 'W':
4662		if (!sub && es->cursor + 1 >= es->linelen)
4663			return (-1);
4664		ncursor = Forwword(argcnt);
4665		break;
4666
4667	case '0':
4668		ncursor = 0;
4669		break;
4670
4671	case '^':
4672		ncursor = 0;
4673		while (ncursor < es->linelen - 1 &&
4674		    ksh_isspace(es->cbuf[ncursor]))
4675			ncursor++;
4676		break;
4677
4678	case '|':
4679		ncursor = argcnt;
4680		if (ncursor > es->linelen)
4681			ncursor = es->linelen;
4682		if (ncursor)
4683			ncursor--;
4684		break;
4685
4686	case '$':
4687		if (es->linelen != 0)
4688			ncursor = es->linelen;
4689		else
4690			ncursor = 0;
4691		break;
4692
4693	case '%':
4694		ncursor = es->cursor;
4695		while (ncursor < es->linelen &&
4696		    (i = bracktype(es->cbuf[ncursor])) == 0)
4697			ncursor++;
4698		if (ncursor == es->linelen)
4699			return (-1);
4700		bcount = 1;
4701		do {
4702			if (i > 0) {
4703				if (++ncursor >= es->linelen)
4704					return (-1);
4705			} else {
4706				if (--ncursor < 0)
4707					return (-1);
4708			}
4709			t = bracktype(es->cbuf[ncursor]);
4710			if (t == i)
4711				bcount++;
4712			else if (t == -i)
4713				bcount--;
4714		} while (bcount != 0);
4715		if (sub && i > 0)
4716			ncursor++;
4717		break;
4718
4719	default:
4720		return (-1);
4721	}
4722	return (ncursor);
4723}
4724
4725static int
4726redo_insert(int count)
4727{
4728	while (count-- > 0)
4729		if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0)
4730			return (-1);
4731	if (es->cursor > 0)
4732		es->cursor--;
4733	insert = 0;
4734	return (0);
4735}
4736
4737static void
4738yank_range(int a, int b)
4739{
4740	yanklen = b - a;
4741	if (yanklen != 0)
4742		memmove(ybuf, &es->cbuf[a], yanklen);
4743}
4744
4745static int
4746bracktype(int ch)
4747{
4748	switch (ch) {
4749
4750	case '(':
4751		return (1);
4752
4753	case '[':
4754		return (2);
4755
4756	case '{':
4757		return (3);
4758
4759	case ')':
4760		return (-1);
4761
4762	case ']':
4763		return (-2);
4764
4765	case '}':
4766		return (-3);
4767
4768	default:
4769		return (0);
4770	}
4771}
4772
4773/*
4774 *	Non user interface editor routines below here
4775 */
4776
4777static void
4778save_cbuf(void)
4779{
4780	memmove(holdbufp, es->cbuf, es->linelen);
4781	holdlen = es->linelen;
4782	holdbufp[holdlen] = '\0';
4783}
4784
4785static void
4786restore_cbuf(void)
4787{
4788	es->cursor = 0;
4789	es->linelen = holdlen;
4790	memmove(es->cbuf, holdbufp, holdlen);
4791}
4792
4793/* return a new edstate */
4794static struct edstate *
4795save_edstate(struct edstate *old)
4796{
4797	struct edstate *news;
4798
4799	news = alloc(sizeof(struct edstate), AEDIT);
4800	news->cbuf = alloc(old->cbufsize, AEDIT);
4801	memcpy(news->cbuf, old->cbuf, old->linelen);
4802	news->cbufsize = old->cbufsize;
4803	news->linelen = old->linelen;
4804	news->cursor = old->cursor;
4805	news->winleft = old->winleft;
4806	return (news);
4807}
4808
4809static void
4810restore_edstate(struct edstate *news, struct edstate *old)
4811{
4812	memcpy(news->cbuf, old->cbuf, old->linelen);
4813	news->linelen = old->linelen;
4814	news->cursor = old->cursor;
4815	news->winleft = old->winleft;
4816	free_edstate(old);
4817}
4818
4819static void
4820free_edstate(struct edstate *old)
4821{
4822	afree(old->cbuf, AEDIT);
4823	afree(old, AEDIT);
4824}
4825
4826/*
4827 * this is used for calling x_escape() in complete_word()
4828 */
4829static int
4830x_vi_putbuf(const char *s, size_t len)
4831{
4832	return (putbuf(s, len, false));
4833}
4834
4835static int
4836putbuf(const char *buf, ssize_t len, bool repl)
4837{
4838	if (len == 0)
4839		return (0);
4840	if (repl) {
4841		if (es->cursor + len >= es->cbufsize)
4842			return (-1);
4843		if (es->cursor + len > es->linelen)
4844			es->linelen = es->cursor + len;
4845	} else {
4846		if (es->linelen + len >= es->cbufsize)
4847			return (-1);
4848		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
4849		    es->linelen - es->cursor);
4850		es->linelen += len;
4851	}
4852	memmove(&es->cbuf[es->cursor], buf, len);
4853	es->cursor += len;
4854	return (0);
4855}
4856
4857static void
4858del_range(int a, int b)
4859{
4860	if (es->linelen != b)
4861		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
4862	es->linelen -= b - a;
4863}
4864
4865static int
4866findch(int ch, int cnt, bool forw, bool incl)
4867{
4868	int ncursor;
4869
4870	if (es->linelen == 0)
4871		return (-1);
4872	ncursor = es->cursor;
4873	while (cnt--) {
4874		do {
4875			if (forw) {
4876				if (++ncursor == es->linelen)
4877					return (-1);
4878			} else {
4879				if (--ncursor < 0)
4880					return (-1);
4881			}
4882		} while (es->cbuf[ncursor] != ch);
4883	}
4884	if (!incl) {
4885		if (forw)
4886			ncursor--;
4887		else
4888			ncursor++;
4889	}
4890	return (ncursor);
4891}
4892
4893static int
4894forwword(int argcnt)
4895{
4896	int ncursor;
4897
4898	ncursor = es->cursor;
4899	while (ncursor < es->linelen && argcnt--) {
4900		if (ksh_isalnux(es->cbuf[ncursor]))
4901			while (ncursor < es->linelen &&
4902			    ksh_isalnux(es->cbuf[ncursor]))
4903				ncursor++;
4904		else if (!ksh_isspace(es->cbuf[ncursor]))
4905			while (ncursor < es->linelen &&
4906			    !ksh_isalnux(es->cbuf[ncursor]) &&
4907			    !ksh_isspace(es->cbuf[ncursor]))
4908				ncursor++;
4909		while (ncursor < es->linelen &&
4910		    ksh_isspace(es->cbuf[ncursor]))
4911			ncursor++;
4912	}
4913	return (ncursor);
4914}
4915
4916static int
4917backword(int argcnt)
4918{
4919	int ncursor;
4920
4921	ncursor = es->cursor;
4922	while (ncursor > 0 && argcnt--) {
4923		while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor]))
4924			;
4925		if (ncursor > 0) {
4926			if (ksh_isalnux(es->cbuf[ncursor]))
4927				while (--ncursor >= 0 &&
4928				    ksh_isalnux(es->cbuf[ncursor]))
4929					;
4930			else
4931				while (--ncursor >= 0 &&
4932				    !ksh_isalnux(es->cbuf[ncursor]) &&
4933				    !ksh_isspace(es->cbuf[ncursor]))
4934					;
4935			ncursor++;
4936		}
4937	}
4938	return (ncursor);
4939}
4940
4941static int
4942endword(int argcnt)
4943{
4944	int ncursor;
4945
4946	ncursor = es->cursor;
4947	while (ncursor < es->linelen && argcnt--) {
4948		while (++ncursor < es->linelen - 1 &&
4949		    ksh_isspace(es->cbuf[ncursor]))
4950			;
4951		if (ncursor < es->linelen - 1) {
4952			if (ksh_isalnux(es->cbuf[ncursor]))
4953				while (++ncursor < es->linelen &&
4954				    ksh_isalnux(es->cbuf[ncursor]))
4955					;
4956			else
4957				while (++ncursor < es->linelen &&
4958				    !ksh_isalnux(es->cbuf[ncursor]) &&
4959				    !ksh_isspace(es->cbuf[ncursor]))
4960					;
4961			ncursor--;
4962		}
4963	}
4964	return (ncursor);
4965}
4966
4967static int
4968Forwword(int argcnt)
4969{
4970	int ncursor;
4971
4972	ncursor = es->cursor;
4973	while (ncursor < es->linelen && argcnt--) {
4974		while (ncursor < es->linelen &&
4975		    !ksh_isspace(es->cbuf[ncursor]))
4976			ncursor++;
4977		while (ncursor < es->linelen &&
4978		    ksh_isspace(es->cbuf[ncursor]))
4979			ncursor++;
4980	}
4981	return (ncursor);
4982}
4983
4984static int
4985Backword(int argcnt)
4986{
4987	int ncursor;
4988
4989	ncursor = es->cursor;
4990	while (ncursor > 0 && argcnt--) {
4991		while (--ncursor >= 0 && ksh_isspace(es->cbuf[ncursor]))
4992			;
4993		while (ncursor >= 0 && !ksh_isspace(es->cbuf[ncursor]))
4994			ncursor--;
4995		ncursor++;
4996	}
4997	return (ncursor);
4998}
4999
5000static int
5001Endword(int argcnt)
5002{
5003	int ncursor;
5004
5005	ncursor = es->cursor;
5006	while (ncursor < es->linelen - 1 && argcnt--) {
5007		while (++ncursor < es->linelen - 1 &&
5008		    ksh_isspace(es->cbuf[ncursor]))
5009			;
5010		if (ncursor < es->linelen - 1) {
5011			while (++ncursor < es->linelen &&
5012			    !ksh_isspace(es->cbuf[ncursor]))
5013				;
5014			ncursor--;
5015		}
5016	}
5017	return (ncursor);
5018}
5019
5020static int
5021grabhist(int save, int n)
5022{
5023	char *hptr;
5024
5025	if (n < 0 || n > hlast)
5026		return (-1);
5027	if (n == hlast) {
5028		restore_cbuf();
5029		ohnum = n;
5030		return (0);
5031	}
5032	(void)histnum(n);
5033	if ((hptr = *histpos()) == NULL) {
5034		internal_warningf("grabhist: bad history array");
5035		return (-1);
5036	}
5037	if (save)
5038		save_cbuf();
5039	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
5040		es->linelen = es->cbufsize - 1;
5041	memmove(es->cbuf, hptr, es->linelen);
5042	es->cursor = 0;
5043	ohnum = n;
5044	return (0);
5045}
5046
5047static int
5048grabsearch(int save, int start, int fwd, const char *pat)
5049{
5050	char *hptr;
5051	int hist;
5052	bool anchored;
5053
5054	if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1))
5055		return (-1);
5056	if (fwd)
5057		start++;
5058	else
5059		start--;
5060	anchored = *pat == '^' ? (++pat, true) : false;
5061	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
5062		/* (start != 0 && fwd && match(holdbufp, pat) >= 0) */
5063		if (start != 0 && fwd && strcmp(holdbufp, pat) >= 0) {
5064			restore_cbuf();
5065			return (0);
5066		} else
5067			return (-1);
5068	}
5069	if (save)
5070		save_cbuf();
5071	histnum(hist);
5072	hptr = *histpos();
5073	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
5074		es->linelen = es->cbufsize - 1;
5075	memmove(es->cbuf, hptr, es->linelen);
5076	es->cursor = 0;
5077	return (hist);
5078}
5079
5080static void
5081redraw_line(bool newl)
5082{
5083	if (wbuf_len)
5084		memset(wbuf[win], ' ', wbuf_len);
5085	if (newl) {
5086		x_putc('\r');
5087		x_putc('\n');
5088	}
5089	x_pprompt();
5090	morec = ' ';
5091}
5092
5093static void
5094refresh(int leftside)
5095{
5096	if (leftside < 0)
5097		leftside = lastref;
5098	else
5099		lastref = leftside;
5100	if (outofwin())
5101		rewindow();
5102	display(wbuf[1 - win], wbuf[win], leftside);
5103	win = 1 - win;
5104}
5105
5106static int
5107outofwin(void)
5108{
5109	int cur, col;
5110
5111	if (es->cursor < es->winleft)
5112		return (1);
5113	col = 0;
5114	cur = es->winleft;
5115	while (cur < es->cursor)
5116		col = newcol((unsigned char)es->cbuf[cur++], col);
5117	if (col >= winwidth)
5118		return (1);
5119	return (0);
5120}
5121
5122static void
5123rewindow(void)
5124{
5125	int tcur, tcol;
5126	int holdcur1, holdcol1;
5127	int holdcur2, holdcol2;
5128
5129	holdcur1 = holdcur2 = tcur = 0;
5130	holdcol1 = holdcol2 = tcol = 0;
5131	while (tcur < es->cursor) {
5132		if (tcol - holdcol2 > winwidth / 2) {
5133			holdcur1 = holdcur2;
5134			holdcol1 = holdcol2;
5135			holdcur2 = tcur;
5136			holdcol2 = tcol;
5137		}
5138		tcol = newcol((unsigned char)es->cbuf[tcur++], tcol);
5139	}
5140	while (tcol - holdcol1 > winwidth / 2)
5141		holdcol1 = newcol((unsigned char)es->cbuf[holdcur1++],
5142		    holdcol1);
5143	es->winleft = holdcur1;
5144}
5145
5146static int
5147newcol(unsigned char ch, int col)
5148{
5149	if (ch == '\t')
5150		return ((col | 7) + 1);
5151	return (col + char_len(ch));
5152}
5153
5154static void
5155display(char *wb1, char *wb2, int leftside)
5156{
5157	unsigned char ch;
5158	char *twb1, *twb2, mc;
5159	int cur, col, cnt;
5160	int ncol = 0;
5161	int moreright;
5162
5163	col = 0;
5164	cur = es->winleft;
5165	moreright = 0;
5166	twb1 = wb1;
5167	while (col < winwidth && cur < es->linelen) {
5168		if (cur == es->cursor && leftside)
5169			ncol = col + pwidth;
5170		if ((ch = es->cbuf[cur]) == '\t')
5171			do {
5172				*twb1++ = ' ';
5173			} while (++col < winwidth && (col & 7) != 0);
5174		else if (col < winwidth) {
5175			if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
5176				*twb1++ = '^';
5177				if (++col < winwidth) {
5178					*twb1++ = UNCTRL(ch);
5179					col++;
5180				}
5181			} else {
5182				*twb1++ = ch;
5183				col++;
5184			}
5185		}
5186		if (cur == es->cursor && !leftside)
5187			ncol = col + pwidth - 1;
5188		cur++;
5189	}
5190	if (cur == es->cursor)
5191		ncol = col + pwidth;
5192	if (col < winwidth) {
5193		while (col < winwidth) {
5194			*twb1++ = ' ';
5195			col++;
5196		}
5197	} else
5198		moreright++;
5199	*twb1 = ' ';
5200
5201	col = pwidth;
5202	cnt = winwidth;
5203	twb1 = wb1;
5204	twb2 = wb2;
5205	while (cnt--) {
5206		if (*twb1 != *twb2) {
5207			if (x_col != col)
5208				ed_mov_opt(col, wb1);
5209			x_putc(*twb1);
5210			x_col++;
5211		}
5212		twb1++;
5213		twb2++;
5214		col++;
5215	}
5216	if (es->winleft > 0 && moreright)
5217		/*
5218		 * POSIX says to use * for this but that is a globbing
5219		 * character and may confuse people; + is more innocuous
5220		 */
5221		mc = '+';
5222	else if (es->winleft > 0)
5223		mc = '<';
5224	else if (moreright)
5225		mc = '>';
5226	else
5227		mc = ' ';
5228	if (mc != morec) {
5229		ed_mov_opt(pwidth + winwidth + 1, wb1);
5230		x_putc(mc);
5231		x_col++;
5232		morec = mc;
5233	}
5234	if (x_col != ncol)
5235		ed_mov_opt(ncol, wb1);
5236}
5237
5238static void
5239ed_mov_opt(int col, char *wb)
5240{
5241	if (col < x_col) {
5242		if (col + 1 < x_col - col) {
5243			x_putc('\r');
5244			x_pprompt();
5245			while (x_col++ < col)
5246				x_putcf(*wb++);
5247		} else {
5248			while (x_col-- > col)
5249				x_putc('\b');
5250		}
5251	} else {
5252		wb = &wb[x_col - pwidth];
5253		while (x_col++ < col)
5254			x_putcf(*wb++);
5255	}
5256	x_col = col;
5257}
5258
5259
5260/* replace word with all expansions (ie, expand word*) */
5261static int
5262expand_word(int cmd)
5263{
5264	static struct edstate *buf;
5265	int rval = 0, nwords, start, end, i;
5266	char **words;
5267
5268	/* Undo previous expansion */
5269	if (cmd == 0 && expanded == EXPAND && buf) {
5270		restore_edstate(es, buf);
5271		buf = 0;
5272		expanded = NONE;
5273		return (0);
5274	}
5275	if (buf) {
5276		free_edstate(buf);
5277		buf = 0;
5278	}
5279
5280	i = XCF_COMMAND_FILE | XCF_FULLPATH;
5281	nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
5282	    &start, &end, &words);
5283	if (nwords == 0) {
5284		vi_error();
5285		return (-1);
5286	}
5287
5288	buf = save_edstate(es);
5289	expanded = EXPAND;
5290	del_range(start, end);
5291	es->cursor = start;
5292	i = 0;
5293	while (i < nwords) {
5294		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
5295			rval = -1;
5296			break;
5297		}
5298		if (++i < nwords && putbuf(T1space, 1, false) != 0) {
5299			rval = -1;
5300			break;
5301		}
5302	}
5303	i = buf->cursor - end;
5304	if (rval == 0 && i > 0)
5305		es->cursor += i;
5306	modified = 1;
5307	hnum = hlast;
5308	insert = INSERT;
5309	lastac = 0;
5310	refresh(0);
5311	return (rval);
5312}
5313
5314static int
5315complete_word(int cmd, int count)
5316{
5317	static struct edstate *buf;
5318	int rval, nwords, start, end, flags;
5319	size_t match_len;
5320	char **words;
5321	char *match;
5322	bool is_unique;
5323
5324	/* Undo previous completion */
5325	if (cmd == 0 && expanded == COMPLETE && buf) {
5326		print_expansions(buf, 0);
5327		expanded = PRINT;
5328		return (0);
5329	}
5330	if (cmd == 0 && expanded == PRINT && buf) {
5331		restore_edstate(es, buf);
5332		buf = 0;
5333		expanded = NONE;
5334		return (0);
5335	}
5336	if (buf) {
5337		free_edstate(buf);
5338		buf = 0;
5339	}
5340
5341	/*
5342	 * XCF_FULLPATH for count 'cause the menu printed by
5343	 * print_expansions() was done this way.
5344	 */
5345	flags = XCF_COMMAND_FILE;
5346	if (count)
5347		flags |= XCF_FULLPATH;
5348	nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
5349	    &start, &end, &words);
5350	if (nwords == 0) {
5351		vi_error();
5352		return (-1);
5353	}
5354	if (count) {
5355		int i;
5356
5357		count--;
5358		if (count >= nwords) {
5359			vi_error();
5360			x_print_expansions(nwords, words,
5361			    tobool(flags & XCF_IS_COMMAND));
5362			x_free_words(nwords, words);
5363			redraw_line(false);
5364			return (-1);
5365		}
5366		/*
5367		 * Expand the count'th word to its basename
5368		 */
5369		if (flags & XCF_IS_COMMAND) {
5370			match = words[count] +
5371			    x_basename(words[count], NULL);
5372			/* If more than one possible match, use full path */
5373			for (i = 0; i < nwords; i++)
5374				if (i != count &&
5375				    strcmp(words[i] + x_basename(words[i],
5376				    NULL), match) == 0) {
5377					match = words[count];
5378					break;
5379				}
5380		} else
5381			match = words[count];
5382		match_len = strlen(match);
5383		is_unique = true;
5384		/* expanded = PRINT;	next call undo */
5385	} else {
5386		match = words[0];
5387		match_len = x_longest_prefix(nwords, words);
5388		/* next call will list completions */
5389		expanded = COMPLETE;
5390		is_unique = nwords == 1;
5391	}
5392
5393	buf = save_edstate(es);
5394	del_range(start, end);
5395	es->cursor = start;
5396
5397	/*
5398	 * escape all shell-sensitive characters and put the result into
5399	 * command buffer
5400	 */
5401	rval = x_escape(match, match_len, x_vi_putbuf);
5402
5403	if (rval == 0 && is_unique) {
5404		/*
5405		 * If exact match, don't undo. Allows directory completions
5406		 * to be used (ie, complete the next portion of the path).
5407		 */
5408		expanded = NONE;
5409
5410		/*
5411		 * append a space if this is a non-directory match
5412		 * and not a parameter or homedir substitution
5413		 */
5414		if (match_len > 0 && !mksh_cdirsep(match[match_len - 1]) &&
5415		    !(flags & XCF_IS_NOSPACE))
5416			rval = putbuf(T1space, 1, false);
5417	}
5418	x_free_words(nwords, words);
5419
5420	modified = 1;
5421	hnum = hlast;
5422	insert = INSERT;
5423	/* prevent this from being redone... */
5424	lastac = 0;
5425	refresh(0);
5426
5427	return (rval);
5428}
5429
5430static int
5431print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
5432{
5433	int start, end, nwords, i;
5434	char **words;
5435
5436	i = XCF_COMMAND_FILE | XCF_FULLPATH;
5437	nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor,
5438	    &start, &end, &words);
5439	if (nwords == 0) {
5440		vi_error();
5441		return (-1);
5442	}
5443	x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND));
5444	x_free_words(nwords, words);
5445	redraw_line(false);
5446	return (0);
5447}
5448
5449/* Similar to x_zotc(emacs.c), but no tab weirdness */
5450static void
5451x_vi_zotc(int c)
5452{
5453	if (ISCTRL(c)) {
5454		x_putc('^');
5455		c = UNCTRL(c);
5456	}
5457	x_putc(c);
5458}
5459
5460static void
5461vi_error(void)
5462{
5463	/* Beem out of any macros as soon as an error occurs */
5464	vi_macro_reset();
5465	x_putc(7);
5466	x_flush();
5467}
5468
5469static void
5470vi_macro_reset(void)
5471{
5472	if (macro.p) {
5473		afree(macro.buf, AEDIT);
5474		memset((char *)&macro, 0, sizeof(macro));
5475	}
5476}
5477#endif /* !MKSH_S_NOVI */
5478
5479/* called from main.c */
5480void
5481x_init(void)
5482{
5483	int i, j;
5484
5485	/*
5486	 * set edchars to force initial binding, except we need
5487	 * default values for ^W for some deficient systems…
5488	 */
5489	edchars.erase = edchars.kill = edchars.intr = edchars.quit =
5490	    edchars.eof = EDCHAR_INITIAL;
5491	edchars.werase = 027;
5492
5493	/* command line editing specific memory allocation */
5494	ainit(AEDIT);
5495	holdbufp = alloc(LINE, AEDIT);
5496
5497	/* initialise Emacs command line editing mode */
5498	x_nextcmd = -1;
5499
5500	x_tab = alloc2(X_NTABS, sizeof(*x_tab), AEDIT);
5501	for (j = 0; j < X_TABSZ; j++)
5502		x_tab[0][j] = XFUNC_insert;
5503	for (i = 1; i < X_NTABS; i++)
5504		for (j = 0; j < X_TABSZ; j++)
5505			x_tab[i][j] = XFUNC_error;
5506	for (i = 0; i < (int)NELEM(x_defbindings); i++)
5507		x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
5508		    = x_defbindings[i].xdb_func;
5509
5510#ifndef MKSH_SMALL
5511	x_atab = alloc2(X_NTABS, sizeof(*x_atab), AEDIT);
5512	for (i = 1; i < X_NTABS; i++)
5513		for (j = 0; j < X_TABSZ; j++)
5514			x_atab[i][j] = NULL;
5515#endif
5516}
5517
5518#ifdef DEBUG_LEAKS
5519void
5520x_done(void)
5521{
5522	if (x_tab != NULL)
5523		afreeall(AEDIT);
5524}
5525#endif
5526
5527void
5528x_initterm(const char *termtype)
5529{
5530	/* default must be 0 (bss) */
5531	x_term_mode = 0;
5532	/* this is what tmux uses, don't ask me about it */
5533	if (!strcmp(termtype, "screen") || !strncmp(termtype, "screen-", 7))
5534		x_term_mode = 1;
5535}
5536
5537#ifndef MKSH_SMALL
5538static char *
5539x_eval_region_helper(const char *cmd, size_t len)
5540{
5541	char * volatile cp;
5542	newenv(E_ERRH);
5543
5544	if (!kshsetjmp(e->jbuf)) {
5545		char *wds = alloc(len + 3, ATEMP);
5546
5547		wds[0] = FUNSUB;
5548		memcpy(wds + 1, cmd, len);
5549		wds[len + 1] = '\0';
5550		wds[len + 2] = EOS;
5551
5552		cp = evalstr(wds, DOSCALAR);
5553		strdupx(cp, cp, AEDIT);
5554	} else
5555		cp = NULL;
5556	quitenv(NULL);
5557	return (cp);
5558}
5559
5560static int
5561x_eval_region(int c MKSH_A_UNUSED)
5562{
5563	char *evbeg, *evend, *cp;
5564	size_t newlen;
5565	/* only for LINE overflow checking */
5566	size_t restlen;
5567
5568	if (xmp == NULL) {
5569		evbeg = xbuf;
5570		evend = xep;
5571	} else if (xmp < xcp) {
5572		evbeg = xmp;
5573		evend = xcp;
5574	} else {
5575		evbeg = xcp;
5576		evend = xmp;
5577	}
5578
5579	x_e_putc2('\r');
5580	x_clrtoeol(' ', false);
5581	x_flush();
5582	x_mode(false);
5583	cp = x_eval_region_helper(evbeg, evend - evbeg);
5584	x_mode(true);
5585
5586	if (cp == NULL) {
5587		/* command cannot be parsed */
5588 x_eval_region_err:
5589		x_e_putc2(7);
5590		x_redraw('\r');
5591		return (KSTD);
5592	}
5593
5594	newlen = strlen(cp);
5595	restlen = xep - evend;
5596	/* check for LINE overflow, until this is dynamically allocated */
5597	if (evbeg + newlen + restlen >= xend)
5598		goto x_eval_region_err;
5599
5600	xmp = evbeg;
5601	xcp = evbeg + newlen;
5602	xep = xcp + restlen;
5603	memmove(xcp, evend, restlen + /* NUL */ 1);
5604	memcpy(xmp, cp, newlen);
5605	afree(cp, AEDIT);
5606	x_adjust();
5607	x_modified();
5608	return (KSTD);
5609}
5610#endif /* !MKSH_SMALL */
5611#endif /* !MKSH_NO_CMDLINE_EDITING */
5612