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