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