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