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