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_voper.c 7.4 (Berkeley) 6/7/85"; 9: #endif not lint 10: 11: #include "ex.h" 12: #include "ex_tty.h" 13: #include "ex_vis.h" 14: 15: #define blank() isspace(wcursor[0]) 16: #define forbid(a) if (a) goto errlab; 17: 18: char vscandir[2] = { '/', 0 }; 19: 20: /* 21: * Decode an operator/operand type command. 22: * Eventually we switch to an operator subroutine in ex_vops.c. 23: * The work here is setting up a function variable to point 24: * to the routine we want, and manipulation of the variables 25: * wcursor and wdot, which mark the other end of the affected 26: * area. If wdot is zero, then the current line is the other end, 27: * and if wcursor is zero, then the first non-blank location of the 28: * other line is implied. 29: */ 30: operate(c, cnt) 31: register int c, cnt; 32: { 33: register int i; 34: int (*moveop)(), (*deleteop)(); 35: register int (*opf)(); 36: bool subop = 0; 37: char *oglobp, *ocurs; 38: register line *addr; 39: line *odot; 40: static char lastFKND, lastFCHR; 41: short d; 42: 43: moveop = vmove, deleteop = vdelete; 44: wcursor = cursor; 45: wdot = NOLINE; 46: notecnt = 0; 47: dir = 1; 48: switch (c) { 49: 50: /* 51: * d delete operator. 52: */ 53: case 'd': 54: moveop = vdelete; 55: deleteop = beep; 56: break; 57: 58: /* 59: * s substitute characters, like c\040, i.e. change space. 60: */ 61: case 's': 62: ungetkey(' '); 63: subop++; 64: /* fall into ... */ 65: 66: /* 67: * c Change operator. 68: */ 69: case 'c': 70: if (c == 'c' && workcmd[0] == 'C' || workcmd[0] == 'S') 71: subop++; 72: moveop = vchange; 73: deleteop = beep; 74: break; 75: 76: /* 77: * ! Filter through a UNIX command. 78: */ 79: case '!': 80: moveop = vfilter; 81: deleteop = beep; 82: break; 83: 84: /* 85: * y Yank operator. Place specified text so that it 86: * can be put back with p/P. Also yanks to named buffers. 87: */ 88: case 'y': 89: moveop = vyankit; 90: deleteop = beep; 91: break; 92: 93: /* 94: * = Reformat operator (for LISP). 95: */ 96: #ifdef LISPCODE 97: case '=': 98: forbid(!value(LISP)); 99: /* fall into ... */ 100: #endif 101: 102: /* 103: * > Right shift operator. 104: * < Left shift operator. 105: */ 106: case '<': 107: case '>': 108: moveop = vshftop; 109: deleteop = beep; 110: break; 111: 112: /* 113: * r Replace character under cursor with single following 114: * character. 115: */ 116: case 'r': 117: vmacchng(1); 118: vrep(cnt); 119: return; 120: 121: default: 122: goto nocount; 123: } 124: vmacchng(1); 125: /* 126: * Had an operator, so accept another count. 127: * Multiply counts together. 128: */ 129: if (isdigit(peekkey()) && peekkey() != '0') { 130: cnt *= vgetcnt(); 131: Xcnt = cnt; 132: forbid (cnt <= 0); 133: } 134: 135: /* 136: * Get next character, mapping it and saving as 137: * part of command for repeat. 138: */ 139: c = map(getesc(),arrows); 140: if (c == 0) 141: return; 142: if (!subop) 143: *lastcp++ = c; 144: nocount: 145: opf = moveop; 146: switch (c) { 147: 148: /* 149: * b Back up a word. 150: * B Back up a word, liberal definition. 151: */ 152: case 'b': 153: case 'B': 154: dir = -1; 155: /* fall into ... */ 156: 157: /* 158: * w Forward a word. 159: * W Forward a word, liberal definition. 160: */ 161: case 'W': 162: case 'w': 163: wdkind = c & ' '; 164: forbid(lfind(2, cnt, opf, 0) < 0); 165: vmoving = 0; 166: break; 167: 168: /* 169: * E to end of following blank/nonblank word 170: */ 171: case 'E': 172: wdkind = 0; 173: goto ein; 174: 175: /* 176: * e To end of following word. 177: */ 178: case 'e': 179: wdkind = 1; 180: ein: 181: forbid(lfind(3, cnt - 1, opf, 0) < 0); 182: vmoving = 0; 183: break; 184: 185: /* 186: * ( Back an s-expression. 187: */ 188: case '(': 189: dir = -1; 190: /* fall into... */ 191: 192: /* 193: * ) Forward an s-expression. 194: */ 195: case ')': 196: forbid(lfind(0, cnt, opf, (line *) 0) < 0); 197: markDOT(); 198: break; 199: 200: /* 201: * { Back an s-expression, but don't stop on atoms. 202: * In text mode, a paragraph. For C, a balanced set 203: * of {}'s. 204: */ 205: case '{': 206: dir = -1; 207: /* fall into... */ 208: 209: /* 210: * } Forward an s-expression, but don't stop on atoms. 211: * In text mode, back paragraph. For C, back a balanced 212: * set of {}'s. 213: */ 214: case '}': 215: forbid(lfind(1, cnt, opf, (line *) 0) < 0); 216: markDOT(); 217: break; 218: 219: /* 220: * % To matching () or {}. If not at ( or { scan for 221: * first such after cursor on this line. 222: */ 223: case '%': 224: vsave(); 225: i = lmatchp((line *) 0); 226: #ifdef TRACE 227: if (trace) 228: fprintf(trace, "after lmatchp in %, dot=%d, wdot=%d, dol=%d\n", lineno(dot), lineno(wdot), lineno(dol)); 229: #endif 230: getDOT(); 231: forbid(!i); 232: if (opf != vmove) 233: if (dir > 0) 234: wcursor++; 235: else 236: cursor++; 237: else 238: markDOT(); 239: vmoving = 0; 240: break; 241: 242: /* 243: * [ Back to beginning of defun, i.e. an ( in column 1. 244: * For text, back to a section macro. 245: * For C, back to a { in column 1 (~~ beg of function.) 246: */ 247: case '[': 248: dir = -1; 249: /* fall into ... */ 250: 251: /* 252: * ] Forward to next defun, i.e. a ( in column 1. 253: * For text, forward section. 254: * For C, forward to a } in column 1 (if delete or such) 255: * or if a move to a { in column 1. 256: */ 257: case ']': 258: if (!vglobp) 259: forbid(getkey() != c); 260: forbid (Xhadcnt); 261: vsave(); 262: i = lbrack(c, opf); 263: getDOT(); 264: forbid(!i); 265: markDOT(); 266: if (ospeed > B300) 267: hold |= HOLDWIG; 268: break; 269: 270: /* 271: * , Invert last find with f F t or T, like inverse 272: * of ;. 273: */ 274: case ',': 275: forbid (lastFKND == 0); 276: c = isupper(lastFKND) ? tolower(lastFKND) : toupper(lastFKND); 277: i = lastFCHR; 278: if (vglobp == 0) 279: vglobp = ""; 280: subop++; 281: goto nocount; 282: 283: /* 284: * 0 To beginning of real line. 285: */ 286: case '0': 287: wcursor = linebuf; 288: vmoving = 0; 289: break; 290: 291: /* 292: * ; Repeat last find with f F t or T. 293: */ 294: case ';': 295: forbid (lastFKND == 0); 296: c = lastFKND; 297: i = lastFCHR; 298: subop++; 299: goto nocount; 300: 301: /* 302: * F Find single character before cursor in current line. 303: * T Like F, but stops before character. 304: */ 305: case 'F': /* inverted find */ 306: case 'T': 307: dir = -1; 308: /* fall into ... */ 309: 310: /* 311: * f Find single character following cursor in current line. 312: * t Like f, but stope before character. 313: */ 314: case 'f': /* find */ 315: case 't': 316: if (!subop) { 317: i = getesc(); 318: if (i == 0) 319: return; 320: *lastcp++ = i; 321: } 322: if (vglobp == 0) 323: lastFKND = c, lastFCHR = i; 324: for (; cnt > 0; cnt--) 325: forbid (find(i) == 0); 326: vmoving = 0; 327: switch (c) { 328: 329: case 'T': 330: wcursor++; 331: break; 332: 333: case 't': 334: wcursor--; 335: case 'f': 336: fixup: 337: if (moveop != vmove) 338: wcursor++; 339: break; 340: } 341: break; 342: 343: /* 344: * | Find specified print column in current line. 345: */ 346: case '|': 347: if (Pline == numbline) 348: cnt += 8; 349: vmovcol = cnt; 350: vmoving = 1; 351: wcursor = vfindcol(cnt); 352: break; 353: 354: /* 355: * ^ To beginning of non-white space on line. 356: */ 357: case '^': 358: wcursor = vskipwh(linebuf); 359: vmoving = 0; 360: break; 361: 362: /* 363: * $ To end of line. 364: */ 365: case '$': 366: if (opf == vmove) { 367: vmoving = 1; 368: vmovcol = 20000; 369: } else 370: vmoving = 0; 371: if (cnt > 1) { 372: if (opf == vmove) { 373: wcursor = 0; 374: cnt--; 375: } else 376: wcursor = linebuf; 377: /* This is wrong at EOF */ 378: wdot = dot + cnt; 379: break; 380: } 381: if (linebuf[0]) { 382: wcursor = strend(linebuf) - 1; 383: goto fixup; 384: } 385: wcursor = linebuf; 386: break; 387: 388: /* 389: * h Back a character. 390: * ^H Back a character. 391: */ 392: case 'h': 393: case CTRL(h): 394: dir = -1; 395: /* fall into ... */ 396: 397: /* 398: * space Forward a character. 399: */ 400: case 'l': 401: case ' ': 402: forbid (margin() || opf == vmove && edge()); 403: while (cnt > 0 && !margin()) 404: wcursor += dir, cnt--; 405: if (margin() && opf == vmove || wcursor < linebuf) 406: wcursor -= dir; 407: vmoving = 0; 408: break; 409: 410: /* 411: * D Delete to end of line, short for d$. 412: */ 413: case 'D': 414: cnt = INF; 415: goto deleteit; 416: 417: /* 418: * X Delete character before cursor. 419: */ 420: case 'X': 421: dir = -1; 422: /* fall into ... */ 423: deleteit: 424: /* 425: * x Delete character at cursor, leaving cursor where it is. 426: */ 427: case 'x': 428: if (margin()) 429: goto errlab; 430: vmacchng(1); 431: while (cnt > 0 && !margin()) 432: wcursor += dir, cnt--; 433: opf = deleteop; 434: vmoving = 0; 435: break; 436: 437: default: 438: /* 439: * Stuttered operators are equivalent to the operator on 440: * a line, thus turn dd into d_. 441: */ 442: if (opf == vmove || c != workcmd[0]) { 443: errlab: 444: beep(); 445: vmacp = 0; 446: return; 447: } 448: /* fall into ... */ 449: 450: /* 451: * _ Target for a line or group of lines. 452: * Stuttering is more convenient; this is mostly 453: * for aesthetics. 454: */ 455: case '_': 456: wdot = dot + cnt - 1; 457: vmoving = 0; 458: wcursor = 0; 459: break; 460: 461: /* 462: * H To first, home line on screen. 463: * Count is for count'th line rather than first. 464: */ 465: case 'H': 466: wdot = (dot - vcline) + cnt - 1; 467: if (opf == vmove) 468: markit(wdot); 469: vmoving = 0; 470: wcursor = 0; 471: break; 472: 473: /* 474: * - Backwards lines, to first non-white character. 475: */ 476: case '-': 477: wdot = dot - cnt; 478: vmoving = 0; 479: wcursor = 0; 480: break; 481: 482: /* 483: * ^P To previous line same column. Ridiculous on the 484: * console of the VAX since it puts console in LSI mode. 485: */ 486: case 'k': 487: case CTRL(p): 488: wdot = dot - cnt; 489: if (vmoving == 0) 490: vmoving = 1, vmovcol = column(cursor); 491: wcursor = 0; 492: break; 493: 494: /* 495: * L To last line on screen, or count'th line from the 496: * bottom. 497: */ 498: case 'L': 499: wdot = dot + vcnt - vcline - cnt; 500: if (opf == vmove) 501: markit(wdot); 502: vmoving = 0; 503: wcursor = 0; 504: break; 505: 506: /* 507: * M To the middle of the screen. 508: */ 509: case 'M': 510: wdot = dot + ((vcnt + 1) / 2) - vcline - 1; 511: if (opf == vmove) 512: markit(wdot); 513: vmoving = 0; 514: wcursor = 0; 515: break; 516: 517: /* 518: * + Forward line, to first non-white. 519: * 520: * CR Convenient synonym for +. 521: */ 522: case '+': 523: case CR: 524: wdot = dot + cnt; 525: vmoving = 0; 526: wcursor = 0; 527: break; 528: 529: /* 530: * ^N To next line, same column if possible. 531: * 532: * LF Linefeed is a convenient synonym for ^N. 533: */ 534: case CTRL(n): 535: case 'j': 536: case NL: 537: wdot = dot + cnt; 538: if (vmoving == 0) 539: vmoving = 1, vmovcol = column(cursor); 540: wcursor = 0; 541: break; 542: 543: /* 544: * n Search to next match of current pattern. 545: */ 546: case 'n': 547: vglobp = vscandir; 548: c = *vglobp++; 549: goto nocount; 550: 551: /* 552: * N Like n but in reverse direction. 553: */ 554: case 'N': 555: vglobp = vscandir[0] == '/' ? "?" : "/"; 556: c = *vglobp++; 557: goto nocount; 558: 559: /* 560: * ' Return to line specified by following mark, 561: * first white position on line. 562: * 563: * ` Return to marked line at remembered column. 564: */ 565: case '\'': 566: case '`': 567: d = c; 568: c = getesc(); 569: if (c == 0) 570: return; 571: c = markreg(c); 572: forbid (c == 0); 573: wdot = getmark(c); 574: forbid (wdot == NOLINE); 575: forbid (Xhadcnt); 576: vmoving = 0; 577: wcursor = d == '`' ? ncols[c - 'a'] : 0; 578: if (opf == vmove && (wdot != dot || (d == '`' && wcursor != cursor))) 579: markDOT(); 580: if (wcursor) { 581: vsave(); 582: getline(*wdot); 583: if (wcursor > strend(linebuf)) 584: wcursor = 0; 585: getDOT(); 586: } 587: if (ospeed > B300) 588: hold |= HOLDWIG; 589: break; 590: 591: /* 592: * G Goto count'th line, or last line if no count 593: * given. 594: */ 595: case 'G': 596: if (!Xhadcnt) 597: cnt = lineDOL(); 598: wdot = zero + cnt; 599: forbid (wdot < one || wdot > dol); 600: if (opf == vmove) 601: markit(wdot); 602: vmoving = 0; 603: wcursor = 0; 604: break; 605: 606: /* 607: * / Scan forward for following re. 608: * ? Scan backward for following re. 609: */ 610: case '/': 611: case '?': 612: forbid (Xhadcnt); 613: vsave(); 614: ocurs = cursor; 615: odot = dot; 616: wcursor = 0; 617: if (readecho(c)) 618: return; 619: if (!vglobp) 620: vscandir[0] = genbuf[0]; 621: oglobp = globp; CP(vutmp, genbuf); globp = vutmp; 622: d = peekc; 623: fromsemi: 624: ungetchar(0); 625: fixech(); 626: CATCH 627: #ifndef CBREAK 628: /* 629: * Lose typeahead (ick). 630: */ 631: vcook(); 632: #endif 633: addr = address(cursor); 634: #ifndef CBREAK 635: vraw(); 636: #endif 637: ONERR 638: #ifndef CBREAK 639: vraw(); 640: #endif 641: slerr: 642: globp = oglobp; 643: dot = odot; 644: cursor = ocurs; 645: ungetchar(d); 646: splitw = 0; 647: vclean(); 648: vjumpto(dot, ocurs, 0); 649: return; 650: ENDCATCH 651: if (globp == 0) 652: globp = ""; 653: else if (peekc) 654: --globp; 655: if (*globp == ';') { 656: /* /foo/;/bar/ */ 657: globp++; 658: dot = addr; 659: cursor = loc1; 660: goto fromsemi; 661: } 662: dot = odot; 663: ungetchar(d); 664: c = 0; 665: if (*globp == 'z') 666: globp++, c = '\n'; 667: if (any(*globp, "^+-.")) 668: c = *globp++; 669: i = 0; 670: while (isdigit(*globp)) 671: i = i * 10 + *globp++ - '0'; 672: if (any(*globp, "^+-.")) 673: c = *globp++; 674: if (*globp) { 675: /* random junk after the pattern */ 676: beep(); 677: goto slerr; 678: } 679: globp = oglobp; 680: splitw = 0; 681: vmoving = 0; 682: wcursor = loc1; 683: if (i != 0) 684: vsetsiz(i); 685: if (opf == vmove) { 686: if (state == ONEOPEN || state == HARDOPEN) 687: outline = destline = WBOT; 688: if (addr != dot || loc1 != cursor) 689: markDOT(); 690: if (loc1 > linebuf && *loc1 == 0) 691: loc1--; 692: if (c) 693: vjumpto(addr, loc1, c); 694: else { 695: vmoving = 0; 696: if (loc1) { 697: vmoving++; 698: vmovcol = column(loc1); 699: } 700: getDOT(); 701: if (state == CRTOPEN && addr != dot) 702: vup1(); 703: vupdown(addr - dot, NOSTR); 704: } 705: return; 706: } 707: lastcp[-1] = 'n'; 708: getDOT(); 709: wdot = addr; 710: break; 711: } 712: /* 713: * Apply. 714: */ 715: if (vreg && wdot == 0) 716: wdot = dot; 717: (*opf)(c); 718: wdot = NOLINE; 719: } 720: 721: /* 722: * Find single character c, in direction dir from cursor. 723: */ 724: find(c) 725: char c; 726: { 727: 728: for(;;) { 729: if (edge()) 730: return (0); 731: wcursor += dir; 732: if (*wcursor == c) 733: return (1); 734: } 735: } 736: 737: /* 738: * Do a word motion with operator op, and cnt more words 739: * to go after this. 740: */ 741: word(op, cnt) 742: register int (*op)(); 743: int cnt; 744: { 745: register int which; 746: register char *iwc; 747: register line *iwdot = wdot; 748: 749: if (dir == 1) { 750: iwc = wcursor; 751: which = wordch(wcursor); 752: while (wordof(which, wcursor)) { 753: if (cnt == 1 && op != vmove && wcursor[1] == 0) { 754: wcursor++; 755: break; 756: } 757: if (!lnext()) 758: return (0); 759: if (wcursor == linebuf) 760: break; 761: } 762: /* Unless last segment of a change skip blanks */ 763: if (op != vchange || cnt > 1) 764: while (!margin() && blank()) 765: wcursor++; 766: else 767: if (wcursor == iwc && iwdot == wdot && *iwc) 768: wcursor++; 769: if (op == vmove && margin()) 770: wcursor--; 771: } else { 772: if (!lnext()) 773: return (0); 774: while (blank()) 775: if (!lnext()) 776: return (0); 777: if (!margin()) { 778: which = wordch(wcursor); 779: while (!margin() && wordof(which, wcursor)) 780: wcursor--; 781: } 782: if (wcursor < linebuf || !wordof(which, wcursor)) 783: wcursor++; 784: } 785: return (1); 786: } 787: 788: /* 789: * To end of word, with operator op and cnt more motions 790: * remaining after this. 791: */ 792: eend(op) 793: register int (*op)(); 794: { 795: register int which; 796: 797: if (!lnext()) 798: return; 799: while (blank()) 800: if (!lnext()) 801: return; 802: which = wordch(wcursor); 803: while (wordof(which, wcursor)) { 804: if (wcursor[1] == 0) { 805: wcursor++; 806: break; 807: } 808: if (!lnext()) 809: return; 810: } 811: if (op != vchange && op != vdelete && wcursor > linebuf) 812: wcursor--; 813: } 814: 815: /* 816: * Wordof tells whether the character at *wc is in a word of 817: * kind which (blank/nonblank words are 0, conservative words 1). 818: */ 819: wordof(which, wc) 820: char which; 821: register char *wc; 822: { 823: 824: if (isspace(*wc)) 825: return (0); 826: return (!wdkind || wordch(wc) == which); 827: } 828: 829: /* 830: * Wordch tells whether character at *wc is a word character 831: * i.e. an alfa, digit, or underscore. 832: */ 833: wordch(wc) 834: char *wc; 835: { 836: register int c; 837: 838: c = wc[0]; 839: return (isalpha(c) || isdigit(c) || c == '_'); 840: } 841: 842: /* 843: * Edge tells when we hit the last character in the current line. 844: */ 845: edge() 846: { 847: 848: if (linebuf[0] == 0) 849: return (1); 850: if (dir == 1) 851: return (wcursor[1] == 0); 852: else 853: return (wcursor == linebuf); 854: } 855: 856: /* 857: * Margin tells us when we have fallen off the end of the line. 858: */ 859: margin() 860: { 861: 862: return (wcursor < linebuf || wcursor[0] == 0); 863: }