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