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