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