xref: /original-bsd/usr.bin/ex/ex_cmdsub.c (revision 889e1095)
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_cmdsub.c	7.11 (Berkeley) 03/01/91";
9 #endif not lint
10 
11 #include "ex.h"
12 #include "ex_argv.h"
13 #include "ex_temp.h"
14 #include "ex_tty.h"
15 #include "ex_vis.h"
16 
17 /*
18  * Command mode subroutines implementing
19  *	append, args, copy, delete, join, move, put,
20  *	shift, tag, yank, z and undo
21  */
22 
23 bool	endline = 1;
24 line	*tad1;
25 static	jnoop();
26 
27 /*
28  * Append after line a lines returned by function f.
29  * Be careful about intermediate states to avoid scramble
30  * if an interrupt comes in.
31  */
32 append(f, a)
33 	int (*f)();
34 	line *a;
35 {
36 	register line *a1, *a2, *rdot;
37 	int nline;
38 
39 	nline = 0;
40 	dot = a;
41 	if(FIXUNDO && !inopen && f!=getsub) {
42 		undap1 = undap2 = dot + 1;
43 		undkind = UNDCHANGE;
44 	}
45 	while ((*f)() == 0) {
46 		if (truedol >= endcore) {
47 			if (morelines() < 0) {
48 				if (FIXUNDO && f == getsub) {
49 					undap1 = addr1;
50 					undap2 = addr2 + 1;
51 				}
52 				error("Out of memory@- too many lines in file");
53 			}
54 		}
55 		nline++;
56 		a1 = truedol + 1;
57 		a2 = a1 + 1;
58 		dot++;
59 		undap2++;
60 		dol++;
61 		unddol++;
62 		truedol++;
63 		for (rdot = dot; a1 > rdot;)
64 			*--a2 = *--a1;
65 		*rdot = 0;
66 		putmark(rdot);
67 		if (f == gettty) {
68 			dirtcnt++;
69 			TSYNC();
70 		}
71 	}
72 	return (nline);
73 }
74 
75 appendnone()
76 {
77 
78 	if(FIXUNDO) {
79 		undkind = UNDCHANGE;
80 		undap1 = undap2 = addr1;
81 	}
82 }
83 
84 /*
85  * Print out the argument list, with []'s around the current name.
86  */
87 pargs()
88 {
89 	register char **av = argv0, *as = args0;
90 	register int ac;
91 
92 	for (ac = 0; ac < argc0; ac++) {
93 		if (ac != 0)
94 			ex_putchar(' ' | QUOTE);
95 		if (ac + argc == argc0 - 1)
96 			ex_printf("[");
97 		lprintf("%s", as);
98 		if (ac + argc == argc0 - 1)
99 			ex_printf("]");
100 		as = av ? *++av : strend(as) + 1;
101 	}
102 	noonl();
103 }
104 
105 /*
106  * Delete lines; two cases are if we are really deleting,
107  * more commonly we are just moving lines to the undo save area.
108  */
109 ex_delete(hush)
110 	bool hush;
111 {
112 	register line *a1, *a2;
113 
114 	nonzero();
115 	if(FIXUNDO) {
116 		register void (*dsavint)();
117 
118 #ifdef TRACE
119 		if (trace)
120 			vudump("before delete");
121 #endif
122 		change();
123 		dsavint = signal(SIGINT, SIG_IGN);
124 		undkind = UNDCHANGE;
125 		a1 = addr1;
126 		squish();
127 		a2 = addr2;
128 		if (a2++ != dol) {
129 			reverse(a1, a2);
130 			reverse(a2, dol + 1);
131 			reverse(a1, dol + 1);
132 		}
133 		dol -= a2 - a1;
134 		unddel = a1 - 1;
135 		if (a1 > dol)
136 			a1 = dol;
137 		dot = a1;
138 		pkill[0] = pkill[1] = 0;
139 		signal(SIGINT, dsavint);
140 #ifdef TRACE
141 		if (trace)
142 			vudump("after delete");
143 #endif
144 	} else {
145 		register line *a3;
146 		register int i;
147 
148 		change();
149 		a1 = addr1;
150 		a2 = addr2 + 1;
151 		a3 = truedol;
152 		i = a2 - a1;
153 		unddol -= i;
154 		undap2 -= i;
155 		dol -= i;
156 		truedol -= i;
157 		do
158 			*a1++ = *a2++;
159 		while (a2 <= a3);
160 		a1 = addr1;
161 		if (a1 > dol)
162 			a1 = dol;
163 		dot = a1;
164 	}
165 	if (!hush)
166 		killed();
167 }
168 
169 deletenone()
170 {
171 
172 	if(FIXUNDO) {
173 		undkind = UNDCHANGE;
174 		squish();
175 		unddel = addr1;
176 	}
177 }
178 
179 /*
180  * Crush out the undo save area, moving the open/visual
181  * save area down in its place.
182  */
183 squish()
184 {
185 	register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
186 
187 	if(FIXUNDO) {
188 		if (inopen == -1)
189 			return;
190 		if (a1 < a2 && a2 < a3)
191 			do
192 				*a1++ = *a2++;
193 			while (a2 < a3);
194 		truedol -= unddol - dol;
195 		unddol = dol;
196 	}
197 }
198 
199 /*
200  * Join lines.  Special hacks put in spaces, two spaces if
201  * preceding line ends with '.', or no spaces if next line starts with ).
202  */
203 static	int jcount, jnoop();
204 
205 join(c)
206 	int c;
207 {
208 	register line *a1;
209 	register char *cp, *cp1;
210 
211 	cp = genbuf;
212 	*cp = 0;
213 	for (a1 = addr1; a1 <= addr2; a1++) {
214 		getline(*a1);
215 		cp1 = linebuf;
216 		if (a1 != addr1 && c == 0) {
217 			while (*cp1 == ' ' || *cp1 == '\t')
218 				cp1++;
219 			if (*cp1 && cp > genbuf && cp[-1] != ' ' && cp[-1] != '\t') {
220 				if (*cp1 != ')') {
221 					*cp++ = ' ';
222 					if (cp[-2] == '.')
223 						*cp++ = ' ';
224 				}
225 			}
226 		}
227 		while (*cp++ = *cp1++)
228 			if (cp > &genbuf[LBSIZE-2])
229 				error("Line overflow|Result line of join would be too long");
230 		cp--;
231 	}
232 	strcLIN(genbuf);
233 	ex_delete(0);
234 	jcount = 1;
235 	if (FIXUNDO)
236 		undap1 = undap2 = addr1;
237 	ignore(append(jnoop, --addr1));
238 	if (FIXUNDO)
239 		vundkind = VMANY;
240 }
241 
242 static
243 jnoop()
244 {
245 
246 	return(--jcount);
247 }
248 
249 /*
250  * Move and copy lines.  Hard work is done by move1 which
251  * is also called by undo.
252  */
253 int	getcopy();
254 
255 move()
256 {
257 	register line *adt;
258 	bool iscopy = 0;
259 
260 	if (Command[0] == 'm') {
261 		setdot1();
262 		markpr(addr2 == dot ? addr1 - 1 : addr2 + 1);
263 	} else {
264 		iscopy++;
265 		setdot();
266 	}
267 	nonzero();
268 	adt = address((char*)0);
269 	if (adt == 0)
270 		serror("%s where?|%s requires a trailing address", Command);
271 	newline();
272 	move1(iscopy, adt);
273 	killed();
274 }
275 
276 move1(cflag, addrt)
277 	int cflag;
278 	line *addrt;
279 {
280 	register line *adt, *ad1, *ad2;
281 	int lines;
282 
283 	adt = addrt;
284 	lines = (addr2 - addr1) + 1;
285 	if (cflag) {
286 		tad1 = addr1;
287 		ad1 = dol;
288 		ignore(append(getcopy, ad1++));
289 		ad2 = dol;
290 	} else {
291 		ad2 = addr2;
292 		for (ad1 = addr1; ad1 <= ad2;)
293 			*ad1++ &= ~01;
294 		ad1 = addr1;
295 	}
296 	ad2++;
297 	if (adt < ad1) {
298 		if (adt + 1 == ad1 && !cflag && !inglobal)
299 			error("That move would do nothing!");
300 		dot = adt + (ad2 - ad1);
301 		if (++adt != ad1) {
302 			reverse(adt, ad1);
303 			reverse(ad1, ad2);
304 			reverse(adt, ad2);
305 		}
306 	} else if (adt >= ad2) {
307 		dot = adt++;
308 		reverse(ad1, ad2);
309 		reverse(ad2, adt);
310 		reverse(ad1, adt);
311 	} else
312 		error("Move to a moved line");
313 	change();
314 	if (!inglobal)
315 		if(FIXUNDO) {
316 			if (cflag) {
317 				undap1 = addrt + 1;
318 				undap2 = undap1 + lines;
319 				deletenone();
320 			} else {
321 				undkind = UNDMOVE;
322 				undap1 = addr1;
323 				undap2 = addr2;
324 				unddel = addrt;
325 				squish();
326 			}
327 		}
328 }
329 
330 getcopy()
331 {
332 
333 	if (tad1 > addr2)
334 		return (EOF);
335 	getline(*tad1++);
336 	return (0);
337 }
338 
339 /*
340  * Put lines in the buffer from the undo save area.
341  */
342 getput()
343 {
344 
345 	if (tad1 > unddol)
346 		return (EOF);
347 	getline(*tad1++);
348 	tad1++;
349 	return (0);
350 }
351 
352 put()
353 {
354 	register int cnt;
355 
356 	if (!FIXUNDO)
357 		error("Cannot put inside global/macro");
358 	cnt = unddol - dol;
359 	if (cnt && inopen && pkill[0] && pkill[1]) {
360 		pragged(1);
361 		return;
362 	}
363 	tad1 = dol + 1;
364 	ignore(append(getput, addr2));
365 	undkind = UNDPUT;
366 	notecnt = cnt;
367 	netchange(cnt);
368 }
369 
370 /*
371  * A tricky put, of a group of lines in the middle
372  * of an existing line.  Only from open/visual.
373  * Argument says pkills have meaning, e.g. called from
374  * put; it is 0 on calls from putreg.
375  */
376 pragged(kill)
377 	bool kill;
378 {
379 	extern char *cursor;
380 	register char *gp = &genbuf[cursor - linebuf];
381 
382 	/*
383 	 * This kind of stuff is TECO's forte.
384 	 * We just grunge along, since it cuts
385 	 * across our line-oriented model of the world
386 	 * almost scrambling our addled brain.
387 	 */
388 	if (!kill)
389 		getDOT();
390 	strcpy(genbuf, linebuf);
391 	getline(*unddol);
392 	if (kill)
393 		*pkill[1] = 0;
394 	strcat(linebuf, gp);
395 	putmark(unddol);
396 	getline(dol[1]);
397 	if (kill)
398 		strcLIN(pkill[0]);
399 	strcpy(gp, linebuf);
400 	strcLIN(genbuf);
401 	putmark(dol+1);
402 	undkind = UNDCHANGE;
403 	undap1 = dot;
404 	undap2 = dot + 1;
405 	unddel = dot - 1;
406 	undo(1);
407 }
408 
409 /*
410  * Shift lines, based on c.
411  * If c is neither < nor >, then this is a lisp aligning =.
412  */
413 shift(c, cnt)
414 	int c;
415 	int cnt;
416 {
417 	register line *addr;
418 	register char *cp;
419 	char *dp;
420 	register int i;
421 
422 	if(FIXUNDO)
423 		save12(), undkind = UNDCHANGE;
424 	cnt *= value(SHIFTWIDTH);
425 	for (addr = addr1; addr <= addr2; addr++) {
426 		dot = addr;
427 #ifdef LISPCODE
428 		if (c == '=' && addr == addr1 && addr != addr2)
429 			continue;
430 #endif
431 		getDOT();
432 		i = whitecnt(linebuf);
433 		switch (c) {
434 
435 		case '>':
436 			if (linebuf[0] == 0)
437 				continue;
438 			cp = genindent(i + cnt);
439 			break;
440 
441 		case '<':
442 			if (i == 0)
443 				continue;
444 			i -= cnt;
445 			cp = i > 0 ? genindent(i) : genbuf;
446 			break;
447 
448 #ifdef LISPCODE
449 		default:
450 			i = lindent(addr);
451 			getDOT();
452 			cp = genindent(i);
453 			break;
454 #endif
455 		}
456 		if (cp + strlen(dp = vpastwh(linebuf)) >= &genbuf[LBSIZE - 2])
457 			error("Line too long|Result line after shift would be too long");
458 		CP(cp, dp);
459 		strcLIN(genbuf);
460 		putmark(addr);
461 	}
462 	killed();
463 }
464 
465 /*
466  * Find a tag in the tags file.
467  * Most work here is in parsing the tags file itself.
468  */
469 tagfind(quick)
470 	bool quick;
471 {
472 	char cmdbuf[BUFSIZ];
473 	char filebuf[FNSIZE];
474 	char tagfbuf[128];
475 	register int c, d;
476 	bool samef = 1;
477 	int tfcount = 0;
478 	int omagic;
479 	char *fn, *fne;
480 	struct stat sbuf;
481 #ifdef FASTTAG
482 	int iof;
483 	char iofbuf[MAXBSIZE];
484 	long mid;	/* assumed byte offset */
485 	long top, bot;	/* length of tag file */
486 #endif
487 
488 	omagic = value(MAGIC);
489 	if (!skipend()) {
490 		register char *lp = lasttag;
491 
492 		while (!iswhite(peekchar()) && !endcmd(peekchar()))
493 			if (lp < &lasttag[sizeof lasttag - 2])
494 				*lp++ = ex_getchar();
495 			else
496 				ignchar();
497 		*lp++ = 0;
498 		if (!endcmd(peekchar()))
499 badtag:
500 			error("Bad tag|Give one tag per line");
501 	} else if (lasttag[0] == 0)
502 		error("No previous tag");
503 	c = ex_getchar();
504 	if (!endcmd(c))
505 		goto badtag;
506 	if (c == EOF)
507 		ungetchar(c);
508 	clrstats();
509 
510 	/*
511 	 * Loop once for each file in tags "path".
512 	 */
513 	CP(tagfbuf, svalue(TAGS));
514 	fne = tagfbuf - 1;
515 	while (fne) {
516 		fn = ++fne;
517 		while (*fne && *fne != ' ')
518 			fne++;
519 		if (*fne == 0)
520 			fne = 0;	/* done, quit after this time */
521 		else
522 			*fne = 0;	/* null terminate filename */
523 #ifdef FASTTAG
524 		iof = topen(fn, iofbuf);
525 		if (iof == -1)
526 			continue;
527 		tfcount++;
528 		fstat(iof, &sbuf);
529 		top = sbuf.st_size;
530 		if (top == 0L )
531 			top = -1L;
532 		bot = 0L;
533 		while (top >= bot) {
534 #else
535 		/*
536 		 * Avoid stdio and scan tag file linearly.
537 		 */
538 		io = open(fn, 0);
539 		if (io<0)
540 			continue;
541 		tfcount++;
542 		if (fstat(io, &sbuf) < 0)
543 			bsize = LBSIZE;
544 		else {
545 			bsize = sbuf.st_blksize;
546 			if (bsize <= 0)
547 				bsize = LBSIZE;
548 		}
549 		while (getfile() == 0) {
550 #endif
551 			/* loop for each tags file entry */
552 			register char *cp = linebuf;
553 			register char *lp = lasttag;
554 			char *oglobp;
555 
556 #ifdef FASTTAG
557 			mid = (top + bot) / 2;
558 			tseek(iof, mid);
559 			if (mid > 0)	/* to get first tag in file to work */
560 				/* scan to next \n */
561 				if(tgets(linebuf, sizeof linebuf, iof)==NULL)
562 					goto goleft;
563 			/* get the line itself */
564 			if(tgets(linebuf, sizeof linebuf, iof)==NULL)
565 				goto goleft;
566 #ifdef TDEBUG
567 			ex_printf("tag: %o %o %o %s\n", bot, mid, top, linebuf);
568 #endif
569 #endif
570 			while (*cp && *lp == *cp)
571 				cp++, lp++;
572 			if ((*lp || !iswhite(*cp)) && (value(TAGLENGTH)==0 ||
573 			    lp-lasttag < value(TAGLENGTH))) {
574 #ifdef FASTTAG
575 				if (*lp > *cp)
576 					bot = mid + 1;
577 				else
578 goleft:
579 					top = mid - 1;
580 #endif
581 				/* Not this tag.  Try the next */
582 				continue;
583 			}
584 
585 			/*
586 			 * We found the tag.  Decode the line in the file.
587 			 */
588 #ifdef FASTTAG
589 			tclose(iof);
590 #else
591 			close(io);
592 #endif
593 			/* Rest of tag if abbreviated */
594 			while (!iswhite(*cp))
595 				cp++;
596 
597 			/* name of file */
598 			while (*cp && iswhite(*cp))
599 				cp++;
600 			if (!*cp)
601 badtags:
602 				serror("%s: Bad tags file entry", lasttag);
603 			lp = filebuf;
604 			while (*cp && *cp != ' ' && *cp != '\t') {
605 				if (lp < &filebuf[sizeof filebuf - 2])
606 					*lp++ = *cp;
607 				cp++;
608 			}
609 			*lp++ = 0;
610 
611 			if (*cp == 0)
612 				goto badtags;
613 			if (dol != zero) {
614 				/*
615 				 * Save current position in 't for ^^ in visual.
616 				 */
617 				names['t'-'a'] = *dot &~ 01;
618 				if (inopen) {
619 					extern char *ncols['z'-'a'+2];
620 					extern char *cursor;
621 
622 					ncols['t'-'a'] = cursor;
623 				}
624 			}
625 			strcpy(cmdbuf, cp);
626 			if (strcmp(filebuf, savedfile) || !edited) {
627 				char cmdbuf2[sizeof filebuf + 10];
628 
629 				/* Different file.  Do autowrite & get it. */
630 				if (!quick) {
631 					ckaw();
632 					if (chng && dol > zero)
633 						error("No write@since last change (:tag! overrides)");
634 				}
635 				oglobp = globp;
636 				strcpy(cmdbuf2, "e! ");
637 				strcat(cmdbuf2, filebuf);
638 				globp = cmdbuf2;
639 				d = peekc; ungetchar(0);
640 				commands(1, 1);
641 				peekc = d;
642 				globp = oglobp;
643 				value(MAGIC) = omagic;
644 				samef = 0;
645 			}
646 
647 			/*
648 			 * Look for pattern in the current file.
649 			 */
650 			oglobp = globp;
651 			globp = cmdbuf;
652 			d = peekc; ungetchar(0);
653 			if (samef)
654 				markpr(dot);
655 			/*
656 			 * BUG: if it isn't found (user edited header
657 			 * line) we get left in nomagic mode.
658 			 */
659 			value(MAGIC) = 0;
660 			commands(1, 1);
661 			peekc = d;
662 			globp = oglobp;
663 			value(MAGIC) = omagic;
664 			return;
665 		}	/* end of "for each tag in file" */
666 
667 		/*
668 		 * No such tag in this file.  Close it and try the next.
669 		 */
670 #ifdef FASTTAG
671 		tclose(iof);
672 #else
673 		close(io);
674 #endif
675 	}	/* end of "for each file in path" */
676 	if (tfcount <= 0)
677 		error("No tags file");
678 	else
679 		serror("%s: No such tag@in tags file", lasttag);
680 }
681 
682 /*
683  * Save lines from addr1 thru addr2 as though
684  * they had been deleted.
685  */
686 yank()
687 {
688 
689 	if (!FIXUNDO)
690 		error("Can't yank inside global/macro");
691 	save12();
692 	undkind = UNDNONE;
693 	killcnt(addr2 - addr1 + 1);
694 }
695 
696 /*
697  * z command; print windows of text in the file.
698  *
699  * If this seems unreasonably arcane, the reasons
700  * are historical.  This is one of the first commands
701  * added to the first ex (then called en) and the
702  * number of facilities here were the major advantage
703  * of en over ed since they allowed more use to be
704  * made of fast terminals w/o typing .,.22p all the time.
705  */
706 bool	zhadpr;
707 bool	znoclear;
708 short	zweight;
709 
710 zop(hadpr)
711 	int hadpr;
712 {
713 	register int c, lines, op;
714 	bool excl;
715 
716 	zhadpr = hadpr;
717 	notempty();
718 	znoclear = 0;
719 	zweight = 0;
720 	excl = exclam();
721 	switch (c = op = ex_getchar()) {
722 
723 	case '^':
724 		zweight = 1;
725 	case '-':
726 	case '+':
727 		while (peekchar() == op) {
728 			ignchar();
729 			zweight++;
730 		}
731 	case '=':
732 	case '.':
733 		c = ex_getchar();
734 		break;
735 
736 	case EOF:
737 		znoclear++;
738 		break;
739 
740 	default:
741 		op = 0;
742 		break;
743 	}
744 	if (isdigit(c)) {
745 		lines = c - '0';
746 		for(;;) {
747 			c = ex_getchar();
748 			if (!isdigit(c))
749 				break;
750 			lines *= 10;
751 			lines += c - '0';
752 		}
753 		if (lines < LINES)
754 			znoclear++;
755 		value(WINDOW) = lines;
756 		if (op == '=')
757 			lines += 2;
758 	} else
759 		lines = op == EOF ? value(SCROLL) : excl ? LINES - 1 : 2*value(SCROLL);
760 	if (inopen || c != EOF) {
761 		ungetchar(c);
762 		newline();
763 	}
764 	addr1 = addr2;
765 	if (addr2 == 0 && dot < dol && op == 0)
766 		addr1 = addr2 = dot+1;
767 	setdot();
768 	zop2(lines, op);
769 }
770 
771 zop2(lines, op)
772 	register int lines;
773 	register int op;
774 {
775 	register line *split;
776 	static void splitit();
777 
778 	split = NULL;
779 	switch (op) {
780 
781 	case EOF:
782 		if (addr2 == dol)
783 			error("\nAt EOF");
784 	case '+':
785 		if (addr2 == dol)
786 			error("At EOF");
787 		addr2 += lines * zweight;
788 		if (addr2 > dol)
789 			error("Hit BOTTOM");
790 		addr2++;
791 	default:
792 		addr1 = addr2;
793 		addr2 += lines-1;
794 		dot = addr2;
795 		break;
796 
797 	case '=':
798 	case '.':
799 		znoclear = 0;
800 		lines--;
801 		lines >>= 1;
802 		if (op == '=')
803 			lines--;
804 		addr1 = addr2 - lines;
805 		if (op == '=')
806 			dot = split = addr2;
807 		addr2 += lines;
808 		if (op == '.') {
809 			markDOT();
810 			dot = addr2;
811 		}
812 		break;
813 
814 	case '^':
815 	case '-':
816 		addr2 -= lines * zweight;
817 		if (addr2 < one)
818 			error("Hit TOP");
819 		lines--;
820 		addr1 = addr2 - lines;
821 		dot = addr2;
822 		break;
823 	}
824 	if (addr1 <= zero)
825 		addr1 = one;
826 	if (addr2 > dol)
827 		addr2 = dol;
828 	if (dot > dol)
829 		dot = dol;
830 	if (addr1 > addr2)
831 		return;
832 	if (op == EOF && zhadpr) {
833 		getline(*addr1);
834 		ex_putchar('\r' | QUOTE);
835 		shudclob = 1;
836 	} else if (znoclear == 0 && CL != NOSTR && !inopen) {
837 		flush1();
838 		vclear();
839 	}
840 	if (addr2 - addr1 > 1)
841 		pstart();
842 	if (split) {
843 		plines(addr1, split - 1, 0);
844 		splitit();
845 		plines(split, split, 0);
846 		splitit();
847 		addr1 = split + 1;
848 	}
849 	plines(addr1, addr2, 0);
850 }
851 
852 static void
853 splitit()
854 {
855 	register int l;
856 
857 	for (l = COLUMNS > 80 ? 40 : COLUMNS / 2; l > 0; l--)
858 		ex_putchar('-');
859 	putnl();
860 }
861 
862 plines(adr1, adr2, movedot)
863 	line *adr1;
864 	register line *adr2;
865 	bool movedot;
866 {
867 	register line *addr;
868 
869 	pofix();
870 	for (addr = adr1; addr <= adr2; addr++) {
871 		getline(*addr);
872 		pline(lineno(addr));
873 		if (inopen)
874 			ex_putchar('\n' | QUOTE);
875 		if (movedot)
876 			dot = addr;
877 	}
878 }
879 
880 pofix()
881 {
882 
883 	if (inopen && Outchar != termchar) {
884 		vnfl();
885 		setoutt();
886 	}
887 }
888 
889 /*
890  * Dudley doright to the rescue.
891  * Undo saves the day again.
892  * A tip of the hatlo hat to Warren Teitleman
893  * who made undo as useful as do.
894  *
895  * Command level undo works easily because
896  * the editor has a unique temporary file
897  * index for every line which ever existed.
898  * We don't have to save large blocks of text,
899  * only the indices which are small.  We do this
900  * by moving them to after the last line in the
901  * line buffer array, and marking down info
902  * about whence they came.
903  *
904  * Undo is its own inverse.
905  */
906 undo(c)
907 	bool c;
908 {
909 	register int i;
910 	register line *jp, *kp;
911 	line *dolp1, *newdol, *newadot;
912 
913 #ifdef TRACE
914 	if (trace)
915 		vudump("before undo");
916 #endif
917 	if (inglobal && inopen <= 0)
918 		error("Can't undo in global@commands");
919 	if (!c)
920 		somechange();
921 	pkill[0] = pkill[1] = 0;
922 	change();
923 	if (undkind == UNDMOVE) {
924  		/*
925 		 * Command to be undone is a move command.
926 		 * This is handled as a special case by noting that
927 		 * a move "a,b m c" can be inverted by another move.
928 		 */
929 		if ((i = (jp = unddel) - undap2) > 0) {
930 			/*
931 			 * when c > b inverse is a+(c-b),c m a-1
932 			 */
933 			addr2 = jp;
934 			addr1 = (jp = undap1) + i;
935 			unddel = jp-1;
936 		} else {
937 			/*
938 			 * when b > c inverse is  c+1,c+1+(b-a) m b
939 			 */
940 			addr1 = ++jp;
941 			addr2 = jp + ((unddel = undap2) - undap1);
942 		}
943 		kp = undap1;
944 		move1(0, unddel);
945 		dot = kp;
946 		Command = "move";
947 		killed();
948 	} else {
949 		int cnt;
950 
951 		newadot = dot;
952 		cnt = lineDOL();
953 		newdol = dol;
954 		dolp1 = dol + 1;
955 		/*
956 		 * Command to be undone is a non-move.
957 		 * All such commands are treated as a combination of
958 		 * a delete command and a append command.
959 		 * We first move the lines appended by the last command
960 		 * from undap1 to undap2-1 so that they are just before the
961 		 * saved deleted lines.
962 		 */
963 		if ((i = (kp = undap2) - (jp = undap1)) > 0) {
964 			if (kp != dolp1) {
965 				reverse(jp, kp);
966 				reverse(kp, dolp1);
967 				reverse(jp, dolp1);
968 			}
969 			/*
970 			 * Account for possible backward motion of target
971 			 * for restoration of saved deleted lines.
972 			 */
973 			if (unddel >= jp)
974 				unddel -= i;
975 			newdol -= i;
976 			/*
977 			 * For the case where no lines are restored, dot
978 			 * is the line before the first line deleted.
979 			 */
980 			dot = jp-1;
981 		}
982 		/*
983 		 * Now put the deleted lines, if any, back where they were.
984 		 * Basic operation is: dol+1,unddol m unddel
985 		 */
986 		if (undkind == UNDPUT) {
987 			unddel = undap1 - 1;
988 			squish();
989 		}
990 		jp = unddel + 1;
991 		if ((i = (kp = unddol) - dol) > 0) {
992 			if (jp != dolp1) {
993 				reverse(jp, dolp1);
994 				reverse(dolp1, ++kp);
995 				reverse(jp, kp);
996 			}
997 			/*
998 			 * Account for possible forward motion of the target
999 			 * for restoration of the deleted lines.
1000 			 */
1001 			if (undap1 >= jp)
1002 				undap1 += i;
1003 			/*
1004 			 * Dot is the first resurrected line.
1005 			 */
1006 			dot = jp;
1007 			newdol += i;
1008 		}
1009 		/*
1010 		 * Clean up so we are invertible
1011 		 */
1012 		unddel = undap1 - 1;
1013 		undap1 = jp;
1014 		undap2 = jp + i;
1015 		dol = newdol;
1016 		netchHAD(cnt);
1017 		if (undkind == UNDALL) {
1018 			dot = undadot;
1019 			undadot = newadot;
1020 		} else
1021 			undkind = UNDCHANGE;
1022 	}
1023 	/*
1024 	 * Defensive programming - after a munged undadot.
1025 	 * Also handle empty buffer case.
1026 	 */
1027 	if ((dot <= zero || dot > dol) && dot != dol)
1028 		dot = one;
1029 #ifdef TRACE
1030 	if (trace)
1031 		vudump("after undo");
1032 #endif
1033 }
1034 
1035 /*
1036  * Be (almost completely) sure there really
1037  * was a change, before claiming to undo.
1038  */
1039 somechange()
1040 {
1041 	register line *ip, *jp;
1042 
1043 	switch (undkind) {
1044 
1045 	case UNDMOVE:
1046 		return;
1047 
1048 	case UNDCHANGE:
1049 		if (undap1 == undap2 && dol == unddol)
1050 			break;
1051 		return;
1052 
1053 	case UNDPUT:
1054 		if (undap1 != undap2)
1055 			return;
1056 		break;
1057 
1058 	case UNDALL:
1059 		if (unddol - dol != lineDOL())
1060 			return;
1061 		for (ip = one, jp = dol + 1; ip <= dol; ip++, jp++)
1062 			if ((*ip &~ 01) != (*jp &~ 01))
1063 				return;
1064 		break;
1065 
1066 	case UNDNONE:
1067 		error("Nothing to undo");
1068 	}
1069 	error("Nothing changed|Last undoable command didn't change anything");
1070 }
1071 
1072 /*
1073  * Map command:
1074  * map src dest
1075  */
1076 mapcmd(un, ab)
1077 	int un;	/* true if this is unmap command */
1078 	int ab;	/* true if this is abbr command */
1079 {
1080 	char lhs[100], rhs[100];	/* max sizes resp. */
1081 	register char *p;
1082 	register int c;		/* mjm: char --> int */
1083 	char *dname;
1084 	struct maps *mp;	/* the map structure we are working on */
1085 
1086 	mp = ab ? abbrevs : exclam() ? immacs : arrows;
1087 	if (skipend()) {
1088 		int i;
1089 
1090 		/* print current mapping values */
1091 		if (peekchar() != EOF)
1092 			ignchar();
1093 		if (un)
1094 			error("Missing lhs");
1095 		if (inopen)
1096 			pofix();
1097 		for (i=0; mp[i].mapto; i++)
1098 			if (mp[i].cap) {
1099 				lprintf("%s", mp[i].descr);
1100 				ex_putchar('\t');
1101 				lprintf("%s", mp[i].cap);
1102 				ex_putchar('\t');
1103 				lprintf("%s", mp[i].mapto);
1104 				putNFL();
1105 			}
1106 		return;
1107 	}
1108 
1109 	ignore(skipwh());
1110 	for (p=lhs; ; ) {
1111 		c = ex_getchar();
1112 		if (c == CTRL('v')) {
1113 			c = ex_getchar();
1114 		} else if (!un && any(c, " \t")) {
1115 			/* End of lhs */
1116 			break;
1117 		} else if (endcmd(c) && c!='"') {
1118 			ungetchar(c);
1119 			if (un) {
1120 				newline();
1121 				*p = 0;
1122 				addmac(lhs, NOSTR, NOSTR, mp);
1123 				return;
1124 			} else
1125 				error("Missing rhs");
1126 		}
1127 		*p++ = c;
1128 	}
1129 	*p = 0;
1130 
1131 	if (skipend())
1132 		error("Missing rhs");
1133 	for (p=rhs; ; ) {
1134 		c = ex_getchar();
1135 		if (c == CTRL('v')) {
1136 			c = ex_getchar();
1137 		} else if (endcmd(c) && c!='"') {
1138 			ungetchar(c);
1139 			break;
1140 		}
1141 		*p++ = c;
1142 	}
1143 	*p = 0;
1144 	newline();
1145 	/*
1146 	 * Special hack for function keys: #1 means key f1, etc.
1147 	 * If the terminal doesn't have function keys, we just use #1.
1148 	 */
1149 	if (lhs[0] == '#') {
1150 		char *fnkey;
1151 		char *fkey();
1152 		char funkey[3];
1153 
1154 		fnkey = fkey(lhs[1] - '0');
1155 		funkey[0] = 'f'; funkey[1] = lhs[1]; funkey[2] = 0;
1156 		if (fnkey)
1157 			strcpy(lhs, fnkey);
1158 		dname = funkey;
1159 	} else {
1160 		dname = lhs;
1161 	}
1162 	addmac(lhs,rhs,dname,mp);
1163 }
1164 
1165 /*
1166  * Add a macro definition to those that already exist. The sequence of
1167  * chars "src" is mapped into "dest". If src is already mapped into something
1168  * this overrides the mapping. There is no recursion. Unmap is done by
1169  * using NOSTR for dest.  Dname is what to show in listings.  mp is
1170  * the structure to affect (arrows, etc).
1171  */
1172 addmac(src,dest,dname,mp)
1173 	register char *src, *dest, *dname;
1174 	register struct maps *mp;
1175 {
1176 	register int slot, zer;
1177 
1178 #ifdef TRACE
1179 	if (trace)
1180 		fprintf(trace, "addmac(src='%s', dest='%s', dname='%s', mp=%x\n", src, dest, dname, mp);
1181 #endif
1182 	if (dest && mp==arrows) {
1183 		/* Make sure user doesn't screw himself */
1184 		/*
1185 		 * Prevent tail recursion. We really should be
1186 		 * checking to see if src is a suffix of dest
1187 		 * but this makes mapping involving escapes that
1188 		 * is reasonable mess up.
1189 		 */
1190 		if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
1191 			error("No tail recursion");
1192 		/*
1193 		 * We don't let the user rob himself of ":", and making
1194 		 * multi char words is a bad idea so we don't allow it.
1195 		 * Note that if user sets mapinput and maps all of return,
1196 		 * linefeed, and escape, he can screw himself. This is
1197 		 * so weird I don't bother to check for it.
1198 		 */
1199 		if (isalpha(src[0]) && src[1] || any(src[0],":"))
1200 			error("Too dangerous to map that");
1201 	}
1202 	else if (dest) {
1203 		/* check for tail recursion in input mode: fussier */
1204 		if (eq(src, dest+strlen(dest)-strlen(src)))
1205 			error("No tail recursion");
1206 	}
1207 	/*
1208 	 * If the src were null it would cause the dest to
1209 	 * be mapped always forever. This is not good.
1210 	 */
1211 	if (src == NOSTR || src[0] == 0)
1212 		error("Missing lhs");
1213 
1214 	/* see if we already have a def for src */
1215 	zer = -1;
1216 	for (slot=0; mp[slot].mapto; slot++) {
1217 		if (mp[slot].cap) {
1218 			if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto))
1219 				break;	/* if so, reuse slot */
1220 		} else {
1221 			zer = slot;	/* remember an empty slot */
1222 		}
1223 	}
1224 
1225 	if (dest == NOSTR) {
1226 		/* unmap */
1227 		if (mp[slot].cap) {
1228 			mp[slot].cap = NOSTR;
1229 			mp[slot].descr = NOSTR;
1230 		} else {
1231 			error("Not mapped|That macro wasn't mapped");
1232 		}
1233 		return;
1234 	}
1235 
1236 	/* reuse empty slot, if we found one and src isn't already defined */
1237 	if (zer >= 0 && mp[slot].mapto == 0)
1238 		slot = zer;
1239 
1240 	/* if not, append to end */
1241 	if (slot >= MAXNOMACS)
1242 		error("Too many macros");
1243 	if (msnext == 0)	/* first time */
1244 		msnext = mapspace;
1245 	/* Check is a bit conservative, we charge for dname even if reusing src */
1246 	if (msnext - mapspace + strlen(dest) + strlen(src) + strlen(dname) + 3 > MAXCHARMACS)
1247 		error("Too much macro text");
1248 	CP(msnext, src);
1249 	mp[slot].cap = msnext;
1250 	msnext += strlen(src) + 1;	/* plus 1 for null on the end */
1251 	CP(msnext, dest);
1252 	mp[slot].mapto = msnext;
1253 	msnext += strlen(dest) + 1;
1254 	if (dname) {
1255 		CP(msnext, dname);
1256 		mp[slot].descr = msnext;
1257 		msnext += strlen(dname) + 1;
1258 	} else {
1259 		/* default descr to string user enters */
1260 		mp[slot].descr = src;
1261 	}
1262 }
1263 
1264 /*
1265  * Implements macros from command mode. c is the buffer to
1266  * get the macro from.
1267  */
1268 cmdmac(c)
1269 char c;
1270 {
1271 	char macbuf[BUFSIZ];
1272 	line *ad, *a1, *a2;
1273 	char *oglobp;
1274 	short pk;
1275 	bool oinglobal;
1276 
1277 	lastmac = c;
1278 	oglobp = globp;
1279 	oinglobal = inglobal;
1280 	pk = peekc; peekc = 0;
1281 	if (inglobal < 2)
1282 		inglobal = 1;
1283 	regbuf(c, macbuf, sizeof(macbuf));
1284 	a1 = addr1; a2 = addr2;
1285 	for (ad=a1; ad<=a2; ad++) {
1286 		globp = macbuf;
1287 		dot = ad;
1288 		commands(1,1);
1289 	}
1290 	globp = oglobp;
1291 	inglobal = oinglobal;
1292 	peekc = pk;
1293 }
1294