xref: /illumos-gate/usr/src/contrib/ast/src/cmd/ksh93/edit/vi.c (revision 5ae8bd53)
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 
103 typedef 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 
188 static const char paren_chars[] = "([{)]}";   /* for % command */
189 
190 static void	cursor(Vi_t*, int);
191 static void	del_line(Vi_t*,int);
192 static int	getcount(Vi_t*,int);
193 static void	getline(Vi_t*,int);
194 static int	getrchar(Vi_t*);
195 static int	mvcursor(Vi_t*,int);
196 static void	pr_string(Vi_t*,const char*);
197 static void	putstring(Vi_t*,int, int);
198 static void	refresh(Vi_t*,int);
199 static void	replace(Vi_t*,int, int);
200 static void	restore_v(Vi_t*);
201 static void	save_last(Vi_t*);
202 static void	save_v(Vi_t*);
203 static int	search(Vi_t*,int);
204 static void	sync_cursor(Vi_t*);
205 static 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  */
217 int 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 
639 static 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 
664 static 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 
708 static 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 
1038 static 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 
1058 static 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 }*/
1105 static 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 
1145 static 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 
1200 static 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 
1237 static 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 
1277 static 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 
1318 static 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 
1540 static 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);
1690 find_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 
1759 static 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 
1776 static 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 
1812 static 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 
1986 static 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 
2042 static 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 
2069 static 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 
2090 static 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  */
2115 static 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 
2133 static 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 
2217 static 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 
2304 static 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 
2320 addin:
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));
2463 chgeol:
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));
2492 deleol:
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));
2591 yankeol:
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  */
2668 static 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