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