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