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