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
27struct edstate {
28	int	winleft;
29	char	*cbuf;
30	int	cbufsize;
31	int	linelen;
32	int	cursor;
33};
34
35
36static int	vi_hook	ARGS((int));
37static void 	vi_reset ARGS((char *, size_t));
38static int	nextstate ARGS((int));
39static int	vi_insert ARGS((int));
40static int	vi_cmd ARGS((int, const char *));
41static int	domove ARGS((int, const char *, int));
42static int	redo_insert ARGS((int));
43static void	yank_range ARGS((int, int));
44static int	bracktype ARGS((int));
45static void	save_cbuf ARGS((void));
46static void	restore_cbuf ARGS((void));
47static void	edit_reset ARGS((char *, size_t));
48static int	putbuf ARGS((const char *, int, int));
49static void	del_range ARGS((int, int));
50static int	findch ARGS((int, int, int, int));
51static int	forwword ARGS((int));
52static int	backword ARGS((int));
53static int	endword ARGS((int));
54static int	Forwword ARGS((int));
55static int	Backword ARGS((int));
56static int	Endword ARGS((int));
57static int	grabhist ARGS((int, int));
58static int	grabsearch ARGS((int, int, int, char *));
59static void	redraw_line ARGS((int));
60static void	refresh ARGS((int));
61static int	outofwin ARGS((void));
62static void	rewindow ARGS((void));
63static int	newcol ARGS((int, int));
64static void	display ARGS((char *, char *, int));
65static void	ed_mov_opt ARGS((int, char *));
66static int	expand_word ARGS((int));
67static int	complete_word ARGS((int, int));
68static int	print_expansions ARGS((struct edstate *, int));
69static int 	char_len ARGS((int));
70static void 	x_vi_zotc ARGS((int));
71static void	vi_pprompt ARGS((int));
72static void	vi_error ARGS((void));
73static void	vi_macro_reset ARGS((void));
74static 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
94const 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
148static char		undocbuf[CMDLEN];
149
150static struct edstate 	*save_edstate ARGS((struct edstate *old));
151static void		restore_edstate ARGS((struct edstate *old, struct edstate *new));
152static void 		free_edstate ARGS((struct edstate *old));
153
154static struct edstate	ebuf;
155static struct edstate	undobuf = { 0, undocbuf, CMDLEN, 0, 0 };
156
157static struct edstate	*es;			/* current editor state */
158static struct edstate	*undo;
159
160static char	ibuf[CMDLEN];		/* input buffer */
161static int	first_insert;		/* set when starting in insert mode */
162static int	saved_inslen;		/* saved inslen for first insert */
163static int	inslen;			/* length of input buffer */
164static int	srchlen;		/* length of current search pattern */
165static char	ybuf[CMDLEN];		/* yank buffer */
166static int	yanklen;		/* length of yank buffer */
167static int	fsavecmd = ' ';		/* last find command */
168static int	fsavech;		/* character to find */
169static char	lastcmd[MAXVICMD];	/* last non-move command */
170static int	lastac;			/* argcnt for lastcmd */
171static int	lastsearch = ' ';	/* last search command */
172static char	srchpat[SRCHLEN];	/* last search pattern */
173static int	insert;			/* non-zero in insert mode */
174static int	hnum;			/* position in history */
175static int	ohnum;			/* history line copied (after mod) */
176static int	hlast;			/* 1 past last position in history */
177static int	modified;		/* buffer has been "modified" */
178static 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 */
186struct 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};
191static struct macro_state macro;
192
193enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
194static enum expand_mode expanded = NONE;/* last input was expanded */
195
196int
197x_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
258static int
259vi_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
532static void
533vi_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
548static int
549nextstate(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
568static int
569vi_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
698static int
699vi_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
1154static int
1155domove(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
1304static int
1305redo_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
1317static void
1318yank_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
1326static int
1327bracktype(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
1359static int	cur_col;		/* current column on line */
1360static int	pwidth;			/* width of prompt */
1361static int	prompt_trunc;		/* how much of prompt to truncate */
1362static int	prompt_skip;		/* how much of prompt to skip */
1363static int	winwidth;		/* width of window */
1364static char	*wbuf[2];		/* window buffers */
1365static int	wbuf_len;		/* length of window buffers (x_cols-3)*/
1366static int	win;			/* window buffer in use */
1367static char	morec;			/* more character at right of window */
1368static int	lastref;		/* argument to last refresh() */
1369static char	holdbuf[CMDLEN];	/* place to hold last edit buffer */
1370static int	holdlen;		/* length of holdbuf */
1371
1372static void
1373save_cbuf()
1374{
1375	memmove(holdbuf, es->cbuf, es->linelen);
1376	holdlen = es->linelen;
1377	holdbuf[holdlen] = '\0';
1378}
1379
1380static void
1381restore_cbuf()
1382{
1383	es->cursor = 0;
1384	es->linelen = holdlen;
1385	memmove(es->cbuf, holdbuf, holdlen);
1386}
1387
1388/* return a new edstate */
1389static struct edstate *
1390save_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
1405static void
1406restore_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
1416static void
1417free_edstate(old)
1418	struct edstate *old;
1419{
1420	afree(old->cbuf, APERM);
1421	afree((char *)old, APERM);
1422}
1423
1424
1425
1426static void
1427edit_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 */
1468static int
1469x_vi_putbuf(s, len)
1470	const char *s;
1471	size_t len;
1472{
1473	return putbuf(s, len, 0);
1474}
1475
1476static int
1477putbuf(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
1501static void
1502del_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
1510static int
1511findch(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
1542static int
1543forwword(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
1566static int
1567backword(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
1592static int
1593endword(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
1619static int
1620Forwword(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
1637static int
1638Backword(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
1654static int
1655Endword(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
1675static int
1676grabhist(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
1704static int
1705grabsearch(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
1740static void
1741redraw_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
1754static void
1755refresh(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
1768static int
1769outofwin()
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
1784static void
1785rewindow()
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
1808static int
1809newcol(ch, col)
1810	int	ch, col;
1811{
1812	if (ch == '\t')
1813		return (col | 7) + 1;
1814	return col + char_len(ch);
1815}
1816
1817static void
1818display(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
1912static void
1913ed_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*) */
1938static int
1939expand_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
1993static int
1994complete_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
2102static int
2103print_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) */
2126static int
2127char_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 */
2142static void
2143x_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
2157static void
2158vi_pprompt(full)
2159	int full;
2160{
2161	pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
2162}
2163
2164static void
2165vi_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
2173static void
2174vi_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