1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2011 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                  Common Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*            http://www.opensource.org/licenses/cpl1.0.txt             *
11*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*                                                                      *
13*              Information and Software Systems Research               *
14*                            AT&T Research                             *
15*                           Florham Park NJ                            *
16*                                                                      *
17*                  David Korn <dgk@research.att.com>                   *
18*                                                                      *
19***********************************************************************/
20#pragma prototyped
21/* Adapted for ksh by David Korn */
22/*+	VI.C			P.D. Sullivan
23 *
24 *	One line editor for the shell based on the vi editor.
25 *
26 *	Questions to:
27 *		P.D. Sullivan
28 *		cbosgd!pds
29-*/
30
31
32#if KSHELL
33#   include	"defs.h"
34#else
35#   include	<ast.h>
36#   include	"FEATURE/options"
37#   include	<ctype.h>
38#endif	/* KSHELL */
39#include	"io.h"
40
41#include	"history.h"
42#include	"edit.h"
43#include	"terminal.h"
44#include	"FEATURE/time"
45
46#if SHOPT_OLDTERMIO
47#   undef ECHOCTL
48#   define echoctl	(vp->ed->e_echoctl)
49#else
50#   ifdef ECHOCTL
51#	define echoctl	ECHOCTL
52#   else
53#	define echoctl	0
54#   endif /* ECHOCTL */
55#endif /*SHOPT_OLDTERMIO */
56
57#ifndef FIORDCHK
58#   define NTICKS	5		/* number of ticks for typeahead */
59#endif /* FIORDCHK */
60
61#define	MAXCHAR	MAXLINE-2		/* max char per line */
62
63#if SHOPT_MULTIBYTE
64#   include	"lexstates.h"
65#   define gencpy(a,b)	ed_gencpy(a,b)
66#   define genncpy(a,b,n)	ed_genncpy(a,b,n)
67#   define genlen(str)	ed_genlen(str)
68#   define digit(c)	((c&~STRIP)==0 && isdigit(c))
69#   define is_print(c)	((c&~STRIP) || isprint(c))
70#   if !_lib_iswprint && !defined(iswprint)
71#	define iswprint(c)	((c&~0177) || isprint(c))
72#   endif
73    static int _isalph(int);
74    static int _ismetach(int);
75    static int _isblank(int);
76#   undef  isblank
77#   define isblank(v)	_isblank(virtual[v])
78#   define isalph(v)	_isalph(virtual[v])
79#   define ismetach(v)	_ismetach(virtual[v])
80#else
81    static genchar	_c;
82#   define gencpy(a,b)	strcpy((char*)(a),(char*)(b))
83#   define genncpy(a,b,n) strncpy((char*)(a),(char*)(b),n)
84#   define genlen(str)	strlen(str)
85#   define isalph(v)	((_c=virtual[v])=='_'||isalnum(_c))
86#   undef  isblank
87#   define isblank(v)	isspace(virtual[v])
88#   define ismetach(v)	ismeta(virtual[v])
89#   define digit(c)	isdigit(c)
90#   define is_print(c)	isprint(c)
91#endif	/* SHOPT_MULTIBYTE */
92
93#if ( 'a' == 97) /* ASCII? */
94#   define fold(c)	((c)&~040)	/* lower and uppercase equivalent */
95#else
96#   define fold(c)	((c)|0100)	/* lower and uppercase equivalent */
97#endif
98
99#ifndef iswascii
100#define iswascii(c)	(!((c)&(~0177)))
101#endif
102
103typedef struct _vi_
104{
105	int direction;
106	int lastmacro;
107	char addnl;		/* boolean - add newline flag */
108	char last_find;		/* last find command */
109	char last_cmd;		/* last command */
110	char repeat_set;
111	char nonewline;
112	int findchar;		/* last find char */
113	genchar *lastline;
114	int first_wind;		/* first column of window */
115	int last_wind;		/* last column in window */
116	int lastmotion;		/* last motion */
117	int long_char; 		/* line bigger than window */
118	int long_line;		/* line bigger than window */
119	int ocur_phys;		/* old current physical position */
120	int ocur_virt;		/* old last virtual position */
121	int ofirst_wind;	/* old window first col */
122	int o_v_char;		/* prev virtual[ocur_virt] */
123	int repeat;		/* repeat count for motion cmds */
124	int lastrepeat;		/* last repeat count for motion cmds */
125	int u_column;		/* undo current column */
126	int U_saved;		/* original virtual saved */
127	genchar *U_space;	/* used for U command */
128	genchar *u_space;	/* used for u command */
129#ifdef FIORDCHK
130	clock_t typeahead;	/* typeahead occurred */
131#else
132	int typeahead;		/* typeahead occurred */
133#endif	/* FIORDCHK */
134#if SHOPT_MULTIBYTE
135	int bigvi;
136#endif
137	Edit_t	*ed;		/* pointer to edit data */
138} Vi_t;
139
140#define editb	(*vp->ed)
141
142#undef putchar
143#define putchar(c)	ed_putchar(vp->ed,c)
144
145#define crallowed	editb.e_crlf
146#define cur_virt	editb.e_cur		/* current virtual column */
147#define cur_phys	editb.e_pcur	/* current phys column cursor is at */
148#define curhline	editb.e_hline		/* current history line */
149#define first_virt	editb.e_fcol		/* first allowable column */
150#define	globals		editb.e_globals		/* local global variables */
151#define histmin		editb.e_hismin
152#define histmax		editb.e_hismax
153#define last_phys	editb.e_peol		/* last column in physical */
154#define last_virt	editb.e_eol		/* last column */
155#define lsearch		editb.e_search		/* last search string */
156#define lookahead	editb.e_lookahead	/* characters in buffer */
157#define previous	editb.e_lbuf		/* lookahead buffer */
158#define max_col		editb.e_llimit		/* maximum column */
159#define Prompt		editb.e_prompt		/* pointer to prompt */
160#define plen		editb.e_plen		/* length of prompt */
161#define physical	editb.e_physbuf		/* physical image */
162#define usreof		editb.e_eof		/* user defined eof char */
163#define usrerase	editb.e_erase		/* user defined erase char */
164#define usrlnext	editb.e_lnext		/* user defined next literal */
165#define usrkill		editb.e_kill		/* user defined kill char */
166#define virtual		editb.e_inbuf	/* pointer to virtual image buffer */
167#define	window		editb.e_window		/* window buffer */
168#define	w_size		editb.e_wsize		/* window size */
169#define	inmacro		editb.e_inmacro		/* true when in macro */
170#define yankbuf		editb.e_killbuf		/* yank/delete buffer */
171
172
173#define	ABORT	-2			/* user abort */
174#define	APPEND	-10			/* append chars */
175#define	BAD	-1			/* failure flag */
176#define	BIGVI	-15			/* user wants real vi */
177#define	CONTROL	-20			/* control mode */
178#define	ENTER	-25			/* enter flag */
179#define	GOOD	0			/* success flag */
180#define	INPUT	-30			/* input mode */
181#define	INSERT	-35			/* insert mode */
182#define	REPLACE	-40			/* replace chars */
183#define	SEARCH	-45			/* search flag */
184#define	TRANSLATE	-50		/* translate virt to phys only */
185
186#define	INVALID	(-1)			/* invalid column */
187
188static const char paren_chars[] = "([{)]}";   /* for % command */
189
190static void	cursor(Vi_t*, int);
191static void	del_line(Vi_t*,int);
192static int	getcount(Vi_t*,int);
193static void	getline(Vi_t*,int);
194static int	getrchar(Vi_t*);
195static int	mvcursor(Vi_t*,int);
196static void	pr_string(Vi_t*,const char*);
197static void	putstring(Vi_t*,int, int);
198static void	refresh(Vi_t*,int);
199static void	replace(Vi_t*,int, int);
200static void	restore_v(Vi_t*);
201static void	save_last(Vi_t*);
202static void	save_v(Vi_t*);
203static int	search(Vi_t*,int);
204static void	sync_cursor(Vi_t*);
205static int	textmod(Vi_t*,int,int);
206
207/*+	VI_READ( fd, shbuf, nchar )
208 *
209 *	This routine implements a one line version of vi and is
210 * called by _filbuf.c
211 *
212-*/
213
214/*
215 * if reedit is non-zero, initialize edit buffer with reedit chars
216 */
217int ed_viread(void *context, int fd, register char *shbuf, int nchar, int reedit)
218{
219	Edit_t *ed = (Edit_t*)context;
220	register int i;			/* general variable */
221	register int term_char=0;	/* read() termination character */
222	register Vi_t *vp = ed->e_vi;
223	char prompt[PRSIZE+2];		/* prompt */
224	genchar Physical[2*MAXLINE];	/* physical image */
225	genchar Ubuf[MAXLINE];	/* used for U command */
226	genchar ubuf[MAXLINE];	/* used for u command */
227	genchar Window[MAXLINE];	/* window image */
228	int Globals[9];			/* local global variables */
229	int esc_or_hang=0;		/* <ESC> or hangup */
230	char cntl_char=0;		/* TRUE if control character present */
231#if SHOPT_RAWONLY
232#   define viraw	1
233#else
234	int viraw = (sh_isoption(SH_VIRAW) || ed->sh->st.trap[SH_KEYTRAP]);
235#   ifndef FIORDCHK
236	clock_t oldtime, newtime;
237	struct tms dummy;
238#   endif /* FIORDCHK */
239#endif /* SHOPT_RAWONLY */
240	if(!vp)
241	{
242		ed->e_vi = vp =  newof(0,Vi_t,1,0);
243		vp->lastline = (genchar*)malloc(MAXLINE*CHARSIZE);
244		vp->direction = -1;
245		vp->ed = ed;
246	}
247
248	/*** setup prompt ***/
249
250	Prompt = prompt;
251	ed_setup(vp->ed,fd, reedit);
252	shbuf[reedit] = 0;
253
254#if !SHOPT_RAWONLY
255	if(!viraw)
256	{
257		/*** Change the eol characters to '\r' and eof  ***/
258		/* in addition to '\n' and make eof an ESC	*/
259		if(tty_alt(ERRIO) < 0)
260			return(reexit?reedit:ed_read(context, fd, shbuf, nchar,0));
261
262#ifdef FIORDCHK
263		ioctl(fd,FIORDCHK,&vp->typeahead);
264#else
265		/* time the current line to determine typeahead */
266		oldtime = times(&dummy);
267#endif /* FIORDCHK */
268#if KSHELL
269		/* abort of interrupt has occurred */
270		if(ed->sh->trapnote&SH_SIGSET)
271			i = -1;
272		else
273#endif /* KSHELL */
274		/*** Read the line ***/
275		i = ed_read(context, fd, shbuf, nchar, 0);
276#ifndef FIORDCHK
277		newtime = times(&dummy);
278		vp->typeahead = ((newtime-oldtime) < NTICKS);
279#endif /* FIORDCHK */
280	    if(echoctl)
281	    {
282		if( i <= 0 )
283		{
284			/*** read error or eof typed ***/
285			tty_cooked(ERRIO);
286			return(i);
287		}
288		term_char = shbuf[--i];
289		if( term_char == '\r' )
290			term_char = '\n';
291		if( term_char=='\n' || term_char==ESC )
292			shbuf[i--] = '\0';
293		else
294			shbuf[i+1] = '\0';
295	    }
296	    else
297	    {
298		register int c = shbuf[0];
299
300		/*** Save and remove the last character if its an eol, ***/
301		/* changing '\r' to '\n' */
302
303		if( i == 0 )
304		{
305			/*** ESC was typed as first char of line ***/
306			esc_or_hang = 1;
307			term_char = ESC;
308			shbuf[i--] = '\0';	/* null terminate line */
309		}
310		else if( i<0 || c==usreof )
311		{
312			/*** read error or eof typed ***/
313			tty_cooked(ERRIO);
314			if( c == usreof )
315				i = 0;
316			return(i);
317		}
318		else
319		{
320			term_char = shbuf[--i];
321			if( term_char == '\r' )
322				term_char = '\n';
323#if !defined(VEOL2) && !defined(ECHOCTL)
324			if(term_char=='\n')
325			{
326				tty_cooked(ERRIO);
327				return(i+1);
328			}
329#endif
330			if( term_char=='\n' || term_char==usreof )
331			{
332				/*** remove terminator & null terminate ***/
333				shbuf[i--] = '\0';
334			}
335			else
336			{
337				/** terminator was ESC, which is not xmitted **/
338				term_char = ESC;
339				shbuf[i+1] = '\0';
340			}
341		}
342	    }
343	}
344	else
345#endif /* SHOPT_RAWONLY */
346	{
347		/*** Set raw mode ***/
348
349#if !SHOPT_RAWONLY
350		if( editb.e_ttyspeed == 0 )
351		{
352			/*** never did TCGETA, so do it ***/
353			/* avoids problem if user does 'sh -o viraw' */
354			tty_alt(ERRIO);
355		}
356#endif /* SHOPT_RAWONLY */
357		if(tty_raw(ERRIO,0) < 0 )
358			return(reedit?reedit:ed_read(context, fd, shbuf, nchar,0));
359		i = last_virt-1;
360	}
361
362	/*** Initialize some things ***/
363
364	virtual = (genchar*)shbuf;
365#if SHOPT_MULTIBYTE
366	virtual = (genchar*)roundof((char*)virtual-(char*)0,sizeof(genchar));
367	shbuf[i+1] = 0;
368	i = ed_internal(shbuf,virtual)-1;
369#endif /* SHOPT_MULTIBYTE */
370	globals = Globals;
371	cur_phys = i + 1;
372	cur_virt = i;
373	first_virt = 0;
374	vp->first_wind = 0;
375	last_virt = i;
376	last_phys = i;
377	vp->last_wind = i;
378	vp->long_line = ' ';
379	vp->long_char = ' ';
380	vp->o_v_char = '\0';
381	vp->ocur_phys = 0;
382	vp->ocur_virt = MAXCHAR;
383	vp->ofirst_wind = 0;
384	physical = Physical;
385	vp->u_column = INVALID - 1;
386	vp->U_space = Ubuf;
387	vp->u_space = ubuf;
388	window = Window;
389	window[0] = '\0';
390
391	if(!yankbuf)
392		yankbuf = (genchar*)malloc(MAXLINE*CHARSIZE);
393	if( vp->last_cmd == '\0' )
394	{
395		/*** first time for this shell ***/
396
397		vp->last_cmd = 'i';
398		vp->findchar = INVALID;
399		vp->lastmotion = '\0';
400		vp->lastrepeat = 1;
401		vp->repeat = 1;
402		*yankbuf = 0;
403	}
404
405	/*** fiddle around with prompt length ***/
406	if( nchar+plen > MAXCHAR )
407		nchar = MAXCHAR - plen;
408	max_col = nchar - 2;
409
410	if( !viraw )
411	{
412		int kill_erase = 0;
413		for(i=(echoctl?last_virt:0); i<last_virt; ++i )
414		{
415			/*** change \r to \n, check for control characters, ***/
416			/* delete appropriate ^Vs,			*/
417			/* and estimate last physical column */
418
419			if( virtual[i] == '\r' )
420				virtual[i] = '\n';
421		    if(!echoctl)
422		    {
423			register int c = virtual[i];
424			if( c<=usrerase)
425			{
426				/*** user typed escaped erase or kill char ***/
427				cntl_char = 1;
428				if(is_print(c))
429					kill_erase++;
430			}
431			else if( !is_print(c) )
432			{
433				cntl_char = 1;
434
435				if( c == usrlnext )
436				{
437					if( i == last_virt )
438					{
439						/*** eol/eof was escaped ***/
440						/* so replace ^V with it */
441						virtual[i] = term_char;
442						break;
443					}
444
445					/*** delete ^V ***/
446					gencpy((&virtual[i]), (&virtual[i+1]));
447					--cur_virt;
448					--last_virt;
449				}
450			}
451		    }
452		}
453
454		/*** copy virtual image to window ***/
455		if(last_virt > 0)
456			last_phys = ed_virt_to_phys(vp->ed,virtual,physical,last_virt,0,0);
457		if( last_phys >= w_size )
458		{
459			/*** line longer than window ***/
460			vp->last_wind = w_size - 1;
461		}
462		else
463			vp->last_wind = last_phys;
464		genncpy(window, virtual, vp->last_wind+1);
465
466		if( term_char!=ESC  && (last_virt==INVALID
467			|| virtual[last_virt]!=term_char) )
468		{
469			/*** Line not terminated with ESC or escaped (^V) ***/
470			/* eol, so return after doing a total update */
471			/* if( (speed is greater or equal to 1200 */
472			/* and something was typed) and */
473			/* (control character present */
474			/* or typeahead occurred) ) */
475
476			tty_cooked(ERRIO);
477			if( editb.e_ttyspeed==FAST && last_virt!=INVALID
478				&& (vp->typeahead || cntl_char) )
479			{
480				refresh(vp,TRANSLATE);
481				pr_string(vp,Prompt);
482				putstring(vp,0, last_phys+1);
483				if(echoctl)
484					ed_crlf(vp->ed);
485				else
486					while(kill_erase-- > 0)
487						putchar(' ');
488			}
489
490			if( term_char=='\n' )
491			{
492				if(!echoctl)
493					ed_crlf(vp->ed);
494				virtual[++last_virt] = '\n';
495			}
496			vp->last_cmd = 'i';
497			save_last(vp);
498#if SHOPT_MULTIBYTE
499			virtual[last_virt+1] = 0;
500			last_virt = ed_external(virtual,shbuf);
501			return(last_virt);
502#else
503			return(++last_virt);
504#endif /* SHOPT_MULTIBYTE */
505		}
506
507		/*** Line terminated with escape, or escaped eol/eof, ***/
508		/*  so set raw mode */
509
510		if( tty_raw(ERRIO,0) < 0 )
511		{
512			tty_cooked(ERRIO);
513			/*
514			 * The following prevents drivers that return 0 on
515			 * causing an infinite loop
516			 */
517			if(esc_or_hang)
518				return(-1);
519			virtual[++last_virt] = '\n';
520#if SHOPT_MULTIBYTE
521			virtual[last_virt+1] = 0;
522			last_virt = ed_external(virtual,shbuf);
523			return(last_virt);
524#else
525			return(++last_virt);
526#endif /* SHOPT_MULTIBYTE */
527		}
528
529		if(echoctl) /*** for cntl-echo erase the ^[ ***/
530			pr_string(vp,"\b\b\b\b      \b\b");
531
532
533		if(crallowed)
534		{
535			/*** start over since there may be ***/
536			/*** a control char, or cursor might not ***/
537			/*** be at left margin (this lets us know ***/
538			/*** where we are ***/
539			cur_phys = 0;
540			window[0] = '\0';
541			pr_string(vp,Prompt);
542			if( term_char==ESC && (last_virt<0 || virtual[last_virt]!=ESC))
543				refresh(vp,CONTROL);
544			else
545				refresh(vp,INPUT);
546		}
547		else
548		{
549			/*** just update everything internally ***/
550			refresh(vp,TRANSLATE);
551		}
552	}
553
554	/*** Handle usrintr, usrquit, or EOF ***/
555
556	i = sigsetjmp(editb.e_env,0);
557	if( i != 0 )
558	{
559		if(vp->ed->e_multiline)
560		{
561			cur_virt = last_virt;
562			sync_cursor(vp);
563		}
564		virtual[0] = '\0';
565		tty_cooked(ERRIO);
566
567		switch(i)
568		{
569		case UEOF:
570			/*** EOF ***/
571			return(0);
572
573		case UINTR:
574			/** interrupt **/
575			return(-1);
576		}
577		return(-1);
578	}
579
580	/*** Get a line from the terminal ***/
581
582	vp->U_saved = 0;
583	if(reedit)
584	{
585		cur_phys = vp->first_wind;
586		vp->ofirst_wind = INVALID;
587		refresh(vp,INPUT);
588	}
589	if(viraw)
590		getline(vp,APPEND);
591	else if(last_virt>=0 && virtual[last_virt]==term_char)
592		getline(vp,APPEND);
593	else
594		getline(vp,ESC);
595	if(vp->ed->e_multiline)
596		cursor(vp, last_phys);
597	/*** add a new line if user typed unescaped \n ***/
598	/* to cause the shell to process the line */
599	tty_cooked(ERRIO);
600	if(ed->e_nlist)
601	{
602		ed->e_nlist = 0;
603		stakset(ed->e_stkptr,ed->e_stkoff);
604	}
605	if( vp->addnl )
606	{
607		virtual[++last_virt] = '\n';
608		ed_crlf(vp->ed);
609	}
610	if( ++last_virt >= 0 )
611	{
612#if SHOPT_MULTIBYTE
613		if(vp->bigvi)
614		{
615			vp->bigvi = 0;
616			shbuf[last_virt-1] = '\n';
617		}
618		else
619		{
620			virtual[last_virt] = 0;
621			last_virt = ed_external(virtual,shbuf);
622		}
623#endif /* SHOPT_MULTIBYTE */
624#if SHOPT_EDPREDICT
625		if(vp->ed->nhlist)
626			ed_histlist(vp->ed,0);
627#endif /* SHOPT_EDPREDICT */
628		return(last_virt);
629	}
630	else
631		return(-1);
632}
633
634
635/*{	APPEND( char, mode )
636 *
637 *	This routine will append char after cur_virt in the virtual image.
638 * mode	=	APPEND, shift chars right before appending
639 *		REPLACE, replace char if possible
640 *
641}*/
642
643static void append(Vi_t *vp,int c, int mode)
644{
645	register int i,j;
646
647	if( last_virt<max_col && last_phys<max_col )
648	{
649		if( mode==APPEND || (cur_virt==last_virt  && last_virt>=0))
650		{
651			j = (cur_virt>=0?cur_virt:0);
652			for(i = ++last_virt;  i > j; --i)
653				virtual[i] = virtual[i-1];
654		}
655		virtual[++cur_virt] = c;
656	}
657	else
658		ed_ringbell();
659	return;
660}
661
662/*{	BACKWORD( nwords, cmd )
663 *
664 *	This routine will position cur_virt at the nth previous word.
665 *
666}*/
667
668static void backword(Vi_t *vp,int nwords, register int cmd)
669{
670	register int tcur_virt = cur_virt;
671	while( nwords-- && tcur_virt > first_virt )
672	{
673		if( !isblank(tcur_virt) && isblank(tcur_virt-1)
674			&& tcur_virt>first_virt )
675			--tcur_virt;
676		else if(cmd != 'B')
677		{
678			register int last = isalph(tcur_virt-1);
679			register int cur = isalph(tcur_virt);
680			if((!cur && last) || (cur && !last))
681				--tcur_virt;
682		}
683		while( isblank(tcur_virt) && tcur_virt>=first_virt )
684			--tcur_virt;
685		if( cmd == 'B' )
686		{
687			while( !isblank(tcur_virt) && tcur_virt>=first_virt )
688				--tcur_virt;
689		}
690		else
691		{
692			if(isalph(tcur_virt))
693				while( isalph(tcur_virt) && tcur_virt>=first_virt )
694					--tcur_virt;
695			else
696				while( !isalph(tcur_virt) && !isblank(tcur_virt)
697					&& tcur_virt>=first_virt )
698					--tcur_virt;
699		}
700		cur_virt = ++tcur_virt;
701	}
702	return;
703}
704
705/*{	CNTLMODE()
706 *
707 *	This routine implements the vi command subset.
708 *	The cursor will always be positioned at the char of interest.
709 *
710}*/
711
712static int cntlmode(Vi_t *vp)
713{
714	register int c;
715	register int i;
716	genchar tmp_u_space[MAXLINE];	/* temporary u_space */
717	genchar *real_u_space;		/* points to real u_space */
718	int tmp_u_column = INVALID;	/* temporary u_column */
719	int was_inmacro;
720
721	if(!vp->U_saved)
722	{
723		/*** save virtual image if never done before ***/
724		virtual[last_virt+1] = '\0';
725		gencpy(vp->U_space, virtual);
726		vp->U_saved = 1;
727	}
728
729	save_last(vp);
730
731	real_u_space = vp->u_space;
732	curhline = histmax;
733	first_virt = 0;
734	vp->repeat = 1;
735	if( cur_virt > INVALID )
736	{
737		/*** make sure cursor is at the last char ***/
738		sync_cursor(vp);
739	}
740
741	/*** Read control char until something happens to cause a ***/
742	/* return to APPEND/REPLACE mode	*/
743
744	while( c=ed_getchar(vp->ed,-1) )
745	{
746		vp->repeat_set = 0;
747		was_inmacro = inmacro;
748		if( c == '0' )
749		{
750			/*** move to leftmost column ***/
751			cur_virt = 0;
752			sync_cursor(vp);
753			continue;
754		}
755
756		if( digit(c) )
757		{
758			c = getcount(vp,c);
759			if( c == '.' )
760				vp->lastrepeat = vp->repeat;
761		}
762
763		/*** see if it's a move cursor command ***/
764
765		if(mvcursor(vp,c))
766		{
767			sync_cursor(vp);
768			vp->repeat = 1;
769			continue;
770		}
771
772		/*** see if it's a repeat of the last command ***/
773
774		if( c == '.' )
775		{
776			c = vp->last_cmd;
777			vp->repeat = vp->lastrepeat;
778			i = textmod(vp,c, c);
779		}
780		else
781		{
782			i = textmod(vp,c, 0);
783		}
784
785		/*** see if it's a text modification command ***/
786
787		switch(i)
788		{
789		case BAD:
790			break;
791
792		default:		/** input mode **/
793			if(!was_inmacro)
794			{
795				vp->last_cmd = c;
796				vp->lastrepeat = vp->repeat;
797			}
798			vp->repeat = 1;
799			if( i == GOOD )
800				continue;
801			return(i);
802		}
803
804		switch( c )
805		{
806			/***** Other stuff *****/
807
808		case cntl('L'):		/** Redraw line **/
809			/*** print the prompt and ***/
810			/* force a total refresh */
811			if(vp->nonewline==0 && !vp->ed->e_nocrnl)
812				putchar('\n');
813			vp->nonewline = 0;
814			pr_string(vp,Prompt);
815			window[0] = '\0';
816			cur_phys = vp->first_wind;
817			vp->ofirst_wind = INVALID;
818			vp->long_line = ' ';
819			break;
820
821		case cntl('V'):
822		{
823			register const char *p = fmtident(e_version);
824			save_v(vp);
825			del_line(vp,BAD);
826			while(c = *p++)
827				append(vp,c,APPEND);
828			refresh(vp,CONTROL);
829			ed_getchar(vp->ed,-1);
830			restore_v(vp);
831			break;
832		}
833
834		case '/':		/** Search **/
835		case '?':
836		case 'N':
837		case 'n':
838			save_v(vp);
839			switch( search(vp,c) )
840			{
841			case GOOD:
842				/*** force a total refresh ***/
843				window[0] = '\0';
844				goto newhist;
845
846			case BAD:
847				/*** no match ***/
848					ed_ringbell();
849
850			default:
851				if( vp->u_column == INVALID )
852					del_line(vp,BAD);
853				else
854					restore_v(vp);
855				break;
856			}
857			break;
858
859		case 'j':		/** get next command **/
860		case '+':		/** get next command **/
861#if SHOPT_EDPREDICT
862			if(vp->ed->hlist)
863			{
864				if(vp->ed->hoff >= vp->ed->hmax)
865					goto ringbell;
866				vp->ed->hoff++;
867				goto hupdate;
868			}
869#endif /* SHOPT_EDPREDICT */
870			curhline += vp->repeat;
871			if( curhline > histmax )
872			{
873				curhline = histmax;
874				goto ringbell;
875			}
876			else if(curhline==histmax && tmp_u_column!=INVALID )
877			{
878				vp->u_space = tmp_u_space;
879				vp->u_column = tmp_u_column;
880				restore_v(vp);
881				vp->u_space = real_u_space;
882				break;
883			}
884			save_v(vp);
885			cur_virt = INVALID;
886			goto newhist;
887
888		case 'k':		/** get previous command **/
889		case '-':		/** get previous command **/
890#if SHOPT_EDPREDICT
891			if(vp->ed->hlist)
892			{
893				if(vp->ed->hoff == 0)
894					goto ringbell;
895				vp->ed->hoff--;
896			 hupdate:
897				ed_histlist(vp->ed,*vp->ed->hlist!=0);
898				vp->nonewline++;
899				ed_ungetchar(vp->ed,cntl('L'));
900				continue;
901			}
902#endif /* SHOPT_EDPREDICT */
903			if( curhline == histmax )
904			{
905				vp->u_space = tmp_u_space;
906				i = vp->u_column;
907				save_v(vp);
908				vp->u_space = real_u_space;
909				tmp_u_column = vp->u_column;
910				vp->u_column = i;
911			}
912
913			curhline -= vp->repeat;
914			if( curhline <= histmin )
915			{
916				curhline += vp->repeat;
917				goto ringbell;
918			}
919			save_v(vp);
920			cur_virt = INVALID;
921		newhist:
922			if(curhline!=histmax || cur_virt==INVALID)
923				hist_copy((char*)virtual, MAXLINE, curhline,-1);
924			else
925			{
926				strcpy((char*)virtual,(char*)vp->u_space);
927#if SHOPT_MULTIBYTE
928				ed_internal((char*)vp->u_space,vp->u_space);
929#endif /* SHOPT_MULTIBYTE */
930			}
931#if SHOPT_MULTIBYTE
932			ed_internal((char*)virtual,virtual);
933#endif /* SHOPT_MULTIBYTE */
934			if((last_virt=genlen(virtual)-1) >= 0  && cur_virt == INVALID)
935				cur_virt = 0;
936#if SHOPT_EDPREDICT
937			if(vp->ed->hlist)
938			{
939				ed_histlist(vp->ed,0);
940				if(c=='\n')
941					ed_ungetchar(vp->ed,c);
942				ed_ungetchar(vp->ed,cntl('L'));
943				vp->nonewline = 1;
944				cur_virt = 0;
945			}
946#endif /*SHOPT_EDPREDICT */
947			break;
948
949
950		case 'u':		/** undo the last thing done **/
951			restore_v(vp);
952			break;
953
954		case 'U':		/** Undo everything **/
955			save_v(vp);
956			if( virtual[0] == '\0' )
957				goto ringbell;
958			else
959			{
960				gencpy(virtual, vp->U_space);
961				last_virt = genlen(vp->U_space) - 1;
962				cur_virt = 0;
963			}
964			break;
965
966#if KSHELL
967		case 'v':
968			if(vp->repeat_set==0)
969				goto vcommand;
970#endif /* KSHELL */
971
972		case 'G':		/** goto command repeat **/
973			if(vp->repeat_set==0)
974				vp->repeat = histmin+1;
975			if( vp->repeat <= histmin || vp->repeat > histmax )
976			{
977				goto ringbell;
978			}
979			curhline = vp->repeat;
980			save_v(vp);
981			if(c == 'G')
982			{
983				cur_virt = INVALID;
984				goto newhist;
985			}
986
987#if KSHELL
988		vcommand:
989			if(ed_fulledit(vp->ed)==GOOD)
990				return(BIGVI);
991			else
992				goto ringbell;
993#endif	/* KSHELL */
994
995		case '#':	/** insert(delete) # to (no)comment command **/
996			if( cur_virt != INVALID )
997			{
998				register genchar *p = &virtual[last_virt+1];
999				*p = 0;
1000				/*** see whether first char is comment char ***/
1001				c = (virtual[0]=='#');
1002				while(p-- >= virtual)
1003				{
1004					if(*p=='\n' || p<virtual)
1005					{
1006						if(c) /* delete '#' */
1007						{
1008							if(p[1]=='#')
1009							{
1010								last_virt--;
1011								gencpy(p+1,p+2);
1012							}
1013						}
1014						else
1015						{
1016							cur_virt = p-virtual;
1017							append(vp,'#', APPEND);
1018						}
1019					}
1020				}
1021				if(c)
1022				{
1023					curhline = histmax;
1024					cur_virt = 0;
1025					break;
1026				}
1027				refresh(vp,INPUT);
1028			}
1029
1030		case '\n':		/** send to shell **/
1031#if SHOPT_EDPREDICT
1032			if(!vp->ed->hlist)
1033			return(ENTER);
1034		case '\t':		/** bring choice to edit **/
1035			if(vp->ed->hlist)
1036			{
1037				if(vp->repeat > vp->ed->nhlist-vp->ed->hoff)
1038					goto ringbell;
1039				curhline = vp->ed->hlist[vp->repeat+vp->ed->hoff-1]->index;
1040				goto newhist;
1041			}
1042			goto ringbell;
1043#else
1044			return(ENTER);
1045#endif /* SHOPT_EDPREDICT */
1046	        case ESC:
1047			/* don't ring bell if next char is '[' */
1048			if(!lookahead)
1049			{
1050				char x;
1051				if(sfpkrd(editb.e_fd,&x,1,'\r',400L,-1)>0)
1052					ed_ungetchar(vp->ed,x);
1053			}
1054			if(lookahead)
1055			{
1056				ed_ungetchar(vp->ed,c=ed_getchar(vp->ed,1));
1057				if(c=='[')
1058				{
1059					vp->repeat = 1;
1060					continue;
1061				}
1062			}
1063		default:
1064		ringbell:
1065			ed_ringbell();
1066			vp->repeat = 1;
1067			continue;
1068		}
1069
1070		refresh(vp,CONTROL);
1071		vp->repeat = 1;
1072	}
1073/* NOTREACHED */
1074	return(0);
1075}
1076
1077/*{	CURSOR( new_current_physical )
1078 *
1079 *	This routine will position the virtual cursor at
1080 * physical column x in the window.
1081 *
1082}*/
1083
1084static void cursor(Vi_t *vp,register int x)
1085{
1086#if SHOPT_MULTIBYTE
1087	while(physical[x]==MARKER)
1088		x++;
1089#endif /* SHOPT_MULTIBYTE */
1090	cur_phys = ed_setcursor(vp->ed, physical, cur_phys,x,vp->first_wind);
1091}
1092
1093/*{	DELETE( nchars, mode )
1094 *
1095 *	Delete nchars from the virtual space and leave cur_virt positioned
1096 * at cur_virt-1.
1097 *
1098 *	If mode	= 'c', do not save the characters deleted
1099 *		= 'd', save them in yankbuf and delete.
1100 *		= 'y', save them in yankbuf but do not delete.
1101 *
1102}*/
1103
1104static void cdelete(Vi_t *vp,register int nchars, int mode)
1105{
1106	register int i;
1107	register genchar *cp;
1108
1109	if( cur_virt < first_virt )
1110	{
1111		ed_ringbell();
1112		return;
1113	}
1114	if( nchars > 0 )
1115	{
1116		cp = virtual+cur_virt;
1117		vp->o_v_char = cp[0];
1118		if( (cur_virt-- + nchars) > last_virt )
1119		{
1120			/*** set nchars to number actually deleted ***/
1121			nchars = last_virt - cur_virt;
1122		}
1123
1124		/*** save characters to be deleted ***/
1125
1126		if( mode != 'c' )
1127		{
1128			i = cp[nchars];
1129			cp[nchars] = 0;
1130			gencpy(yankbuf,cp);
1131			cp[nchars] = i;
1132		}
1133
1134		/*** now delete these characters ***/
1135
1136		if( mode != 'y' )
1137		{
1138			gencpy(cp,cp+nchars);
1139			last_virt -= nchars;
1140		}
1141	}
1142	return;
1143}
1144
1145/*{	DEL_LINE( mode )
1146 *
1147 *	This routine will delete the line.
1148 *	mode = GOOD, do a save_v()
1149 *
1150}*/
1151static void del_line(register Vi_t *vp, int mode)
1152{
1153	if( last_virt == INVALID )
1154		return;
1155
1156	if( mode == GOOD )
1157		save_v(vp);
1158
1159	cur_virt = 0;
1160	first_virt = 0;
1161	cdelete(vp,last_virt+1, BAD);
1162	refresh(vp,CONTROL);
1163
1164	cur_virt = INVALID;
1165	cur_phys = 0;
1166	vp->findchar = INVALID;
1167	last_phys = INVALID;
1168	last_virt = INVALID;
1169	vp->last_wind = INVALID;
1170	vp->first_wind = 0;
1171	vp->o_v_char = '\0';
1172	vp->ocur_phys = 0;
1173	vp->ocur_virt = MAXCHAR;
1174	vp->ofirst_wind = 0;
1175	window[0] = '\0';
1176	return;
1177}
1178
1179/*{	DELMOTION( motion, mode )
1180 *
1181 *	Delete thru motion.
1182 *
1183 *	mode	= 'd', save deleted characters, delete
1184 *		= 'c', do not save characters, change
1185 *		= 'y', save characters, yank
1186 *
1187 *	Returns 1 if operation successful; else 0.
1188 *
1189}*/
1190
1191static int delmotion(Vi_t *vp,int motion, int mode)
1192{
1193	register int begin, end, delta;
1194	/* the following saves a register */
1195
1196	if( cur_virt == INVALID )
1197		return(0);
1198	if( mode != 'y' )
1199		save_v(vp);
1200	begin = cur_virt;
1201
1202	/*** fake out the motion routines by appending a blank ***/
1203
1204	virtual[++last_virt] = ' ';
1205	end = mvcursor(vp,motion);
1206	virtual[last_virt--] = 0;
1207	if(!end)
1208		return(0);
1209
1210	end = cur_virt;
1211	if( mode=='c' && end>begin && strchr("wW", motion) )
1212	{
1213		/*** called by change operation, user really expects ***/
1214		/* the effect of the eE commands, so back up to end of word */
1215		while( end>begin && isblank(end-1) )
1216			--end;
1217		if( end == begin )
1218			++end;
1219	}
1220
1221	delta = end - begin;
1222	if( delta >= 0 )
1223	{
1224		cur_virt = begin;
1225		if( strchr("eE;,TtFf%", motion) )
1226			++delta;
1227	}
1228	else
1229	{
1230		delta = -delta + (motion=='%');
1231	}
1232
1233	cdelete(vp,delta, mode);
1234	if( mode == 'y' )
1235		cur_virt = begin;
1236	return(1);
1237}
1238
1239
1240/*{	ENDWORD( nwords, cmd )
1241 *
1242 *	This routine will move cur_virt to the end of the nth word.
1243 *
1244}*/
1245
1246static void endword(Vi_t *vp, int nwords, register int cmd)
1247{
1248	register int tcur_virt = cur_virt;
1249	while( nwords-- )
1250	{
1251		if( !isblank(tcur_virt) && tcur_virt<=last_virt )
1252			++tcur_virt;
1253		while( isblank(tcur_virt) && tcur_virt<=last_virt )
1254			++tcur_virt;
1255		if( cmd == 'E' )
1256		{
1257			while( !isblank(tcur_virt) && tcur_virt<=last_virt )
1258				++tcur_virt;
1259		}
1260		else
1261		{
1262			if( isalph(tcur_virt) )
1263				while( isalph(tcur_virt) && tcur_virt<=last_virt )
1264					++tcur_virt;
1265			else
1266				while( !isalph(tcur_virt) && !isblank(tcur_virt)
1267					&& tcur_virt<=last_virt )
1268					++tcur_virt;
1269		}
1270		if( tcur_virt > first_virt )
1271			tcur_virt--;
1272	}
1273	cur_virt = tcur_virt;
1274	return;
1275}
1276
1277/*{	FORWARD( nwords, cmd )
1278 *
1279 *	This routine will move cur_virt forward to the next nth word.
1280 *
1281}*/
1282
1283static void forward(Vi_t *vp,register int nwords, int cmd)
1284{
1285	register int tcur_virt = cur_virt;
1286	while( nwords-- )
1287	{
1288		if( cmd == 'W' )
1289		{
1290			while( !isblank(tcur_virt) && tcur_virt < last_virt )
1291				++tcur_virt;
1292		}
1293		else
1294		{
1295			if( isalph(tcur_virt) )
1296			{
1297				while( isalph(tcur_virt) && tcur_virt<last_virt )
1298					++tcur_virt;
1299			}
1300			else
1301			{
1302				while( !isalph(tcur_virt) && !isblank(tcur_virt)
1303					&& tcur_virt < last_virt )
1304					++tcur_virt;
1305			}
1306		}
1307		while( isblank(tcur_virt) && tcur_virt < last_virt )
1308			++tcur_virt;
1309	}
1310	cur_virt = tcur_virt;
1311	return;
1312}
1313
1314
1315
1316/*{	GETCOUNT(c)
1317 *
1318 *	Set repeat to the user typed number and return the terminating
1319 * character.
1320 *
1321}*/
1322
1323static int getcount(register Vi_t *vp,register int c)
1324{
1325	register int i;
1326
1327	/*** get any repeat count ***/
1328
1329	if( c == '0' )
1330		return(c);
1331
1332	vp->repeat_set++;
1333	i = 0;
1334	while( digit(c) )
1335	{
1336		i = i*10 + c - '0';
1337		c = ed_getchar(vp->ed,-1);
1338	}
1339
1340	if( i > 0 )
1341		vp->repeat *= i;
1342	return(c);
1343}
1344
1345
1346/*{	GETLINE( mode )
1347 *
1348 *	This routine will fetch a line.
1349 *	mode	= APPEND, allow escape to cntlmode subroutine
1350 *		  appending characters.
1351 *		= REPLACE, allow escape to cntlmode subroutine
1352 *		  replacing characters.
1353 *		= SEARCH, no escape allowed
1354 *		= ESC, enter control mode immediately
1355 *
1356 *	The cursor will always be positioned after the last
1357 * char printed.
1358 *
1359 *	This routine returns when cr, nl, or (eof in column 0) is
1360 * received (column 0 is the first char position).
1361 *
1362}*/
1363
1364static void getline(register Vi_t* vp,register int mode)
1365{
1366	register int c;
1367	register int tmp;
1368	int	max_virt=0, last_save=0;
1369	genchar saveline[MAXLINE];
1370	vp->addnl = 1;
1371
1372	if( mode == ESC )
1373	{
1374		/*** go directly to control mode ***/
1375		goto escape;
1376	}
1377
1378	for(;;)
1379	{
1380		if( (c=ed_getchar(vp->ed,mode==SEARCH?1:-2)) == usreof )
1381			c = UEOF;
1382		else if( c == usrerase )
1383			c = UERASE;
1384		else if( c == usrkill )
1385			c = UKILL;
1386		else if( c == editb.e_werase )
1387			c = UWERASE;
1388		else if( c == usrlnext )
1389			c = ULNEXT;
1390		else if(mode==SEARCH && c==editb.e_intr)
1391			c = UINTR;
1392
1393		if( c == ULNEXT)
1394		{
1395			/*** implement ^V to escape next char ***/
1396			c = ed_getchar(vp->ed,2);
1397			append(vp,c, mode);
1398			refresh(vp,INPUT);
1399			continue;
1400		}
1401
1402		switch( c )
1403		{
1404		case ESC:		/** enter control mode **/
1405			if(!sh_isoption(SH_VI))
1406			{
1407				append(vp,c, mode);
1408				break;
1409			}
1410			if( mode == SEARCH )
1411			{
1412				ed_ringbell();
1413				continue;
1414			}
1415			else
1416			{
1417	escape:
1418				if( mode == REPLACE )
1419				{
1420					c = max_virt-cur_virt;
1421					if(c > 0 && last_save>=cur_virt)
1422					{
1423						genncpy((&virtual[cur_virt]),&saveline[cur_virt],c);
1424						if(last_virt>=last_save)
1425							last_virt=last_save-1;
1426						refresh(vp,INPUT);
1427					}
1428					--cur_virt;
1429				}
1430				tmp = cntlmode(vp);
1431				if( tmp == ENTER || tmp == BIGVI )
1432				{
1433#if SHOPT_MULTIBYTE
1434					vp->bigvi = (tmp==BIGVI);
1435#endif /* SHOPT_MULTIBYTE */
1436					return;
1437				}
1438				if( tmp == INSERT )
1439				{
1440					mode = APPEND;
1441					continue;
1442				}
1443				mode = tmp;
1444				if(mode==REPLACE)
1445				{
1446					c = last_save = last_virt+1;
1447					if(c >= MAXLINE)
1448						c = MAXLINE-1;
1449					genncpy(saveline, virtual, c);
1450				}
1451			}
1452			break;
1453
1454		case UINTR:
1455				first_virt = 0;
1456				cdelete(vp,cur_virt+1, BAD);
1457				cur_virt = -1;
1458				return;
1459		case UERASE:		/** user erase char **/
1460				/*** treat as backspace ***/
1461
1462		case '\b':		/** backspace **/
1463			if( virtual[cur_virt] == '\\' )
1464			{
1465				cdelete(vp,1, BAD);
1466				append(vp,usrerase, mode);
1467			}
1468			else
1469			{
1470				if( mode==SEARCH && cur_virt==0 )
1471				{
1472					first_virt = 0;
1473					cdelete(vp,1, BAD);
1474					return;
1475				}
1476				if(mode==REPLACE || (last_save>0 && last_virt<=last_save))
1477				{
1478					if(cur_virt<=first_virt)
1479						ed_ringbell();
1480					else if(mode==REPLACE)
1481						--cur_virt;
1482					mode = REPLACE;
1483					sync_cursor(vp);
1484					continue;
1485				}
1486				else
1487					cdelete(vp,1, BAD);
1488			}
1489			break;
1490
1491		case UWERASE:		/** delete back word **/
1492			if( cur_virt > first_virt &&
1493				!isblank(cur_virt) &&
1494				!ispunct(virtual[cur_virt]) &&
1495				isblank(cur_virt-1) )
1496			{
1497				cdelete(vp,1, BAD);
1498			}
1499			else
1500			{
1501				tmp = cur_virt;
1502				backword(vp,1, 'W');
1503				cdelete(vp,tmp - cur_virt + 1, BAD);
1504			}
1505			break;
1506
1507		case UKILL:		/** user kill line char **/
1508			if( virtual[cur_virt] == '\\' )
1509			{
1510				cdelete(vp,1, BAD);
1511				append(vp,usrkill, mode);
1512			}
1513			else
1514			{
1515				if( mode == SEARCH )
1516				{
1517					cur_virt = 1;
1518					delmotion(vp, '$', BAD);
1519				}
1520				else if(first_virt)
1521				{
1522					tmp = cur_virt;
1523					cur_virt = first_virt;
1524					cdelete(vp,tmp - cur_virt + 1, BAD);
1525				}
1526				else
1527					del_line(vp,GOOD);
1528			}
1529			break;
1530
1531		case UEOF:		/** eof char **/
1532			if( cur_virt != INVALID )
1533				continue;
1534			vp->addnl = 0;
1535
1536		case '\n':		/** newline or return **/
1537			if( mode != SEARCH )
1538				save_last(vp);
1539			refresh(vp,INPUT);
1540			last_phys++;
1541			return;
1542
1543		case '\t':		/** command completion **/
1544			if(mode!=SEARCH && last_virt>=0 && (vp->ed->e_tabcount|| !isblank(cur_virt)) && vp->ed->sh->nextprompt)
1545			{
1546				if(vp->ed->e_tabcount==0)
1547				{
1548					ed_ungetchar(vp->ed,'\\');
1549					vp->ed->e_tabcount=1;
1550					goto escape;
1551				}
1552				else if(vp->ed->e_tabcount==1)
1553				{
1554					ed_ungetchar(vp->ed,'=');
1555					goto escape;
1556				}
1557				vp->ed->e_tabcount = 0;
1558			}
1559			/* FALL THRU*/
1560		default:
1561			if( mode == REPLACE )
1562			{
1563				if( cur_virt < last_virt )
1564				{
1565					replace(vp,c, 1);
1566					if(cur_virt>max_virt)
1567						max_virt = cur_virt;
1568					continue;
1569				}
1570				cdelete(vp,1, BAD);
1571				mode = APPEND;
1572				max_virt = last_virt+3;
1573			}
1574			append(vp,c, mode);
1575			break;
1576		}
1577		refresh(vp,INPUT);
1578
1579	}
1580}
1581
1582/*{	MVCURSOR( motion )
1583 *
1584 *	This routine will move the virtual cursor according to motion
1585 * for repeat times.
1586 *
1587 * It returns GOOD if successful; else BAD.
1588 *
1589}*/
1590
1591static int mvcursor(register Vi_t* vp,register int motion)
1592{
1593	register int count;
1594	register int tcur_virt;
1595	register int incr = -1;
1596	register int bound = 0;
1597
1598	switch(motion)
1599	{
1600		/***** Cursor move commands *****/
1601
1602	case '0':		/** First column **/
1603		tcur_virt = 0;
1604		break;
1605
1606	case '^':		/** First nonblank character **/
1607		tcur_virt = first_virt;
1608		while( isblank(tcur_virt) && tcur_virt < last_virt )
1609			++tcur_virt;
1610		break;
1611
1612	case '|':
1613		tcur_virt = vp->repeat-1;
1614		if(tcur_virt <= last_virt)
1615			break;
1616		/* fall through */
1617
1618	case '$':		/** End of line **/
1619		tcur_virt = last_virt;
1620		break;
1621
1622	case '[':
1623		switch(motion=getcount(vp,ed_getchar(vp->ed,-1)))
1624		{
1625		    case 'A':
1626#if SHOPT_EDPREDICT
1627			if(!vp->ed->hlist && cur_virt>=0  && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt)
1628#else
1629			if(cur_virt>=0  && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt)
1630#endif /* SHOPT_EDPREDICT */
1631			{
1632				virtual[last_virt + 1] = '\0';
1633#if SHOPT_MULTIBYTE
1634				ed_external(virtual,lsearch+1);
1635#else
1636				strcpy(lsearch+1,virtual);
1637#endif /* SHOPT_MULTIBYTE */
1638				*lsearch = '^';
1639				vp->direction = -2;
1640				ed_ungetchar(vp->ed,'n');
1641			}
1642			else if(cur_virt==0 && vp->direction == -2)
1643				ed_ungetchar(vp->ed,'n');
1644			else
1645				ed_ungetchar(vp->ed,'k');
1646			return(1);
1647		    case 'B':
1648			ed_ungetchar(vp->ed,'j');
1649			return(1);
1650		    case 'C':
1651			motion = last_virt;
1652			incr = 1;
1653			goto walk;
1654		    case 'D':
1655			motion = first_virt;
1656			goto walk;
1657		    case 'H':
1658			tcur_virt = 0;
1659			break;
1660		    case 'Y':
1661			tcur_virt = last_virt;
1662			break;
1663		    default:
1664			ed_ungetchar(vp->ed,motion);
1665			return(0);
1666		}
1667		break;
1668
1669	case 'h':		/** Left one **/
1670	case '\b':
1671		motion = first_virt;
1672		goto walk;
1673
1674	case ' ':
1675	case 'l':		/** Right one **/
1676		motion = last_virt;
1677		incr = 1;
1678	walk:
1679		tcur_virt = cur_virt;
1680		if( incr*tcur_virt < motion)
1681		{
1682			tcur_virt += vp->repeat*incr;
1683			if( incr*tcur_virt > motion)
1684				tcur_virt = motion;
1685		}
1686		else
1687			return(0);
1688		break;
1689
1690	case 'B':
1691	case 'b':		/** back word **/
1692		tcur_virt = cur_virt;
1693		backword(vp,vp->repeat, motion);
1694		if( cur_virt == tcur_virt )
1695			return(0);
1696		return(1);
1697
1698	case 'E':
1699	case 'e':		/** end of word **/
1700		tcur_virt = cur_virt;
1701		if(tcur_virt >=0)
1702			endword(vp, vp->repeat, motion);
1703		if( cur_virt == tcur_virt )
1704			return(0);
1705		return(1);
1706
1707	case ',':		/** reverse find old char **/
1708	case ';':		/** find old char **/
1709		switch(vp->last_find)
1710		{
1711		case 't':
1712		case 'f':
1713			if(motion==';')
1714			{
1715				bound = last_virt;
1716				incr = 1;
1717			}
1718			goto find_b;
1719
1720		case 'T':
1721		case 'F':
1722			if(motion==',')
1723			{
1724				bound = last_virt;
1725				incr = 1;
1726			}
1727			goto find_b;
1728
1729		default:
1730			return(0);
1731		}
1732
1733
1734	case 't':		/** find up to new char forward **/
1735	case 'f':		/** find new char forward **/
1736		bound = last_virt;
1737		incr = 1;
1738
1739	case 'T':		/** find up to new char backward **/
1740	case 'F':		/** find new char backward **/
1741		vp->last_find = motion;
1742		if((vp->findchar=getrchar(vp))==ESC)
1743			return(1);
1744find_b:
1745		tcur_virt = cur_virt;
1746		count = vp->repeat;
1747		while( count-- )
1748		{
1749			while( incr*(tcur_virt+=incr) <= bound
1750				&& virtual[tcur_virt] != vp->findchar );
1751			if( incr*tcur_virt > bound )
1752			{
1753				return(0);
1754			}
1755		}
1756		if( fold(vp->last_find) == 'T' )
1757			tcur_virt -= incr;
1758		break;
1759
1760        case '%':
1761	{
1762		int nextmotion;
1763		int nextc;
1764		tcur_virt = cur_virt;
1765		while( tcur_virt <= last_virt
1766			&& strchr(paren_chars,virtual[tcur_virt])==(char*)0)
1767				tcur_virt++;
1768		if(tcur_virt > last_virt )
1769			return(0);
1770		nextc = virtual[tcur_virt];
1771		count = strchr(paren_chars,nextc)-paren_chars;
1772		if(count < 3)
1773		{
1774			incr = 1;
1775			bound = last_virt;
1776			nextmotion = paren_chars[count+3];
1777		}
1778		else
1779			nextmotion = paren_chars[count-3];
1780		count = 1;
1781		while(count >0 &&  incr*(tcur_virt+=incr) <= bound)
1782		{
1783		        if(virtual[tcur_virt] == nextmotion)
1784		        	count--;
1785		        else if(virtual[tcur_virt]==nextc)
1786		        	count++;
1787		}
1788		if(count)
1789			return(0);
1790		break;
1791	}
1792
1793	case 'W':
1794	case 'w':		/** forward word **/
1795		tcur_virt = cur_virt;
1796		forward(vp,vp->repeat, motion);
1797		if( tcur_virt == cur_virt )
1798			return(0);
1799		return(1);
1800
1801	default:
1802		return(0);
1803	}
1804	cur_virt = tcur_virt;
1805
1806	return(1);
1807}
1808
1809/*
1810 * print a string
1811 */
1812
1813static void pr_string(register Vi_t *vp, register const char *sp)
1814{
1815	/*** copy string sp ***/
1816	register char *ptr = editb.e_outptr;
1817	while(*sp)
1818		*ptr++ = *sp++;
1819	editb.e_outptr = ptr;
1820	return;
1821}
1822
1823/*{	PUTSTRING( column, nchars )
1824 *
1825 *	Put nchars starting at column of physical into the workspace
1826 * to be printed.
1827 *
1828}*/
1829
1830static void putstring(register Vi_t *vp,register int col, register int nchars)
1831{
1832	while( nchars-- )
1833		putchar(physical[col++]);
1834	return;
1835}
1836
1837/*{	REFRESH( mode )
1838 *
1839 *	This routine will refresh the crt so the physical image matches
1840 * the virtual image and display the proper window.
1841 *
1842 *	mode	= CONTROL, refresh in control mode, ie. leave cursor
1843 *			positioned at last char printed.
1844 *		= INPUT, refresh in input mode; leave cursor positioned
1845 *			after last char printed.
1846 *		= TRANSLATE, perform virtual to physical translation
1847 *			and adjust left margin only.
1848 *
1849 *		+-------------------------------+
1850 *		|   | |    virtual	  | |   |
1851 *		+-------------------------------+
1852 *		  cur_virt		last_virt
1853 *
1854 *		+-----------------------------------------------+
1855 *		|	  | |	        physical	 | |    |
1856 *		+-----------------------------------------------+
1857 *			cur_phys			last_phys
1858 *
1859 *				0			w_size - 1
1860 *				+-----------------------+
1861 *				| | |  window		|
1862 *				+-----------------------+
1863 *				cur_window = cur_phys - first_wind
1864}*/
1865
1866static void refresh(register Vi_t* vp, int mode)
1867{
1868	register int p;
1869	register int regb;
1870	register int first_w = vp->first_wind;
1871	int p_differ;
1872	int new_lw;
1873	int ncur_phys;
1874	int opflag;			/* search optimize flag */
1875
1876#	define	w	regb
1877#	define	v	regb
1878
1879	/*** find out if it's necessary to start translating at beginning ***/
1880
1881	if(lookahead>0)
1882	{
1883		p = previous[lookahead-1];
1884		if(p != ESC && p != '\n' && p != '\r')
1885			mode = TRANSLATE;
1886	}
1887	v = cur_virt;
1888#if SHOPT_EDPREDICT
1889	if(mode==INPUT && v>0 && virtual[0]=='#' && virtual[v]!='*')
1890	{
1891		int		n;
1892		virtual[last_virt+1] = 0;
1893#   if SHOPT_MULTIBYTE
1894		ed_external(virtual,(char*)virtual);
1895#   endif /* SHOPT_MULTIBYTE */
1896		n = ed_histgen(vp->ed,(char*)virtual);
1897#   if SHOPT_MULTIBYTE
1898		ed_internal((char*)virtual,virtual);
1899#   endif /* SHOPT_MULTIBYTE */
1900		if(vp->ed->hlist)
1901		{
1902			ed_histlist(vp->ed,n);
1903			pr_string(vp,Prompt);
1904			vp->ocur_virt = INVALID;
1905			ed_setcursor(vp->ed,physical,0,cur_phys,0);
1906		}
1907		else
1908			ed_ringbell();
1909	}
1910	else if(mode==INPUT && v<=1 && vp->ed->hlist)
1911		ed_histlist(vp->ed,0);
1912#endif /* SHOPT_EDPREDICT */
1913	if( v<vp->ocur_virt || vp->ocur_virt==INVALID
1914		|| ( v==vp->ocur_virt
1915			&& (!is_print(virtual[v]) || !is_print(vp->o_v_char))) )
1916	{
1917		opflag = 0;
1918		p = 0;
1919		v = 0;
1920	}
1921	else
1922	{
1923		opflag = 1;
1924		p = vp->ocur_phys;
1925		v = vp->ocur_virt;
1926		if( !is_print(virtual[v]) )
1927		{
1928			/*** avoid double ^'s ***/
1929			++p;
1930			++v;
1931		}
1932	}
1933	virtual[last_virt+1] = 0;
1934	ncur_phys = ed_virt_to_phys(vp->ed,virtual,physical,cur_virt,v,p);
1935	p = genlen(physical);
1936	if( --p < 0 )
1937		last_phys = 0;
1938	else
1939		last_phys = p;
1940
1941	/*** see if this was a translate only ***/
1942
1943	if( mode == TRANSLATE )
1944		return;
1945
1946	/*** adjust left margin if necessary ***/
1947
1948	if( ncur_phys<first_w || ncur_phys>=(first_w + w_size) )
1949	{
1950		cursor(vp,first_w);
1951		first_w = ncur_phys - (w_size>>1);
1952		if( first_w < 0 )
1953			first_w = 0;
1954		vp->first_wind = cur_phys = first_w;
1955	}
1956
1957	/*** attempt to optimize search somewhat to find ***/
1958	/*** out where physical and window images differ ***/
1959
1960	if( first_w==vp->ofirst_wind && ncur_phys>=vp->ocur_phys && opflag==1 )
1961	{
1962		p = vp->ocur_phys;
1963		w = p - first_w;
1964	}
1965	else
1966	{
1967		p = first_w;
1968		w = 0;
1969	}
1970
1971	for(; (p<=last_phys && w<=vp->last_wind); ++p, ++w)
1972	{
1973		if( window[w] != physical[p] )
1974			break;
1975	}
1976	p_differ = p;
1977
1978	if( (p>last_phys || p>=first_w+w_size) && w>vp->last_wind
1979		&& cur_virt==vp->ocur_virt )
1980	{
1981		/*** images are identical ***/
1982		return;
1983	}
1984
1985	/*** copy the physical image to the window image ***/
1986
1987	if( last_virt != INVALID )
1988	{
1989		while( p <= last_phys && w < w_size )
1990			window[w++] = physical[p++];
1991	}
1992	new_lw = w;
1993
1994	/*** erase trailing characters if needed ***/
1995
1996	while( w <= vp->last_wind )
1997		window[w++] = ' ';
1998	vp->last_wind = --w;
1999
2000	p = p_differ;
2001
2002	/*** move cursor to start of difference ***/
2003
2004	cursor(vp,p);
2005
2006	/*** and output difference ***/
2007
2008	w = p - first_w;
2009	while( w <= vp->last_wind )
2010		putchar(window[w++]);
2011
2012	cur_phys = w + first_w;
2013	vp->last_wind = --new_lw;
2014
2015	if( last_phys >= w_size )
2016	{
2017		if( first_w == 0 )
2018			vp->long_char = '>';
2019		else if( last_phys < (first_w+w_size) )
2020			vp->long_char = '<';
2021		else
2022			vp->long_char = '*';
2023	}
2024	else
2025		vp->long_char = ' ';
2026
2027	if( vp->long_line != vp->long_char )
2028	{
2029		/*** indicate lines longer than window ***/
2030		while( w++ < w_size )
2031		{
2032			putchar(' ');
2033			++cur_phys;
2034		}
2035		putchar(vp->long_char);
2036		++cur_phys;
2037		vp->long_line = vp->long_char;
2038	}
2039
2040	if(vp->ed->e_multiline &&  vp->ofirst_wind==INVALID && !vp->ed->e_nocrnl)
2041		ed_setcursor(vp->ed, physical, last_phys+1, last_phys+1, -1);
2042	vp->ed->e_nocrnl = 0;
2043	vp->ocur_phys = ncur_phys;
2044	vp->ocur_virt = cur_virt;
2045	vp->ofirst_wind = first_w;
2046
2047	if( mode==INPUT && cur_virt>INVALID )
2048		++ncur_phys;
2049
2050	cursor(vp,ncur_phys);
2051	ed_flush(vp->ed);
2052	return;
2053}
2054
2055/*{	REPLACE( char, increment )
2056 *
2057 *	Replace the cur_virt character with char.  This routine attempts
2058 * to avoid using refresh().
2059 *
2060 *	increment	= 1, increment cur_virt after replacement.
2061 *			= 0, leave cur_virt where it is.
2062 *
2063}*/
2064
2065static void replace(register Vi_t *vp, register int c, register int increment)
2066{
2067	register int cur_window;
2068
2069	if( cur_virt == INVALID )
2070	{
2071		/*** can't replace invalid cursor ***/
2072		ed_ringbell();
2073		return;
2074	}
2075	cur_window = cur_phys - vp->first_wind;
2076	if( vp->ocur_virt == INVALID || !is_print(c)
2077		|| !is_print(virtual[cur_virt])
2078		|| !is_print(vp->o_v_char)
2079#if SHOPT_MULTIBYTE
2080		|| !iswascii(c) || mbwidth(vp->o_v_char)>1
2081		|| !iswascii(virtual[cur_virt])
2082#endif /* SHOPT_MULTIBYTE */
2083		|| (increment && (cur_window==w_size-1)
2084			|| !is_print(virtual[cur_virt+1])) )
2085	{
2086		/*** must use standard refresh routine ***/
2087
2088		cdelete(vp,1, BAD);
2089		append(vp,c, APPEND);
2090		if( increment && cur_virt<last_virt )
2091			++cur_virt;
2092		refresh(vp,CONTROL);
2093	}
2094	else
2095	{
2096		virtual[cur_virt] = c;
2097		physical[cur_phys] = c;
2098		window[cur_window] = c;
2099		putchar(c);
2100		if(increment)
2101		{
2102			c = virtual[++cur_virt];
2103			++cur_phys;
2104		}
2105		else
2106		{
2107			putchar('\b');
2108		}
2109		vp->o_v_char = c;
2110		ed_flush(vp->ed);
2111	}
2112	return;
2113}
2114
2115/*{	RESTORE_V()
2116 *
2117 *	Restore the contents of virtual space from u_space.
2118 *
2119}*/
2120
2121static void restore_v(register Vi_t *vp)
2122{
2123	register int tmpcol;
2124	genchar tmpspace[MAXLINE];
2125
2126	if( vp->u_column == INVALID-1 )
2127	{
2128		/*** never saved anything ***/
2129		ed_ringbell();
2130		return;
2131	}
2132	gencpy(tmpspace, vp->u_space);
2133	tmpcol = vp->u_column;
2134	save_v(vp);
2135	gencpy(virtual, tmpspace);
2136	cur_virt = tmpcol;
2137	last_virt = genlen(tmpspace) - 1;
2138	vp->ocur_virt = MAXCHAR;	/** invalidate refresh optimization **/
2139	return;
2140}
2141
2142/*{	SAVE_LAST()
2143 *
2144 *	If the user has typed something, save it in last line.
2145 *
2146}*/
2147
2148static void save_last(register Vi_t* vp)
2149{
2150	register int i;
2151
2152	if( (i = cur_virt - first_virt + 1) > 0 )
2153	{
2154		/*** save last thing user typed ***/
2155		if(i >= MAXLINE)
2156			i = MAXLINE-1;
2157		genncpy(vp->lastline, (&virtual[first_virt]), i);
2158		vp->lastline[i] = '\0';
2159	}
2160	return;
2161}
2162
2163/*{	SAVE_V()
2164 *
2165 *	This routine will save the contents of virtual in u_space.
2166 *
2167}*/
2168
2169static void save_v(register Vi_t *vp)
2170{
2171	if(!inmacro)
2172	{
2173		virtual[last_virt + 1] = '\0';
2174		gencpy(vp->u_space, virtual);
2175		vp->u_column = cur_virt;
2176	}
2177	return;
2178}
2179
2180/*{	SEARCH( mode )
2181 *
2182 *	Search history file for regular expression.
2183 *
2184 *	mode	= '/'	require search string and search new to old
2185 *	mode	= '?'	require search string and search old to new
2186 *	mode	= 'N'	repeat last search in reverse direction
2187 *	mode	= 'n'	repeat last search
2188 *
2189}*/
2190
2191/*
2192 * search for <string> in the current command
2193 */
2194static int curline_search(Vi_t *vp, const char *string)
2195{
2196	register int len=strlen(string);
2197	register const char *dp,*cp=string, *dpmax;
2198#if SHOPT_MULTIBYTE
2199	ed_external(vp->u_space,(char*)vp->u_space);
2200#endif /* SHOPT_MULTIBYTE */
2201	for(dp=(char*)vp->u_space,dpmax=dp+strlen(dp)-len; dp<=dpmax; dp++)
2202	{
2203		if(*dp==*cp && memcmp(cp,dp,len)==0)
2204			return(dp-(char*)vp->u_space);
2205	}
2206#if SHOPT_MULTIBYTE
2207	ed_internal((char*)vp->u_space,vp->u_space);
2208#endif /* SHOPT_MULTIBYTE */
2209	return(-1);
2210}
2211
2212static int search(register Vi_t* vp,register int mode)
2213{
2214	register int new_direction;
2215	register int oldcurhline;
2216	register int i;
2217	Histloc_t  location;
2218
2219	if( vp->direction == -2 && mode != 'n')
2220		vp->direction = -1;
2221	if( mode == '/' || mode == '?')
2222	{
2223		/*** new search expression ***/
2224		del_line(vp,BAD);
2225		append(vp,mode, APPEND);
2226		refresh(vp,INPUT);
2227		first_virt = 1;
2228		getline(vp,SEARCH);
2229		first_virt = 0;
2230		virtual[last_virt + 1] = '\0';	/*** make null terminated ***/
2231		vp->direction = mode=='/' ? -1 : 1;
2232	}
2233
2234	if( cur_virt == INVALID )
2235	{
2236		/*** no operation ***/
2237		return(ABORT);
2238	}
2239
2240	if( cur_virt==0 ||  fold(mode)=='N' )
2241	{
2242		/*** user wants repeat of last search ***/
2243		del_line(vp,BAD);
2244		strcpy( ((char*)virtual)+1, lsearch);
2245#if SHOPT_MULTIBYTE
2246		*((char*)virtual) = '/';
2247		ed_internal((char*)virtual,virtual);
2248#endif /* SHOPT_MULTIBYTE */
2249	}
2250
2251	if( mode == 'N' )
2252		new_direction = -vp->direction;
2253	else
2254		new_direction = vp->direction;
2255
2256
2257	/*** now search ***/
2258
2259	oldcurhline = curhline;
2260#if SHOPT_MULTIBYTE
2261	ed_external(virtual,(char*)virtual);
2262#endif /* SHOPT_MULTIBYTE */
2263	if(mode=='?' && (i=curline_search(vp,((char*)virtual)+1))>=0)
2264	{
2265		location.hist_command = curhline;
2266		location.hist_char = i;
2267	}
2268	else
2269	{
2270		i = INVALID;
2271		if( new_direction==1 && curhline >= histmax )
2272			curhline = histmin + 1;
2273		location = hist_find(shgd->hist_ptr,((char*)virtual)+1, curhline, 1, new_direction);
2274	}
2275	cur_virt = i;
2276	strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE);
2277	if( (curhline=location.hist_command) >=0 )
2278	{
2279		vp->ocur_virt = INVALID;
2280		return(GOOD);
2281	}
2282
2283	/*** could not find matching line ***/
2284
2285	curhline = oldcurhline;
2286	return(BAD);
2287}
2288
2289/*{	SYNC_CURSOR()
2290 *
2291 *	This routine will move the physical cursor to the same
2292 * column as the virtual cursor.
2293 *
2294}*/
2295
2296static void sync_cursor(register Vi_t *vp)
2297{
2298	register int p;
2299	register int v;
2300	register int c;
2301	int new_phys;
2302
2303	if( cur_virt == INVALID )
2304		return;
2305
2306	/*** find physical col that corresponds to virtual col ***/
2307
2308	new_phys = 0;
2309	if(vp->first_wind==vp->ofirst_wind && cur_virt>vp->ocur_virt && vp->ocur_virt!=INVALID)
2310	{
2311		/*** try to optimize search a little ***/
2312		p = vp->ocur_phys + 1;
2313#if SHOPT_MULTIBYTE
2314		while(physical[p]==MARKER)
2315			p++;
2316#endif /* SHOPT_MULTIBYTE */
2317		v = vp->ocur_virt + 1;
2318	}
2319	else
2320	{
2321		p = 0;
2322		v = 0;
2323	}
2324	for(; v <= last_virt; ++p, ++v)
2325	{
2326#if SHOPT_MULTIBYTE
2327		int d;
2328		c = virtual[v];
2329		if((d = mbwidth(c)) > 1)
2330		{
2331			if( v != cur_virt )
2332				p += (d-1);
2333		}
2334		else if(!iswprint(c))
2335#else
2336		c = virtual[v];
2337		if(!isprint(c))
2338#endif	/* SHOPT_MULTIBYTE */
2339		{
2340			if( c == '\t' )
2341			{
2342				p -= ((p+editb.e_plen)%TABSIZE);
2343				p += (TABSIZE-1);
2344			}
2345			else
2346			{
2347				++p;
2348			}
2349		}
2350		if( v == cur_virt )
2351		{
2352			new_phys = p;
2353			break;
2354		}
2355	}
2356
2357	if( new_phys < vp->first_wind || new_phys >= vp->first_wind + w_size )
2358	{
2359		/*** asked to move outside of window ***/
2360
2361		window[0] = '\0';
2362		refresh(vp,CONTROL);
2363		return;
2364	}
2365
2366	cursor(vp,new_phys);
2367	ed_flush(vp->ed);
2368	vp->ocur_phys = cur_phys;
2369	vp->ocur_virt = cur_virt;
2370	vp->o_v_char = virtual[vp->ocur_virt];
2371
2372	return;
2373}
2374
2375/*{	TEXTMOD( command, mode )
2376 *
2377 *	Modify text operations.
2378 *
2379 *	mode != 0, repeat previous operation
2380 *
2381}*/
2382
2383static int textmod(register Vi_t *vp,register int c, int mode)
2384{
2385	register int i;
2386	register genchar *p = vp->lastline;
2387	register int trepeat = vp->repeat;
2388	genchar *savep;
2389
2390	if(mode && (fold(vp->lastmotion)=='F' || fold(vp->lastmotion)=='T'))
2391		vp->lastmotion = ';';
2392
2393	if( fold(c) == 'P' )
2394	{
2395		/*** change p from lastline to yankbuf ***/
2396		p = yankbuf;
2397	}
2398
2399addin:
2400	switch( c )
2401	{
2402			/***** Input commands *****/
2403
2404#if KSHELL
2405        case '\t':
2406		if(vp->ed->e_tabcount!=1)
2407			return(BAD);
2408		c = '=';
2409	case '*':		/** do file name expansion in place **/
2410	case '\\':		/** do file name completion in place **/
2411		if( cur_virt == INVALID )
2412			return(BAD);
2413	case '=':		/** list file name expansions **/
2414		save_v(vp);
2415		i = last_virt;
2416		++last_virt;
2417		mode = cur_virt-1;
2418		virtual[last_virt] = 0;
2419		if(ed_expand(vp->ed,(char*)virtual, &cur_virt, &last_virt, c, vp->repeat_set?vp->repeat:-1)<0)
2420		{
2421			if(vp->ed->e_tabcount)
2422			{
2423				vp->ed->e_tabcount=2;
2424				ed_ungetchar(vp->ed,'\t');
2425				--last_virt;
2426				return(APPEND);
2427			}
2428			last_virt = i;
2429			ed_ringbell();
2430		}
2431		else if(c == '=' && !vp->repeat_set)
2432		{
2433			last_virt = i;
2434			vp->nonewline++;
2435			ed_ungetchar(vp->ed,cntl('L'));
2436			return(GOOD);
2437		}
2438		else
2439		{
2440			--cur_virt;
2441			--last_virt;
2442			vp->ocur_virt = MAXCHAR;
2443			if(c=='=' || (mode<cur_virt && (virtual[cur_virt]==' ' || virtual[cur_virt]=='/')))
2444				vp->ed->e_tabcount = 0;
2445			return(APPEND);
2446		}
2447		break;
2448
2449	case '@':		/** macro expansion **/
2450		if( mode )
2451			c = vp->lastmacro;
2452		else
2453			if((c=getrchar(vp))==ESC)
2454				return(GOOD);
2455		if(!inmacro)
2456			vp->lastmacro = c;
2457		if(ed_macro(vp->ed,c))
2458		{
2459			save_v(vp);
2460			inmacro++;
2461			return(GOOD);
2462		}
2463		ed_ringbell();
2464		return(BAD);
2465
2466#endif	/* KSHELL */
2467	case '_':		/** append last argument of prev command **/
2468		save_v(vp);
2469		{
2470			genchar tmpbuf[MAXLINE];
2471			if(vp->repeat_set==0)
2472				vp->repeat = -1;
2473			p = (genchar*)hist_word((char*)tmpbuf,MAXLINE,vp->repeat);
2474			if(p==0)
2475			{
2476				ed_ringbell();
2477				break;
2478			}
2479#if SHOPT_MULTIBYTE
2480			ed_internal((char*)p,tmpbuf);
2481			p = tmpbuf;
2482#endif /* SHOPT_MULTIBYTE */
2483			i = ' ';
2484			do
2485			{
2486				append(vp,i,APPEND);
2487			}
2488			while(i = *p++);
2489			return(APPEND);
2490		}
2491
2492	case 'A':		/** append to end of line **/
2493		cur_virt = last_virt;
2494		sync_cursor(vp);
2495
2496	case 'a':		/** append **/
2497		if( fold(mode) == 'A' )
2498		{
2499			c = 'p';
2500			goto addin;
2501		}
2502		save_v(vp);
2503		if( cur_virt != INVALID )
2504		{
2505			first_virt = cur_virt + 1;
2506			cursor(vp,cur_phys + 1);
2507			ed_flush(vp->ed);
2508		}
2509		return(APPEND);
2510
2511	case 'I':		/** insert at beginning of line **/
2512		cur_virt = first_virt;
2513		sync_cursor(vp);
2514
2515	case 'i':		/** insert **/
2516		if( fold(mode) == 'I' )
2517		{
2518			c = 'P';
2519			goto addin;
2520		}
2521		save_v(vp);
2522		if( cur_virt != INVALID )
2523 		{
2524 			vp->o_v_char = virtual[cur_virt];
2525			first_virt = cur_virt--;
2526  		}
2527		return(INSERT);
2528
2529	case 'C':		/** change to eol **/
2530		c = '$';
2531		goto chgeol;
2532
2533	case 'c':		/** change **/
2534		if( mode )
2535			c = vp->lastmotion;
2536		else
2537			c = getcount(vp,ed_getchar(vp->ed,-1));
2538chgeol:
2539		vp->lastmotion = c;
2540		if( c == 'c' )
2541		{
2542			del_line(vp,GOOD);
2543			return(APPEND);
2544		}
2545
2546		if(!delmotion(vp, c, 'c'))
2547			return(BAD);
2548
2549		if( mode == 'c' )
2550		{
2551			c = 'p';
2552			trepeat = 1;
2553			goto addin;
2554		}
2555		first_virt = cur_virt + 1;
2556		return(APPEND);
2557
2558	case 'D':		/** delete to eol **/
2559		c = '$';
2560		goto deleol;
2561
2562	case 'd':		/** delete **/
2563		if( mode )
2564			c = vp->lastmotion;
2565		else
2566			c = getcount(vp,ed_getchar(vp->ed,-1));
2567deleol:
2568		vp->lastmotion = c;
2569		if( c == 'd' )
2570		{
2571			del_line(vp,GOOD);
2572			break;
2573		}
2574		if(!delmotion(vp, c, 'd'))
2575			return(BAD);
2576		if( cur_virt < last_virt )
2577			++cur_virt;
2578		break;
2579
2580	case 'P':
2581		if( p[0] == '\0' )
2582			return(BAD);
2583		if( cur_virt != INVALID )
2584		{
2585			i = virtual[cur_virt];
2586			if(!is_print(i))
2587				vp->ocur_virt = INVALID;
2588			--cur_virt;
2589		}
2590
2591	case 'p':		/** print **/
2592		if( p[0] == '\0' )
2593			return(BAD);
2594
2595		if( mode != 's' && mode != 'c' )
2596		{
2597			save_v(vp);
2598			if( c == 'P' )
2599			{
2600				/*** fix stored cur_virt ***/
2601				++vp->u_column;
2602			}
2603		}
2604		if( mode == 'R' )
2605			mode = REPLACE;
2606		else
2607			mode = APPEND;
2608		savep = p;
2609		for(i=0; i<trepeat; ++i)
2610		{
2611			while(c= *p++)
2612				append(vp,c,mode);
2613			p = savep;
2614		}
2615		break;
2616
2617	case 'R':		/* Replace many chars **/
2618		if( mode == 'R' )
2619		{
2620			c = 'P';
2621			goto addin;
2622		}
2623		save_v(vp);
2624		if( cur_virt != INVALID )
2625			first_virt = cur_virt;
2626		return(REPLACE);
2627
2628	case 'r':		/** replace **/
2629		if( mode )
2630			c = *p;
2631		else
2632			if((c=getrchar(vp))==ESC)
2633				return(GOOD);
2634		*p = c;
2635		save_v(vp);
2636		while(trepeat--)
2637			replace(vp,c, trepeat!=0);
2638		return(GOOD);
2639
2640	case 'S':		/** Substitute line - cc **/
2641		c = 'c';
2642		goto chgeol;
2643
2644	case 's':		/** substitute **/
2645		save_v(vp);
2646		cdelete(vp,vp->repeat, BAD);
2647		if( mode )
2648		{
2649			c = 'p';
2650			trepeat = 1;
2651			goto addin;
2652		}
2653		first_virt = cur_virt + 1;
2654		return(APPEND);
2655
2656	case 'Y':		/** Yank to end of line **/
2657		c = '$';
2658		goto yankeol;
2659
2660	case 'y':		/** yank thru motion **/
2661		if( mode )
2662			c = vp->lastmotion;
2663		else
2664			c = getcount(vp,ed_getchar(vp->ed,-1));
2665yankeol:
2666		vp->lastmotion = c;
2667		if( c == 'y' )
2668		{
2669			gencpy(yankbuf, virtual);
2670		}
2671		else if(!delmotion(vp, c, 'y'))
2672		{
2673			return(BAD);
2674		}
2675		break;
2676
2677	case 'x':		/** delete repeat chars forward - dl **/
2678		c = 'l';
2679		goto deleol;
2680
2681	case 'X':		/** delete repeat chars backward - dh **/
2682		c = 'h';
2683		goto deleol;
2684
2685	case '~':		/** invert case and advance **/
2686		if( cur_virt != INVALID )
2687		{
2688			save_v(vp);
2689			i = INVALID;
2690			while(trepeat-->0 && i!=cur_virt)
2691			{
2692				i = cur_virt;
2693				c = virtual[cur_virt];
2694#if SHOPT_MULTIBYTE
2695				if((c&~STRIP)==0)
2696#endif /* SHOPT_MULTIBYTE */
2697				if( isupper(c) )
2698					c = tolower(c);
2699				else if( islower(c) )
2700					c = toupper(c);
2701				replace(vp,c, 1);
2702			}
2703			return(GOOD);
2704		}
2705		else
2706			return(BAD);
2707
2708	default:
2709		return(BAD);
2710	}
2711	refresh(vp,CONTROL);
2712	return(GOOD);
2713}
2714
2715
2716#if SHOPT_MULTIBYTE
2717    static int _isalph(register int v)
2718    {
2719#ifdef _lib_iswalnum
2720	return(iswalnum(v) || v=='_');
2721#else
2722	return((v&~STRIP) || isalnum(v) || v=='_');
2723#endif
2724    }
2725
2726
2727    static int _isblank(register int v)
2728    {
2729	return((v&~STRIP)==0 && isspace(v));
2730    }
2731
2732    static int _ismetach(register int v)
2733    {
2734	return((v&~STRIP)==0 && ismeta(v));
2735    }
2736
2737#endif	/* SHOPT_MULTIBYTE */
2738
2739/*
2740 * get a character, after ^V processing
2741 */
2742static int getrchar(register Vi_t *vp)
2743{
2744	register int c;
2745	if((c=ed_getchar(vp->ed,1))== usrlnext)
2746		c = ed_getchar(vp->ed,2);
2747	return(c);
2748}
2749