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