oksh-noxz

[fork] Portable OpenBSD ksh, based on the Public Domain Korn Shell (pdksh).
git clone https://noxz.tech/git/oksh-noxz.git
Log | Files | Tags

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