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