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