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