xref: /openbsd/bin/ksh/vi.c (revision dda28197)
1 /*	$OpenBSD: vi.c,v 1.60 2021/03/12 02:10:25 millert Exp $	*/
2 
3 /*
4  *	vi command editing
5  *	written by John Rochester (initially for nsh)
6  *	bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
7  *
8  */
9 #include "config.h"
10 #ifdef VI
11 
12 #include <sys/stat.h>		/* completion */
13 
14 #include <ctype.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #ifndef SMALL
18 # include <term.h>
19 # include <curses.h>
20 #endif
21 
22 #include "sh.h"
23 #include "edit.h"
24 
25 #undef CTRL
26 #define	CTRL(x)		((x) & 0x1F)	/* ASCII */
27 
28 struct edstate {
29 	char	*cbuf;		/* main buffer to build the command line */
30 	int	cbufsize;	/* number of bytes allocated for cbuf */
31 	int	linelen;	/* current number of bytes in cbuf */
32 	int	winleft;	/* first byte# in cbuf to be displayed */
33 	int	cursor;		/* byte# in cbuf having the cursor */
34 };
35 
36 
37 static int	vi_hook(int);
38 static void	vi_reset(char *, size_t);
39 static int	nextstate(int);
40 static int	vi_insert(int);
41 static int	vi_cmd(int, const char *);
42 static int	domove(int, const char *, int);
43 static int	redo_insert(int);
44 static void	yank_range(int, int);
45 static int	bracktype(int);
46 static void	save_cbuf(void);
47 static void	restore_cbuf(void);
48 static void	edit_reset(char *, size_t);
49 static int	putbuf(const char *, int, int);
50 static void	del_range(int, int);
51 static int	findch(int, int, int, int);
52 static int	forwword(int);
53 static int	backword(int);
54 static int	endword(int);
55 static int	Forwword(int);
56 static int	Backword(int);
57 static int	Endword(int);
58 static int	grabhist(int, int);
59 static int	grabsearch(int, int, int, char *);
60 static void	do_clear_screen(void);
61 static void	redraw_line(int, int);
62 static void	refresh_line(int);
63 static int	outofwin(void);
64 static void	rewindow(void);
65 static int	newcol(int, int);
66 static void	display(char *, char *, int);
67 static void	ed_mov_opt(int, char *);
68 static int	expand_word(int);
69 static int	complete_word(int, int);
70 static int	print_expansions(struct edstate *);
71 static int	char_len(int);
72 static void	x_vi_zotc(int);
73 static void	vi_pprompt(int);
74 static void	vi_error(void);
75 static void	vi_macro_reset(void);
76 static int	x_vi_putbuf(const char *, size_t);
77 static int	isu8cont(unsigned char);
78 
79 #define C_	0x1		/* a valid command that isn't a M_, E_, U_ */
80 #define M_	0x2		/* movement command (h, l, etc.) */
81 #define E_	0x4		/* extended command (c, d, y) */
82 #define X_	0x8		/* long command (@, f, F, t, T, etc.) */
83 #define U_	0x10		/* an UN-undoable command (that isn't a M_) */
84 #define B_	0x20		/* bad command (^@) */
85 #define Z_	0x40		/* repeat count defaults to 0 (not 1) */
86 #define S_	0x80		/* search (/, ?) */
87 
88 #define is_bad(c)	(classify[(c)&0x7f]&B_)
89 #define is_cmd(c)	(classify[(c)&0x7f]&(M_|E_|C_|U_))
90 #define is_move(c)	(classify[(c)&0x7f]&M_)
91 #define is_extend(c)	(classify[(c)&0x7f]&E_)
92 #define is_long(c)	(classify[(c)&0x7f]&X_)
93 #define is_undoable(c)	(!(classify[(c)&0x7f]&U_))
94 #define is_srch(c)	(classify[(c)&0x7f]&S_)
95 #define is_zerocount(c)	(classify[(c)&0x7f]&Z_)
96 
97 const unsigned char	classify[128] = {
98    /*       0       1       2       3       4       5       6       7        */
99    /*   0   ^@     ^A      ^B      ^C      ^D      ^E      ^F      ^G        */
100 	    B_,     0,      0,      0,      0,      C_|U_,  C_|Z_,  0,
101    /*  01   ^H     ^I      ^J      ^K      ^L      ^M      ^N      ^O        */
102 	    M_,     C_|Z_,  0,      0,      C_|U_,  0,      C_,     0,
103    /*  02   ^P     ^Q      ^R      ^S      ^T      ^U      ^V      ^W        */
104 	    C_,     0,      C_|U_,  0,      0,      0,      C_,     0,
105    /*  03   ^X     ^Y      ^Z      ^[      ^\      ^]      ^^      ^_        */
106 	    C_,     0,      0,      C_|Z_,  0,      0,      0,      0,
107    /*  04  <space>  !       "       #       $       %       &       '        */
108 	    M_,     0,      0,      C_,     M_,     M_,     0,      0,
109    /*  05   (       )       *       +       ,       -       .       /        */
110 	    0,      0,      C_,     C_,     M_,     C_,     0,      C_|S_,
111    /*  06   0       1       2       3       4       5       6       7        */
112 	    M_,     0,      0,      0,      0,      0,      0,      0,
113    /*  07   8       9       :       ;       <       =       >       ?        */
114 	    0,      0,      0,      M_,     0,      C_,     0,      C_|S_,
115    /* 010   @       A       B       C       D       E       F       G        */
116 	    C_|X_,  C_,     M_,     C_,     C_,     M_,     M_|X_,  C_|U_|Z_,
117    /* 011   H       I       J       K       L       M       N       O        */
118 	    0,      C_,     0,      0,      0,      0,      C_|U_,  0,
119    /* 012   P       Q       R       S       T       U       V       W        */
120 	    C_,     0,      C_,     C_,     M_|X_,  C_,     0,      M_,
121    /* 013   X       Y       Z       [       \       ]       ^       _        */
122 	    C_,     C_|U_,  0,      0,      C_|Z_,  0,      M_,     C_|Z_,
123    /* 014   `       a       b       c       d       e       f       g        */
124 	    0,      C_,     M_,     E_,     E_,     M_,     M_|X_,  C_|Z_,
125    /* 015   h       i       j       k       l       m       n       o        */
126 	    M_,     C_,     C_|U_,  C_|U_,  M_,     0,      C_|U_,  0,
127    /* 016   p       q       r       s       t       u       v       w        */
128 	    C_,     0,      X_,     C_,     M_|X_,  C_|U_,  C_|U_|Z_,M_,
129    /* 017   x       y       z       {       |       }       ~      ^?        */
130 	    C_,     E_|U_,  0,      0,      M_|Z_,  0,      C_,     0
131 };
132 
133 #define MAXVICMD	3
134 #define SRCHLEN		40
135 
136 #define INSERT		1
137 #define REPLACE		2
138 
139 #define VNORMAL		0		/* command, insert or replace mode */
140 #define VARG1		1		/* digit prefix (first, eg, 5l) */
141 #define VEXTCMD		2		/* cmd + movement (eg, cl) */
142 #define VARG2		3		/* digit prefix (second, eg, 2c3l) */
143 #define VXCH		4		/* f, F, t, T, @ */
144 #define VFAIL		5		/* bad command */
145 #define VCMD		6		/* single char command (eg, X) */
146 #define VREDO		7		/* . */
147 #define VLIT		8		/* ^V */
148 #define VSEARCH		9		/* /, ? */
149 
150 static char		undocbuf[LINE];
151 
152 static struct edstate	*save_edstate(struct edstate *old);
153 static void		restore_edstate(struct edstate *old, struct edstate *new);
154 static void		free_edstate(struct edstate *old);
155 
156 static struct edstate	ebuf;
157 static struct edstate	undobuf = { undocbuf, LINE, 0, 0, 0 };
158 
159 static struct edstate	*es;			/* current editor state */
160 static struct edstate	*undo;
161 
162 static char	ibuf[LINE];		/* input buffer */
163 static int	first_insert;		/* set when starting in insert mode */
164 static int	saved_inslen;		/* saved inslen for first insert */
165 static int	inslen;			/* length of input buffer */
166 static int	srchlen;		/* number of bytes in search pattern */
167 static char	ybuf[LINE];		/* yank buffer */
168 static int	yanklen;		/* length of yank buffer */
169 static int	fsavecmd = ' ';		/* last find command */
170 static int	fsavech;		/* character to find */
171 static char	lastcmd[MAXVICMD];	/* last non-move command */
172 static int	lastac;			/* argcnt for lastcmd */
173 static int	lastsearch = ' ';	/* last search command */
174 static char	srchpat[SRCHLEN];	/* last search pattern */
175 static int	insert;			/* mode: INSERT, REPLACE, or 0 */
176 static int	hnum;			/* position in history */
177 static int	ohnum;			/* history line copied (after mod) */
178 static int	hlast;			/* 1 past last position in history */
179 static int	modified;		/* buffer has been "modified" */
180 static int	state;
181 
182 /* Information for keeping track of macros that are being expanded.
183  * The format of buf is the alias contents followed by a null byte followed
184  * by the name (letter) of the alias.  The end of the buffer is marked by
185  * a double null.  The name of the alias is stored so recursive macros can
186  * be detected.
187  */
188 struct macro_state {
189     unsigned char	*p;	/* current position in buf */
190     unsigned char	*buf;	/* pointer to macro(s) being expanded */
191     int			len;	/* how much data in buffer */
192 };
193 static struct macro_state macro;
194 
195 enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
196 static enum expand_mode expanded = NONE;/* last input was expanded */
197 
198 int
199 x_vi(char *buf, size_t len)
200 {
201 	int	c;
202 
203 	vi_reset(buf, len > LINE ? LINE : len);
204 	vi_pprompt(1);
205 	x_flush();
206 	while (1) {
207 		if (macro.p) {
208 			c = (unsigned char)*macro.p++;
209 			/* end of current macro? */
210 			if (!c) {
211 				/* more macros left to finish? */
212 				if (*macro.p++)
213 					continue;
214 				/* must be the end of all the macros */
215 				vi_macro_reset();
216 				c = x_getc();
217 			}
218 		} else
219 			c = x_getc();
220 
221 		if (c == -1)
222 			break;
223 		if (state != VLIT) {
224 			if (c == edchars.intr || c == edchars.quit) {
225 				/* pretend we got an interrupt */
226 				x_vi_zotc(c);
227 				x_flush();
228 				trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
229 				x_mode(false);
230 				unwind(LSHELL);
231 			} else if (c == edchars.eof) {
232 				if (es->linelen == 0) {
233 					x_vi_zotc(edchars.eof);
234 					c = -1;
235 					break;
236 				}
237 				continue;
238 			}
239 		}
240 		if (vi_hook(c))
241 			break;
242 		x_flush();
243 	}
244 
245 	x_putc('\r'); x_putc('\n'); x_flush();
246 
247 	if (c == -1 || len <= (size_t)es->linelen)
248 		return -1;
249 
250 	if (es->cbuf != buf)
251 		memmove(buf, es->cbuf, es->linelen);
252 
253 	buf[es->linelen++] = '\n';
254 
255 	return es->linelen;
256 }
257 
258 static int
259 vi_hook(int ch)
260 {
261 	static char	curcmd[MAXVICMD], locpat[SRCHLEN];
262 	static int	cmdlen, argc1, argc2;
263 
264 	switch (state) {
265 
266 	case VNORMAL:
267 		if (insert != 0) {
268 			if (ch == CTRL('v')) {
269 				state = VLIT;
270 				ch = '^';
271 			}
272 			switch (vi_insert(ch)) {
273 			case -1:
274 				vi_error();
275 				state = VNORMAL;
276 				break;
277 			case 0:
278 				if (state == VLIT) {
279 					es->cursor--;
280 					refresh_line(0);
281 				} else
282 					refresh_line(insert != 0);
283 				break;
284 			case 1:
285 				return 1;
286 			}
287 		} else {
288 			if (ch == '\r' || ch == '\n')
289 				return 1;
290 			cmdlen = 0;
291 			argc1 = 0;
292 			if (ch >= '1' && ch <= '9') {
293 				argc1 = ch - '0';
294 				state = VARG1;
295 			} else {
296 				curcmd[cmdlen++] = ch;
297 				state = nextstate(ch);
298 				if (state == VSEARCH) {
299 					save_cbuf();
300 					es->cursor = 0;
301 					es->linelen = 0;
302 					if (ch == '/') {
303 						if (putbuf("/", 1, 0) != 0)
304 							return -1;
305 					} else if (putbuf("?", 1, 0) != 0)
306 						return -1;
307 					refresh_line(0);
308 				}
309 			}
310 		}
311 		break;
312 
313 	case VLIT:
314 		if (is_bad(ch)) {
315 			del_range(es->cursor, es->cursor + 1);
316 			vi_error();
317 		} else
318 			es->cbuf[es->cursor++] = ch;
319 		refresh_line(1);
320 		state = VNORMAL;
321 		break;
322 
323 	case VARG1:
324 		if (isdigit(ch))
325 			argc1 = argc1 * 10 + ch - '0';
326 		else {
327 			curcmd[cmdlen++] = ch;
328 			state = nextstate(ch);
329 		}
330 		break;
331 
332 	case VEXTCMD:
333 		argc2 = 0;
334 		if (ch >= '1' && ch <= '9') {
335 			argc2 = ch - '0';
336 			state = VARG2;
337 			return 0;
338 		} else {
339 			curcmd[cmdlen++] = ch;
340 			if (ch == curcmd[0])
341 				state = VCMD;
342 			else if (is_move(ch))
343 				state = nextstate(ch);
344 			else
345 				state = VFAIL;
346 		}
347 		break;
348 
349 	case VARG2:
350 		if (isdigit(ch))
351 			argc2 = argc2 * 10 + ch - '0';
352 		else {
353 			if (argc1 == 0)
354 				argc1 = argc2;
355 			else
356 				argc1 *= argc2;
357 			curcmd[cmdlen++] = ch;
358 			if (ch == curcmd[0])
359 				state = VCMD;
360 			else if (is_move(ch))
361 				state = nextstate(ch);
362 			else
363 				state = VFAIL;
364 		}
365 		break;
366 
367 	case VXCH:
368 		if (ch == CTRL('['))
369 			state = VNORMAL;
370 		else {
371 			curcmd[cmdlen++] = ch;
372 			state = VCMD;
373 		}
374 		break;
375 
376 	case VSEARCH:
377 		if (ch == '\r' || ch == '\n' /*|| ch == CTRL('[')*/ ) {
378 			restore_cbuf();
379 			/* Repeat last search? */
380 			if (srchlen == 0) {
381 				if (!srchpat[0]) {
382 					vi_error();
383 					state = VNORMAL;
384 					refresh_line(0);
385 					return 0;
386 				}
387 			} else {
388 				locpat[srchlen] = '\0';
389 				(void) strlcpy(srchpat, locpat, sizeof srchpat);
390 			}
391 			state = VCMD;
392 		} else if (ch == edchars.erase || ch == CTRL('h')) {
393 			if (srchlen != 0) {
394 				do {
395 					srchlen--;
396 					es->linelen -= char_len(
397 					    (unsigned char)locpat[srchlen]);
398 				} while (srchlen > 0 &&
399 				    isu8cont(locpat[srchlen]));
400 				es->cursor = es->linelen;
401 				refresh_line(0);
402 				return 0;
403 			}
404 			restore_cbuf();
405 			state = VNORMAL;
406 			refresh_line(0);
407 		} else if (ch == edchars.kill) {
408 			srchlen = 0;
409 			es->linelen = 1;
410 			es->cursor = 1;
411 			refresh_line(0);
412 			return 0;
413 		} else if (ch == edchars.werase) {
414 			struct edstate new_es, *save_es;
415 			int i;
416 			int n = srchlen;
417 
418 			new_es.cursor = n;
419 			new_es.cbuf = locpat;
420 
421 			save_es = es;
422 			es = &new_es;
423 			n = backword(1);
424 			es = save_es;
425 
426 			for (i = srchlen; --i >= n; )
427 				es->linelen -= char_len((unsigned char)locpat[i]);
428 			srchlen = n;
429 			es->cursor = es->linelen;
430 			refresh_line(0);
431 			return 0;
432 		} else {
433 			if (srchlen == SRCHLEN - 1)
434 				vi_error();
435 			else {
436 				locpat[srchlen++] = ch;
437 				if ((ch & 0x80) && Flag(FVISHOW8)) {
438 					if (es->linelen + 2 > es->cbufsize)
439 						vi_error();
440 					es->cbuf[es->linelen++] = 'M';
441 					es->cbuf[es->linelen++] = '-';
442 					ch &= 0x7f;
443 				}
444 				if (ch < ' ' || ch == 0x7f) {
445 					if (es->linelen + 2 > es->cbufsize)
446 						vi_error();
447 					es->cbuf[es->linelen++] = '^';
448 					es->cbuf[es->linelen++] = ch ^ '@';
449 				} else {
450 					if (es->linelen >= es->cbufsize)
451 						vi_error();
452 					es->cbuf[es->linelen++] = ch;
453 				}
454 				es->cursor = es->linelen;
455 				refresh_line(0);
456 			}
457 			return 0;
458 		}
459 		break;
460 	}
461 
462 	switch (state) {
463 	case VCMD:
464 		state = VNORMAL;
465 		switch (vi_cmd(argc1, curcmd)) {
466 		case -1:
467 			vi_error();
468 			refresh_line(0);
469 			break;
470 		case 0:
471 			if (insert != 0)
472 				inslen = 0;
473 			refresh_line(insert != 0);
474 			break;
475 		case 1:
476 			refresh_line(0);
477 			return 1;
478 		case 2:
479 			/* back from a 'v' command - don't redraw the screen */
480 			return 1;
481 		}
482 		break;
483 
484 	case VREDO:
485 		state = VNORMAL;
486 		if (argc1 != 0)
487 			lastac = argc1;
488 		switch (vi_cmd(lastac, lastcmd)) {
489 		case -1:
490 			vi_error();
491 			refresh_line(0);
492 			break;
493 		case 0:
494 			if (insert != 0) {
495 				if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
496 				    lastcmd[0] == 'C') {
497 					if (redo_insert(1) != 0)
498 						vi_error();
499 				} else {
500 					if (redo_insert(lastac) != 0)
501 						vi_error();
502 				}
503 			}
504 			refresh_line(0);
505 			break;
506 		case 1:
507 			refresh_line(0);
508 			return 1;
509 		case 2:
510 			/* back from a 'v' command - can't happen */
511 			break;
512 		}
513 		break;
514 
515 	case VFAIL:
516 		state = VNORMAL;
517 		vi_error();
518 		break;
519 	}
520 	return 0;
521 }
522 
523 static void
524 vi_reset(char *buf, size_t len)
525 {
526 	state = VNORMAL;
527 	ohnum = hnum = hlast = histnum(-1) + 1;
528 	insert = INSERT;
529 	saved_inslen = inslen;
530 	first_insert = 1;
531 	inslen = 0;
532 	modified = 1;
533 	vi_macro_reset();
534 	edit_reset(buf, len);
535 }
536 
537 static int
538 nextstate(int ch)
539 {
540 	if (is_extend(ch))
541 		return VEXTCMD;
542 	else if (is_srch(ch))
543 		return VSEARCH;
544 	else if (is_long(ch))
545 		return VXCH;
546 	else if (ch == '.')
547 		return VREDO;
548 	else if (is_cmd(ch))
549 		return VCMD;
550 	else
551 		return VFAIL;
552 }
553 
554 static int
555 vi_insert(int ch)
556 {
557 	int	tcursor;
558 
559 	if (ch == edchars.erase || ch == CTRL('h')) {
560 		if (insert == REPLACE) {
561 			if (es->cursor == undo->cursor) {
562 				vi_error();
563 				return 0;
564 			}
565 		} else {
566 			if (es->cursor == 0) {
567 				/* x_putc(BEL); no annoying bell here */
568 				return 0;
569 			}
570 		}
571 		tcursor = es->cursor - 1;
572 		while(tcursor > 0 && isu8cont(es->cbuf[tcursor]))
573 			tcursor--;
574 		if (insert == INSERT)
575 			memmove(es->cbuf + tcursor, es->cbuf + es->cursor,
576 			    es->linelen - es->cursor);
577 		if (insert == REPLACE && es->cursor < undo->linelen)
578 			memcpy(es->cbuf + tcursor, undo->cbuf + tcursor,
579 			    es->cursor - tcursor);
580 		else
581 			es->linelen -= es->cursor - tcursor;
582 		if (inslen < es->cursor - tcursor)
583 			inslen = 0;
584 		else
585 			inslen -= es->cursor - tcursor;
586 		es->cursor = tcursor;
587 		expanded = NONE;
588 		return 0;
589 	}
590 	if (ch == edchars.kill) {
591 		if (es->cursor != 0) {
592 			inslen = 0;
593 			memmove(es->cbuf, &es->cbuf[es->cursor],
594 			    es->linelen - es->cursor);
595 			es->linelen -= es->cursor;
596 			es->cursor = 0;
597 		}
598 		expanded = NONE;
599 		return 0;
600 	}
601 	if (ch == edchars.werase) {
602 		if (es->cursor != 0) {
603 			tcursor = backword(1);
604 			memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
605 			    es->linelen - es->cursor);
606 			es->linelen -= es->cursor - tcursor;
607 			if (inslen < es->cursor - tcursor)
608 				inslen = 0;
609 			else
610 				inslen -= es->cursor - tcursor;
611 			es->cursor = tcursor;
612 		}
613 		expanded = NONE;
614 		return 0;
615 	}
616 	/* If any chars are entered before escape, trash the saved insert
617 	 * buffer (if user inserts & deletes char, ibuf gets trashed and
618 	 * we don't want to use it)
619 	 */
620 	if (first_insert && ch != CTRL('['))
621 		saved_inslen = 0;
622 	switch (ch) {
623 	case '\0':
624 		return -1;
625 
626 	case '\r':
627 	case '\n':
628 		return 1;
629 
630 	case CTRL('['):
631 		expanded = NONE;
632 		if (first_insert) {
633 			first_insert = 0;
634 			if (inslen == 0) {
635 				inslen = saved_inslen;
636 				return redo_insert(0);
637 			}
638 			lastcmd[0] = 'a';
639 			lastac = 1;
640 		}
641 		if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
642 		    lastcmd[0] == 'C')
643 			return redo_insert(0);
644 		else
645 			return redo_insert(lastac - 1);
646 
647 	/* { Begin nonstandard vi commands */
648 	case CTRL('x'):
649 		expand_word(0);
650 		break;
651 
652 	case CTRL('f'):
653 		complete_word(0, 0);
654 		break;
655 
656 	case CTRL('e'):
657 		print_expansions(es);
658 		break;
659 
660 	case CTRL('l'):
661 		do_clear_screen();
662 		break;
663 
664 	case CTRL('r'):
665 		redraw_line(1, 0);
666 		break;
667 
668 	case CTRL('i'):
669 		if (Flag(FVITABCOMPLETE)) {
670 			complete_word(0, 0);
671 			break;
672 		}
673 		/* FALLTHROUGH */
674 	/* End nonstandard vi commands } */
675 
676 	default:
677 		if (es->linelen >= es->cbufsize - 1)
678 			return -1;
679 		ibuf[inslen++] = ch;
680 		if (insert == INSERT) {
681 			memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor],
682 			    es->linelen - es->cursor);
683 			es->linelen++;
684 		}
685 		es->cbuf[es->cursor++] = ch;
686 		if (insert == REPLACE && es->cursor > es->linelen)
687 			es->linelen++;
688 		expanded = NONE;
689 	}
690 	return 0;
691 }
692 
693 static int
694 vi_cmd(int argcnt, const char *cmd)
695 {
696 	int		ncursor;
697 	int		cur, c1, c2, c3 = 0;
698 	int		any;
699 	struct edstate	*t;
700 
701 	if (argcnt == 0 && !is_zerocount(*cmd))
702 		argcnt = 1;
703 
704 	if (is_move(*cmd)) {
705 		if ((cur = domove(argcnt, cmd, 0)) >= 0) {
706 			if (cur == es->linelen && cur != 0)
707 				while (isu8cont(es->cbuf[--cur]))
708 					continue;
709 			es->cursor = cur;
710 		} else
711 			return -1;
712 	} else {
713 		/* Don't save state in middle of macro.. */
714 		if (is_undoable(*cmd) && !macro.p) {
715 			undo->winleft = es->winleft;
716 			memmove(undo->cbuf, es->cbuf, es->linelen);
717 			undo->linelen = es->linelen;
718 			undo->cursor = es->cursor;
719 			lastac = argcnt;
720 			memmove(lastcmd, cmd, MAXVICMD);
721 		}
722 		switch (*cmd) {
723 
724 		case CTRL('l'):
725 			do_clear_screen();
726 			break;
727 
728 		case CTRL('r'):
729 			redraw_line(1, 0);
730 			break;
731 
732 		case '@':
733 			{
734 				static char alias[] = "_\0";
735 				struct tbl *ap;
736 				int	olen, nlen;
737 				char	*p, *nbuf;
738 
739 				/* lookup letter in alias list... */
740 				alias[1] = cmd[1];
741 				ap = ktsearch(&aliases, alias, hash(alias));
742 				if (!cmd[1] || !ap || !(ap->flag & ISSET))
743 					return -1;
744 				/* check if this is a recursive call... */
745 				if ((p = (char *) macro.p))
746 					while ((p = strchr(p, '\0')) && p[1])
747 						if (*++p == cmd[1])
748 							return -1;
749 				/* insert alias into macro buffer */
750 				nlen = strlen(ap->val.s) + 1;
751 				olen = !macro.p ? 2 :
752 				    macro.len - (macro.p - macro.buf);
753 				nbuf = alloc(nlen + 1 + olen, APERM);
754 				memcpy(nbuf, ap->val.s, nlen);
755 				nbuf[nlen++] = cmd[1];
756 				if (macro.p) {
757 					memcpy(nbuf + nlen, macro.p, olen);
758 					afree(macro.buf, APERM);
759 					nlen += olen;
760 				} else {
761 					nbuf[nlen++] = '\0';
762 					nbuf[nlen++] = '\0';
763 				}
764 				macro.p = macro.buf = (unsigned char *) nbuf;
765 				macro.len = nlen;
766 			}
767 			break;
768 
769 		case 'a':
770 			modified = 1; hnum = hlast;
771 			if (es->linelen != 0)
772 				while (isu8cont(es->cbuf[++es->cursor]))
773 					continue;
774 			insert = INSERT;
775 			break;
776 
777 		case 'A':
778 			modified = 1; hnum = hlast;
779 			del_range(0, 0);
780 			es->cursor = es->linelen;
781 			insert = INSERT;
782 			break;
783 
784 		case 'S':
785 			es->cursor = domove(1, "^", 1);
786 			del_range(es->cursor, es->linelen);
787 			modified = 1; hnum = hlast;
788 			insert = INSERT;
789 			break;
790 
791 		case 'Y':
792 			cmd = "y$";
793 			/* ahhhhhh... */
794 		case 'c':
795 		case 'd':
796 		case 'y':
797 			if (*cmd == cmd[1]) {
798 				c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
799 				c2 = es->linelen;
800 			} else if (!is_move(cmd[1]))
801 				return -1;
802 			else {
803 				if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
804 					return -1;
805 				if (*cmd == 'c' &&
806 				    (cmd[1]=='w' || cmd[1]=='W') &&
807 				    !isspace((unsigned char)es->cbuf[es->cursor])) {
808 					while (isspace(
809 					    (unsigned char)es->cbuf[--ncursor]))
810 						;
811 					ncursor++;
812 				}
813 				if (ncursor > es->cursor) {
814 					c1 = es->cursor;
815 					c2 = ncursor;
816 				} else {
817 					c1 = ncursor;
818 					c2 = es->cursor;
819 					if (cmd[1] == '%')
820 						c2++;
821 				}
822 			}
823 			if (*cmd != 'c' && c1 != c2)
824 				yank_range(c1, c2);
825 			if (*cmd != 'y') {
826 				del_range(c1, c2);
827 				es->cursor = c1;
828 			}
829 			if (*cmd == 'c') {
830 				modified = 1; hnum = hlast;
831 				insert = INSERT;
832 			}
833 			break;
834 
835 		case 'p':
836 			modified = 1; hnum = hlast;
837 			if (es->linelen != 0)
838 				es->cursor++;
839 			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
840 				;
841 			if (es->cursor != 0)
842 				es->cursor--;
843 			if (argcnt != 0)
844 				return -1;
845 			break;
846 
847 		case 'P':
848 			modified = 1; hnum = hlast;
849 			any = 0;
850 			while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
851 				any = 1;
852 			if (any && es->cursor != 0)
853 				es->cursor--;
854 			if (argcnt != 0)
855 				return -1;
856 			break;
857 
858 		case 'C':
859 			modified = 1; hnum = hlast;
860 			del_range(es->cursor, es->linelen);
861 			insert = INSERT;
862 			break;
863 
864 		case 'D':
865 			yank_range(es->cursor, es->linelen);
866 			del_range(es->cursor, es->linelen);
867 			if (es->cursor != 0)
868 				es->cursor--;
869 			break;
870 
871 		case 'g':
872 			if (!argcnt)
873 				argcnt = hlast;
874 			/* FALLTHROUGH */
875 		case 'G':
876 			if (!argcnt)
877 				argcnt = 1;
878 			else
879 				argcnt = hlast - (source->line - argcnt);
880 			if (grabhist(modified, argcnt - 1) < 0)
881 				return -1;
882 			else {
883 				modified = 0;
884 				hnum = argcnt - 1;
885 			}
886 			break;
887 
888 		case 'i':
889 			modified = 1; hnum = hlast;
890 			insert = INSERT;
891 			break;
892 
893 		case 'I':
894 			modified = 1; hnum = hlast;
895 			es->cursor = domove(1, "^", 1);
896 			insert = INSERT;
897 			break;
898 
899 		case 'j':
900 		case '+':
901 		case CTRL('n'):
902 			if (grabhist(modified, hnum + argcnt) < 0)
903 				return -1;
904 			else {
905 				modified = 0;
906 				hnum += argcnt;
907 			}
908 			break;
909 
910 		case 'k':
911 		case '-':
912 		case CTRL('p'):
913 			if (grabhist(modified, hnum - argcnt) < 0)
914 				return -1;
915 			else {
916 				modified = 0;
917 				hnum -= argcnt;
918 			}
919 			break;
920 
921 		case 'r':
922 			if (es->linelen == 0)
923 				return -1;
924 			modified = 1; hnum = hlast;
925 			if (cmd[1] == 0)
926 				vi_error();
927 			else {
928 				c1 = 0;
929 				for (cur = es->cursor;
930 				    cur < es->linelen; cur++) {
931 					if (!isu8cont(es->cbuf[cur]))
932 						c1++;
933 					if (c1 > argcnt)
934 						break;
935 				}
936 				if (argcnt > c1)
937 					return -1;
938 
939 				del_range(es->cursor, cur);
940 				while (argcnt-- > 0)
941 					putbuf(&cmd[1], 1, 0);
942 				while (es->cursor > 0)
943 					if (!isu8cont(es->cbuf[--es->cursor]))
944 						break;
945 				es->cbuf[es->linelen] = '\0';
946 			}
947 			break;
948 
949 		case 'R':
950 			modified = 1; hnum = hlast;
951 			insert = REPLACE;
952 			break;
953 
954 		case 's':
955 			if (es->linelen == 0)
956 				return -1;
957 			modified = 1; hnum = hlast;
958 			for (cur = es->cursor; cur < es->linelen; cur++)
959 				if (!isu8cont(es->cbuf[cur]))
960 					if (argcnt-- == 0)
961 						break;
962 			del_range(es->cursor, cur);
963 			insert = INSERT;
964 			break;
965 
966 		case 'v':
967 			if (es->linelen == 0 && argcnt == 0)
968 				return -1;
969 			if (!argcnt) {
970 				if (modified) {
971 					es->cbuf[es->linelen] = '\0';
972 					source->line++;
973 					histsave(source->line, es->cbuf, 1);
974 				} else
975 					argcnt = source->line + 1
976 						- (hlast - hnum);
977 			}
978 			shf_snprintf(es->cbuf, es->cbufsize,
979 			    argcnt ? "%s %d" : "%s",
980 			    "fc -e ${VISUAL:-${EDITOR:-vi}} --",
981 			    argcnt);
982 			es->linelen = strlen(es->cbuf);
983 			return 2;
984 
985 		case 'x':
986 			if (es->linelen == 0)
987 				return -1;
988 			modified = 1; hnum = hlast;
989 			for (cur = es->cursor; cur < es->linelen; cur++)
990 				if (!isu8cont(es->cbuf[cur]))
991 					if (argcnt-- == 0)
992 						break;
993 			yank_range(es->cursor, cur);
994 			del_range(es->cursor, cur);
995 			break;
996 
997 		case 'X':
998 			if (es->cursor == 0)
999 				return -1;
1000 			modified = 1; hnum = hlast;
1001 			for (cur = es->cursor; cur > 0; cur--)
1002 				if (!isu8cont(es->cbuf[cur]))
1003 					if (argcnt-- == 0)
1004 						break;
1005 			yank_range(cur, es->cursor);
1006 			del_range(cur, es->cursor);
1007 			es->cursor = cur;
1008 			break;
1009 
1010 		case 'u':
1011 			t = es;
1012 			es = undo;
1013 			undo = t;
1014 			break;
1015 
1016 		case 'U':
1017 			if (!modified)
1018 				return -1;
1019 			if (grabhist(modified, ohnum) < 0)
1020 				return -1;
1021 			modified = 0;
1022 			hnum = ohnum;
1023 			break;
1024 
1025 		case '?':
1026 			if (hnum == hlast)
1027 				hnum = -1;
1028 			/* ahhh */
1029 		case '/':
1030 			c3 = 1;
1031 			srchlen = 0;
1032 			lastsearch = *cmd;
1033 			/* FALLTHROUGH */
1034 		case 'n':
1035 		case 'N':
1036 			if (lastsearch == ' ')
1037 				return -1;
1038 			if (lastsearch == '?')
1039 				c1 = 1;
1040 			else
1041 				c1 = 0;
1042 			if (*cmd == 'N')
1043 				c1 = !c1;
1044 			if ((c2 = grabsearch(modified, hnum,
1045 			    c1, srchpat)) < 0) {
1046 				if (c3) {
1047 					restore_cbuf();
1048 					refresh_line(0);
1049 				}
1050 				return -1;
1051 			} else {
1052 				modified = 0;
1053 				hnum = c2;
1054 				ohnum = hnum;
1055 			}
1056 			break;
1057 		case '_': {
1058 			int	inspace;
1059 			char	*p, *sp;
1060 
1061 			if (histnum(-1) < 0)
1062 				return -1;
1063 			p = *histpos();
1064 #define issp(c)		(isspace((unsigned char)(c)) || (c) == '\n')
1065 			if (argcnt) {
1066 				while (*p && issp(*p))
1067 					p++;
1068 				while (*p && --argcnt) {
1069 					while (*p && !issp(*p))
1070 						p++;
1071 					while (*p && issp(*p))
1072 						p++;
1073 				}
1074 				if (!*p)
1075 					return -1;
1076 				sp = p;
1077 			} else {
1078 				sp = p;
1079 				inspace = 0;
1080 				while (*p) {
1081 					if (issp(*p))
1082 						inspace = 1;
1083 					else if (inspace) {
1084 						inspace = 0;
1085 						sp = p;
1086 					}
1087 					p++;
1088 				}
1089 				p = sp;
1090 			}
1091 			modified = 1; hnum = hlast;
1092 			if (es->cursor != es->linelen)
1093 				es->cursor++;
1094 			while (*p && !issp(*p)) {
1095 				argcnt++;
1096 				p++;
1097 			}
1098 			if (putbuf(" ", 1, 0) != 0)
1099 				argcnt = -1;
1100 			else if (putbuf(sp, argcnt, 0) != 0)
1101 				argcnt = -1;
1102 			if (argcnt < 0) {
1103 				if (es->cursor != 0)
1104 					es->cursor--;
1105 				return -1;
1106 			}
1107 			insert = INSERT;
1108 			}
1109 			break;
1110 
1111 		case '~': {
1112 			char	*p;
1113 			unsigned char c;
1114 			int	i;
1115 
1116 			if (es->linelen == 0)
1117 				return -1;
1118 			for (i = 0; i < argcnt; i++) {
1119 				p = &es->cbuf[es->cursor];
1120 				c = (unsigned char)*p;
1121 				if (islower(c)) {
1122 					modified = 1; hnum = hlast;
1123 					*p = toupper(c);
1124 				} else if (isupper(c)) {
1125 					modified = 1; hnum = hlast;
1126 					*p = tolower(c);
1127 				}
1128 				if (es->cursor < es->linelen - 1)
1129 					es->cursor++;
1130 			}
1131 			break;
1132 			}
1133 
1134 		case '#':
1135 		    {
1136 			int ret = x_do_comment(es->cbuf, es->cbufsize,
1137 			    &es->linelen);
1138 			if (ret >= 0)
1139 				es->cursor = 0;
1140 			return ret;
1141 		    }
1142 
1143 		case '=':			/* at&t ksh */
1144 		case CTRL('e'):			/* Nonstandard vi/ksh */
1145 			print_expansions(es);
1146 			break;
1147 
1148 
1149 		case CTRL('i'):			/* Nonstandard vi/ksh */
1150 			if (!Flag(FVITABCOMPLETE))
1151 				return -1;
1152 			complete_word(1, argcnt);
1153 			break;
1154 
1155 		case CTRL('['):			/* some annoying at&t ksh's */
1156 			if (!Flag(FVIESCCOMPLETE))
1157 				return -1;
1158 		case '\\':			/* at&t ksh */
1159 		case CTRL('f'):			/* Nonstandard vi/ksh */
1160 			complete_word(1, argcnt);
1161 			break;
1162 
1163 
1164 		case '*':			/* at&t ksh */
1165 		case CTRL('x'):			/* Nonstandard vi/ksh */
1166 			expand_word(1);
1167 			break;
1168 		}
1169 		if (insert == 0 && es->cursor >= es->linelen)
1170 			while (es->cursor > 0)
1171 				if (!isu8cont(es->cbuf[--es->cursor]))
1172 					break;
1173 	}
1174 	return 0;
1175 }
1176 
1177 static int
1178 domove(int argcnt, const char *cmd, int sub)
1179 {
1180 	int	bcount, i = 0, t;
1181 	int	ncursor = 0;
1182 
1183 	switch (*cmd) {
1184 
1185 	case 'b':
1186 	case 'B':
1187 		if (!sub && es->cursor == 0)
1188 			return -1;
1189 		ncursor = (*cmd == 'b' ? backword : Backword)(argcnt);
1190 		break;
1191 
1192 	case 'e':
1193 	case 'E':
1194 		if (!sub && es->cursor + 1 >= es->linelen)
1195 			return -1;
1196 		ncursor = (*cmd == 'e' ? endword : Endword)(argcnt);
1197 		if (!sub)
1198 			while (isu8cont((unsigned char)es->cbuf[--ncursor]))
1199 				continue;
1200 		break;
1201 
1202 	case 'f':
1203 	case 'F':
1204 	case 't':
1205 	case 'T':
1206 		fsavecmd = *cmd;
1207 		fsavech = cmd[1];
1208 		/* drop through */
1209 
1210 	case ',':
1211 	case ';':
1212 		if (fsavecmd == ' ')
1213 			return -1;
1214 		i = fsavecmd == 'f' || fsavecmd == 'F';
1215 		t = fsavecmd > 'a';
1216 		if (*cmd == ',')
1217 			t = !t;
1218 		if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
1219 			return -1;
1220 		if (sub && t)
1221 			ncursor++;
1222 		break;
1223 
1224 	case 'h':
1225 	case CTRL('h'):
1226 		if (!sub && es->cursor == 0)
1227 			return -1;
1228 		for (ncursor = es->cursor; ncursor > 0; ncursor--)
1229 			if (!isu8cont(es->cbuf[ncursor]))
1230 				if (argcnt-- == 0)
1231 					break;
1232 		break;
1233 
1234 	case ' ':
1235 	case 'l':
1236 		if (!sub && es->cursor + 1 >= es->linelen)
1237 			return -1;
1238 		for (ncursor = es->cursor; ncursor < es->linelen; ncursor++)
1239 			if (!isu8cont(es->cbuf[ncursor]))
1240 				if (argcnt-- == 0)
1241 					break;
1242 		break;
1243 
1244 	case 'w':
1245 	case 'W':
1246 		if (!sub && es->cursor + 1 >= es->linelen)
1247 			return -1;
1248 		ncursor = (*cmd == 'w' ? forwword : Forwword)(argcnt);
1249 		break;
1250 
1251 	case '0':
1252 		ncursor = 0;
1253 		break;
1254 
1255 	case '^':
1256 		ncursor = 0;
1257 		while (ncursor < es->linelen - 1 &&
1258 		    isspace((unsigned char)es->cbuf[ncursor]))
1259 			ncursor++;
1260 		break;
1261 
1262 	case '|':
1263 		ncursor = argcnt;
1264 		if (ncursor > es->linelen)
1265 			ncursor = es->linelen;
1266 		if (ncursor)
1267 			ncursor--;
1268 		while (isu8cont(es->cbuf[ncursor]))
1269 			ncursor--;
1270 		break;
1271 
1272 	case '$':
1273 		ncursor = es->linelen;
1274 		break;
1275 
1276 	case '%':
1277 		ncursor = es->cursor;
1278 		while (ncursor < es->linelen &&
1279 		    (i = bracktype(es->cbuf[ncursor])) == 0)
1280 			ncursor++;
1281 		if (ncursor == es->linelen)
1282 			return -1;
1283 		bcount = 1;
1284 		do {
1285 			if (i > 0) {
1286 				if (++ncursor >= es->linelen)
1287 					return -1;
1288 			} else {
1289 				if (--ncursor < 0)
1290 					return -1;
1291 			}
1292 			t = bracktype(es->cbuf[ncursor]);
1293 			if (t == i)
1294 				bcount++;
1295 			else if (t == -i)
1296 				bcount--;
1297 		} while (bcount != 0);
1298 		if (sub && i > 0)
1299 			ncursor++;
1300 		break;
1301 
1302 	default:
1303 		return -1;
1304 	}
1305 	return ncursor;
1306 }
1307 
1308 static int
1309 redo_insert(int count)
1310 {
1311 	while (count-- > 0)
1312 		if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
1313 			return -1;
1314 	if (es->cursor > 0)
1315 		while (isu8cont(es->cbuf[--es->cursor]))
1316 			continue;
1317 	insert = 0;
1318 	return 0;
1319 }
1320 
1321 static void
1322 yank_range(int a, int b)
1323 {
1324 	yanklen = b - a;
1325 	if (yanklen != 0)
1326 		memmove(ybuf, &es->cbuf[a], yanklen);
1327 }
1328 
1329 static int
1330 bracktype(int ch)
1331 {
1332 	switch (ch) {
1333 
1334 	case '(':
1335 		return 1;
1336 
1337 	case '[':
1338 		return 2;
1339 
1340 	case '{':
1341 		return 3;
1342 
1343 	case ')':
1344 		return -1;
1345 
1346 	case ']':
1347 		return -2;
1348 
1349 	case '}':
1350 		return -3;
1351 
1352 	default:
1353 		return 0;
1354 	}
1355 }
1356 
1357 /*
1358  *	Non user interface editor routines below here
1359  */
1360 
1361 static int	cur_col;		/* current display column */
1362 static int	pwidth;			/* display columns needed for prompt */
1363 static int	prompt_trunc;		/* how much of prompt to truncate */
1364 static int	prompt_skip;		/* how much of prompt to skip */
1365 static int	winwidth;		/* available column positions */
1366 static char	*wbuf[2];		/* current & previous window buffer */
1367 static int	wbuf_len;		/* length of window buffers (x_cols-3)*/
1368 static int	win;			/* number of window buffer in use */
1369 static char	morec;			/* more character at right of window */
1370 static char	holdbuf[LINE];		/* place to hold last edit buffer */
1371 static int	holdlen;		/* length of holdbuf */
1372 
1373 static void
1374 save_cbuf(void)
1375 {
1376 	memmove(holdbuf, es->cbuf, es->linelen);
1377 	holdlen = es->linelen;
1378 	holdbuf[holdlen] = '\0';
1379 }
1380 
1381 static void
1382 restore_cbuf(void)
1383 {
1384 	es->cursor = 0;
1385 	es->linelen = holdlen;
1386 	memmove(es->cbuf, holdbuf, holdlen);
1387 }
1388 
1389 /* return a new edstate */
1390 static struct edstate *
1391 save_edstate(struct edstate *old)
1392 {
1393 	struct edstate *new;
1394 
1395 	new = alloc(sizeof(struct edstate), APERM);
1396 	new->cbuf = alloc(old->cbufsize, APERM);
1397 	memcpy(new->cbuf, old->cbuf, old->linelen);
1398 	new->cbufsize = old->cbufsize;
1399 	new->linelen = old->linelen;
1400 	new->cursor = old->cursor;
1401 	new->winleft = old->winleft;
1402 	return new;
1403 }
1404 
1405 static void
1406 restore_edstate(struct edstate *new, struct edstate *old)
1407 {
1408 	memcpy(new->cbuf, old->cbuf, old->linelen);
1409 	new->linelen = old->linelen;
1410 	new->cursor = old->cursor;
1411 	new->winleft = old->winleft;
1412 	free_edstate(old);
1413 }
1414 
1415 static void
1416 free_edstate(struct edstate *old)
1417 {
1418 	afree(old->cbuf, APERM);
1419 	afree(old, APERM);
1420 }
1421 
1422 
1423 
1424 static void
1425 edit_reset(char *buf, size_t len)
1426 {
1427 	const char *p;
1428 
1429 	es = &ebuf;
1430 	es->cbuf = buf;
1431 	es->cbufsize = len;
1432 	undo = &undobuf;
1433 	undo->cbufsize = len;
1434 
1435 	es->linelen = undo->linelen = 0;
1436 	es->cursor = undo->cursor = 0;
1437 	es->winleft = undo->winleft = 0;
1438 
1439 	cur_col = pwidth = promptlen(prompt, &p);
1440 	prompt_skip = p - prompt;
1441 	if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
1442 		cur_col = x_cols - 3 - MIN_EDIT_SPACE;
1443 		prompt_trunc = pwidth - cur_col;
1444 		pwidth -= prompt_trunc;
1445 	} else
1446 		prompt_trunc = 0;
1447 	if (!wbuf_len || wbuf_len != x_cols - 3) {
1448 		wbuf_len = x_cols - 3;
1449 		wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
1450 		wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
1451 	}
1452 	(void) memset(wbuf[0], ' ', wbuf_len);
1453 	(void) memset(wbuf[1], ' ', wbuf_len);
1454 	winwidth = x_cols - pwidth - 3;
1455 	win = 0;
1456 	morec = ' ';
1457 	holdlen = 0;
1458 }
1459 
1460 /*
1461  * this is used for calling x_escape() in complete_word()
1462  */
1463 static int
1464 x_vi_putbuf(const char *s, size_t len)
1465 {
1466 	return putbuf(s, len, 0);
1467 }
1468 
1469 static int
1470 putbuf(const char *buf, int len, int repl)
1471 {
1472 	if (len == 0)
1473 		return 0;
1474 	if (repl) {
1475 		if (es->cursor + len >= es->cbufsize)
1476 			return -1;
1477 		if (es->cursor + len > es->linelen)
1478 			es->linelen = es->cursor + len;
1479 	} else {
1480 		if (es->linelen + len >= es->cbufsize)
1481 			return -1;
1482 		memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
1483 		    es->linelen - es->cursor);
1484 		es->linelen += len;
1485 	}
1486 	memmove(&es->cbuf[es->cursor], buf, len);
1487 	es->cursor += len;
1488 	return 0;
1489 }
1490 
1491 static void
1492 del_range(int a, int b)
1493 {
1494 	if (es->linelen != b)
1495 		memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
1496 	es->linelen -= b - a;
1497 }
1498 
1499 static int
1500 findch(int ch, int cnt, int forw, int incl)
1501 {
1502 	int	ncursor;
1503 
1504 	if (es->linelen == 0)
1505 		return -1;
1506 	ncursor = es->cursor;
1507 	while (cnt--) {
1508 		do {
1509 			if (forw) {
1510 				if (++ncursor == es->linelen)
1511 					return -1;
1512 			} else {
1513 				if (--ncursor < 0)
1514 					return -1;
1515 			}
1516 		} while (es->cbuf[ncursor] != ch);
1517 	}
1518 	if (!incl) {
1519 		if (forw)
1520 			ncursor--;
1521 		else
1522 			ncursor++;
1523 	}
1524 	return ncursor;
1525 }
1526 
1527 /* Move right one character, and then to the beginning of the next word. */
1528 static int
1529 forwword(int argcnt)
1530 {
1531 	int ncursor, skip_space, want_letnum;
1532 	unsigned char uc;
1533 
1534 	ncursor = es->cursor;
1535 	while (ncursor < es->linelen && argcnt--) {
1536 		skip_space = 0;
1537 		want_letnum = -1;
1538 		ncursor--;
1539 		while (++ncursor < es->linelen) {
1540 			uc = es->cbuf[ncursor];
1541 			if (isspace(uc)) {
1542 				skip_space = 1;
1543 				continue;
1544 			} else if (skip_space)
1545 				break;
1546 			if (uc & 0x80)
1547 				continue;
1548 			if (want_letnum == -1)
1549 				want_letnum = letnum(uc);
1550 			else if (want_letnum != letnum(uc))
1551 				break;
1552 		}
1553 	}
1554 	return ncursor;
1555 }
1556 
1557 /* Move left one character, and then to the beginning of the word. */
1558 static int
1559 backword(int argcnt)
1560 {
1561 	int ncursor, skip_space, want_letnum;
1562 	unsigned char uc;
1563 
1564 	ncursor = es->cursor;
1565 	while (ncursor > 0 && argcnt--) {
1566 		skip_space = 1;
1567 		want_letnum = -1;
1568 		while (ncursor-- > 0) {
1569 			uc = es->cbuf[ncursor];
1570 			if (isspace(uc)) {
1571 				if (skip_space)
1572 					continue;
1573 				else
1574 					break;
1575 			}
1576 			skip_space = 0;
1577 			if (uc & 0x80)
1578 				continue;
1579 			if (want_letnum == -1)
1580 				want_letnum = letnum(uc);
1581 			else if (want_letnum != letnum(uc))
1582 				break;
1583 		}
1584 		ncursor++;
1585 	}
1586 	return ncursor;
1587 }
1588 
1589 /* Move right one character, and then to the byte after the word. */
1590 static int
1591 endword(int argcnt)
1592 {
1593 	int ncursor, skip_space, want_letnum;
1594 	unsigned char uc;
1595 
1596 	ncursor = es->cursor;
1597 	while (ncursor < es->linelen && argcnt--) {
1598 		skip_space = 1;
1599 		want_letnum = -1;
1600 		while (++ncursor < es->linelen) {
1601 			uc = es->cbuf[ncursor];
1602 			if (isspace(uc)) {
1603 				if (skip_space)
1604 					continue;
1605 				else
1606 					break;
1607 			}
1608 			skip_space = 0;
1609 			if (uc & 0x80)
1610 				continue;
1611 			if (want_letnum == -1)
1612 				want_letnum = letnum(uc);
1613 			else if (want_letnum != letnum(uc))
1614 				break;
1615 		}
1616 	}
1617 	return ncursor;
1618 }
1619 
1620 /* Move right one character, and then to the beginning of the next big word. */
1621 static int
1622 Forwword(int argcnt)
1623 {
1624 	int	ncursor;
1625 
1626 	ncursor = es->cursor;
1627 	while (ncursor < es->linelen && argcnt--) {
1628 		while (!isspace((unsigned char)es->cbuf[ncursor]) &&
1629 		    ncursor < es->linelen)
1630 			ncursor++;
1631 		while (isspace((unsigned char)es->cbuf[ncursor]) &&
1632 		    ncursor < es->linelen)
1633 			ncursor++;
1634 	}
1635 	return ncursor;
1636 }
1637 
1638 /* Move left one character, and then to the beginning of the big word. */
1639 static int
1640 Backword(int argcnt)
1641 {
1642 	int	ncursor;
1643 
1644 	ncursor = es->cursor;
1645 	while (ncursor > 0 && argcnt--) {
1646 		while (--ncursor >= 0 &&
1647 		    isspace((unsigned char)es->cbuf[ncursor]))
1648 			;
1649 		while (ncursor >= 0 &&
1650 		    !isspace((unsigned char)es->cbuf[ncursor]))
1651 			ncursor--;
1652 		ncursor++;
1653 	}
1654 	return ncursor;
1655 }
1656 
1657 /* Move right one character, and then to the byte after the big word. */
1658 static int
1659 Endword(int argcnt)
1660 {
1661 	int	ncursor;
1662 
1663 	ncursor = es->cursor;
1664 	while (ncursor < es->linelen && argcnt--) {
1665 		while (++ncursor < es->linelen &&
1666 		    isspace((unsigned char)es->cbuf[ncursor]))
1667 			;
1668 		while (ncursor < es->linelen &&
1669 		    !isspace((unsigned char)es->cbuf[ncursor]))
1670 			ncursor++;
1671 	}
1672 	return ncursor;
1673 }
1674 
1675 static int
1676 grabhist(int save, int n)
1677 {
1678 	char	*hptr;
1679 
1680 	if (n < 0 || n > hlast)
1681 		return -1;
1682 	if (n == hlast) {
1683 		restore_cbuf();
1684 		ohnum = n;
1685 		return 0;
1686 	}
1687 	(void) histnum(n);
1688 	if ((hptr = *histpos()) == NULL) {
1689 		internal_warningf("%s: bad history array", __func__);
1690 		return -1;
1691 	}
1692 	if (save)
1693 		save_cbuf();
1694 	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
1695 		es->linelen = es->cbufsize - 1;
1696 	memmove(es->cbuf, hptr, es->linelen);
1697 	es->cursor = 0;
1698 	ohnum = n;
1699 	return 0;
1700 }
1701 
1702 static int
1703 grabsearch(int save, int start, int fwd, char *pat)
1704 {
1705 	char	*hptr;
1706 	int	hist;
1707 	int	anchored;
1708 
1709 	if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
1710 		return -1;
1711 	if (fwd)
1712 		start++;
1713 	else
1714 		start--;
1715 	anchored = *pat == '^' ? (++pat, 1) : 0;
1716 	if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
1717 		/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
1718 		/* XXX should strcmp be strncmp? */
1719 		if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
1720 			restore_cbuf();
1721 			return 0;
1722 		} else
1723 			return -1;
1724 	}
1725 	if (save)
1726 		save_cbuf();
1727 	histnum(hist);
1728 	hptr = *histpos();
1729 	if ((es->linelen = strlen(hptr)) >= es->cbufsize)
1730 		es->linelen = es->cbufsize - 1;
1731 	memmove(es->cbuf, hptr, es->linelen);
1732 	es->cursor = 0;
1733 	return hist;
1734 }
1735 
1736 static void
1737 do_clear_screen(void)
1738 {
1739 	int neednl = 1;
1740 
1741 #ifndef SMALL
1742 	if (cur_term != NULL && clear_screen != NULL) {
1743 		if (tputs(clear_screen, 1, x_putc) != ERR)
1744 			neednl = 0;
1745 	}
1746 #endif
1747 	/* Only print the full prompt if we cleared the screen. */
1748 	redraw_line(neednl, !neednl);
1749 }
1750 
1751 static void
1752 redraw_line(int neednl, int full)
1753 {
1754 	(void) memset(wbuf[win], ' ', wbuf_len);
1755 	if (neednl) {
1756 		x_putc('\r');
1757 		x_putc('\n');
1758 	}
1759 	vi_pprompt(full);
1760 	cur_col = pwidth;
1761 	morec = ' ';
1762 }
1763 
1764 static void
1765 refresh_line(int leftside)
1766 {
1767 	if (outofwin())
1768 		rewindow();
1769 	display(wbuf[1 - win], wbuf[win], leftside);
1770 	win = 1 - win;
1771 }
1772 
1773 static int
1774 outofwin(void)
1775 {
1776 	int	cur, col;
1777 
1778 	if (es->cursor < es->winleft)
1779 		return 1;
1780 	col = 0;
1781 	cur = es->winleft;
1782 	while (cur < es->cursor)
1783 		col = newcol((unsigned char) es->cbuf[cur++], col);
1784 	if (col >= winwidth)
1785 		return 1;
1786 	return 0;
1787 }
1788 
1789 static void
1790 rewindow(void)
1791 {
1792 	int	tcur, tcol;
1793 	int	holdcur1, holdcol1;
1794 	int	holdcur2, holdcol2;
1795 
1796 	holdcur1 = holdcur2 = tcur = 0;
1797 	holdcol1 = holdcol2 = tcol = 0;
1798 	while (tcur < es->cursor) {
1799 		if (tcol - holdcol2 > winwidth / 2) {
1800 			holdcur1 = holdcur2;
1801 			holdcol1 = holdcol2;
1802 			holdcur2 = tcur;
1803 			holdcol2 = tcol;
1804 		}
1805 		tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
1806 	}
1807 	while (tcol - holdcol1 > winwidth / 2)
1808 		holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
1809 		    holdcol1);
1810 	es->winleft = holdcur1;
1811 }
1812 
1813 /* Printing the byte ch at display column col moves to which column? */
1814 static int
1815 newcol(int ch, int col)
1816 {
1817 	if (ch == '\t')
1818 		return (col | 7) + 1;
1819 	if (isu8cont(ch))
1820 		return col;
1821 	return col + char_len(ch);
1822 }
1823 
1824 /* Display wb1 assuming that wb2 is currently displayed. */
1825 static void
1826 display(char *wb1, char *wb2, int leftside)
1827 {
1828 	char	*twb1;	/* pointer into the buffer to display */
1829 	char	*twb2;	/* pointer into the previous display buffer */
1830 	static int lastb = -1; /* last byte# written from wb1, if UTF-8 */
1831 	int	 cur;	/* byte# in the main command line buffer */
1832 	int	 col;	/* display column loop variable */
1833 	int	 ncol;	/* display column of the cursor */
1834 	int	 cnt;	/* remaining display columns to fill */
1835 	int	 moreright;
1836 	char	 mc;	/* new "more character" at the right of window */
1837 	unsigned char ch;
1838 
1839 	/*
1840 	 * Fill the current display buffer with data from cbuf.
1841 	 * In this first loop, col does not include the prompt.
1842 	 */
1843 
1844 	ncol = col = 0;
1845 	cur = es->winleft;
1846 	moreright = 0;
1847 	twb1 = wb1;
1848 	while (col < winwidth && cur < es->linelen) {
1849 		if (cur == es->cursor && leftside)
1850 			ncol = col + pwidth;
1851 		if ((ch = es->cbuf[cur]) == '\t') {
1852 			do {
1853 				*twb1++ = ' ';
1854 			} while (++col < winwidth && (col & 7) != 0);
1855 		} else {
1856 			if ((ch & 0x80) && Flag(FVISHOW8)) {
1857 				*twb1++ = 'M';
1858 				if (++col < winwidth) {
1859 					*twb1++ = '-';
1860 					col++;
1861 				}
1862 				ch &= 0x7f;
1863 			}
1864 			if (col < winwidth) {
1865 				if (ch < ' ' || ch == 0x7f) {
1866 					*twb1++ = '^';
1867 					if (++col < winwidth) {
1868 						*twb1++ = ch ^ '@';
1869 						col++;
1870 					}
1871 				} else {
1872 					*twb1++ = ch;
1873 					if (!isu8cont(ch))
1874 						col++;
1875 				}
1876 			}
1877 		}
1878 		if (cur == es->cursor && !leftside)
1879 			ncol = col + pwidth - 1;
1880 		cur++;
1881 	}
1882 	if (cur == es->cursor)
1883 		ncol = col + pwidth;
1884 
1885 	/* Pad the current display buffer to the right margin. */
1886 
1887 	if (col < winwidth) {
1888 		while (col < winwidth) {
1889 			*twb1++ = ' ';
1890 			col++;
1891 		}
1892 	} else
1893 		moreright++;
1894 	*twb1 = ' ';
1895 
1896 	/*
1897 	 * Update the terminal display with data from wb1.
1898 	 * In this final loop, col includes the prompt.
1899 	 */
1900 
1901 	col = pwidth;
1902 	cnt = winwidth;
1903 	for (twb1 = wb1, twb2 = wb2; cnt; twb1++, twb2++) {
1904 		if (*twb1 != *twb2) {
1905 
1906 			/*
1907 			 * When a byte changes in the middle of a UTF-8
1908 			 * character, back up to the start byte, unless
1909 			 * the previous byte was the last one written.
1910 			 */
1911 
1912 			if (col > 0 && isu8cont(*twb1)) {
1913 				col--;
1914 				if (lastb >= 0 && twb1 == wb1 + lastb + 1)
1915 					cur_col = col;
1916 				else while (twb1 > wb1 && isu8cont(*twb1)) {
1917 					twb1--;
1918 					twb2--;
1919 				}
1920 			}
1921 
1922 			if (cur_col != col)
1923 				ed_mov_opt(col, wb1);
1924 
1925 			/*
1926 			 * Always write complete characters, and
1927 			 * advance all pointers accordingly.
1928 			 */
1929 
1930 			x_putc(*twb1);
1931 			while (isu8cont(twb1[1])) {
1932 				x_putc(*++twb1);
1933 				twb2++;
1934 			}
1935 			lastb = *twb1 & 0x80 ? twb1 - wb1 : -1;
1936 			cur_col++;
1937 		} else if (isu8cont(*twb1))
1938 			continue;
1939 
1940 		/*
1941 		 * For changed continuation bytes, we backed up.
1942 		 * For unchanged ones, we jumped to the next byte.
1943 		 * So, getting here, we had a real column.
1944 		 */
1945 
1946 		col++;
1947 		cnt--;
1948 	}
1949 
1950 	/* Update the "more character". */
1951 
1952 	if (es->winleft > 0 && moreright)
1953 		/* POSIX says to use * for this but that is a globbing
1954 		 * character and may confuse people; + is more innocuous
1955 		 */
1956 		mc = '+';
1957 	else if (es->winleft > 0)
1958 		mc = '<';
1959 	else if (moreright)
1960 		mc = '>';
1961 	else
1962 		mc = ' ';
1963 	if (mc != morec) {
1964 		ed_mov_opt(pwidth + winwidth + 1, wb1);
1965 		x_putc(mc);
1966 		cur_col++;
1967 		morec = mc;
1968 		lastb = -1;
1969 	}
1970 
1971 	/* Move the cursor to its new position. */
1972 
1973 	if (cur_col != ncol) {
1974 		ed_mov_opt(ncol, wb1);
1975 		lastb = -1;
1976 	}
1977 }
1978 
1979 /* Move the display cursor to display column number col. */
1980 static void
1981 ed_mov_opt(int col, char *wb)
1982 {
1983 	int ci;
1984 
1985 	/* The cursor is already at the right place. */
1986 
1987 	if (cur_col == col)
1988 		return;
1989 
1990 	/* The cursor is too far right. */
1991 
1992 	if (cur_col > col) {
1993 		if (cur_col > 2 * col + 1) {
1994 			/* Much too far right, redraw from scratch. */
1995 			x_putc('\r');
1996 			vi_pprompt(0);
1997 			cur_col = pwidth;
1998 		} else {
1999 			/* Slightly too far right, back up. */
2000 			do {
2001 				x_putc('\b');
2002 			} while (--cur_col > col);
2003 			return;
2004 		}
2005 	}
2006 
2007 	/* Advance the cursor. */
2008 
2009 	for (ci = pwidth; ci < col || isu8cont(*wb);
2010 	     ci = newcol((unsigned char)*wb++, ci))
2011 		if (ci > cur_col || (ci == cur_col && !isu8cont(*wb)))
2012 			x_putc(*wb);
2013 	cur_col = ci;
2014 }
2015 
2016 
2017 /* replace word with all expansions (ie, expand word*) */
2018 static int
2019 expand_word(int command)
2020 {
2021 	static struct edstate *buf;
2022 	int rval = 0;
2023 	int nwords;
2024 	int start, end;
2025 	char **words;
2026 	int i;
2027 
2028 	/* Undo previous expansion */
2029 	if (command == 0 && expanded == EXPAND && buf) {
2030 		restore_edstate(es, buf);
2031 		buf = NULL;
2032 		expanded = NONE;
2033 		return 0;
2034 	}
2035 	if (buf) {
2036 		free_edstate(buf);
2037 		buf = NULL;
2038 	}
2039 
2040 	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
2041 	    es->cbuf, es->linelen, es->cursor,
2042 	    &start, &end, &words, NULL);
2043 	if (nwords == 0) {
2044 		vi_error();
2045 		return -1;
2046 	}
2047 
2048 	buf = save_edstate(es);
2049 	expanded = EXPAND;
2050 	del_range(start, end);
2051 	es->cursor = start;
2052 	for (i = 0; i < nwords; ) {
2053 		if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
2054 			rval = -1;
2055 			break;
2056 		}
2057 		if (++i < nwords && putbuf(" ", 1, 0) != 0) {
2058 			rval = -1;
2059 			break;
2060 		}
2061 	}
2062 	i = buf->cursor - end;
2063 	if (rval == 0 && i > 0)
2064 		es->cursor += i;
2065 	modified = 1; hnum = hlast;
2066 	insert = INSERT;
2067 	lastac = 0;
2068 	refresh_line(0);
2069 	return rval;
2070 }
2071 
2072 static int
2073 complete_word(int command, int count)
2074 {
2075 	static struct edstate *buf;
2076 	int rval = 0;
2077 	int nwords;
2078 	int start, end;
2079 	char **words;
2080 	char *match;
2081 	int match_len;
2082 	int is_unique;
2083 	int is_command;
2084 
2085 	/* Undo previous completion */
2086 	if (command == 0 && expanded == COMPLETE && buf) {
2087 		print_expansions(buf);
2088 		expanded = PRINT;
2089 		return 0;
2090 	}
2091 	if (command == 0 && expanded == PRINT && buf) {
2092 		restore_edstate(es, buf);
2093 		buf = NULL;
2094 		expanded = NONE;
2095 		return 0;
2096 	}
2097 	if (buf) {
2098 		free_edstate(buf);
2099 		buf = NULL;
2100 	}
2101 
2102 	/* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
2103 	 * was done this way.
2104 	 */
2105 	nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
2106 	    es->cbuf, es->linelen, es->cursor,
2107 	    &start, &end, &words, &is_command);
2108 	if (nwords == 0) {
2109 		vi_error();
2110 		return -1;
2111 	}
2112 	if (count) {
2113 		int i;
2114 
2115 		count--;
2116 		if (count >= nwords) {
2117 			vi_error();
2118 			x_print_expansions(nwords, words, is_command);
2119 			x_free_words(nwords, words);
2120 			redraw_line(0, 0);
2121 			return -1;
2122 		}
2123 		/*
2124 		 * Expand the count'th word to its basename
2125 		 */
2126 		if (is_command) {
2127 			match = words[count] +
2128 			    x_basename(words[count], NULL);
2129 			/* If more than one possible match, use full path */
2130 			for (i = 0; i < nwords; i++)
2131 				if (i != count &&
2132 				    strcmp(words[i] + x_basename(words[i],
2133 				    NULL), match) == 0) {
2134 					match = words[count];
2135 					break;
2136 				}
2137 		} else
2138 			match = words[count];
2139 		match_len = strlen(match);
2140 		is_unique = 1;
2141 		/* expanded = PRINT;	next call undo */
2142 	} else {
2143 		match = words[0];
2144 		match_len = x_longest_prefix(nwords, words);
2145 		expanded = COMPLETE;	/* next call will list completions */
2146 		is_unique = nwords == 1;
2147 	}
2148 
2149 	buf = save_edstate(es);
2150 	del_range(start, end);
2151 	es->cursor = start;
2152 
2153 	/* escape all shell-sensitive characters and put the result into
2154 	 * command buffer */
2155 	rval = x_escape(match, match_len, x_vi_putbuf);
2156 
2157 	if (rval == 0 && is_unique) {
2158 		/* If exact match, don't undo.  Allows directory completions
2159 		 * to be used (ie, complete the next portion of the path).
2160 		 */
2161 		expanded = NONE;
2162 
2163 		/* If not a directory, add a space to the end... */
2164 		if (match_len > 0 && match[match_len - 1] != '/')
2165 			rval = putbuf(" ", 1, 0);
2166 	}
2167 	x_free_words(nwords, words);
2168 
2169 	modified = 1; hnum = hlast;
2170 	insert = INSERT;
2171 	lastac = 0;	 /* prevent this from being redone... */
2172 	refresh_line(0);
2173 
2174 	return rval;
2175 }
2176 
2177 static int
2178 print_expansions(struct edstate *e)
2179 {
2180 	int nwords;
2181 	int start, end;
2182 	char **words;
2183 	int is_command;
2184 
2185 	nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
2186 	    e->cbuf, e->linelen, e->cursor,
2187 	    &start, &end, &words, &is_command);
2188 	if (nwords == 0) {
2189 		vi_error();
2190 		return -1;
2191 	}
2192 	x_print_expansions(nwords, words, is_command);
2193 	x_free_words(nwords, words);
2194 	redraw_line(0, 0);
2195 	return 0;
2196 }
2197 
2198 /*
2199  * The number of bytes needed to encode byte c.
2200  * Control bytes get "M-" or "^" prepended.
2201  * This function does not handle tabs.
2202  */
2203 static int
2204 char_len(int c)
2205 {
2206 	int len = 1;
2207 
2208 	if ((c & 0x80) && Flag(FVISHOW8)) {
2209 		len += 2;
2210 		c &= 0x7f;
2211 	}
2212 	if (c < ' ' || c == 0x7f)
2213 		len++;
2214 	return len;
2215 }
2216 
2217 /* Similar to x_zotc(emacs.c), but no tab weirdness */
2218 static void
2219 x_vi_zotc(int c)
2220 {
2221 	if (Flag(FVISHOW8) && (c & 0x80)) {
2222 		x_puts("M-");
2223 		c &= 0x7f;
2224 	}
2225 	if (c < ' ' || c == 0x7f) {
2226 		x_putc('^');
2227 		c ^= '@';
2228 	}
2229 	x_putc(c);
2230 }
2231 
2232 static void
2233 vi_pprompt(int full)
2234 {
2235 	pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
2236 }
2237 
2238 static void
2239 vi_error(void)
2240 {
2241 	/* Beem out of any macros as soon as an error occurs */
2242 	vi_macro_reset();
2243 	x_putc(BEL);
2244 	x_flush();
2245 }
2246 
2247 static void
2248 vi_macro_reset(void)
2249 {
2250 	if (macro.p) {
2251 		afree(macro.buf, APERM);
2252 		memset((char *) &macro, 0, sizeof(macro));
2253 	}
2254 }
2255 
2256 static int
2257 isu8cont(unsigned char c)
2258 {
2259 	return !Flag(FVISHOW8) && (c & (0x80 | 0x40)) == 0x80;
2260 }
2261 #endif	/* VI */
2262