Bill added more buffers, and I put in sccs.
[unix-history] / usr / src / usr.bin / ex / ex_cmdsub.c
CommitLineData
7c4625ef
MH
1/* Copyright (c) 1980 Regents of the University of California */
2static char *sccsid = "@(#)ex_cmdsub.c 4.2 %G%";
4beab9c4
MH
3#include "ex.h"
4#include "ex_argv.h"
5#include "ex_temp.h"
6#include "ex_tty.h"
d266c416 7#include "ex_vis.h"
4beab9c4
MH
8
9/*
10 * Command mode subroutines implementing
11 * append, args, copy, delete, join, move, put,
12 * shift, tag, yank, z and undo
13 */
14
15bool endline = 1;
16line *tad1;
d266c416 17static jnoop();
4beab9c4
MH
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 */
24append(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;
887e3e0d 33 if(FIXUNDO && !inopen && f!=getsub) {
4beab9c4
MH
34 undap1 = undap2 = dot + 1;
35 undkind = UNDCHANGE;
36 }
37 while ((*f)() == 0) {
38 if (truedol >= endcore) {
39 if (morelines() < 0) {
887e3e0d 40 if (FIXUNDO && f == getsub) {
4beab9c4
MH
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
67appendnone()
68{
69
887e3e0d 70 if(FIXUNDO) {
4beab9c4
MH
71 undkind = UNDCHANGE;
72 undap1 = undap2 = addr1;
73 }
74}
75
76/*
77 * Print out the argument list, with []'s around the current name.
78 */
79pargs()
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 */
101delete(hush)
102 bool hush;
103{
104 register line *a1, *a2;
105
106 nonzero();
887e3e0d 107 if(FIXUNDO) {
4beab9c4
MH
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
153deletenone()
154{
155
887e3e0d 156 if(FIXUNDO) {
4beab9c4
MH
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 */
167squish()
168{
169 register line *a1 = dol + 1, *a2 = unddol + 1, *a3 = truedol + 1;
170
887e3e0d
MH
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 }
4beab9c4
MH
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 */
187static int jcount, jnoop();
188
189join(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;
d266c416
MH
219 if (FIXUNDO)
220 undap1 = undap2 = addr1;
4beab9c4 221 ignore(append(jnoop, --addr1));
d266c416
MH
222 if (FIXUNDO)
223 vundkind = VMANY;
4beab9c4
MH
224}
225
226static
227jnoop()
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 */
237int getcopy();
238
239move()
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
260move1(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)
887e3e0d
MH
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 }
4beab9c4
MH
311 }
312}
313
314getcopy()
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 */
326getput()
327{
328
329 if (tad1 > unddol)
330 return (EOF);
331 getline(*tad1++);
332 tad1++;
333 return (0);
334}
335
336put()
337{
338 register int cnt;
339
887e3e0d
MH
340 if (!FIXUNDO)
341 error("Cannot put inside global/macro");
4beab9c4
MH
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 */
360pragged(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 */
397shift(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
887e3e0d 406 if(FIXUNDO)
4beab9c4
MH
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 */
453tagfind(quick)
454 bool quick;
455{
456 char cmdbuf[BUFSIZ];
457 char filebuf[FNSIZE];
887e3e0d 458 char tagfbuf[128];
4beab9c4
MH
459 register int c, d;
460 bool samef = 1;
887e3e0d
MH
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
4beab9c4
MH
479
480 omagic = value(MAGIC);
4beab9c4
MH
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()))
491badtag:
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();
887e3e0d
MH
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;
d266c416 534 tfcount++;
4beab9c4 535 while (getfile() == 0) {
887e3e0d
MH
536#endif
537 /* loop for each tags file entry */
4beab9c4
MH
538 register char *cp = linebuf;
539 register char *lp = lasttag;
540 char *oglobp;
541
887e3e0d
MH
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
4beab9c4
MH
550 while (*cp && *lp == *cp)
551 cp++, lp++;
d266c416 552 if ((*lp || !iswhite(*cp)) && (value(TAGLENGTH)==0 || lp-lasttag < value(TAGLENGTH))) {
887e3e0d
MH
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 */
4beab9c4 560 continue;
887e3e0d
MH
561 }
562
563 /*
564 * We found the tag. Decode the line in the file.
565 */
566#ifdef VMUNIX
567 fclose(iof);
568#else
4beab9c4 569 close(io);
887e3e0d 570#endif
d266c416
MH
571 /* Rest of tag if abbreviated */
572 while (!iswhite(*cp))
573 cp++;
574
887e3e0d 575 /* name of file */
4beab9c4
MH
576 while (*cp && iswhite(*cp))
577 cp++;
578 if (!*cp)
579badtags:
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;
887e3e0d 588
4beab9c4
MH
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) {
d266c416 597 extern char *ncols['z'-'a'+2];
4beab9c4
MH
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
887e3e0d 607 /* Different file. Do autowrite & get it. */
4beab9c4
MH
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;
44232d5b 621 value(MAGIC) = omagic;
4beab9c4
MH
622 samef = 0;
623 }
887e3e0d
MH
624
625 /*
626 * Look for pattern in the current file.
627 */
4beab9c4
MH
628 oglobp = globp;
629 globp = cmdbuf;
630 d = peekc; ungetchar(0);
631 if (samef)
632 markpr(dot);
887e3e0d
MH
633 /*
634 * BUG: if it isn't found (user edited header
635 * line) we get left in nomagic mode.
636 */
44232d5b 637 value(MAGIC) = 0;
4beab9c4
MH
638 commands(1, 1);
639 peekc = d;
640 globp = oglobp;
641 value(MAGIC) = omagic;
642 return;
887e3e0d
MH
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)
4beab9c4 655 error("No tags file");
887e3e0d
MH
656 else
657 serror("%s: No such tag@in tags file", lasttag);
4beab9c4
MH
658}
659
660/*
661 * Save lines from addr1 thru addr2 as though
662 * they had been deleted.
663 */
664yank()
665{
666
887e3e0d
MH
667 if (!FIXUNDO)
668 error("Can't yank inside global/macro");
4beab9c4
MH
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 */
684bool zhadpr;
685bool znoclear;
686short zweight;
687
688zop(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
749zop2(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
829static
830splitit()
831{
832 register int l;
833
834 for (l = COLUMNS > 80 ? 40 : COLUMNS / 2; l > 0; l--)
835 putchar('-');
836 putnl();
837}
838
839plines(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
857pofix()
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 */
883undo(c)
884 bool c;
885{
886 register int i;
887 register line *jp, *kp;
888 line *dolp1, *newdol, *newadot;
889
887e3e0d
MH
890#ifdef TRACE
891 if (trace)
892 vudump("before undo");
893#endif
4beab9c4
MH
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;
887e3e0d
MH
997 } else
998 undkind = UNDCHANGE;
4beab9c4 999 }
d266c416
MH
1000 /*
1001 * Defensive programming - after a munged undadot.
1002 * Also handle empty buffer case.
1003 */
1004 if ((dot <= zero || dot > dol) && dot != dol)
4beab9c4 1005 dot = one;
887e3e0d
MH
1006#ifdef TRACE
1007 if (trace)
1008 vudump("after undo");
1009#endif
4beab9c4
MH
1010}
1011
1012/*
1013 * Be (almost completely) sure there really
1014 * was a change, before claiming to undo.
1015 */
1016somechange()
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 */
d266c416 1053mapcmd(un, ab)
4beab9c4 1054 int un; /* true if this is unmap command */
d266c416 1055 int ab; /* true if this is abbr command */
4beab9c4 1056{
d266c416 1057 char lhs[100], rhs[100]; /* max sizes resp. */
4beab9c4
MH
1058 register char *p;
1059 register char c;
1060 char *dname;
887e3e0d 1061 struct maps *mp; /* the map structure we are working on */
4beab9c4 1062
d266c416 1063 mp = ab ? abbrevs : exclam() ? immacs : arrows;
4beab9c4
MH
1064 if (skipend()) {
1065 int i;
1066
1067 /* print current mapping values */
1068 if (peekchar() != EOF)
1069 ignchar();
d266c416
MH
1070 if (un)
1071 error("Missing lhs");
4beab9c4
MH
1072 if (inopen)
1073 pofix();
887e3e0d
MH
1074 for (i=0; mp[i].mapto; i++)
1075 if (mp[i].cap) {
1076 lprintf("%s", mp[i].descr);
4beab9c4 1077 putchar('\t');
887e3e0d 1078 lprintf("%s", mp[i].cap);
4beab9c4 1079 putchar('\t');
887e3e0d 1080 lprintf("%s", mp[i].mapto);
4beab9c4
MH
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();
d266c416
MH
1091 } else if (!un && any(c, " \t")) {
1092 /* End of lhs */
1093 break;
1094 } else if (endcmd(c) && c!='"') {
4beab9c4
MH
1095 ungetchar(c);
1096 if (un) {
1097 newline();
d266c416 1098 *p = 0;
887e3e0d 1099 addmac(lhs, NOSTR, NOSTR, mp);
4beab9c4
MH
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();
d266c416 1114 } else if (endcmd(c) && c!='"') {
4beab9c4
MH
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 }
887e3e0d 1139 addmac(lhs,rhs,dname,mp);
4beab9c4
MH
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
887e3e0d
MH
1146 * using NOSTR for dest. Dname is what to show in listings. mp is
1147 * the structure to affect (arrows, etc).
4beab9c4 1148 */
887e3e0d 1149addmac(src,dest,dname,mp)
4beab9c4 1150 register char *src, *dest, *dname;
887e3e0d 1151 register struct maps *mp;
4beab9c4
MH
1152{
1153 register int slot, zer;
1154
d266c416
MH
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
887e3e0d 1159 if (dest && mp==arrows) {
4beab9c4
MH
1160 /* Make sure user doesn't screw himself */
1161 /*
44232d5b
MH
1162 * Prevent tail recursion. We really should be
1163 * checking to see if src is a suffix of dest
887e3e0d
MH
1164 * but this makes mapping involving escapes that
1165 * is reasonable mess up.
4beab9c4 1166 */
44232d5b
MH
1167 if (src[1] == 0 && src[0] == dest[strlen(dest)-1])
1168 error("No tail recursion");
4beab9c4
MH
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");
4beab9c4 1178 }
887e3e0d
MH
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");
4beab9c4
MH
1190
1191 /* see if we already have a def for src */
1192 zer = -1;
887e3e0d
MH
1193 for (slot=0; mp[slot].mapto; slot++) {
1194 if (mp[slot].cap) {
d266c416 1195 if (eq(src, mp[slot].cap) || eq(src, mp[slot].mapto))
4beab9c4
MH
1196 break; /* if so, reuse slot */
1197 } else {
1198 zer = slot; /* remember an empty slot */
1199 }
1200 }
1201
1202 if (dest == NOSTR) {
1203 /* unmap */
887e3e0d
MH
1204 if (mp[slot].cap) {
1205 mp[slot].cap = NOSTR;
1206 mp[slot].descr = NOSTR;
4beab9c4
MH
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 */
887e3e0d 1214 if (zer >= 0 && mp[slot].mapto == 0)
4beab9c4
MH
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);
887e3e0d 1226 mp[slot].cap = msnext;
4beab9c4
MH
1227 msnext += strlen(src) + 1; /* plus 1 for null on the end */
1228 CP(msnext, dest);
887e3e0d 1229 mp[slot].mapto = msnext;
4beab9c4
MH
1230 msnext += strlen(dest) + 1;
1231 if (dname) {
1232 CP(msnext, dname);
887e3e0d 1233 mp[slot].descr = msnext;
4beab9c4
MH
1234 msnext += strlen(dname) + 1;
1235 } else {
1236 /* default descr to string user enters */
887e3e0d 1237 mp[slot].descr = src;
4beab9c4
MH
1238 }
1239}
1240
1241/*
1242 * Implements macros from command mode. c is the buffer to
1243 * get the macro from.
1244 */
1245cmdmac(c)
1246char 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}