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