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