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