loksh-noxz

[fork] a Linux port of OpenBSD's ksh
git clone https://noxz.tech/git/loksh-noxz.git
Log | Files | README

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