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