1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1982-2010 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;		/* 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) || 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(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		return(last_virt);
625	}
626	else
627		return(-1);
628}
629
630
631/*{	APPEND( char, mode )
632 *
633 *	This routine will append char after cur_virt in the virtual image.
634 * mode	=	APPEND, shift chars right before appending
635 *		REPLACE, replace char if possible
636 *
637}*/
638
639static void append(Vi_t *vp,int c, int mode)
640{
641	register int i,j;
642
643	if( last_virt<max_col && last_phys<max_col )
644	{
645		if( mode==APPEND || (cur_virt==last_virt  && last_virt>=0))
646		{
647			j = (cur_virt>=0?cur_virt:0);
648			for(i = ++last_virt;  i > j; --i)
649				virtual[i] = virtual[i-1];
650		}
651		virtual[++cur_virt] = c;
652	}
653	else
654		ed_ringbell();
655	return;
656}
657
658/*{	BACKWORD( nwords, cmd )
659 *
660 *	This routine will position cur_virt at the nth previous word.
661 *
662}*/
663
664static void backword(Vi_t *vp,int nwords, register int cmd)
665{
666	register int tcur_virt = cur_virt;
667	while( nwords-- && tcur_virt > first_virt )
668	{
669		if( !isblank(tcur_virt) && isblank(tcur_virt-1)
670			&& tcur_virt>first_virt )
671			--tcur_virt;
672		else if(cmd != 'B')
673		{
674			register int last = isalph(tcur_virt-1);
675			register int cur = isalph(tcur_virt);
676			if((!cur && last) || (cur && !last))
677				--tcur_virt;
678		}
679		while( isblank(tcur_virt) && tcur_virt>=first_virt )
680			--tcur_virt;
681		if( cmd == 'B' )
682		{
683			while( !isblank(tcur_virt) && tcur_virt>=first_virt )
684				--tcur_virt;
685		}
686		else
687		{
688			if(isalph(tcur_virt))
689				while( isalph(tcur_virt) && tcur_virt>=first_virt )
690					--tcur_virt;
691			else
692				while( !isalph(tcur_virt) && !isblank(tcur_virt)
693					&& tcur_virt>=first_virt )
694					--tcur_virt;
695		}
696		cur_virt = ++tcur_virt;
697	}
698	return;
699}
700
701/*{	CNTLMODE()
702 *
703 *	This routine implements the vi command subset.
704 *	The cursor will always be positioned at the char of interest.
705 *
706}*/
707
708static int cntlmode(Vi_t *vp)
709{
710	register int c;
711	register int i;
712	genchar tmp_u_space[MAXLINE];	/* temporary u_space */
713	genchar *real_u_space;		/* points to real u_space */
714	int tmp_u_column = INVALID;	/* temporary u_column */
715	int was_inmacro;
716
717	if(!vp->U_saved)
718	{
719		/*** save virtual image if never done before ***/
720		virtual[last_virt+1] = '\0';
721		gencpy(vp->U_space, virtual);
722		vp->U_saved = 1;
723	}
724
725	save_last(vp);
726
727	real_u_space = vp->u_space;
728	curhline = histmax;
729	first_virt = 0;
730	vp->repeat = 1;
731	if( cur_virt > INVALID )
732	{
733		/*** make sure cursor is at the last char ***/
734		sync_cursor(vp);
735	}
736
737	/*** Read control char until something happens to cause a ***/
738	/* return to APPEND/REPLACE mode	*/
739
740	while( c=ed_getchar(vp->ed,-1) )
741	{
742		vp->repeat_set = 0;
743		was_inmacro = inmacro;
744		if( c == '0' )
745		{
746			/*** move to leftmost column ***/
747			cur_virt = 0;
748			sync_cursor(vp);
749			continue;
750		}
751
752		if( digit(c) )
753		{
754			c = getcount(vp,c);
755			if( c == '.' )
756				vp->lastrepeat = vp->repeat;
757		}
758
759		/*** see if it's a move cursor command ***/
760
761		if(mvcursor(vp,c))
762		{
763			sync_cursor(vp);
764			vp->repeat = 1;
765			continue;
766		}
767
768		/*** see if it's a repeat of the last command ***/
769
770		if( c == '.' )
771		{
772			c = vp->last_cmd;
773			vp->repeat = vp->lastrepeat;
774			i = textmod(vp,c, c);
775		}
776		else
777		{
778			i = textmod(vp,c, 0);
779		}
780
781		/*** see if it's a text modification command ***/
782
783		switch(i)
784		{
785		case BAD:
786			break;
787
788		default:		/** input mode **/
789			if(!was_inmacro)
790			{
791				vp->last_cmd = c;
792				vp->lastrepeat = vp->repeat;
793			}
794			vp->repeat = 1;
795			if( i == GOOD )
796				continue;
797			return(i);
798		}
799
800		switch( c )
801		{
802			/***** Other stuff *****/
803
804		case cntl('L'):		/** Redraw line **/
805			/*** print the prompt and ***/
806			/* force a total refresh */
807			if(vp->nonewline==0 && !vp->ed->e_nocrnl)
808				putchar('\n');
809			vp->nonewline = 0;
810			pr_string(vp,Prompt);
811			window[0] = '\0';
812			cur_phys = vp->first_wind;
813			vp->ofirst_wind = INVALID;
814			vp->long_line = ' ';
815			break;
816
817		case cntl('V'):
818		{
819			register const char *p = fmtident(e_version);
820			save_v(vp);
821			del_line(vp,BAD);
822			while(c = *p++)
823				append(vp,c,APPEND);
824			refresh(vp,CONTROL);
825			ed_getchar(vp->ed,-1);
826			restore_v(vp);
827			break;
828		}
829
830		case '/':		/** Search **/
831		case '?':
832		case 'N':
833		case 'n':
834			save_v(vp);
835			switch( search(vp,c) )
836			{
837			case GOOD:
838				/*** force a total refresh ***/
839				window[0] = '\0';
840				goto newhist;
841
842			case BAD:
843				/*** no match ***/
844					ed_ringbell();
845				/* FALLTHROUGH */
846
847			default:
848				if( vp->u_column == INVALID )
849					del_line(vp,BAD);
850				else
851					restore_v(vp);
852				break;
853			}
854			break;
855
856		case 'j':		/** get next command **/
857		case '+':		/** get next command **/
858			curhline += vp->repeat;
859			if( curhline > histmax )
860			{
861				curhline = histmax;
862				goto ringbell;
863			}
864			else if(curhline==histmax && tmp_u_column!=INVALID )
865			{
866				vp->u_space = tmp_u_space;
867				vp->u_column = tmp_u_column;
868				restore_v(vp);
869				vp->u_space = real_u_space;
870				break;
871			}
872			save_v(vp);
873			cur_virt = INVALID;
874			goto newhist;
875
876		case 'k':		/** get previous command **/
877		case '-':		/** get previous command **/
878			if( curhline == histmax )
879			{
880				vp->u_space = tmp_u_space;
881				i = vp->u_column;
882				save_v(vp);
883				vp->u_space = real_u_space;
884				tmp_u_column = vp->u_column;
885				vp->u_column = i;
886			}
887
888			curhline -= vp->repeat;
889			if( curhline <= histmin )
890			{
891				curhline += vp->repeat;
892				goto ringbell;
893			}
894			save_v(vp);
895			cur_virt = INVALID;
896		newhist:
897			if(curhline!=histmax || cur_virt==INVALID)
898				hist_copy((char*)virtual, MAXLINE, curhline,-1);
899			else
900			{
901				strcpy((char*)virtual,(char*)vp->u_space);
902#if SHOPT_MULTIBYTE
903				ed_internal((char*)vp->u_space,vp->u_space);
904#endif /* SHOPT_MULTIBYTE */
905			}
906#if SHOPT_MULTIBYTE
907			ed_internal((char*)virtual,virtual);
908#endif /* SHOPT_MULTIBYTE */
909			if((last_virt=genlen(virtual)-1) >= 0  && cur_virt == INVALID)
910				cur_virt = 0;
911			break;
912
913
914		case 'u':		/** undo the last thing done **/
915			restore_v(vp);
916			break;
917
918		case 'U':		/** Undo everything **/
919			save_v(vp);
920			if( virtual[0] == '\0' )
921				goto ringbell;
922			else
923			{
924				gencpy(virtual, vp->U_space);
925				last_virt = genlen(vp->U_space) - 1;
926				cur_virt = 0;
927			}
928			break;
929
930#if KSHELL
931		case 'v':
932			if(vp->repeat_set==0)
933				goto vcommand;
934#endif /* KSHELL */
935			/* FALLTHROUGH */
936
937		case 'G':		/** goto command repeat **/
938			if(vp->repeat_set==0)
939				vp->repeat = histmin+1;
940			if( vp->repeat <= histmin || vp->repeat > histmax )
941			{
942				goto ringbell;
943			}
944			curhline = vp->repeat;
945			save_v(vp);
946			if(c == 'G')
947			{
948				cur_virt = INVALID;
949				goto newhist;
950			}
951
952#if KSHELL
953		vcommand:
954			if(ed_fulledit(vp->ed)==GOOD)
955				return(BIGVI);
956			else
957				goto ringbell;
958#endif	/* KSHELL */
959
960		case '#':	/** insert(delete) # to (no)comment command **/
961			if( cur_virt != INVALID )
962			{
963				register genchar *p = &virtual[last_virt+1];
964				*p = 0;
965				/*** see whether first char is comment char ***/
966				c = (virtual[0]=='#');
967				while(p-- >= virtual)
968				{
969					if(*p=='\n' || p<virtual)
970					{
971						if(c) /* delete '#' */
972						{
973							if(p[1]=='#')
974							{
975								last_virt--;
976								gencpy(p+1,p+2);
977							}
978						}
979						else
980						{
981							cur_virt = p-virtual;
982							append(vp,'#', APPEND);
983						}
984					}
985				}
986				if(c)
987				{
988					curhline = histmax;
989					cur_virt = 0;
990					break;
991				}
992				refresh(vp,INPUT);
993			}
994			/* FALLTHROUGH */
995
996		case '\n':		/** send to shell **/
997			return(ENTER);
998
999	        case ESC:
1000			/* don't ring bell if next char is '[' */
1001			if(!lookahead)
1002			{
1003				char x;
1004				if(sfpkrd(editb.e_fd,&x,1,'\r',400L,-1)>0)
1005					ed_ungetchar(vp->ed,x);
1006			}
1007			if(lookahead)
1008			{
1009				ed_ungetchar(vp->ed,c=ed_getchar(vp->ed,1));
1010				if(c=='[')
1011				{
1012					vp->repeat = 1;
1013					continue;
1014				}
1015			}
1016			/* FALLTHROUGH */
1017		default:
1018		ringbell:
1019			ed_ringbell();
1020			vp->repeat = 1;
1021			continue;
1022		}
1023
1024		refresh(vp,CONTROL);
1025		vp->repeat = 1;
1026	}
1027/* NOTREACHED */
1028	return(0);
1029}
1030
1031/*{	CURSOR( new_current_physical )
1032 *
1033 *	This routine will position the virtual cursor at
1034 * physical column x in the window.
1035 *
1036}*/
1037
1038static void cursor(Vi_t *vp,register int x)
1039{
1040#if SHOPT_MULTIBYTE
1041	while(physical[x]==MARKER)
1042		x++;
1043#endif /* SHOPT_MULTIBYTE */
1044	cur_phys = ed_setcursor(vp->ed, physical, cur_phys,x,vp->first_wind);
1045}
1046
1047/*{	DELETE( nchars, mode )
1048 *
1049 *	Delete nchars from the virtual space and leave cur_virt positioned
1050 * at cur_virt-1.
1051 *
1052 *	If mode	= 'c', do not save the characters deleted
1053 *		= 'd', save them in yankbuf and delete.
1054 *		= 'y', save them in yankbuf but do not delete.
1055 *
1056}*/
1057
1058static void cdelete(Vi_t *vp,register int nchars, int mode)
1059{
1060	register int i;
1061	register genchar *cp;
1062
1063	if( cur_virt < first_virt )
1064	{
1065		ed_ringbell();
1066		return;
1067	}
1068	if( nchars > 0 )
1069	{
1070		cp = virtual+cur_virt;
1071		vp->o_v_char = cp[0];
1072		if( (cur_virt-- + nchars) > last_virt )
1073		{
1074			/*** set nchars to number actually deleted ***/
1075			nchars = last_virt - cur_virt;
1076		}
1077
1078		/*** save characters to be deleted ***/
1079
1080		if( mode != 'c' )
1081		{
1082			i = cp[nchars];
1083			cp[nchars] = 0;
1084			gencpy(yankbuf,cp);
1085			cp[nchars] = i;
1086		}
1087
1088		/*** now delete these characters ***/
1089
1090		if( mode != 'y' )
1091		{
1092			gencpy(cp,cp+nchars);
1093			last_virt -= nchars;
1094		}
1095	}
1096	return;
1097}
1098
1099/*{	DEL_LINE( mode )
1100 *
1101 *	This routine will delete the line.
1102 *	mode = GOOD, do a save_v()
1103 *
1104}*/
1105static void del_line(register Vi_t *vp, int mode)
1106{
1107	if( last_virt == INVALID )
1108		return;
1109
1110	if( mode == GOOD )
1111		save_v(vp);
1112
1113	cur_virt = 0;
1114	first_virt = 0;
1115	cdelete(vp,last_virt+1, BAD);
1116	refresh(vp,CONTROL);
1117
1118	cur_virt = INVALID;
1119	cur_phys = 0;
1120	vp->findchar = INVALID;
1121	last_phys = INVALID;
1122	last_virt = INVALID;
1123	vp->last_wind = INVALID;
1124	vp->first_wind = 0;
1125	vp->o_v_char = '\0';
1126	vp->ocur_phys = 0;
1127	vp->ocur_virt = MAXCHAR;
1128	vp->ofirst_wind = 0;
1129	window[0] = '\0';
1130	return;
1131}
1132
1133/*{	DELMOTION( motion, mode )
1134 *
1135 *	Delete thru motion.
1136 *
1137 *	mode	= 'd', save deleted characters, delete
1138 *		= 'c', do not save characters, change
1139 *		= 'y', save characters, yank
1140 *
1141 *	Returns 1 if operation successful; else 0.
1142 *
1143}*/
1144
1145static int delmotion(Vi_t *vp,int motion, int mode)
1146{
1147	register int begin, end, delta;
1148	/* the following saves a register */
1149
1150	if( cur_virt == INVALID )
1151		return(0);
1152	if( mode != 'y' )
1153		save_v(vp);
1154	begin = cur_virt;
1155
1156	/*** fake out the motion routines by appending a blank ***/
1157
1158	virtual[++last_virt] = ' ';
1159	end = mvcursor(vp,motion);
1160	virtual[last_virt--] = 0;
1161	if(!end)
1162		return(0);
1163
1164	end = cur_virt;
1165	if( mode=='c' && end>begin && strchr("wW", motion) )
1166	{
1167		/*** called by change operation, user really expects ***/
1168		/* the effect of the eE commands, so back up to end of word */
1169		while( end>begin && isblank(end-1) )
1170			--end;
1171		if( end == begin )
1172			++end;
1173	}
1174
1175	delta = end - begin;
1176	if( delta >= 0 )
1177	{
1178		cur_virt = begin;
1179		if( strchr("eE;,TtFf%", motion) )
1180			++delta;
1181	}
1182	else
1183	{
1184		delta = -delta + (motion=='%');
1185	}
1186
1187	cdelete(vp,delta, mode);
1188	if( mode == 'y' )
1189		cur_virt = begin;
1190	return(1);
1191}
1192
1193
1194/*{	ENDWORD( nwords, cmd )
1195 *
1196 *	This routine will move cur_virt to the end of the nth word.
1197 *
1198}*/
1199
1200static void endword(Vi_t *vp, int nwords, register int cmd)
1201{
1202	register int tcur_virt = cur_virt;
1203	while( nwords-- )
1204	{
1205		if( !isblank(tcur_virt) && tcur_virt<=last_virt )
1206			++tcur_virt;
1207		while( isblank(tcur_virt) && tcur_virt<=last_virt )
1208			++tcur_virt;
1209		if( cmd == 'E' )
1210		{
1211			while( !isblank(tcur_virt) && tcur_virt<=last_virt )
1212				++tcur_virt;
1213		}
1214		else
1215		{
1216			if( isalph(tcur_virt) )
1217				while( isalph(tcur_virt) && tcur_virt<=last_virt )
1218					++tcur_virt;
1219			else
1220				while( !isalph(tcur_virt) && !isblank(tcur_virt)
1221					&& tcur_virt<=last_virt )
1222					++tcur_virt;
1223		}
1224		if( tcur_virt > first_virt )
1225			tcur_virt--;
1226	}
1227	cur_virt = tcur_virt;
1228	return;
1229}
1230
1231/*{	FORWARD( nwords, cmd )
1232 *
1233 *	This routine will move cur_virt forward to the next nth word.
1234 *
1235}*/
1236
1237static void forward(Vi_t *vp,register int nwords, int cmd)
1238{
1239	register int tcur_virt = cur_virt;
1240	while( nwords-- )
1241	{
1242		if( cmd == 'W' )
1243		{
1244			while( !isblank(tcur_virt) && tcur_virt < last_virt )
1245				++tcur_virt;
1246		}
1247		else
1248		{
1249			if( isalph(tcur_virt) )
1250			{
1251				while( isalph(tcur_virt) && tcur_virt<last_virt )
1252					++tcur_virt;
1253			}
1254			else
1255			{
1256				while( !isalph(tcur_virt) && !isblank(tcur_virt)
1257					&& tcur_virt < last_virt )
1258					++tcur_virt;
1259			}
1260		}
1261		while( isblank(tcur_virt) && tcur_virt < last_virt )
1262			++tcur_virt;
1263	}
1264	cur_virt = tcur_virt;
1265	return;
1266}
1267
1268
1269
1270/*{	GETCOUNT(c)
1271 *
1272 *	Set repeat to the user typed number and return the terminating
1273 * character.
1274 *
1275}*/
1276
1277static int getcount(register Vi_t *vp,register int c)
1278{
1279	register int i;
1280
1281	/*** get any repeat count ***/
1282
1283	if( c == '0' )
1284		return(c);
1285
1286	vp->repeat_set++;
1287	i = 0;
1288	while( digit(c) )
1289	{
1290		i = i*10 + c - '0';
1291		c = ed_getchar(vp->ed,-1);
1292	}
1293
1294	if( i > 0 )
1295		vp->repeat *= i;
1296	return(c);
1297}
1298
1299
1300/*{	GETLINE( mode )
1301 *
1302 *	This routine will fetch a line.
1303 *	mode	= APPEND, allow escape to cntlmode subroutine
1304 *		  appending characters.
1305 *		= REPLACE, allow escape to cntlmode subroutine
1306 *		  replacing characters.
1307 *		= SEARCH, no escape allowed
1308 *		= ESC, enter control mode immediately
1309 *
1310 *	The cursor will always be positioned after the last
1311 * char printed.
1312 *
1313 *	This routine returns when cr, nl, or (eof in column 0) is
1314 * received (column 0 is the first char position).
1315 *
1316}*/
1317
1318static void getline(register Vi_t* vp,register int mode)
1319{
1320	register int c;
1321	register int tmp;
1322	int	max_virt=0, last_save=0;
1323	genchar saveline[MAXLINE];
1324
1325	vp->addnl = 1;
1326
1327	if( mode == ESC )
1328	{
1329		/*** go directly to control mode ***/
1330		goto escape;
1331	}
1332
1333	for(;;)
1334	{
1335		if( (c=ed_getchar(vp->ed,mode==SEARCH?1:-2)) == usreof )
1336			c = UEOF;
1337		else if( c == usrerase )
1338			c = UERASE;
1339		else if( c == usrkill )
1340			c = UKILL;
1341		else if( c == editb.e_werase )
1342			c = UWERASE;
1343		else if( c == usrlnext )
1344			c = ULNEXT;
1345
1346		if( c == ULNEXT)
1347		{
1348			/*** implement ^V to escape next char ***/
1349			c = ed_getchar(vp->ed,2);
1350			append(vp,c, mode);
1351			refresh(vp,INPUT);
1352			continue;
1353		}
1354
1355		switch( c )
1356		{
1357		case ESC:		/** enter control mode **/
1358			if(!sh_isoption(SH_VI))
1359			{
1360				append(vp,c, mode);
1361				break;
1362			}
1363			if( mode == SEARCH )
1364			{
1365				ed_ringbell();
1366				continue;
1367			}
1368			else
1369			{
1370	escape:
1371				if( mode == REPLACE )
1372				{
1373					c = max_virt-cur_virt;
1374					if(c > 0 && last_save>=cur_virt)
1375					{
1376						genncpy((&virtual[cur_virt]),&saveline[cur_virt],c);
1377						if(last_virt>=last_save)
1378							last_virt=last_save-1;
1379						refresh(vp,INPUT);
1380					}
1381					--cur_virt;
1382				}
1383				tmp = cntlmode(vp);
1384				if( tmp == ENTER || tmp == BIGVI )
1385				{
1386#if SHOPT_MULTIBYTE
1387					vp->bigvi = (tmp==BIGVI);
1388#endif /* SHOPT_MULTIBYTE */
1389					return;
1390				}
1391				if( tmp == INSERT )
1392				{
1393					mode = APPEND;
1394					continue;
1395				}
1396				mode = tmp;
1397				if(mode==REPLACE)
1398				{
1399					c = last_save = last_virt+1;
1400					if(c >= MAXLINE)
1401						c = MAXLINE-1;
1402					genncpy(saveline, virtual, c);
1403				}
1404			}
1405			break;
1406
1407		case UERASE:		/** user erase char **/
1408				/*** treat as backspace ***/
1409
1410		case '\b':		/** backspace **/
1411			if( virtual[cur_virt] == '\\' )
1412			{
1413				cdelete(vp,1, BAD);
1414				append(vp,usrerase, mode);
1415			}
1416			else
1417			{
1418				if( mode==SEARCH && cur_virt==0 )
1419				{
1420					first_virt = 0;
1421					cdelete(vp,1, BAD);
1422					return;
1423				}
1424				if(mode==REPLACE || (last_save>0 && last_virt<=last_save))
1425				{
1426					if(cur_virt<=first_virt)
1427						ed_ringbell();
1428					else if(mode==REPLACE)
1429						--cur_virt;
1430					mode = REPLACE;
1431					sync_cursor(vp);
1432					continue;
1433				}
1434				else
1435					cdelete(vp,1, BAD);
1436			}
1437			break;
1438
1439		case UWERASE:		/** delete back word **/
1440			if( cur_virt > first_virt &&
1441				!isblank(cur_virt) &&
1442				!ispunct(virtual[cur_virt]) &&
1443				isblank(cur_virt-1) )
1444			{
1445				cdelete(vp,1, BAD);
1446			}
1447			else
1448			{
1449				tmp = cur_virt;
1450				backword(vp,1, 'W');
1451				cdelete(vp,tmp - cur_virt + 1, BAD);
1452			}
1453			break;
1454
1455		case UKILL:		/** user kill line char **/
1456			if( virtual[cur_virt] == '\\' )
1457			{
1458				cdelete(vp,1, BAD);
1459				append(vp,usrkill, mode);
1460			}
1461			else
1462			{
1463				if( mode == SEARCH )
1464				{
1465					cur_virt = 1;
1466					delmotion(vp, '$', BAD);
1467				}
1468				else if(first_virt)
1469				{
1470					tmp = cur_virt;
1471					cur_virt = first_virt;
1472					cdelete(vp,tmp - cur_virt + 1, BAD);
1473				}
1474				else
1475					del_line(vp,GOOD);
1476			}
1477			break;
1478
1479		case UEOF:		/** eof char **/
1480			if( cur_virt != INVALID )
1481				continue;
1482			vp->addnl = 0;
1483			/* FALLTHROUGH */
1484
1485		case '\n':		/** newline or return **/
1486			if( mode != SEARCH )
1487				save_last(vp);
1488			refresh(vp,INPUT);
1489			last_phys++;
1490			return;
1491
1492		case '\t':		/** command completion **/
1493			if(mode!=SEARCH && last_virt>=0 && (vp->ed->e_tabcount|| !isblank(cur_virt)) && vp->ed->sh->nextprompt)
1494			{
1495				if(vp->ed->e_tabcount==0)
1496				{
1497					ed_ungetchar(vp->ed,'\\');
1498					vp->ed->e_tabcount=1;
1499					goto escape;
1500				}
1501				else if(vp->ed->e_tabcount==1)
1502				{
1503					ed_ungetchar(vp->ed,'=');
1504					goto escape;
1505				}
1506				vp->ed->e_tabcount = 0;
1507			}
1508			/* FALL THRU*/
1509		default:
1510			if( mode == REPLACE )
1511			{
1512				if( cur_virt < last_virt )
1513				{
1514					replace(vp,c, 1);
1515					if(cur_virt>max_virt)
1516						max_virt = cur_virt;
1517					continue;
1518				}
1519				cdelete(vp,1, BAD);
1520				mode = APPEND;
1521				max_virt = last_virt+3;
1522			}
1523			append(vp,c, mode);
1524			break;
1525		}
1526		refresh(vp,INPUT);
1527
1528	}
1529}
1530
1531/*{	MVCURSOR( motion )
1532 *
1533 *	This routine will move the virtual cursor according to motion
1534 * for repeat times.
1535 *
1536 * It returns GOOD if successful; else BAD.
1537 *
1538}*/
1539
1540static int mvcursor(register Vi_t* vp,register int motion)
1541{
1542	register int count;
1543	register int tcur_virt;
1544	register int incr = -1;
1545	register int bound = 0;
1546
1547	switch(motion)
1548	{
1549		/***** Cursor move commands *****/
1550
1551	case '0':		/** First column **/
1552		tcur_virt = 0;
1553		break;
1554
1555	case '^':		/** First nonblank character **/
1556		tcur_virt = first_virt;
1557		while( isblank(tcur_virt) && tcur_virt < last_virt )
1558			++tcur_virt;
1559		break;
1560
1561	case '|':
1562		tcur_virt = vp->repeat-1;
1563		if(tcur_virt <= last_virt)
1564			break;
1565		/* fall through */
1566
1567	case '$':		/** End of line **/
1568		tcur_virt = last_virt;
1569		break;
1570
1571	case '[':
1572		switch(motion=getcount(vp,ed_getchar(vp->ed,-1)))
1573		{
1574		    case 'A':
1575			if(cur_virt>=0  && cur_virt<(SEARCHSIZE-2) && cur_virt == last_virt)
1576			{
1577				virtual[last_virt + 1] = '\0';
1578#if SHOPT_MULTIBYTE
1579				ed_external(virtual,lsearch+1);
1580#else
1581				strcpy(lsearch+1,virtual);
1582#endif /* SHOPT_MULTIBYTE */
1583				*lsearch = '^';
1584				vp->direction = -2;
1585				ed_ungetchar(vp->ed,'n');
1586			}
1587			else if(cur_virt==0 && vp->direction == -2)
1588				ed_ungetchar(vp->ed,'n');
1589			else
1590				ed_ungetchar(vp->ed,'k');
1591			return(1);
1592		    case 'B':
1593			ed_ungetchar(vp->ed,'j');
1594			return(1);
1595		    case 'C':
1596			motion = last_virt;
1597			incr = 1;
1598			goto walk;
1599		    case 'D':
1600			motion = first_virt;
1601			goto walk;
1602		    case 'H':
1603			tcur_virt = 0;
1604			break;
1605		    case 'Y':
1606			tcur_virt = last_virt;
1607			break;
1608		    default:
1609			ed_ungetchar(vp->ed,motion);
1610			return(0);
1611		}
1612		break;
1613
1614	case 'h':		/** Left one **/
1615	case '\b':
1616		motion = first_virt;
1617		goto walk;
1618
1619	case ' ':
1620	case 'l':		/** Right one **/
1621		motion = last_virt;
1622		incr = 1;
1623	walk:
1624		tcur_virt = cur_virt;
1625		if( incr*tcur_virt < motion)
1626		{
1627			tcur_virt += vp->repeat*incr;
1628			if( incr*tcur_virt > motion)
1629				tcur_virt = motion;
1630		}
1631		else
1632			return(0);
1633		break;
1634
1635	case 'B':
1636	case 'b':		/** back word **/
1637		tcur_virt = cur_virt;
1638		backword(vp,vp->repeat, motion);
1639		if( cur_virt == tcur_virt )
1640			return(0);
1641		return(1);
1642
1643	case 'E':
1644	case 'e':		/** end of word **/
1645		tcur_virt = cur_virt;
1646		if(tcur_virt >=0)
1647			endword(vp, vp->repeat, motion);
1648		if( cur_virt == tcur_virt )
1649			return(0);
1650		return(1);
1651
1652	case ',':		/** reverse find old char **/
1653	case ';':		/** find old char **/
1654		switch(vp->last_find)
1655		{
1656		case 't':
1657		case 'f':
1658			if(motion==';')
1659			{
1660				bound = last_virt;
1661				incr = 1;
1662			}
1663			goto find_b;
1664
1665		case 'T':
1666		case 'F':
1667			if(motion==',')
1668			{
1669				bound = last_virt;
1670				incr = 1;
1671			}
1672			goto find_b;
1673
1674		default:
1675			return(0);
1676		}
1677
1678
1679	case 't':		/** find up to new char forward **/
1680	case 'f':		/** find new char forward **/
1681		bound = last_virt;
1682		incr = 1;
1683		/* FALLTHROUGH */
1684
1685	case 'T':		/** find up to new char backward **/
1686	case 'F':		/** find new char backward **/
1687		vp->last_find = motion;
1688		if((vp->findchar=getrchar(vp))==ESC)
1689			return(1);
1690find_b:
1691		tcur_virt = cur_virt;
1692		count = vp->repeat;
1693		while( count-- )
1694		{
1695			while( incr*(tcur_virt+=incr) <= bound
1696				&& virtual[tcur_virt] != vp->findchar );
1697			if( incr*tcur_virt > bound )
1698			{
1699				return(0);
1700			}
1701		}
1702		if( fold(vp->last_find) == 'T' )
1703			tcur_virt -= incr;
1704		break;
1705
1706        case '%':
1707	{
1708		int nextmotion;
1709		int nextc;
1710		tcur_virt = cur_virt;
1711		while( tcur_virt <= last_virt
1712			&& strchr(paren_chars,virtual[tcur_virt])==(char*)0)
1713				tcur_virt++;
1714		if(tcur_virt > last_virt )
1715			return(0);
1716		nextc = virtual[tcur_virt];
1717		count = strchr(paren_chars,nextc)-paren_chars;
1718		if(count < 3)
1719		{
1720			incr = 1;
1721			bound = last_virt;
1722			nextmotion = paren_chars[count+3];
1723		}
1724		else
1725			nextmotion = paren_chars[count-3];
1726		count = 1;
1727		while(count >0 &&  incr*(tcur_virt+=incr) <= bound)
1728		{
1729		        if(virtual[tcur_virt] == nextmotion)
1730		        	count--;
1731		        else if(virtual[tcur_virt]==nextc)
1732		        	count++;
1733		}
1734		if(count)
1735			return(0);
1736		break;
1737	}
1738
1739	case 'W':
1740	case 'w':		/** forward word **/
1741		tcur_virt = cur_virt;
1742		forward(vp,vp->repeat, motion);
1743		if( tcur_virt == cur_virt )
1744			return(0);
1745		return(1);
1746
1747	default:
1748		return(0);
1749	}
1750	cur_virt = tcur_virt;
1751
1752	return(1);
1753}
1754
1755/*
1756 * print a string
1757 */
1758
1759static void pr_string(register Vi_t *vp, register const char *sp)
1760{
1761	/*** copy string sp ***/
1762	register char *ptr = editb.e_outptr;
1763	while(*sp)
1764		*ptr++ = *sp++;
1765	editb.e_outptr = ptr;
1766	return;
1767}
1768
1769/*{	PUTSTRING( column, nchars )
1770 *
1771 *	Put nchars starting at column of physical into the workspace
1772 * to be printed.
1773 *
1774}*/
1775
1776static void putstring(register Vi_t *vp,register int col, register int nchars)
1777{
1778	while( nchars-- )
1779		putchar(physical[col++]);
1780	return;
1781}
1782
1783/*{	REFRESH( mode )
1784 *
1785 *	This routine will refresh the crt so the physical image matches
1786 * the virtual image and display the proper window.
1787 *
1788 *	mode	= CONTROL, refresh in control mode, ie. leave cursor
1789 *			positioned at last char printed.
1790 *		= INPUT, refresh in input mode; leave cursor positioned
1791 *			after last char printed.
1792 *		= TRANSLATE, perform virtual to physical translation
1793 *			and adjust left margin only.
1794 *
1795 *		+-------------------------------+
1796 *		|   | |    virtual	  | |   |
1797 *		+-------------------------------+
1798 *		  cur_virt		last_virt
1799 *
1800 *		+-----------------------------------------------+
1801 *		|	  | |	        physical	 | |    |
1802 *		+-----------------------------------------------+
1803 *			cur_phys			last_phys
1804 *
1805 *				0			w_size - 1
1806 *				+-----------------------+
1807 *				| | |  window		|
1808 *				+-----------------------+
1809 *				cur_window = cur_phys - first_wind
1810}*/
1811
1812static void refresh(register Vi_t* vp, int mode)
1813{
1814	register int p;
1815	register int regb;
1816	register int first_w = vp->first_wind;
1817	int p_differ;
1818	int new_lw;
1819	int ncur_phys;
1820	int opflag;			/* search optimize flag */
1821
1822#	define	w	regb
1823#	define	v	regb
1824
1825	/*** find out if it's necessary to start translating at beginning ***/
1826
1827	if(lookahead>0)
1828	{
1829		p = previous[lookahead-1];
1830		if(p != ESC && p != '\n' && p != '\r')
1831			mode = TRANSLATE;
1832	}
1833	v = cur_virt;
1834	if( v<vp->ocur_virt || vp->ocur_virt==INVALID
1835		|| ( v==vp->ocur_virt
1836			&& (!is_print(virtual[v]) || !is_print(vp->o_v_char))) )
1837	{
1838		opflag = 0;
1839		p = 0;
1840		v = 0;
1841	}
1842	else
1843	{
1844		opflag = 1;
1845		p = vp->ocur_phys;
1846		v = vp->ocur_virt;
1847		if( !is_print(virtual[v]) )
1848		{
1849			/*** avoid double ^'s ***/
1850			++p;
1851			++v;
1852		}
1853	}
1854	virtual[last_virt+1] = 0;
1855	ncur_phys = ed_virt_to_phys(vp->ed,virtual,physical,cur_virt,v,p);
1856	p = genlen(physical);
1857	if( --p < 0 )
1858		last_phys = 0;
1859	else
1860		last_phys = p;
1861
1862	/*** see if this was a translate only ***/
1863
1864	if( mode == TRANSLATE )
1865		return;
1866
1867	/*** adjust left margin if necessary ***/
1868
1869	if( ncur_phys<first_w || ncur_phys>=(first_w + w_size) )
1870	{
1871		cursor(vp,first_w);
1872		first_w = ncur_phys - (w_size>>1);
1873		if( first_w < 0 )
1874			first_w = 0;
1875		vp->first_wind = cur_phys = first_w;
1876	}
1877
1878	/*** attempt to optimize search somewhat to find ***/
1879	/*** out where physical and window images differ ***/
1880
1881	if( first_w==vp->ofirst_wind && ncur_phys>=vp->ocur_phys && opflag==1 )
1882	{
1883		p = vp->ocur_phys;
1884		w = p - first_w;
1885	}
1886	else
1887	{
1888		p = first_w;
1889		w = 0;
1890	}
1891
1892	for(; (p<=last_phys && w<=vp->last_wind); ++p, ++w)
1893	{
1894		if( window[w] != physical[p] )
1895			break;
1896	}
1897	p_differ = p;
1898
1899	if( (p>last_phys || p>=first_w+w_size) && w>vp->last_wind
1900		&& cur_virt==vp->ocur_virt )
1901	{
1902		/*** images are identical ***/
1903		return;
1904	}
1905
1906	/*** copy the physical image to the window image ***/
1907
1908	if( last_virt != INVALID )
1909	{
1910		while( p <= last_phys && w < w_size )
1911			window[w++] = physical[p++];
1912	}
1913	new_lw = w;
1914
1915	/*** erase trailing characters if needed ***/
1916
1917	while( w <= vp->last_wind )
1918		window[w++] = ' ';
1919	vp->last_wind = --w;
1920
1921	p = p_differ;
1922
1923	/*** move cursor to start of difference ***/
1924
1925	cursor(vp,p);
1926
1927	/*** and output difference ***/
1928
1929	w = p - first_w;
1930	while( w <= vp->last_wind )
1931		putchar(window[w++]);
1932
1933	cur_phys = w + first_w;
1934	vp->last_wind = --new_lw;
1935
1936	if( last_phys >= w_size )
1937	{
1938		if( first_w == 0 )
1939			vp->long_char = '>';
1940		else if( last_phys < (first_w+w_size) )
1941			vp->long_char = '<';
1942		else
1943			vp->long_char = '*';
1944	}
1945	else
1946		vp->long_char = ' ';
1947
1948	if( vp->long_line != vp->long_char )
1949	{
1950		/*** indicate lines longer than window ***/
1951		while( w++ < w_size )
1952		{
1953			putchar(' ');
1954			++cur_phys;
1955		}
1956		putchar(vp->long_char);
1957		++cur_phys;
1958		vp->long_line = vp->long_char;
1959	}
1960
1961	if(vp->ed->e_multiline &&  vp->ofirst_wind==INVALID && !vp->ed->e_nocrnl)
1962		ed_setcursor(vp->ed, physical, last_phys+1, last_phys+1, -1);
1963	vp->ed->e_nocrnl = 0;
1964	vp->ocur_phys = ncur_phys;
1965	vp->ocur_virt = cur_virt;
1966	vp->ofirst_wind = first_w;
1967
1968	if( mode==INPUT && cur_virt>INVALID )
1969		++ncur_phys;
1970
1971	cursor(vp,ncur_phys);
1972	ed_flush(vp->ed);
1973	return;
1974}
1975
1976/*{	REPLACE( char, increment )
1977 *
1978 *	Replace the cur_virt character with char.  This routine attempts
1979 * to avoid using refresh().
1980 *
1981 *	increment	= 1, increment cur_virt after replacement.
1982 *			= 0, leave cur_virt where it is.
1983 *
1984}*/
1985
1986static void replace(register Vi_t *vp, register int c, register int increment)
1987{
1988	register int cur_window;
1989
1990	if( cur_virt == INVALID )
1991	{
1992		/*** can't replace invalid cursor ***/
1993		ed_ringbell();
1994		return;
1995	}
1996	cur_window = cur_phys - vp->first_wind;
1997	if( vp->ocur_virt == INVALID || !is_print(c)
1998		|| !is_print(virtual[cur_virt])
1999		|| !is_print(vp->o_v_char)
2000#if SHOPT_MULTIBYTE
2001		|| !iswascii(c) || mbwidth(vp->o_v_char)>1
2002		|| !iswascii(virtual[cur_virt])
2003#endif /* SHOPT_MULTIBYTE */
2004		|| (increment && (cur_window==w_size-1)
2005			|| !is_print(virtual[cur_virt+1])) )
2006	{
2007		/*** must use standard refresh routine ***/
2008
2009		cdelete(vp,1, BAD);
2010		append(vp,c, APPEND);
2011		if( increment && cur_virt<last_virt )
2012			++cur_virt;
2013		refresh(vp,CONTROL);
2014	}
2015	else
2016	{
2017		virtual[cur_virt] = c;
2018		physical[cur_phys] = c;
2019		window[cur_window] = c;
2020		putchar(c);
2021		if(increment)
2022		{
2023			c = virtual[++cur_virt];
2024			++cur_phys;
2025		}
2026		else
2027		{
2028			putchar('\b');
2029		}
2030		vp->o_v_char = c;
2031		ed_flush(vp->ed);
2032	}
2033	return;
2034}
2035
2036/*{	RESTORE_V()
2037 *
2038 *	Restore the contents of virtual space from u_space.
2039 *
2040}*/
2041
2042static void restore_v(register Vi_t *vp)
2043{
2044	register int tmpcol;
2045	genchar tmpspace[MAXLINE];
2046
2047	if( vp->u_column == INVALID-1 )
2048	{
2049		/*** never saved anything ***/
2050		ed_ringbell();
2051		return;
2052	}
2053	gencpy(tmpspace, vp->u_space);
2054	tmpcol = vp->u_column;
2055	save_v(vp);
2056	gencpy(virtual, tmpspace);
2057	cur_virt = tmpcol;
2058	last_virt = genlen(tmpspace) - 1;
2059	vp->ocur_virt = MAXCHAR;	/** invalidate refresh optimization **/
2060	return;
2061}
2062
2063/*{	SAVE_LAST()
2064 *
2065 *	If the user has typed something, save it in last line.
2066 *
2067}*/
2068
2069static void save_last(register Vi_t* vp)
2070{
2071	register int i;
2072
2073	if( (i = cur_virt - first_virt + 1) > 0 )
2074	{
2075		/*** save last thing user typed ***/
2076		if(i >= MAXLINE)
2077			i = MAXLINE-1;
2078		genncpy(vp->lastline, (&virtual[first_virt]), i);
2079		vp->lastline[i] = '\0';
2080	}
2081	return;
2082}
2083
2084/*{	SAVE_V()
2085 *
2086 *	This routine will save the contents of virtual in u_space.
2087 *
2088}*/
2089
2090static void save_v(register Vi_t *vp)
2091{
2092	if(!inmacro)
2093	{
2094		virtual[last_virt + 1] = '\0';
2095		gencpy(vp->u_space, virtual);
2096		vp->u_column = cur_virt;
2097	}
2098	return;
2099}
2100
2101/*{	SEARCH( mode )
2102 *
2103 *	Search history file for regular expression.
2104 *
2105 *	mode	= '/'	require search string and search new to old
2106 *	mode	= '?'	require search string and search old to new
2107 *	mode	= 'N'	repeat last search in reverse direction
2108 *	mode	= 'n'	repeat last search
2109 *
2110}*/
2111
2112/*
2113 * search for <string> in the current command
2114 */
2115static int curline_search(Vi_t *vp, const char *string)
2116{
2117	register int len=strlen(string);
2118	register const char *dp,*cp=string, *dpmax;
2119#if SHOPT_MULTIBYTE
2120	ed_external(vp->u_space,(char*)vp->u_space);
2121#endif /* SHOPT_MULTIBYTE */
2122	for(dp=(char*)vp->u_space,dpmax=dp+strlen(dp)-len; dp<=dpmax; dp++)
2123	{
2124		if(*dp==*cp && memcmp(cp,dp,len)==0)
2125			return(dp-(char*)vp->u_space);
2126	}
2127#if SHOPT_MULTIBYTE
2128	ed_internal((char*)vp->u_space,vp->u_space);
2129#endif /* SHOPT_MULTIBYTE */
2130	return(-1);
2131}
2132
2133static int search(register Vi_t* vp,register int mode)
2134{
2135	register int new_direction;
2136	register int oldcurhline;
2137	register int i;
2138	Histloc_t  location;
2139
2140	if( vp->direction == -2 && mode != 'n')
2141		vp->direction = -1;
2142	if( mode == '/' || mode == '?')
2143	{
2144		/*** new search expression ***/
2145		del_line(vp,BAD);
2146		append(vp,mode, APPEND);
2147		refresh(vp,INPUT);
2148		first_virt = 1;
2149		getline(vp,SEARCH);
2150		first_virt = 0;
2151		virtual[last_virt + 1] = '\0';	/*** make null terminated ***/
2152		vp->direction = mode=='/' ? -1 : 1;
2153	}
2154
2155	if( cur_virt == INVALID )
2156	{
2157		/*** no operation ***/
2158		return(ABORT);
2159	}
2160
2161	if( cur_virt==0 ||  fold(mode)=='N' )
2162	{
2163		/*** user wants repeat of last search ***/
2164		del_line(vp,BAD);
2165		strcpy( ((char*)virtual)+1, lsearch);
2166#if SHOPT_MULTIBYTE
2167		*((char*)virtual) = '/';
2168		ed_internal((char*)virtual,virtual);
2169#endif /* SHOPT_MULTIBYTE */
2170	}
2171
2172	if( mode == 'N' )
2173		new_direction = -vp->direction;
2174	else
2175		new_direction = vp->direction;
2176
2177
2178	/*** now search ***/
2179
2180	oldcurhline = curhline;
2181#if SHOPT_MULTIBYTE
2182	ed_external(virtual,(char*)virtual);
2183#endif /* SHOPT_MULTIBYTE */
2184	if(mode=='?' && (i=curline_search(vp,((char*)virtual)+1))>=0)
2185	{
2186		location.hist_command = curhline;
2187		location.hist_char = i;
2188	}
2189	else
2190	{
2191		i = INVALID;
2192		if( new_direction==1 && curhline >= histmax )
2193			curhline = histmin + 1;
2194		location = hist_find(sh.hist_ptr,((char*)virtual)+1, curhline, 1, new_direction);
2195	}
2196	cur_virt = i;
2197	strncpy(lsearch, ((char*)virtual)+1, SEARCHSIZE);
2198	if( (curhline=location.hist_command) >=0 )
2199	{
2200		vp->ocur_virt = INVALID;
2201		return(GOOD);
2202	}
2203
2204	/*** could not find matching line ***/
2205
2206	curhline = oldcurhline;
2207	return(BAD);
2208}
2209
2210/*{	SYNC_CURSOR()
2211 *
2212 *	This routine will move the physical cursor to the same
2213 * column as the virtual cursor.
2214 *
2215}*/
2216
2217static void sync_cursor(register Vi_t *vp)
2218{
2219	register int p;
2220	register int v;
2221	register int c;
2222	int new_phys;
2223
2224	if( cur_virt == INVALID )
2225		return;
2226
2227	/*** find physical col that corresponds to virtual col ***/
2228
2229	new_phys = 0;
2230	if(vp->first_wind==vp->ofirst_wind && cur_virt>vp->ocur_virt && vp->ocur_virt!=INVALID)
2231	{
2232		/*** try to optimize search a little ***/
2233		p = vp->ocur_phys + 1;
2234#if SHOPT_MULTIBYTE
2235		while(physical[p]==MARKER)
2236			p++;
2237#endif /* SHOPT_MULTIBYTE */
2238		v = vp->ocur_virt + 1;
2239	}
2240	else
2241	{
2242		p = 0;
2243		v = 0;
2244	}
2245	for(; v <= last_virt; ++p, ++v)
2246	{
2247#if SHOPT_MULTIBYTE
2248		int d;
2249		c = virtual[v];
2250		if((d = mbwidth(c)) > 1)
2251		{
2252			if( v != cur_virt )
2253				p += (d-1);
2254		}
2255		else if(!iswprint(c))
2256#else
2257		c = virtual[v];
2258		if(!isprint(c))
2259#endif	/* SHOPT_MULTIBYTE */
2260		{
2261			if( c == '\t' )
2262			{
2263				p -= ((p+editb.e_plen)%TABSIZE);
2264				p += (TABSIZE-1);
2265			}
2266			else
2267			{
2268				++p;
2269			}
2270		}
2271		if( v == cur_virt )
2272		{
2273			new_phys = p;
2274			break;
2275		}
2276	}
2277
2278	if( new_phys < vp->first_wind || new_phys >= vp->first_wind + w_size )
2279	{
2280		/*** asked to move outside of window ***/
2281
2282		window[0] = '\0';
2283		refresh(vp,CONTROL);
2284		return;
2285	}
2286
2287	cursor(vp,new_phys);
2288	ed_flush(vp->ed);
2289	vp->ocur_phys = cur_phys;
2290	vp->ocur_virt = cur_virt;
2291	vp->o_v_char = virtual[vp->ocur_virt];
2292
2293	return;
2294}
2295
2296/*{	TEXTMOD( command, mode )
2297 *
2298 *	Modify text operations.
2299 *
2300 *	mode != 0, repeat previous operation
2301 *
2302}*/
2303
2304static int textmod(register Vi_t *vp,register int c, int mode)
2305{
2306	register int i;
2307	register genchar *p = vp->lastline;
2308	register int trepeat = vp->repeat;
2309	genchar *savep;
2310
2311	if(mode && (fold(vp->lastmotion)=='F' || fold(vp->lastmotion)=='T'))
2312		vp->lastmotion = ';';
2313
2314	if( fold(c) == 'P' )
2315	{
2316		/*** change p from lastline to yankbuf ***/
2317		p = yankbuf;
2318	}
2319
2320addin:
2321	switch( c )
2322	{
2323			/***** Input commands *****/
2324
2325#if KSHELL
2326        case '\t':
2327		if(vp->ed->e_tabcount!=1)
2328			return(BAD);
2329		c = '=';
2330		/* FALLTHROUGH */
2331	case '*':		/** do file name expansion in place **/
2332	case '\\':		/** do file name completion in place **/
2333		if( cur_virt == INVALID )
2334			return(BAD);
2335		/* FALLTHROUGH */
2336	case '=':		/** list file name expansions **/
2337		save_v(vp);
2338		i = last_virt;
2339		++last_virt;
2340		mode = cur_virt-1;
2341		virtual[last_virt] = 0;
2342		if(ed_expand(vp->ed,(char*)virtual, &cur_virt, &last_virt, c, vp->repeat_set?vp->repeat:-1)<0)
2343		{
2344			if(vp->ed->e_tabcount)
2345			{
2346				vp->ed->e_tabcount=2;
2347				ed_ungetchar(vp->ed,'\t');
2348				--last_virt;
2349				return(APPEND);
2350			}
2351			last_virt = i;
2352			ed_ringbell();
2353		}
2354		else if(c == '=' && !vp->repeat_set)
2355		{
2356			last_virt = i;
2357			vp->nonewline++;
2358			ed_ungetchar(vp->ed,cntl('L'));
2359			return(GOOD);
2360		}
2361		else
2362		{
2363			--cur_virt;
2364			--last_virt;
2365			vp->ocur_virt = MAXCHAR;
2366			if(c=='=' || (mode<cur_virt && (virtual[cur_virt]==' ' || virtual[cur_virt]=='/')))
2367				vp->ed->e_tabcount = 0;
2368			return(APPEND);
2369		}
2370		break;
2371
2372	case '@':		/** macro expansion **/
2373		if( mode )
2374			c = vp->lastmacro;
2375		else
2376			if((c=getrchar(vp))==ESC)
2377				return(GOOD);
2378		if(!inmacro)
2379			vp->lastmacro = c;
2380		if(ed_macro(vp->ed,c))
2381		{
2382			save_v(vp);
2383			inmacro++;
2384			return(GOOD);
2385		}
2386		ed_ringbell();
2387		return(BAD);
2388
2389#endif	/* KSHELL */
2390	case '_':		/** append last argument of prev command **/
2391		save_v(vp);
2392		{
2393			genchar tmpbuf[MAXLINE];
2394			if(vp->repeat_set==0)
2395				vp->repeat = -1;
2396			p = (genchar*)hist_word((char*)tmpbuf,MAXLINE,vp->repeat);
2397			if(p==0)
2398			{
2399				ed_ringbell();
2400				break;
2401			}
2402#if SHOPT_MULTIBYTE
2403			ed_internal((char*)p,tmpbuf);
2404			p = tmpbuf;
2405#endif /* SHOPT_MULTIBYTE */
2406			i = ' ';
2407			do
2408			{
2409				append(vp,i,APPEND);
2410			}
2411			while(i = *p++);
2412			return(APPEND);
2413		}
2414
2415	case 'A':		/** append to end of line **/
2416		cur_virt = last_virt;
2417		sync_cursor(vp);
2418		/* FALLTHROUGH */
2419
2420	case 'a':		/** append **/
2421		if( fold(mode) == 'A' )
2422		{
2423			c = 'p';
2424			goto addin;
2425		}
2426		save_v(vp);
2427		if( cur_virt != INVALID )
2428		{
2429			first_virt = cur_virt + 1;
2430			cursor(vp,cur_phys + 1);
2431			ed_flush(vp->ed);
2432		}
2433		return(APPEND);
2434
2435	case 'I':		/** insert at beginning of line **/
2436		cur_virt = first_virt;
2437		sync_cursor(vp);
2438		/* FALLTHROUGH */
2439
2440	case 'i':		/** insert **/
2441		if( fold(mode) == 'I' )
2442		{
2443			c = 'P';
2444			goto addin;
2445		}
2446		save_v(vp);
2447		if( cur_virt != INVALID )
2448 		{
2449 			vp->o_v_char = virtual[cur_virt];
2450			first_virt = cur_virt--;
2451  		}
2452		return(INSERT);
2453
2454	case 'C':		/** change to eol **/
2455		c = '$';
2456		goto chgeol;
2457
2458	case 'c':		/** change **/
2459		if( mode )
2460			c = vp->lastmotion;
2461		else
2462			c = getcount(vp,ed_getchar(vp->ed,-1));
2463chgeol:
2464		vp->lastmotion = c;
2465		if( c == 'c' )
2466		{
2467			del_line(vp,GOOD);
2468			return(APPEND);
2469		}
2470
2471		if(!delmotion(vp, c, 'c'))
2472			return(BAD);
2473
2474		if( mode == 'c' )
2475		{
2476			c = 'p';
2477			trepeat = 1;
2478			goto addin;
2479		}
2480		first_virt = cur_virt + 1;
2481		return(APPEND);
2482
2483	case 'D':		/** delete to eol **/
2484		c = '$';
2485		goto deleol;
2486
2487	case 'd':		/** delete **/
2488		if( mode )
2489			c = vp->lastmotion;
2490		else
2491			c = getcount(vp,ed_getchar(vp->ed,-1));
2492deleol:
2493		vp->lastmotion = c;
2494		if( c == 'd' )
2495		{
2496			del_line(vp,GOOD);
2497			break;
2498		}
2499		if(!delmotion(vp, c, 'd'))
2500			return(BAD);
2501		if( cur_virt < last_virt )
2502			++cur_virt;
2503		break;
2504
2505	case 'P':
2506		if( p[0] == '\0' )
2507			return(BAD);
2508		if( cur_virt != INVALID )
2509		{
2510			i = virtual[cur_virt];
2511			if(!is_print(i))
2512				vp->ocur_virt = INVALID;
2513			--cur_virt;
2514		}
2515		/* FALLTHROUGH */
2516
2517	case 'p':		/** print **/
2518		if( p[0] == '\0' )
2519			return(BAD);
2520
2521		if( mode != 's' && mode != 'c' )
2522		{
2523			save_v(vp);
2524			if( c == 'P' )
2525			{
2526				/*** fix stored cur_virt ***/
2527				++vp->u_column;
2528			}
2529		}
2530		if( mode == 'R' )
2531			mode = REPLACE;
2532		else
2533			mode = APPEND;
2534		savep = p;
2535		for(i=0; i<trepeat; ++i)
2536		{
2537			while(c= *p++)
2538				append(vp,c,mode);
2539			p = savep;
2540		}
2541		break;
2542
2543	case 'R':		/* Replace many chars **/
2544		if( mode == 'R' )
2545		{
2546			c = 'P';
2547			goto addin;
2548		}
2549		save_v(vp);
2550		if( cur_virt != INVALID )
2551			first_virt = cur_virt;
2552		return(REPLACE);
2553
2554	case 'r':		/** replace **/
2555		if( mode )
2556			c = *p;
2557		else
2558			if((c=getrchar(vp))==ESC)
2559				return(GOOD);
2560		*p = c;
2561		save_v(vp);
2562		while(trepeat--)
2563			replace(vp,c, trepeat!=0);
2564		return(GOOD);
2565
2566	case 'S':		/** Substitute line - cc **/
2567		c = 'c';
2568		goto chgeol;
2569
2570	case 's':		/** substitute **/
2571		save_v(vp);
2572		cdelete(vp,vp->repeat, BAD);
2573		if( mode )
2574		{
2575			c = 'p';
2576			trepeat = 1;
2577			goto addin;
2578		}
2579		first_virt = cur_virt + 1;
2580		return(APPEND);
2581
2582	case 'Y':		/** Yank to end of line **/
2583		c = '$';
2584		goto yankeol;
2585
2586	case 'y':		/** yank thru motion **/
2587		if( mode )
2588			c = vp->lastmotion;
2589		else
2590			c = getcount(vp,ed_getchar(vp->ed,-1));
2591yankeol:
2592		vp->lastmotion = c;
2593		if( c == 'y' )
2594		{
2595			gencpy(yankbuf, virtual);
2596		}
2597		else if(!delmotion(vp, c, 'y'))
2598		{
2599			return(BAD);
2600		}
2601		break;
2602
2603	case 'x':		/** delete repeat chars forward - dl **/
2604		c = 'l';
2605		goto deleol;
2606
2607	case 'X':		/** delete repeat chars backward - dh **/
2608		c = 'h';
2609		goto deleol;
2610
2611	case '~':		/** invert case and advance **/
2612		if( cur_virt != INVALID )
2613		{
2614			save_v(vp);
2615			i = INVALID;
2616			while(trepeat-->0 && i!=cur_virt)
2617			{
2618				i = cur_virt;
2619				c = virtual[cur_virt];
2620#if SHOPT_MULTIBYTE
2621				if((c&~STRIP)==0)
2622#endif /* SHOPT_MULTIBYTE */
2623				if( isupper(c) )
2624					c = tolower(c);
2625				else if( islower(c) )
2626					c = toupper(c);
2627				replace(vp,c, 1);
2628			}
2629			return(GOOD);
2630		}
2631		else
2632			return(BAD);
2633
2634	default:
2635		return(BAD);
2636	}
2637	refresh(vp,CONTROL);
2638	return(GOOD);
2639}
2640
2641
2642#if SHOPT_MULTIBYTE
2643    static int _isalph(register int v)
2644    {
2645#ifdef _lib_iswalnum
2646	return(iswalnum(v) || v=='_');
2647#else
2648	return((v&~STRIP) || isalnum(v) || v=='_');
2649#endif
2650    }
2651
2652
2653    static int _isblank(register int v)
2654    {
2655	return((v&~STRIP)==0 && isspace(v));
2656    }
2657
2658    static int _ismetach(register int v)
2659    {
2660	return((v&~STRIP)==0 && ismeta(v));
2661    }
2662
2663#endif	/* SHOPT_MULTIBYTE */
2664
2665/*
2666 * get a character, after ^V processing
2667 */
2668static int getrchar(register Vi_t *vp)
2669{
2670	register int c;
2671	if((c=ed_getchar(vp->ed,1))== usrlnext)
2672		c = ed_getchar(vp->ed,2);
2673	return(c);
2674}
2675