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