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