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