xref: /illumos-gate/usr/src/cmd/vi/port/ex_vmain.c (revision 2f172c55ef76964744bc62b4500ece87f3089b4d)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 
30 /* Copyright (c) 1981 Regents of the University of California */
31 
32 #pragma ident	"%Z%%M%	%I%	%E% SMI"
33 
34 #include "ex.h"
35 #include "ex_tty.h"
36 #include "ex_vis.h"
37 #ifndef PRESUNEUC
38 #include <wctype.h>
39 /* Undef putchar/getchar if they're defined. */
40 #ifdef putchar
41 #	undef putchar
42 #endif
43 #ifdef getchar
44 #	undef getchar
45 #endif
46 #endif /* PRESUNEUC */
47 
48 /*
49  * This is the main routine for visual.
50  * We here decode the count and possible named buffer specification
51  * preceding a command and interpret a few of the commands.
52  * Commands which involve a target (i.e. an operator) are decoded
53  * in the routine operate in ex_voperate.c.
54  */
55 
56 #define	forbid(a)	{ if (a) goto fonfon; }
57 
58 extern int windowchg;
59 extern int sigok;
60 #ifdef XPG6
61 int redisplay;	/* XPG6 assertion 313 [count]r\n :  Also used in ex_vops2.c */
62 #endif
63 void redraw(), windowinit();
64 
65 #ifdef XPG4
66 extern int P_cursor_offset;
67 #endif
68 
69 void
70 vmain(void)
71 {
72 	int c, cnt, i;
73 	wchar_t esave[TUBECOLS];
74 	extern wchar_t atube[];
75 	unsigned char *oglobp;
76 	short d;
77 	line *addr;
78 	int ind, nlput;
79 	int shouldpo = 0;
80 	int tag_reset_wrap = 0;
81 	int onumber, olist, (*OPline)(), (*OPutchar)();
82 
83 
84 	vch_mac = VC_NOTINMAC;
85 	ixlatctl(0);
86 
87 	/*
88 	 * If we started as a vi command (on the command line)
89 	 * then go process initial commands (recover, next or tag).
90 	 */
91 	if (initev) {
92 		oglobp = globp;
93 		globp = initev;
94 		hadcnt = cnt = 0;
95 		i = tchng;
96 		addr = dot;
97 		goto doinit;
98 	}
99 
100 	vshowmode("");		/* As a precaution */
101 	/*
102 	 * NB:
103 	 *
104 	 * The current line is always in the line buffer linebuf,
105 	 * and the cursor at the position cursor.  You should do
106 	 * a vsave() before moving off the line to make sure the disk
107 	 * copy is updated if it has changed, and a getDOT() to get
108 	 * the line back if you mung linebuf.  The motion
109 	 * routines in ex_vwind.c handle most of this.
110 	 */
111 	for (;;) {
112 		/*
113 		 * Decode a visual command.
114 		 * First sync the temp file if there has been a reasonable
115 		 * amount of change.  Clear state for decoding of next
116 		 * command.
117 		 */
118 		TSYNC();
119 		vglobp = 0;
120 		vreg = 0;
121 		hold = 0;
122 		seenprompt = 1;
123 		wcursor = 0;
124 		Xhadcnt = hadcnt = 0;
125 		Xcnt = cnt = 1;
126 		splitw = 0;
127 		if (i = holdupd && !windowchg) {
128 			if (state == VISUAL) {
129 				sigok = 1;
130 				(void)peekkey();
131 				sigok = 0;
132 			}
133 
134 			holdupd = 0;
135 /*
136 			if (LINE(0) < ZERO) {
137 				vclear();
138 				vcnt = 0;
139 				i = 3;
140 			}
141 */
142 			if (state != VISUAL) {
143 				vcnt = 0;
144 				vsave();
145 				vrepaint(cursor);
146 			} else if (i == 3)
147 				vredraw(WTOP);
148 			else
149 				vsync(WTOP);
150 			vfixcurs();
151 		} else if(windowchg)
152 			redraw();
153 
154 #ifdef XPG6
155 		if (redisplay) {
156 			/* XPG6 assertion 313 & 254 : after [count]r\n */
157 			fixdisplay();
158 		}
159 		redisplay = 0;
160 #endif
161 		/*
162 		 * Gobble up counts and named buffer specifications.
163 		 */
164 		for (;;) {
165 looptop:
166 #ifdef MDEBUG
167 			if (trace)
168 				fprintf(trace, "pc=%c",peekkey());
169 #endif
170 			sigok = 1;
171 			c = peekkey();
172 			sigok = 0;
173 			if (isdigit(peekkey()) && peekkey() != '0') {
174 				hadcnt = 1;
175 				cnt = vgetcnt();
176 				forbid (cnt <= 0);
177 			}
178 			if (peekkey() != '"')
179 				break;
180 			(void)getkey(), c = getkey();
181 			/*
182 			 * Buffer names be letters or digits.
183 			 * But not '0' as that is the source of
184 			 * an 'empty' named buffer spec in the routine
185 			 * kshift (see ex_temp.c).
186 			 */
187 			if(!isascii(c) && MB_CUR_MAX > 1) {
188 				/* get rest of character */
189 				wchar_t wchar;
190 				char multic[MULTI_BYTE_MAX];
191 				ungetkey(c);
192 				(void)_mbftowc(multic, &wchar, getkey, &Peekkey);
193 			}
194 			forbid (c == '0' || !isalpha(c) && !isascii(c) && !isdigit(c));
195 			vreg = c;
196 		}
197 reread:
198 		/*
199 		 * Come to reread from below after some macro expansions.
200 		 * The call to map allows use of function key pads
201 		 * by performing a terminal dependent mapping of inputs.
202 		 */
203 #ifdef MDEBUG
204 		if (trace)
205 			fprintf(trace,"pcb=%c,",peekkey());
206 #endif
207 		op = getkey();
208 		maphopcnt = 0;
209 		do {
210 			/*
211 			 * Keep mapping the char as long as it changes.
212 			 * This allows for double mappings, e.g., q to #,
213 			 * #1 to something else.
214 			 */
215 			c = op;
216 			op = map(c, arrows, 0);
217 #ifdef MDEBUG
218 			if (trace)
219 				fprintf(trace,"pca=%c,",c);
220 #endif
221 			/*
222 			 * Maybe the mapped to char is a count. If so, we have
223 			 * to go back to the "for" to interpret it. Likewise
224 			 * for a buffer name.
225 			 */
226 			if ((isdigit(c) && c!='0') || c == '"') {
227 				ungetkey(c);
228 				goto looptop;
229 			}
230 			if (!value(vi_REMAP)) {
231 				c = op;
232 				break;
233 			}
234 			if (++maphopcnt > 256)
235 				error(gettext("Infinite macro loop"));
236 		} while (c != op);
237 
238 		/*
239 		 * Begin to build an image of this command for possible
240 		 * later repeat in the buffer workcmd.  It will be copied
241 		 * to lastcmd by the routine setLAST
242 		 * if/when completely specified.
243 		 */
244 		lastcp = workcmd;
245 		if (!vglobp)
246 			*lastcp++ = c;
247 
248 		/*
249 		 * First level command decode.
250 		 */
251 		switch (c) {
252 
253 		/*
254 		 * ^L		Clear screen e.g. after transmission error.
255 		 */
256 
257 		/*
258 		 * ^R		Retype screen, getting rid of @ lines.
259 		 *		If in open, equivalent to ^L.
260 		 *		On terminals where the right arrow key sends
261 		 *		^L we make ^R act like ^L, since there is no
262 		 *		way to get ^L.  These terminals (adm31, tvi)
263 		 *		are intelligent so ^R is useless.  Soroc
264 		 *		will probably foul this up, but nobody has
265 		 *		one of them.
266 		 */
267 		case CTRL('l'):
268 		case CTRL('r'):
269 			if (c == CTRL('l') || (key_right && *key_right==CTRL('l'))) {
270 				vclear();
271 				vdirty(0, vcnt);
272 			}
273 			if (state != VISUAL) {
274 				/*
275 				 * Get a clean line, throw away the
276 				 * memory of what is displayed now,
277 				 * and move back onto the current line.
278 				 */
279 				vclean();
280 				vcnt = 0;
281 				vmoveto(dot, cursor, 0);
282 				continue;
283 			}
284 			vredraw(WTOP);
285 			/*
286 			 * Weird glitch -- when we enter visual
287 			 * in a very small window we may end up with
288 			 * no lines on the screen because the line
289 			 * at the top is too long.  This forces the screen
290 			 * to be expanded to make room for it (after
291 			 * we have printed @'s ick showing we goofed).
292 			 */
293 			if (vcnt == 0)
294 				vrepaint(cursor);
295 			vfixcurs();
296 			continue;
297 
298 		/*
299 		 * $		Escape just cancels the current command
300 		 *		with a little feedback.
301 		 */
302 		case ESCAPE:
303 			(void) beep();
304 			continue;
305 
306 		/*
307 		 * @   		Macros. Bring in the macro and put it
308 		 *		in vmacbuf, point vglobp there and punt.
309 		 */
310 		 case '@':
311 			c = getesc();
312 			if (c == 0)
313 				continue;
314 			if (c == '@')
315 				c = lastmac;
316 			if (isupper(c))
317 				c = tolower(c);
318 			forbid(!islower(c));
319 			lastmac = c;
320 			vsave();
321 			CATCH
322 				unsigned char tmpbuf[BUFSIZE];
323 
324 				regbuf(c, tmpbuf, sizeof (vmacbuf));
325 				macpush(tmpbuf, 1);
326 			ONERR
327 				lastmac = 0;
328 				splitw = 0;
329 				getDOT();
330 				vrepaint(cursor);
331 				continue;
332 			ENDCATCH
333 			vmacp = vmacbuf;
334 			goto reread;
335 
336 		/*
337 		 * .		Repeat the last (modifying) open/visual command.
338 		 */
339 		case '.':
340 			/*
341 			 * Check that there was a last command, and
342 			 * take its count and named buffer unless they
343 			 * were given anew.  Special case if last command
344 			 * referenced a numeric named buffer -- increment
345 			 * the number and go to a named buffer again.
346 			 * This allows a sequence like "1pu.u.u...
347 			 * to successively look for stuff in the kill chain
348 			 * much as one does in EMACS with C-Y and M-Y.
349 			 */
350 			forbid (lastcmd[0] == 0);
351 			if (hadcnt)
352 				lastcnt = cnt;
353 			if (vreg)
354 				lastreg = vreg;
355 			else if (isdigit(lastreg) && lastreg < '9')
356 				lastreg++;
357 			vreg = lastreg;
358 			cnt = lastcnt;
359 			hadcnt = lasthad;
360 			vglobp = lastcmd;
361 			goto reread;
362 
363 		/*
364 		 * ^U		Scroll up.  A count sticks around for
365 		 *		future scrolls as the scroll amount.
366 		 *		Attempt to hold the indentation from the
367 		 *		top of the screen (in logical lines).
368 		 *
369 		 * BUG:		A ^U near the bottom of the screen
370 		 *		on a dumb terminal (which can't roll back)
371 		 *		causes the screen to be cleared and then
372 		 *		redrawn almost as it was.  In this case
373 		 *		one should simply move the cursor.
374 		 */
375 		case CTRL('u'):
376 			if (hadcnt)
377 				vSCROLL = cnt;
378 			cnt = vSCROLL;
379 			if (state == VISUAL)
380 				ind = vcline, cnt += ind;
381 			else
382 				ind = 0;
383 			vmoving = 0;
384 			vup(cnt, ind, 1);
385 			vnline((unsigned char *)NOSTR);
386 			continue;
387 
388 		/*
389 		 * ^D		Scroll down.  Like scroll up.
390 		 */
391 		case CTRL('d'):
392 #ifdef TRACE
393 		if (trace)
394 			fprintf(trace, "before vdown in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
395 #endif
396 			if (hadcnt)
397 				vSCROLL = cnt;
398 			cnt = vSCROLL;
399 			if (state == VISUAL)
400 				ind = vcnt - vcline - 1, cnt += ind;
401 			else
402 				ind = 0;
403 			vmoving = 0;
404 			vdown(cnt, ind, 1);
405 #ifdef TRACE
406 		if (trace)
407 			fprintf(trace, "before vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
408 #endif
409 			vnline((unsigned char *)NOSTR);
410 #ifdef TRACE
411 		if (trace)
412 			fprintf(trace, "after vnline in ^D, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol));
413 #endif
414 			continue;
415 
416 		/*
417 		 * ^E		Glitch the screen down (one) line.
418 		 *		Cursor left on same line in file.
419 		 */
420 		case CTRL('e'):
421 			if (state != VISUAL)
422 				continue;
423 			if (!hadcnt)
424 				cnt = 1;
425 			/* Bottom line of file already on screen */
426 			forbid(lineDOL()-lineDOT() <= vcnt-1-vcline);
427 			ind = vcnt - vcline - 1 + cnt;
428 			vdown(ind, ind, 1);
429 			vnline(cursor);
430 			continue;
431 
432 		/*
433 		 * ^Y		Like ^E but up
434 		 */
435 		case CTRL('y'):
436 			if (state != VISUAL)
437 				continue;
438 			if (!hadcnt)
439 				cnt = 1;
440 			forbid(lineDOT()-1<=vcline); /* line 1 already there */
441 			ind = vcline + cnt;
442 			vup(ind, ind, 1);
443 			vnline(cursor);
444 			continue;
445 
446 
447 		/*
448 		 * m		Mark position in mark register given
449 		 *		by following letter.  Return is
450 		 *		accomplished via ' or `; former
451 		 *		to beginning of line where mark
452 		 *		was set, latter to column where marked.
453 		 */
454 		case 'm':
455 			/*
456 			 * Getesc is generally used when a character
457 			 * is read as a latter part of a command
458 			 * to allow one to hit rubout/escape to cancel
459 			 * what you have typed so far.  These characters
460 			 * are mapped to 0 by the subroutine.
461 			 */
462 			c = getesc();
463 			if (c == 0)
464 				continue;
465 
466 			/*
467 			 * Markreg checks that argument is a letter
468 			 * and also maps ' and ` to the end of the range
469 			 * to allow '' or `` to reference the previous
470 			 * context mark.
471 			 */
472 			c = markreg(c);
473 			forbid (c == 0);
474 			vsave();
475 			names[c - 'a'] = (*dot &~ 01);
476 			ncols[c - 'a'] = cursor;
477 			anymarks = 1;
478 			continue;
479 
480 		/*
481 		 * ^F		Window forwards, with 2 lines of continuity.
482 		 *		Count repeats.
483 		 */
484 		case CTRL('f'):
485 			vsave();
486 			if (vcnt > 2) {
487 				addr = dot + (vcnt - vcline) - 2 + (cnt-1)*basWLINES;
488 				forbid(addr > dol);
489 				dot = addr;
490 				vcnt = vcline = 0;
491 			}
492 			vzop(0, 0, '+');
493 			continue;
494 
495 		/*
496 		 * ^B		Window backwards, with 2 lines of continuity.
497 		 *		Inverse of ^F.
498 		 */
499 		case CTRL('b'):
500 			vsave();
501 			if (one + vcline != dot && vcnt > 2) {
502 				addr = dot - vcline + 2 - (cnt-1)*basWLINES;
503 				forbid (addr <= zero);
504 				dot = addr;
505 				vcnt = vcline = 0;
506 			}
507 			vzop(0, 0, '^');
508 			continue;
509 
510 		/*
511 		 * z		Screen adjustment, taking a following character:
512 		 *			zcarriage_return		current line to top
513 		 *			z<NL>		like zcarriage_return
514 		 *			z-		current line to bottom
515 		 *		also z+, z^ like ^F and ^B.
516 		 *		A preceding count is line to use rather
517 		 *		than current line.  A count between z and
518 		 *		specifier character changes the screen size
519 		 *		for the redraw.
520 		 *
521 		 */
522 		case 'z':
523 			if (state == VISUAL) {
524 				i = vgetcnt();
525 				if (i > 0)
526 					vsetsiz(i);
527 				c = getesc();
528 				if (c == 0)
529 					continue;
530 			}
531 			vsave();
532 			vzop(hadcnt, cnt, c);
533 			continue;
534 
535 		/*
536 		 * Y		Yank lines, abbreviation for y_ or yy.
537 		 *		Yanked lines can be put later if no
538 		 *		changes intervene, or can be put in named
539 		 *		buffers and put anytime in this session.
540 		 */
541 		case 'Y':
542 			ungetkey('_');
543 			c = 'y';
544 			break;
545 
546 		/*
547 		 * J		Join lines, 2 by default.  Count is number
548 		 *		of lines to join (no join operator sorry.)
549 		 */
550 		case 'J':
551 			forbid (dot == dol);
552 			if (cnt == 1)
553 				cnt = 2;
554 			if (cnt > (i = dol - dot + 1))
555 				cnt = i;
556 			vsave();
557 			vmacchng(1);
558 			setLAST();
559 			cursor = strend(linebuf);
560 			vremote(cnt, join, 0);
561 			notenam = (unsigned char *)"join";
562 			vmoving = 0;
563 			killU();
564 			vreplace(vcline, cnt, 1);
565 			if (!*cursor && cursor > linebuf)
566 				cursor--;
567 			if (notecnt == 2)
568 				notecnt = 0;
569 			vrepaint(cursor);
570 			continue;
571 
572 		/*
573 		 * S		Substitute text for whole lines, abbrev for c_.
574 		 *		Count is number of lines to change.
575 		 */
576 		case 'S':
577 			ungetkey('_');
578 			c = 'c';
579 			break;
580 
581 		/*
582 		 * O		Create a new line above current and accept new
583 		 *		input text, to an escape, there.
584 		 *		A count specifies, for dumb terminals when
585 		 *		slowopen is not set, the number of physical
586 		 *		line space to open on the screen.
587 		 *
588 		 * o		Like O, but opens lines below.
589 		 */
590 		case 'O':
591 		case 'o':
592 			vmacchng(1);
593 			voOpen(c, cnt);
594 			continue;
595 
596 		/*
597 		 * C		Change text to end of line, short for c$.
598 		 */
599 		case 'C':
600 			if (*cursor) {
601 				ungetkey('$'), c = 'c';
602 				break;
603 			}
604 			goto appnd;
605 
606 		/*
607 		 * ~	Switch case of letter under cursor
608 		 */
609 		case '~':
610 			{
611 				unsigned char mbuf[2049];
612 				unsigned char *ccursor = cursor;
613 #ifdef PRESUNEUC
614 				int tmp, length;
615 				wchar_t wchar;
616 #else
617 				int tmp, len, n;
618 				wchar_t wc;
619 #endif /* PRESUNEUC */
620 				unsigned char tmp1;
621 				setLAST();
622 				for (tmp = 0; tmp + 3 < 2048; ) {
623 				/*
624 				 * Use multiple 'r' commands to replace
625 				 * alpha with alternate case.
626 				 */
627 
628 					if(cnt-- <= 0)
629 						break;
630 #ifdef PRESUNEUC
631 					length = mbtowc(&wchar, (char *)ccursor, MULTI_BYTE_MAX);
632 #else
633 					len = mbtowc(&wc, (char *)ccursor, MULTI_BYTE_MAX);
634 #endif /* PRESUNEUC */
635 #ifdef PRESUNEUC
636 					if(length > 1) {
637 #else
638 					n = iswalpha(wc);
639 					if(len > 1 && !iswalpha(wc)) {
640 #endif /* PRESUNEUC */
641 						mbuf[tmp+0] = ' ';
642 						tmp++;
643 #ifdef PRESUNEUC
644 						ccursor += length;
645 #else
646 						ccursor += len;
647 #endif /* PRESUNEUC */
648 						continue;
649 					}
650 					mbuf[tmp] = 'r';
651 #ifdef PRESUNEUC
652 					mbuf[tmp+1] = *ccursor++;
653 #else
654 					ccursor += ((len > 0) ? len : 1);
655 #endif /* PRESUNEUC */
656 				/*
657 				 * If pointing to an alpha character,
658 				 * change the case.
659 				 */
660 
661 					tmp1 = mbuf[tmp+1];
662 #ifdef PRESUNEUC
663 					if (isupper((unsigned char)tmp1))
664 						mbuf[tmp+1] = tolower((unsigned char)tmp1);
665 					else
666 						mbuf[tmp+1] = toupper((unsigned char)tmp1);
667 #else
668 					if (iswupper(wc))
669 						len = wctomb((char *)(mbuf + tmp + 1),
670 							(wc = towlower(wc)));
671 					else
672 						len = wctomb((char *)(mbuf + tmp + 1),
673 							(wc = towupper(wc)));
674 					tmp += len - 1;
675 #endif /* PRESUNEUC */
676 					if(*ccursor)
677 				/*
678 				 * If at end of line do not advance
679 				 * to the next character, else use a
680 				 * space to advance 1 column.
681 				 */
682 						mbuf[tmp+2] = ' ';
683 					else {
684 						mbuf[tmp+2] = '\0';
685 						tmp +=3;
686 						break;
687 					}
688 					tmp += 3;
689 				}
690 
691 				mbuf[tmp] = 0;
692 				macpush(mbuf, 1);
693 			}
694 			continue;
695 
696 
697 		/*
698 		 * A		Append at end of line, short for $a.
699 		 */
700 		case 'A':
701 			operate('$', 1);
702 appnd:
703 			c = 'a';
704 			/* fall into ... */
705 
706 		/*
707 		 * a		Appends text after cursor.  Text can continue
708 		 *		through arbitrary number of lines.
709 		 */
710 		case 'a':
711 			if (*cursor) {
712 				wchar_t wchar;
713 				int length = mbtowc(&wchar, (char *)cursor, MULTI_BYTE_MAX);
714 				if (state == HARDOPEN) {
715 					if(length < 0) {
716 						putoctal = 1;
717 						putchar(*cursor);
718 						putoctal = 0;
719 					} else
720 						putchar(wchar);
721 				}
722 				if(length < 0)
723 					cursor++;
724 				else
725 					cursor += length;
726 			}
727 			goto insrt;
728 
729 		/*
730 		 * I		Insert at beginning of whitespace of line,
731 		 *		short for ^i.
732 		 */
733 		case 'I':
734 			operate('^', 1);
735 			c = 'i';
736 			/* fall into ... */
737 
738 		/*
739 		 * R		Replace characters, one for one, by input
740 		 *		(logically), like repeated r commands.
741 		 *
742 		 * BUG:		This is like the typeover mode of many other
743 		 *		editors, and is only rarely useful.  Its
744 		 *		implementation is a hack in a low level
745 		 *		routine and it doesn't work very well, e.g.
746 		 *		you can't move around within a R, etc.
747 		 */
748 		case 'R':
749 			/* fall into... */
750 
751 		/*
752 		 * i		Insert text to an escape in the buffer.
753 		 *		Text is arbitrary.  This command reminds of
754 		 *		the i command in bare teco.
755 		 */
756 		case 'i':
757 insrt:
758 			/*
759 			 * Common code for all the insertion commands.
760 			 * Save for redo, position cursor, prepare for append
761 			 * at command and in visual undo.  Note that nothing
762 			 * is doomed, unless R when all is, and save the
763 			 * current line in a the undo temporary buffer.
764 			 */
765 			vmacchng(1);
766 			setLAST();
767 			vcursat(cursor);
768 			prepapp();
769 			vnoapp();
770 			doomed = c == 'R' ? 10000 : 0;
771 			if(FIXUNDO)
772 				vundkind = VCHNG;
773 			vmoving = 0;
774 			CP(vutmp, linebuf);
775 
776 			/*
777 			 * If this is a repeated command, then suppress
778 			 * fake insert mode on dumb terminals which looks
779 			 * ridiculous and wastes lots of time even at 9600B.
780 			 */
781 			if (vglobp)
782 				hold = HOLDQIK;
783 			vappend(c, cnt, 0);
784 			continue;
785 
786 		/*
787 		 * 	An attention, normally a DEL, just beeps.
788 		 *	If you are a vi command within ex, then
789 		 *	two ATTN's will drop you back to command mode.
790 		 */
791 		case ATTN:
792 			(void) beep();
793 			if (initev || peekkey() != ATTN)
794 				continue;
795 			/* fall into... */
796 
797 		/*
798 		 * ^\		A quit always gets command mode.
799 		 */
800 		case QUIT:
801 			/*
802 			 * Have to be careful if we were called
803 			 *	g/xxx/vi
804 			 * since a return will just start up again.
805 			 * So we simulate an interrupt.
806 			 */
807 			if (inglobal)
808 				onintr(0);
809 			/* fall into... */
810 
811 #ifdef notdef
812 		/*
813 		 * q		Quit back to command mode, unless called as
814 		 *		vi on command line in which case dont do it
815 		 */
816 		case 'q':	/* quit */
817 			if (initev) {
818 				vsave();
819 				CATCH
820 					error(gettext("Q gets ex command mode, :q leaves vi"));
821 				ENDCATCH
822 				splitw = 0;
823 				getDOT();
824 				vrepaint(cursor);
825 				continue;
826 			}
827 #endif
828 			/* fall into... */
829 
830 		/*
831 		 * Q		Is like q, but always gets to command mode
832 		 *		even if command line invocation was as vi.
833 		 */
834 		case 'Q':
835 			vsave();
836 			/*
837 			 * If we are in the middle of a macro, throw away
838 			 * the rest and fix up undo.
839 			 * This code copied from getbr().
840 			 */
841 			if (vmacp) {
842 				vmacp = 0;
843 				if (inopen == -1)	/* don't mess up undo for esc esc */
844 					vundkind = VMANY;
845 				inopen = 1;	/* restore old setting now that macro done */
846 			}
847 			ixlatctl(1);
848 			return;
849 
850 
851 		/*
852 		 * ZZ		Like :x
853 		 */
854 		 case 'Z':
855 			forbid(getkey() != 'Z');
856 			oglobp = globp;
857 			globp = (unsigned char *)"x";
858 			vclrech(0);
859 			goto gogo;
860 
861 		/*
862 		 * P		Put back text before cursor or before current
863 		 *		line.  If text was whole lines goes back
864 		 *		as whole lines.  If part of a single line
865 		 *		or parts of whole lines splits up current
866 		 *		line to form many new lines.
867 		 *		May specify a named buffer, or the delete
868 		 *		saving buffers 1-9.
869 		 *
870 		 * p		Like P but after rather than before.
871 		 */
872 		case 'P':
873 		case 'p':
874 			vmoving = 0;
875 #ifdef XPG4
876 			P_cursor_offset = 0;
877 #endif
878 #ifdef notdef
879 			forbid (!vreg && value(vi_UNDOMACRO) && inopen < 0);
880 #endif
881 			/*
882 			 * If previous delete was partial line, use an
883 			 * append or insert to put it back so as to
884 			 * use insert mode on intelligent terminals.
885 			 */
886 			if (!vreg && DEL[0]) {
887 				setLAST();
888 				forbid ((unsigned char)DEL[128] == 0200);
889 				vglobp = DEL;
890 				ungetkey(c == 'p' ? 'a' : 'i');
891 				goto reread;
892 			}
893 
894 			/*
895 			 * If a register wasn't specified, then make
896 			 * sure there is something to put back.
897 			 */
898 			forbid (!vreg && unddol == dol);
899 			/*
900 			 * If we just did a macro the whole buffer is in
901 			 * the undo save area.  We don't want to put THAT.
902 			 */
903 			forbid (vundkind == VMANY && undkind==UNDALL);
904 			vsave();
905 			vmacchng(1);
906 			setLAST();
907 			i = 0;
908 			if (vreg && partreg(vreg) || !vreg && pkill[0]) {
909 				/*
910 				 * Restoring multiple lines which were partial
911 				 * lines; will leave cursor in middle
912 				 * of line after shoving restored text in to
913 				 * split the current line.
914 				 */
915 				i++;
916 				if (c == 'p' && *cursor)
917 					cursor = nextchr(cursor);
918 			} else {
919 				/*
920 				 * In whole line case, have to back up dot
921 				 * for P; also want to clear cursor so
922 				 * cursor will eventually be positioned
923 				 * at the beginning of the first put line.
924 				 */
925 				cursor = 0;
926 				if (c == 'P') {
927 					dot--, vcline--;
928 					c = 'p';
929 				}
930 			}
931 			killU();
932 
933 			/*
934 			 * The call to putreg can potentially
935 			 * bomb since there may be nothing in a named buffer.
936 			 * We thus put a catch in here.  If we didn't and
937 			 * there was an error we would end up in command mode.
938 			 */
939 			addr = dol;	/* old dol */
940 			CATCH
941 				vremote(1,
942 				    vreg ? (int (*)())putreg : put, vreg);
943 			ONERR
944 				if (vreg == -1) {
945 					splitw = 0;
946 					if (op == 'P')
947 						dot++, vcline++;
948 					goto pfixup;
949 				}
950 			ENDCATCH
951 			splitw = 0;
952 			nlput = dol - addr + 1;
953 			if (!i) {
954 				/*
955 				 * Increment undap1, undap2 to make up
956 				 * for their incorrect initialization in the
957 				 * routine vremote before calling put/putreg.
958 				 */
959 				if (FIXUNDO)
960 					undap1++, undap2++;
961 				vcline++;
962 				nlput--;
963 
964 				/*
965 				 * After a put want current line first line,
966 				 * and dot was made the last line put in code
967 				 * run so far.  This is why we increment vcline
968 				 * above and decrease dot here.
969 				 */
970 				dot -= nlput - 1;
971 			}
972 #ifdef TRACE
973 			if (trace)
974 				fprintf(trace, "vreplace(%d, %d, %d), undap1=%d, undap2=%d, dot=%d\n", vcline, i, nlput, lineno(undap1), lineno(undap2), lineno(dot));
975 #endif
976 			vreplace(vcline, i, nlput);
977 #ifdef XPG4
978 			if (op == 'P' && i > 0) {
979 				dot += nlput - 1;
980 				vcline += nlput - 1;
981 				cursor += P_cursor_offset;
982 			}
983 #endif
984 			if (state != VISUAL) {
985 				/*
986 				 * Special case in open mode.
987 				 * Force action on the screen when a single
988 				 * line is put even if it is identical to
989 				 * the current line, e.g. on YP; otherwise
990 				 * you can't tell anything happened.
991 				 */
992 				vjumpto(dot, cursor, '.');
993 				continue;
994 			}
995 pfixup:
996 			vrepaint(cursor);
997 			vfixcurs();
998 			continue;
999 
1000 		/*
1001 		 * ^^		Return to previous file.
1002 		 *		Like a :e #, and thus can be used after a
1003 		 *		"No Write" diagnostic.
1004 		 */
1005 		case CTRL('^'):
1006 			forbid (hadcnt);
1007 			vsave();
1008 			ckaw();
1009 			oglobp = globp;
1010 			if (value(vi_AUTOWRITE) && !value(vi_READONLY))
1011 				globp = (unsigned char *)"e! #";
1012 			else
1013 				globp = (unsigned char *)"e #";
1014 			goto gogo;
1015 
1016 #ifdef TAG_STACK
1017                 /*
1018                  * ^T           Pop the tag stack if enabled or else reset it
1019                  *              if not.
1020                  */
1021                 case CTRL('t'):
1022                         forbid (hadcnt);
1023                         vsave();
1024                         oglobp = globp;
1025                         globp = (unsigned char *) "pop";
1026                         goto gogo;
1027 #endif
1028 		/*
1029 		 * ^]		Takes word after cursor as tag, and then does
1030 		 *		tag command.  Read ``go right to''.
1031 		 *		This is not a search, so the wrapscan setting
1032 		 *		must be ignored.  If set, then it is unset
1033 		 *		here and restored later.
1034 		 */
1035 		case CTRL(']'):
1036 			grabtag();
1037 			oglobp = globp;
1038 			if (value(vi_WRAPSCAN) == 0) {
1039 				tag_reset_wrap = 1;
1040 				value(vi_WRAPSCAN) = 1;
1041 			}
1042 			globp = (unsigned char *)"tag";
1043 			goto gogo;
1044 
1045 		/*
1046 		 * &		Like :&
1047 		 */
1048 		 case '&':
1049 			oglobp = globp;
1050 			globp = (unsigned char *)"&";
1051 			goto gogo;
1052 
1053 		/*
1054 		 * ^G		Bring up a status line at the bottom of
1055 		 *		the screen, like a :file command.
1056 		 *
1057 		 * BUG:		Was ^S but doesn't work in cbreak mode
1058 		 */
1059 		case CTRL('g'):
1060 			oglobp = globp;
1061 			globp = (unsigned char *)"file";
1062 gogo:
1063 			addr = dot;
1064 			vsave();
1065 			goto doinit;
1066 
1067 #ifdef SIGTSTP
1068 		/*
1069 		 * ^Z:	suspend editor session and temporarily return
1070 		 * 	to shell.  Only works with Berkeley/IIASA process
1071 		 *	control in kernel.
1072 		 */
1073 		case CTRL('z'):
1074 			forbid(dosusp == 0);
1075 			vsave();
1076 			oglobp = globp;
1077 			globp = (unsigned char *)"stop";
1078 			goto gogo;
1079 #endif
1080 
1081 		/*
1082 		 * :		Read a command from the echo area and
1083 		 *		execute it in command mode.
1084 		 */
1085 		case ':':
1086 			forbid (hadcnt);
1087 			vsave();
1088 			i = tchng;
1089 			addr = dot;
1090 			if (readecho(c)) {
1091 				esave[0] = 0;
1092 				goto fixup;
1093 			}
1094 			getDOT();
1095 			/*
1096 			 * Use the visual undo buffer to store the global
1097 			 * string for command mode, since it is idle right now.
1098 			 */
1099 			oglobp = globp; strcpy(vutmp, genbuf+1); globp = vutmp;
1100 doinit:
1101 			esave[0] = 0;
1102 			fixech();
1103 
1104 			/*
1105 			 * Have to finagle around not to lose last
1106 			 * character after this command (when run from ex
1107 			 * command mode).  This is clumsy.
1108 			 */
1109 			d = peekc; ungetchar(0);
1110 			if (shouldpo) {
1111 				/*
1112 				 * So after a "Hit return..." ":", we do
1113 				 * another "Hit return..." the next time
1114 				 */
1115 				pofix();
1116 				shouldpo = 0;
1117 			}
1118 			CATCH
1119 				/*
1120 				 * Save old values of options so we can
1121 				 * notice when they change; switch into
1122 				 * cooked mode so we are interruptible.
1123 				 */
1124 				onumber = value(vi_NUMBER);
1125 				olist = value(vi_LIST);
1126 				OPline = Pline;
1127 				OPutchar = Putchar;
1128 #ifndef CBREAK
1129 				vcook();
1130 #endif
1131 				commands(1, 1);
1132 				if (dot == zero && dol > zero)
1133 					dot = one;
1134 #ifndef CBREAK
1135 				vraw();
1136 #endif
1137 			ONERR
1138 #ifndef CBREAK
1139 				vraw();
1140 #endif
1141 				copy(esave, vtube[WECHO], TUBECOLS * sizeof(wchar_t));
1142 			ENDCATCH
1143 			fixol();
1144 			Pline = OPline;
1145 			Putchar = OPutchar;
1146 			ungetchar(d);
1147 			globp = oglobp;
1148 
1149 			/*
1150 			 * If we ended up with no lines in the buffer, make
1151 			 * a line.
1152 			 */
1153 			if (dot == zero) {
1154 				fixzero();
1155 			}
1156 			splitw = 0;
1157 
1158 			/*
1159 			 * Special case: did list/number options change?
1160 			 */
1161 			if (onumber != value(vi_NUMBER))
1162 				setnumb(value(vi_NUMBER));
1163 			if (olist != value(vi_LIST))
1164 				setlist(value(vi_LIST));
1165 
1166 fixup:
1167 			/*
1168 			 * If a change occurred, other than
1169 			 * a write which clears changes, then
1170 			 * we should allow an undo even if .
1171 			 * didn't move.
1172 			 *
1173 			 * BUG: You can make this wrong by
1174 			 * tricking around with multiple commands
1175 			 * on one line of : escape, and including
1176 			 * a write command there, but it's not
1177 			 * worth worrying about.
1178 			 */
1179 			if (FIXUNDO && tchng && tchng != i)
1180 				vundkind = VMANY, cursor = 0;
1181 
1182 			/*
1183 			 * If we are about to do another :, hold off
1184 			 * updating of screen.
1185 			 */
1186 			if (vcnt < 0 && Peekkey == ':') {
1187 				getDOT();
1188 				shouldpo = 1;
1189 				continue;
1190 			}
1191 			shouldpo = 0;
1192 
1193 			/*
1194 			 * In the case where the file being edited is
1195 			 * new; e.g. if the initial state hasn't been
1196 			 * saved yet, then do so now.
1197 			 */
1198 			if (unddol == truedol) {
1199 				vundkind = VNONE;
1200 				Vlines = lineDOL();
1201 				if (!inglobal)
1202 					savevis();
1203 				addr = zero;
1204 				vcnt = 0;
1205 				if (esave[0] == 0)
1206 					copy(esave, vtube[WECHO], TUBECOLS * sizeof(wchar_t));
1207 			}
1208 
1209 			/*
1210 			 * If the current line moved reset the cursor position.
1211 			 */
1212 			if (dot != addr) {
1213 				vmoving = 0;
1214 				cursor = 0;
1215 			}
1216 
1217 			/*
1218 			 * If current line is not on screen or if we are
1219 			 * in open mode and . moved, then redraw.
1220 			 */
1221 			i = vcline + (dot - addr);
1222 			if(windowchg)
1223 				windowinit();
1224 			if (i < 0 || i >= vcnt && i >= -vcnt || state != VISUAL && dot != addr) {
1225 				if (state == CRTOPEN)
1226 					vup1();
1227 				if (vcnt > 0)
1228 					vcnt = 0;
1229 				vjumpto(dot, (unsigned char *) 0, '.');
1230 			} else {
1231 				/*
1232 				 * Current line IS on screen.
1233 				 * If we did a [Hit return...] then
1234 				 * restore vcnt and clear screen if in visual
1235 				 */
1236 				vcline = i;
1237 				if (vcnt < 0) {
1238 					vcnt = -vcnt;
1239 					if (state == VISUAL)
1240 						vclear();
1241 					else if (state == CRTOPEN) {
1242 						vcnt = 0;
1243 					}
1244 				}
1245 
1246 				/*
1247 				 * Limit max value of vcnt based on $
1248 				 */
1249 				i = vcline + lineDOL() - lineDOT() + 1;
1250 				if (i < vcnt)
1251 					vcnt = i;
1252 
1253 				/*
1254 				 * Dirty and repaint.
1255 				 */
1256 				vdirty(0, lines);
1257 				vrepaint(cursor);
1258 			}
1259 
1260 			/*
1261 			 * If in visual, put back the echo area
1262 			 * if it was clobbered.
1263 			 */
1264 			if (state == VISUAL) {
1265 				int sdc = destcol, sdl = destline;
1266 
1267 				splitw++;
1268 				vigoto(WECHO, 0);
1269 				for (i = 0; i < TUBECOLS - 1; i++) {
1270 					if (esave[i] == 0)
1271 						break;
1272 					if(esave[i] != FILLER)
1273 						(void) vputchar(esave[i]);
1274 				}
1275 				splitw = 0;
1276 				vgoto(sdl, sdc);
1277 			}
1278 			if (tag_reset_wrap == 1) {
1279 				tag_reset_wrap = 0;
1280 				value(vi_WRAPSCAN) = 0;
1281 			}
1282 			continue;
1283 
1284 		/*
1285 		 * u		undo the last changing command.
1286 		 */
1287 		case 'u':
1288 			vundo(1);
1289 			continue;
1290 
1291 		/*
1292 		 * U		restore current line to initial state.
1293 		 */
1294 		case 'U':
1295 			vUndo();
1296 			continue;
1297 
1298 fonfon:
1299 			(void) beep();
1300 			vmacp = 0;
1301 			inopen = 1;	/* might have been -1 */
1302 			continue;
1303 		}
1304 
1305 		/*
1306 		 * Rest of commands are decoded by the operate
1307 		 * routine.
1308 		 */
1309 		operate(c, cnt);
1310 	}
1311 }
1312 
1313 /*
1314  * Grab the word after the cursor so we can look for it as a tag.
1315  */
1316 void
1317 grabtag(void)
1318 {
1319 	unsigned char *cp, *dp;
1320 
1321 	cp = vpastwh(cursor);
1322 	if (*cp) {
1323 		dp = lasttag;
1324 		do {
1325 			if (dp < &lasttag[sizeof lasttag - 2])
1326 				*dp++ = *cp;
1327 			cp++;
1328 			/* only allow ascii alphabetics */
1329 		} while ((isascii(*cp) && isalpha(*cp)) || isdigit(*cp) || *cp == '_');
1330 		*dp++ = 0;
1331 	}
1332 }
1333 
1334 /*
1335  * Before appending lines, set up addr1 and
1336  * the command mode undo information.
1337  */
1338 void
1339 prepapp(void)
1340 {
1341 
1342 	addr1 = dot;
1343 	deletenone();
1344 	addr1++;
1345 	appendnone();
1346 }
1347 
1348 /*
1349  * Execute function f with the address bounds addr1
1350  * and addr2 surrounding cnt lines starting at dot.
1351  */
1352 void
1353 vremote(cnt, f, arg)
1354 	int cnt, (*f)(), arg;
1355 {
1356 	int oing = inglobal;
1357 
1358 	addr1 = dot;
1359 	addr2 = dot + cnt - 1;
1360 	inglobal = 0;
1361 	if (FIXUNDO)
1362 		undap1 = undap2 = dot;
1363 	(*f)(arg);
1364 	inglobal = oing;
1365 	if (FIXUNDO)
1366 		vundkind = VMANY;
1367 	/*
1368 	 * XPG6 assertion 273: For the following commands, don't set vmcurs
1369 	 * to 0, so that undo positions the cursor column correctly when
1370 	 * we've moved off the initial line that was changed eg. when G has
1371 	 * moved us off the line, or when a multi-line change was done.
1372 	 */
1373 	if (lastcmd[0] != 'C' && lastcmd[0] != 'c' && lastcmd[0] != 'o' &&
1374 	    lastcmd[0] != 'R' && lastcmd[0] != 'S' && lastcmd[0] != 's' &&
1375 	    lastcmd[0] != 'i' && lastcmd[0] != 'a' && lastcmd[0] != 'A') {
1376 		vmcurs = 0;
1377 	}
1378 }
1379 
1380 /*
1381  * Save the current contents of linebuf, if it has changed.
1382  */
1383 void
1384 vsave(void)
1385 {
1386 	unsigned char temp[LBSIZE];
1387 
1388 	strncpy(temp, linebuf, sizeof (temp));
1389 	if (FIXUNDO && vundkind == VCHNG || vundkind == VCAPU) {
1390 		/*
1391 		 * If the undo state is saved in the temporary buffer
1392 		 * vutmp, then we sync this into the temp file so that
1393 		 * we will be able to undo even after we have moved off
1394 		 * the line.  It would be possible to associate a line
1395 		 * with vutmp but we assume that vutmp is only associated
1396 		 * with line dot (e.g. in case ':') above, so beware.
1397 		 */
1398 		prepapp();
1399 		strcLIN(vutmp);
1400 		putmark(dot);
1401 		vremote(1, yank, 0);
1402 		vundkind = VMCHNG;
1403 		notecnt = 0;
1404 		undkind = UNDCHANGE;
1405 	}
1406 	/*
1407 	 * Get the line out of the temp file and do nothing if it hasn't
1408 	 * changed.  This may seem like a loss, but the line will
1409 	 * almost always be in a read buffer so this may well avoid disk i/o.
1410 	 */
1411 	getDOT();
1412 	if (strncmp(linebuf, temp, sizeof (temp)) == 0)
1413 		return;
1414 	strcLIN(temp);
1415 	putmark(dot);
1416 }
1417 
1418 #undef	forbid
1419 #define	forbid(a)	if (a) { (void) beep(); return; }
1420 
1421 /*
1422  * Do a z operation.
1423  * Code here is rather long, and very uninteresting.
1424  */
1425 void
1426 vzop(bool hadcnt, int cnt, int c)
1427 {
1428 	line *addr;
1429 
1430 	if (state != VISUAL) {
1431 		/*
1432 		 * Z from open; always like a z=.
1433 		 * This code is a mess and should be cleaned up.
1434 		 */
1435 		vmoveitup(1, 1);
1436 		vgoto(outline, 0);
1437 		ostop(normf);
1438 		setoutt();
1439 		addr2 = dot;
1440 		vclear();
1441 		destline = WECHO;
1442 		zop2(Xhadcnt ? Xcnt : value(vi_WINDOW) - 1, '=');
1443 		if (state == CRTOPEN)
1444 			putnl();
1445 		putNFL();
1446 		termreset();
1447 		Outchar = vputchar;
1448 		(void)ostart();
1449 		vcnt = 0;
1450 		outline = destline = 0;
1451 		vjumpto(dot, cursor, 0);
1452 		return;
1453 	}
1454 	if (hadcnt) {
1455 		addr = zero + cnt;
1456 		if (addr < one)
1457 			addr = one;
1458 		if (addr > dol)
1459 			addr = dol;
1460 		markit(addr);
1461 	} else
1462 		switch (c) {
1463 
1464 		case '+':
1465 			addr = dot + vcnt - vcline;
1466 			break;
1467 
1468 		case '^':
1469 			addr = dot - vcline - 1;
1470 			forbid (addr < one);
1471 			c = '-';
1472 			break;
1473 
1474 		default:
1475 			addr = dot;
1476 			break;
1477 		}
1478 	switch (c) {
1479 
1480 	case '.':
1481 	case '-':
1482 		break;
1483 
1484 	case '^':
1485 		forbid (addr <= one);
1486 		break;
1487 
1488 	case '+':
1489 		forbid (addr >= dol);
1490 		/* fall into ... */
1491 
1492 	case CR:
1493 	case NL:
1494 		c = CR;
1495 		break;
1496 
1497 	default:
1498 		(void) beep();
1499 		return;
1500 	}
1501 	vmoving = 0;
1502 	vjumpto(addr, (unsigned char *)NOSTR, c);
1503 }
1504