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