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