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