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.6 (Berkeley) 01/02/88"; 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, (line *) 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, (line *) 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 flusho(); 719 wdot = NOLINE; 720 } 721 722 /* 723 * Find single character c, in direction dir from cursor. 724 */ 725 find(c) 726 char c; 727 { 728 729 for(;;) { 730 if (edge()) 731 return (0); 732 wcursor += dir; 733 if (*wcursor == c) 734 return (1); 735 } 736 } 737 738 /* 739 * Do a word motion with operator op, and cnt more words 740 * to go after this. 741 */ 742 word(op, cnt) 743 register int (*op)(); 744 int cnt; 745 { 746 register int which; 747 register char *iwc; 748 register line *iwdot = wdot; 749 750 if (dir == 1) { 751 iwc = wcursor; 752 which = wordch(wcursor); 753 while (wordof(which, wcursor)) { 754 if (cnt == 1 && op != vmove && wcursor[1] == 0) { 755 wcursor++; 756 break; 757 } 758 if (!lnext()) 759 return (0); 760 if (wcursor == linebuf) 761 break; 762 } 763 /* Unless last segment of a change skip blanks */ 764 if (op != vchange || cnt > 1) 765 while (!margin() && blank()) 766 wcursor++; 767 else 768 if (wcursor == iwc && iwdot == wdot && *iwc) 769 wcursor++; 770 if (op == vmove && margin()) 771 wcursor--; 772 } else { 773 if (!lnext()) 774 return (0); 775 while (blank()) 776 if (!lnext()) 777 return (0); 778 if (!margin()) { 779 which = wordch(wcursor); 780 while (!margin() && wordof(which, wcursor)) 781 wcursor--; 782 } 783 if (wcursor < linebuf || !wordof(which, wcursor)) 784 wcursor++; 785 } 786 return (1); 787 } 788 789 /* 790 * To end of word, with operator op and cnt more motions 791 * remaining after this. 792 */ 793 eend(op) 794 register int (*op)(); 795 { 796 register int which; 797 798 if (!lnext()) 799 return; 800 while (blank()) 801 if (!lnext()) 802 return; 803 which = wordch(wcursor); 804 while (wordof(which, wcursor)) { 805 if (wcursor[1] == 0) { 806 wcursor++; 807 break; 808 } 809 if (!lnext()) 810 return; 811 } 812 if (op != vchange && op != vdelete && wcursor > linebuf) 813 wcursor--; 814 } 815 816 /* 817 * Wordof tells whether the character at *wc is in a word of 818 * kind which (blank/nonblank words are 0, conservative words 1). 819 */ 820 wordof(which, wc) 821 char which; 822 register char *wc; 823 { 824 825 if (isspace(*wc)) 826 return (0); 827 return (!wdkind || wordch(wc) == which); 828 } 829 830 /* 831 * Wordch tells whether character at *wc is a word character 832 * i.e. an alfa, digit, or underscore. 833 */ 834 wordch(wc) 835 char *wc; 836 { 837 register int c; 838 839 c = wc[0]; 840 return (isalpha(c) || isdigit(c) || c == '_'); 841 } 842 843 /* 844 * Edge tells when we hit the last character in the current line. 845 */ 846 edge() 847 { 848 849 if (linebuf[0] == 0) 850 return (1); 851 if (dir == 1) 852 return (wcursor[1] == 0); 853 else 854 return (wcursor == linebuf); 855 } 856 857 /* 858 * Margin tells us when we have fallen off the end of the line. 859 */ 860 margin() 861 { 862 863 return (wcursor < linebuf || wcursor[0] == 0); 864 } 865