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