1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * textobject.c: functions for text objects 12 */ 13 #include "vim.h" 14 15 static int cls(void); 16 static int skip_chars(int, int); 17 18 /* 19 * Find the start of the next sentence, searching in the direction specified 20 * by the "dir" argument. The cursor is positioned on the start of the next 21 * sentence when found. If the next sentence is found, return OK. Return FAIL 22 * otherwise. See ":h sentence" for the precise definition of a "sentence" 23 * text object. 24 */ 25 int 26 findsent(int dir, long count) 27 { 28 pos_T pos, tpos; 29 pos_T prev_pos; 30 int c; 31 int (*func)(pos_T *); 32 int startlnum; 33 int noskip = FALSE; // do not skip blanks 34 int cpo_J; 35 int found_dot; 36 37 pos = curwin->w_cursor; 38 if (dir == FORWARD) 39 func = incl; 40 else 41 func = decl; 42 43 while (count--) 44 { 45 prev_pos = pos; 46 47 /* 48 * if on an empty line, skip up to a non-empty line 49 */ 50 if (gchar_pos(&pos) == NUL) 51 { 52 do 53 if ((*func)(&pos) == -1) 54 break; 55 while (gchar_pos(&pos) == NUL); 56 if (dir == FORWARD) 57 goto found; 58 } 59 /* 60 * if on the start of a paragraph or a section and searching forward, 61 * go to the next line 62 */ 63 else if (dir == FORWARD && pos.col == 0 && 64 startPS(pos.lnum, NUL, FALSE)) 65 { 66 if (pos.lnum == curbuf->b_ml.ml_line_count) 67 return FAIL; 68 ++pos.lnum; 69 goto found; 70 } 71 else if (dir == BACKWARD) 72 decl(&pos); 73 74 // go back to the previous non-white non-punctuation character 75 found_dot = FALSE; 76 while (c = gchar_pos(&pos), VIM_ISWHITE(c) 77 || vim_strchr((char_u *)".!?)]\"'", c) != NULL) 78 { 79 tpos = pos; 80 if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) 81 break; 82 83 if (found_dot) 84 break; 85 if (vim_strchr((char_u *) ".!?", c) != NULL) 86 found_dot = TRUE; 87 88 if (vim_strchr((char_u *) ")]\"'", c) != NULL 89 && vim_strchr((char_u *) ".!?)]\"'", gchar_pos(&tpos)) == NULL) 90 break; 91 92 decl(&pos); 93 } 94 95 // remember the line where the search started 96 startlnum = pos.lnum; 97 cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; 98 99 for (;;) // find end of sentence 100 { 101 c = gchar_pos(&pos); 102 if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) 103 { 104 if (dir == BACKWARD && pos.lnum != startlnum) 105 ++pos.lnum; 106 break; 107 } 108 if (c == '.' || c == '!' || c == '?') 109 { 110 tpos = pos; 111 do 112 if ((c = inc(&tpos)) == -1) 113 break; 114 while (vim_strchr((char_u *)")]\"'", c = gchar_pos(&tpos)) 115 != NULL); 116 if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL 117 || (cpo_J && (c == ' ' && inc(&tpos) >= 0 118 && gchar_pos(&tpos) == ' '))) 119 { 120 pos = tpos; 121 if (gchar_pos(&pos) == NUL) // skip NUL at EOL 122 inc(&pos); 123 break; 124 } 125 } 126 if ((*func)(&pos) == -1) 127 { 128 if (count) 129 return FAIL; 130 noskip = TRUE; 131 break; 132 } 133 } 134 found: 135 // skip white space 136 while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) 137 if (incl(&pos) == -1) 138 break; 139 140 if (EQUAL_POS(prev_pos, pos)) 141 { 142 // didn't actually move, advance one character and try again 143 if ((*func)(&pos) == -1) 144 { 145 if (count) 146 return FAIL; 147 break; 148 } 149 ++count; 150 } 151 } 152 153 setpcmark(); 154 curwin->w_cursor = pos; 155 return OK; 156 } 157 158 /* 159 * Find the next paragraph or section in direction 'dir'. 160 * Paragraphs are currently supposed to be separated by empty lines. 161 * If 'what' is NUL we go to the next paragraph. 162 * If 'what' is '{' or '}' we go to the next section. 163 * If 'both' is TRUE also stop at '}'. 164 * Return TRUE if the next paragraph or section was found. 165 */ 166 int 167 findpar( 168 int *pincl, // Return: TRUE if last char is to be included 169 int dir, 170 long count, 171 int what, 172 int both) 173 { 174 linenr_T curr; 175 int did_skip; // TRUE after separating lines have been skipped 176 int first; // TRUE on first line 177 int posix = (vim_strchr(p_cpo, CPO_PARA) != NULL); 178 #ifdef FEAT_FOLDING 179 linenr_T fold_first; // first line of a closed fold 180 linenr_T fold_last; // last line of a closed fold 181 int fold_skipped; // TRUE if a closed fold was skipped this 182 // iteration 183 #endif 184 185 curr = curwin->w_cursor.lnum; 186 187 while (count--) 188 { 189 did_skip = FALSE; 190 for (first = TRUE; ; first = FALSE) 191 { 192 if (*ml_get(curr) != NUL) 193 did_skip = TRUE; 194 195 #ifdef FEAT_FOLDING 196 // skip folded lines 197 fold_skipped = FALSE; 198 if (first && hasFolding(curr, &fold_first, &fold_last)) 199 { 200 curr = ((dir > 0) ? fold_last : fold_first) + dir; 201 fold_skipped = TRUE; 202 } 203 #endif 204 205 // POSIX has its own ideas of what a paragraph boundary is and it 206 // doesn't match historical Vi: It also stops at a "{" in the 207 // first column and at an empty line. 208 if (!first && did_skip && (startPS(curr, what, both) 209 || (posix && what == NUL && *ml_get(curr) == '{'))) 210 break; 211 212 #ifdef FEAT_FOLDING 213 if (fold_skipped) 214 curr -= dir; 215 #endif 216 if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) 217 { 218 if (count) 219 return FALSE; 220 curr -= dir; 221 break; 222 } 223 } 224 } 225 setpcmark(); 226 if (both && *ml_get(curr) == '}') // include line with '}' 227 ++curr; 228 curwin->w_cursor.lnum = curr; 229 if (curr == curbuf->b_ml.ml_line_count && what != '}') 230 { 231 char_u *line = ml_get(curr); 232 233 // Put the cursor on the last character in the last line and make the 234 // motion inclusive. 235 if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) 236 { 237 --curwin->w_cursor.col; 238 curwin->w_cursor.col -= 239 (*mb_head_off)(line, line + curwin->w_cursor.col); 240 *pincl = TRUE; 241 } 242 } 243 else 244 curwin->w_cursor.col = 0; 245 return TRUE; 246 } 247 248 /* 249 * check if the string 's' is a nroff macro that is in option 'opt' 250 */ 251 static int 252 inmacro(char_u *opt, char_u *s) 253 { 254 char_u *macro; 255 256 for (macro = opt; macro[0]; ++macro) 257 { 258 // Accept two characters in the option being equal to two characters 259 // in the line. A space in the option matches with a space in the 260 // line or the line having ended. 261 if ( (macro[0] == s[0] 262 || (macro[0] == ' ' 263 && (s[0] == NUL || s[0] == ' '))) 264 && (macro[1] == s[1] 265 || ((macro[1] == NUL || macro[1] == ' ') 266 && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) 267 break; 268 ++macro; 269 if (macro[0] == NUL) 270 break; 271 } 272 return (macro[0] != NUL); 273 } 274 275 /* 276 * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. 277 * If 'para' is '{' or '}' only check for sections. 278 * If 'both' is TRUE also stop at '}' 279 */ 280 int 281 startPS(linenr_T lnum, int para, int both) 282 { 283 char_u *s; 284 285 s = ml_get(lnum); 286 if (*s == para || *s == '\f' || (both && *s == '}')) 287 return TRUE; 288 if (*s == '.' && (inmacro(p_sections, s + 1) || 289 (!para && inmacro(p_para, s + 1)))) 290 return TRUE; 291 return FALSE; 292 } 293 294 /* 295 * The following routines do the word searches performed by the 'w', 'W', 296 * 'b', 'B', 'e', and 'E' commands. 297 */ 298 299 /* 300 * To perform these searches, characters are placed into one of three 301 * classes, and transitions between classes determine word boundaries. 302 * 303 * The classes are: 304 * 305 * 0 - white space 306 * 1 - punctuation 307 * 2 or higher - keyword characters (letters, digits and underscore) 308 */ 309 310 static int cls_bigword; // TRUE for "W", "B" or "E" 311 312 /* 313 * cls() - returns the class of character at curwin->w_cursor 314 * 315 * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars 316 * from class 2 and higher are reported as class 1 since only white space 317 * boundaries are of interest. 318 */ 319 static int 320 cls(void) 321 { 322 int c; 323 324 c = gchar_cursor(); 325 if (c == ' ' || c == '\t' || c == NUL) 326 return 0; 327 if (enc_dbcs != 0 && c > 0xFF) 328 { 329 // If cls_bigword, report multi-byte chars as class 1. 330 if (enc_dbcs == DBCS_KOR && cls_bigword) 331 return 1; 332 333 // process code leading/trailing bytes 334 return dbcs_class(((unsigned)c >> 8), (c & 0xFF)); 335 } 336 if (enc_utf8) 337 { 338 c = utf_class(c); 339 if (c != 0 && cls_bigword) 340 return 1; 341 return c; 342 } 343 344 // If cls_bigword is TRUE, report all non-blanks as class 1. 345 if (cls_bigword) 346 return 1; 347 348 if (vim_iswordc(c)) 349 return 2; 350 return 1; 351 } 352 353 354 /* 355 * fwd_word(count, type, eol) - move forward one word 356 * 357 * Returns FAIL if the cursor was already at the end of the file. 358 * If eol is TRUE, last word stops at end of line (for operators). 359 */ 360 int 361 fwd_word( 362 long count, 363 int bigword, // "W", "E" or "B" 364 int eol) 365 { 366 int sclass; // starting class 367 int i; 368 int last_line; 369 370 curwin->w_cursor.coladd = 0; 371 cls_bigword = bigword; 372 while (--count >= 0) 373 { 374 #ifdef FEAT_FOLDING 375 // When inside a range of folded lines, move to the last char of the 376 // last line. 377 if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) 378 coladvance((colnr_T)MAXCOL); 379 #endif 380 sclass = cls(); 381 382 /* 383 * We always move at least one character, unless on the last 384 * character in the buffer. 385 */ 386 last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); 387 i = inc_cursor(); 388 if (i == -1 || (i >= 1 && last_line)) // started at last char in file 389 return FAIL; 390 if (i >= 1 && eol && count == 0) // started at last char in line 391 return OK; 392 393 /* 394 * Go one char past end of current word (if any) 395 */ 396 if (sclass != 0) 397 while (cls() == sclass) 398 { 399 i = inc_cursor(); 400 if (i == -1 || (i >= 1 && eol && count == 0)) 401 return OK; 402 } 403 404 /* 405 * go to next non-white 406 */ 407 while (cls() == 0) 408 { 409 /* 410 * We'll stop if we land on a blank line 411 */ 412 if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL) 413 break; 414 415 i = inc_cursor(); 416 if (i == -1 || (i >= 1 && eol && count == 0)) 417 return OK; 418 } 419 } 420 return OK; 421 } 422 423 /* 424 * bck_word() - move backward 'count' words 425 * 426 * If stop is TRUE and we are already on the start of a word, move one less. 427 * 428 * Returns FAIL if top of the file was reached. 429 */ 430 int 431 bck_word(long count, int bigword, int stop) 432 { 433 int sclass; // starting class 434 435 curwin->w_cursor.coladd = 0; 436 cls_bigword = bigword; 437 while (--count >= 0) 438 { 439 #ifdef FEAT_FOLDING 440 // When inside a range of folded lines, move to the first char of the 441 // first line. 442 if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) 443 curwin->w_cursor.col = 0; 444 #endif 445 sclass = cls(); 446 if (dec_cursor() == -1) // started at start of file 447 return FAIL; 448 449 if (!stop || sclass == cls() || sclass == 0) 450 { 451 /* 452 * Skip white space before the word. 453 * Stop on an empty line. 454 */ 455 while (cls() == 0) 456 { 457 if (curwin->w_cursor.col == 0 458 && LINEEMPTY(curwin->w_cursor.lnum)) 459 goto finished; 460 if (dec_cursor() == -1) // hit start of file, stop here 461 return OK; 462 } 463 464 /* 465 * Move backward to start of this word. 466 */ 467 if (skip_chars(cls(), BACKWARD)) 468 return OK; 469 } 470 471 inc_cursor(); // overshot - forward one 472 finished: 473 stop = FALSE; 474 } 475 return OK; 476 } 477 478 /* 479 * end_word() - move to the end of the word 480 * 481 * There is an apparent bug in the 'e' motion of the real vi. At least on the 482 * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' 483 * motion crosses blank lines. When the real vi crosses a blank line in an 484 * 'e' motion, the cursor is placed on the FIRST character of the next 485 * non-blank line. The 'E' command, however, works correctly. Since this 486 * appears to be a bug, I have not duplicated it here. 487 * 488 * Returns FAIL if end of the file was reached. 489 * 490 * If stop is TRUE and we are already on the end of a word, move one less. 491 * If empty is TRUE stop on an empty line. 492 */ 493 int 494 end_word( 495 long count, 496 int bigword, 497 int stop, 498 int empty) 499 { 500 int sclass; // starting class 501 502 curwin->w_cursor.coladd = 0; 503 cls_bigword = bigword; 504 while (--count >= 0) 505 { 506 #ifdef FEAT_FOLDING 507 // When inside a range of folded lines, move to the last char of the 508 // last line. 509 if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) 510 coladvance((colnr_T)MAXCOL); 511 #endif 512 sclass = cls(); 513 if (inc_cursor() == -1) 514 return FAIL; 515 516 /* 517 * If we're in the middle of a word, we just have to move to the end 518 * of it. 519 */ 520 if (cls() == sclass && sclass != 0) 521 { 522 /* 523 * Move forward to end of the current word 524 */ 525 if (skip_chars(sclass, FORWARD)) 526 return FAIL; 527 } 528 else if (!stop || sclass == 0) 529 { 530 /* 531 * We were at the end of a word. Go to the end of the next word. 532 * First skip white space, if 'empty' is TRUE, stop at empty line. 533 */ 534 while (cls() == 0) 535 { 536 if (empty && curwin->w_cursor.col == 0 537 && LINEEMPTY(curwin->w_cursor.lnum)) 538 goto finished; 539 if (inc_cursor() == -1) // hit end of file, stop here 540 return FAIL; 541 } 542 543 /* 544 * Move forward to the end of this word. 545 */ 546 if (skip_chars(cls(), FORWARD)) 547 return FAIL; 548 } 549 dec_cursor(); // overshot - one char backward 550 finished: 551 stop = FALSE; // we move only one word less 552 } 553 return OK; 554 } 555 556 /* 557 * Move back to the end of the word. 558 * 559 * Returns FAIL if start of the file was reached. 560 */ 561 int 562 bckend_word( 563 long count, 564 int bigword, // TRUE for "B" 565 int eol) // TRUE: stop at end of line. 566 { 567 int sclass; // starting class 568 int i; 569 570 curwin->w_cursor.coladd = 0; 571 cls_bigword = bigword; 572 while (--count >= 0) 573 { 574 sclass = cls(); 575 if ((i = dec_cursor()) == -1) 576 return FAIL; 577 if (eol && i == 1) 578 return OK; 579 580 /* 581 * Move backward to before the start of this word. 582 */ 583 if (sclass != 0) 584 { 585 while (cls() == sclass) 586 if ((i = dec_cursor()) == -1 || (eol && i == 1)) 587 return OK; 588 } 589 590 /* 591 * Move backward to end of the previous word 592 */ 593 while (cls() == 0) 594 { 595 if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) 596 break; 597 if ((i = dec_cursor()) == -1 || (eol && i == 1)) 598 return OK; 599 } 600 } 601 return OK; 602 } 603 604 /* 605 * Skip a row of characters of the same class. 606 * Return TRUE when end-of-file reached, FALSE otherwise. 607 */ 608 static int 609 skip_chars(int cclass, int dir) 610 { 611 while (cls() == cclass) 612 if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) 613 return TRUE; 614 return FALSE; 615 } 616 617 #if defined(FEAT_TEXTOBJ) || defined(PROTO) 618 /* 619 * Go back to the start of the word or the start of white space 620 */ 621 static void 622 back_in_line(void) 623 { 624 int sclass; // starting class 625 626 sclass = cls(); 627 for (;;) 628 { 629 if (curwin->w_cursor.col == 0) // stop at start of line 630 break; 631 dec_cursor(); 632 if (cls() != sclass) // stop at start of word 633 { 634 inc_cursor(); 635 break; 636 } 637 } 638 } 639 640 static void 641 find_first_blank(pos_T *posp) 642 { 643 int c; 644 645 while (decl(posp) != -1) 646 { 647 c = gchar_pos(posp); 648 if (!VIM_ISWHITE(c)) 649 { 650 incl(posp); 651 break; 652 } 653 } 654 } 655 656 /* 657 * Skip count/2 sentences and count/2 separating white spaces. 658 */ 659 static void 660 findsent_forward( 661 long count, 662 int at_start_sent) // cursor is at start of sentence 663 { 664 while (count--) 665 { 666 findsent(FORWARD, 1L); 667 if (at_start_sent) 668 find_first_blank(&curwin->w_cursor); 669 if (count == 0 || at_start_sent) 670 decl(&curwin->w_cursor); 671 at_start_sent = !at_start_sent; 672 } 673 } 674 675 /* 676 * Find word under cursor, cursor at end. 677 * Used while an operator is pending, and in Visual mode. 678 */ 679 int 680 current_word( 681 oparg_T *oap, 682 long count, 683 int include, // TRUE: include word and white space 684 int bigword) // FALSE == word, TRUE == WORD 685 { 686 pos_T start_pos; 687 pos_T pos; 688 int inclusive = TRUE; 689 int include_white = FALSE; 690 691 cls_bigword = bigword; 692 CLEAR_POS(&start_pos); 693 694 // Correct cursor when 'selection' is exclusive 695 if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor)) 696 dec_cursor(); 697 698 /* 699 * When Visual mode is not active, or when the VIsual area is only one 700 * character, select the word and/or white space under the cursor. 701 */ 702 if (!VIsual_active || EQUAL_POS(curwin->w_cursor, VIsual)) 703 { 704 /* 705 * Go to start of current word or white space. 706 */ 707 back_in_line(); 708 start_pos = curwin->w_cursor; 709 710 /* 711 * If the start is on white space, and white space should be included 712 * (" word"), or start is not on white space, and white space should 713 * not be included ("word"), find end of word. 714 */ 715 if ((cls() == 0) == include) 716 { 717 if (end_word(1L, bigword, TRUE, TRUE) == FAIL) 718 return FAIL; 719 } 720 else 721 { 722 /* 723 * If the start is not on white space, and white space should be 724 * included ("word "), or start is on white space and white 725 * space should not be included (" "), find start of word. 726 * If we end up in the first column of the next line (single char 727 * word) back up to end of the line. 728 */ 729 fwd_word(1L, bigword, TRUE); 730 if (curwin->w_cursor.col == 0) 731 decl(&curwin->w_cursor); 732 else 733 oneleft(); 734 735 if (include) 736 include_white = TRUE; 737 } 738 739 if (VIsual_active) 740 { 741 // should do something when inclusive == FALSE ! 742 VIsual = start_pos; 743 redraw_curbuf_later(INVERTED); // update the inversion 744 } 745 else 746 { 747 oap->start = start_pos; 748 oap->motion_type = MCHAR; 749 } 750 --count; 751 } 752 753 /* 754 * When count is still > 0, extend with more objects. 755 */ 756 while (count > 0) 757 { 758 inclusive = TRUE; 759 if (VIsual_active && LT_POS(curwin->w_cursor, VIsual)) 760 { 761 /* 762 * In Visual mode, with cursor at start: move cursor back. 763 */ 764 if (decl(&curwin->w_cursor) == -1) 765 return FAIL; 766 if (include != (cls() != 0)) 767 { 768 if (bck_word(1L, bigword, TRUE) == FAIL) 769 return FAIL; 770 } 771 else 772 { 773 if (bckend_word(1L, bigword, TRUE) == FAIL) 774 return FAIL; 775 (void)incl(&curwin->w_cursor); 776 } 777 } 778 else 779 { 780 /* 781 * Move cursor forward one word and/or white area. 782 */ 783 if (incl(&curwin->w_cursor) == -1) 784 return FAIL; 785 if (include != (cls() == 0)) 786 { 787 if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) 788 return FAIL; 789 /* 790 * If end is just past a new-line, we don't want to include 791 * the first character on the line. 792 * Put cursor on last char of white. 793 */ 794 if (oneleft() == FAIL) 795 inclusive = FALSE; 796 } 797 else 798 { 799 if (end_word(1L, bigword, TRUE, TRUE) == FAIL) 800 return FAIL; 801 } 802 } 803 --count; 804 } 805 806 if (include_white && (cls() != 0 807 || (curwin->w_cursor.col == 0 && !inclusive))) 808 { 809 /* 810 * If we don't include white space at the end, move the start 811 * to include some white space there. This makes "daw" work 812 * better on the last word in a sentence (and "2daw" on last-but-one 813 * word). Also when "2daw" deletes "word." at the end of the line 814 * (cursor is at start of next line). 815 * But don't delete white space at start of line (indent). 816 */ 817 pos = curwin->w_cursor; // save cursor position 818 curwin->w_cursor = start_pos; 819 if (oneleft() == OK) 820 { 821 back_in_line(); 822 if (cls() == 0 && curwin->w_cursor.col > 0) 823 { 824 if (VIsual_active) 825 VIsual = curwin->w_cursor; 826 else 827 oap->start = curwin->w_cursor; 828 } 829 } 830 curwin->w_cursor = pos; // put cursor back at end 831 } 832 833 if (VIsual_active) 834 { 835 if (*p_sel == 'e' && inclusive && LTOREQ_POS(VIsual, curwin->w_cursor)) 836 inc_cursor(); 837 if (VIsual_mode == 'V') 838 { 839 VIsual_mode = 'v'; 840 redraw_cmdline = TRUE; // show mode later 841 } 842 } 843 else 844 oap->inclusive = inclusive; 845 846 return OK; 847 } 848 849 /* 850 * Find sentence(s) under the cursor, cursor at end. 851 * When Visual active, extend it by one or more sentences. 852 */ 853 int 854 current_sent(oparg_T *oap, long count, int include) 855 { 856 pos_T start_pos; 857 pos_T pos; 858 int start_blank; 859 int c; 860 int at_start_sent; 861 long ncount; 862 863 start_pos = curwin->w_cursor; 864 pos = start_pos; 865 findsent(FORWARD, 1L); // Find start of next sentence. 866 867 /* 868 * When the Visual area is bigger than one character: Extend it. 869 */ 870 if (VIsual_active && !EQUAL_POS(start_pos, VIsual)) 871 { 872 extend: 873 if (LT_POS(start_pos, VIsual)) 874 { 875 /* 876 * Cursor at start of Visual area. 877 * Find out where we are: 878 * - in the white space before a sentence 879 * - in a sentence or just after it 880 * - at the start of a sentence 881 */ 882 at_start_sent = TRUE; 883 decl(&pos); 884 while (LT_POS(pos, curwin->w_cursor)) 885 { 886 c = gchar_pos(&pos); 887 if (!VIM_ISWHITE(c)) 888 { 889 at_start_sent = FALSE; 890 break; 891 } 892 incl(&pos); 893 } 894 if (!at_start_sent) 895 { 896 findsent(BACKWARD, 1L); 897 if (EQUAL_POS(curwin->w_cursor, start_pos)) 898 at_start_sent = TRUE; // exactly at start of sentence 899 else 900 // inside a sentence, go to its end (start of next) 901 findsent(FORWARD, 1L); 902 } 903 if (include) // "as" gets twice as much as "is" 904 count *= 2; 905 while (count--) 906 { 907 if (at_start_sent) 908 find_first_blank(&curwin->w_cursor); 909 c = gchar_cursor(); 910 if (!at_start_sent || (!include && !VIM_ISWHITE(c))) 911 findsent(BACKWARD, 1L); 912 at_start_sent = !at_start_sent; 913 } 914 } 915 else 916 { 917 /* 918 * Cursor at end of Visual area. 919 * Find out where we are: 920 * - just before a sentence 921 * - just before or in the white space before a sentence 922 * - in a sentence 923 */ 924 incl(&pos); 925 at_start_sent = TRUE; 926 // not just before a sentence 927 if (!EQUAL_POS(pos, curwin->w_cursor)) 928 { 929 at_start_sent = FALSE; 930 while (LT_POS(pos, curwin->w_cursor)) 931 { 932 c = gchar_pos(&pos); 933 if (!VIM_ISWHITE(c)) 934 { 935 at_start_sent = TRUE; 936 break; 937 } 938 incl(&pos); 939 } 940 if (at_start_sent) // in the sentence 941 findsent(BACKWARD, 1L); 942 else // in/before white before a sentence 943 curwin->w_cursor = start_pos; 944 } 945 946 if (include) // "as" gets twice as much as "is" 947 count *= 2; 948 findsent_forward(count, at_start_sent); 949 if (*p_sel == 'e') 950 ++curwin->w_cursor.col; 951 } 952 return OK; 953 } 954 955 /* 956 * If the cursor started on a blank, check if it is just before the start 957 * of the next sentence. 958 */ 959 while (c = gchar_pos(&pos), VIM_ISWHITE(c)) // VIM_ISWHITE() is a macro 960 incl(&pos); 961 if (EQUAL_POS(pos, curwin->w_cursor)) 962 { 963 start_blank = TRUE; 964 find_first_blank(&start_pos); // go back to first blank 965 } 966 else 967 { 968 start_blank = FALSE; 969 findsent(BACKWARD, 1L); 970 start_pos = curwin->w_cursor; 971 } 972 if (include) 973 ncount = count * 2; 974 else 975 { 976 ncount = count; 977 if (start_blank) 978 --ncount; 979 } 980 if (ncount > 0) 981 findsent_forward(ncount, TRUE); 982 else 983 decl(&curwin->w_cursor); 984 985 if (include) 986 { 987 /* 988 * If the blank in front of the sentence is included, exclude the 989 * blanks at the end of the sentence, go back to the first blank. 990 * If there are no trailing blanks, try to include leading blanks. 991 */ 992 if (start_blank) 993 { 994 find_first_blank(&curwin->w_cursor); 995 c = gchar_pos(&curwin->w_cursor); // VIM_ISWHITE() is a macro 996 if (VIM_ISWHITE(c)) 997 decl(&curwin->w_cursor); 998 } 999 else if (c = gchar_cursor(), !VIM_ISWHITE(c)) 1000 find_first_blank(&start_pos); 1001 } 1002 1003 if (VIsual_active) 1004 { 1005 // Avoid getting stuck with "is" on a single space before a sentence. 1006 if (EQUAL_POS(start_pos, curwin->w_cursor)) 1007 goto extend; 1008 if (*p_sel == 'e') 1009 ++curwin->w_cursor.col; 1010 VIsual = start_pos; 1011 VIsual_mode = 'v'; 1012 redraw_cmdline = TRUE; // show mode later 1013 redraw_curbuf_later(INVERTED); // update the inversion 1014 } 1015 else 1016 { 1017 // include a newline after the sentence, if there is one 1018 if (incl(&curwin->w_cursor) == -1) 1019 oap->inclusive = TRUE; 1020 else 1021 oap->inclusive = FALSE; 1022 oap->start = start_pos; 1023 oap->motion_type = MCHAR; 1024 } 1025 return OK; 1026 } 1027 1028 /* 1029 * Find block under the cursor, cursor at end. 1030 * "what" and "other" are two matching parenthesis/brace/etc. 1031 */ 1032 int 1033 current_block( 1034 oparg_T *oap, 1035 long count, 1036 int include, // TRUE == include white space 1037 int what, // '(', '{', etc. 1038 int other) // ')', '}', etc. 1039 { 1040 pos_T old_pos; 1041 pos_T *pos = NULL; 1042 pos_T start_pos; 1043 pos_T *end_pos; 1044 pos_T old_start, old_end; 1045 char_u *save_cpo; 1046 int sol = FALSE; // '{' at start of line 1047 1048 old_pos = curwin->w_cursor; 1049 old_end = curwin->w_cursor; // remember where we started 1050 old_start = old_end; 1051 1052 /* 1053 * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. 1054 */ 1055 if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) 1056 { 1057 setpcmark(); 1058 if (what == '{') // ignore indent 1059 while (inindent(1)) 1060 if (inc_cursor() != 0) 1061 break; 1062 if (gchar_cursor() == what) 1063 // cursor on '(' or '{', move cursor just after it 1064 ++curwin->w_cursor.col; 1065 } 1066 else if (LT_POS(VIsual, curwin->w_cursor)) 1067 { 1068 old_start = VIsual; 1069 curwin->w_cursor = VIsual; // cursor at low end of Visual 1070 } 1071 else 1072 old_end = VIsual; 1073 1074 /* 1075 * Search backwards for unclosed '(', '{', etc.. 1076 * Put this position in start_pos. 1077 * Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the 1078 * user wants. 1079 */ 1080 save_cpo = p_cpo; 1081 p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); 1082 while (count-- > 0) 1083 { 1084 if ((pos = findmatch(NULL, what)) == NULL) 1085 break; 1086 curwin->w_cursor = *pos; 1087 start_pos = *pos; // the findmatch for end_pos will overwrite *pos 1088 } 1089 p_cpo = save_cpo; 1090 1091 /* 1092 * Search for matching ')', '}', etc. 1093 * Put this position in curwin->w_cursor. 1094 */ 1095 if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) 1096 { 1097 curwin->w_cursor = old_pos; 1098 return FAIL; 1099 } 1100 curwin->w_cursor = *end_pos; 1101 1102 /* 1103 * Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. 1104 * If the ending '}', ')' or ']' is only preceded by indent, skip that 1105 * indent. But only if the resulting area is not smaller than what we 1106 * started with. 1107 */ 1108 while (!include) 1109 { 1110 incl(&start_pos); 1111 sol = (curwin->w_cursor.col == 0); 1112 decl(&curwin->w_cursor); 1113 while (inindent(1)) 1114 { 1115 sol = TRUE; 1116 if (decl(&curwin->w_cursor) != 0) 1117 break; 1118 } 1119 1120 /* 1121 * In Visual mode, when the resulting area is not bigger than what we 1122 * started with, extend it to the next block, and then exclude again. 1123 */ 1124 if (!LT_POS(start_pos, old_start) && !LT_POS(old_end, curwin->w_cursor) 1125 && VIsual_active) 1126 { 1127 curwin->w_cursor = old_start; 1128 decl(&curwin->w_cursor); 1129 if ((pos = findmatch(NULL, what)) == NULL) 1130 { 1131 curwin->w_cursor = old_pos; 1132 return FAIL; 1133 } 1134 start_pos = *pos; 1135 curwin->w_cursor = *pos; 1136 if ((end_pos = findmatch(NULL, other)) == NULL) 1137 { 1138 curwin->w_cursor = old_pos; 1139 return FAIL; 1140 } 1141 curwin->w_cursor = *end_pos; 1142 } 1143 else 1144 break; 1145 } 1146 1147 if (VIsual_active) 1148 { 1149 if (*p_sel == 'e') 1150 inc(&curwin->w_cursor); 1151 if (sol && gchar_cursor() != NUL) 1152 inc(&curwin->w_cursor); // include the line break 1153 VIsual = start_pos; 1154 VIsual_mode = 'v'; 1155 redraw_curbuf_later(INVERTED); // update the inversion 1156 showmode(); 1157 } 1158 else 1159 { 1160 oap->start = start_pos; 1161 oap->motion_type = MCHAR; 1162 oap->inclusive = FALSE; 1163 if (sol) 1164 incl(&curwin->w_cursor); 1165 else if (LTOREQ_POS(start_pos, curwin->w_cursor)) 1166 // Include the character under the cursor. 1167 oap->inclusive = TRUE; 1168 else 1169 // End is before the start (no text in between <>, [], etc.): don't 1170 // operate on any text. 1171 curwin->w_cursor = start_pos; 1172 } 1173 1174 return OK; 1175 } 1176 1177 /* 1178 * Return TRUE if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". 1179 * When "end_tag" is TRUE return TRUE if the cursor is on "</aaa>". 1180 */ 1181 static int 1182 in_html_tag( 1183 int end_tag) 1184 { 1185 char_u *line = ml_get_curline(); 1186 char_u *p; 1187 int c; 1188 int lc = NUL; 1189 pos_T pos; 1190 1191 if (enc_dbcs) 1192 { 1193 char_u *lp = NULL; 1194 1195 // We search forward until the cursor, because searching backwards is 1196 // very slow for DBCS encodings. 1197 for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p)) 1198 if (*p == '>' || *p == '<') 1199 { 1200 lc = *p; 1201 lp = p; 1202 } 1203 if (*p != '<') // check for '<' under cursor 1204 { 1205 if (lc != '<') 1206 return FALSE; 1207 p = lp; 1208 } 1209 } 1210 else 1211 { 1212 for (p = line + curwin->w_cursor.col; p > line; ) 1213 { 1214 if (*p == '<') // find '<' under/before cursor 1215 break; 1216 MB_PTR_BACK(line, p); 1217 if (*p == '>') // find '>' before cursor 1218 break; 1219 } 1220 if (*p != '<') 1221 return FALSE; 1222 } 1223 1224 pos.lnum = curwin->w_cursor.lnum; 1225 pos.col = (colnr_T)(p - line); 1226 1227 MB_PTR_ADV(p); 1228 if (end_tag) 1229 // check that there is a '/' after the '<' 1230 return *p == '/'; 1231 1232 // check that there is no '/' after the '<' 1233 if (*p == '/') 1234 return FALSE; 1235 1236 // check that the matching '>' is not preceded by '/' 1237 for (;;) 1238 { 1239 if (inc(&pos) < 0) 1240 return FALSE; 1241 c = *ml_get_pos(&pos); 1242 if (c == '>') 1243 break; 1244 lc = c; 1245 } 1246 return lc != '/'; 1247 } 1248 1249 /* 1250 * Find tag block under the cursor, cursor at end. 1251 */ 1252 int 1253 current_tagblock( 1254 oparg_T *oap, 1255 long count_arg, 1256 int include) // TRUE == include white space 1257 { 1258 long count = count_arg; 1259 long n; 1260 pos_T old_pos; 1261 pos_T start_pos; 1262 pos_T end_pos; 1263 pos_T old_start, old_end; 1264 char_u *spat, *epat; 1265 char_u *p; 1266 char_u *cp; 1267 int len; 1268 int r; 1269 int do_include = include; 1270 int save_p_ws = p_ws; 1271 int retval = FAIL; 1272 int is_inclusive = TRUE; 1273 1274 p_ws = FALSE; 1275 1276 old_pos = curwin->w_cursor; 1277 old_end = curwin->w_cursor; // remember where we started 1278 old_start = old_end; 1279 if (!VIsual_active || *p_sel == 'e') 1280 decl(&old_end); // old_end is inclusive 1281 1282 /* 1283 * If we start on "<aaa>" select that block. 1284 */ 1285 if (!VIsual_active || EQUAL_POS(VIsual, curwin->w_cursor)) 1286 { 1287 setpcmark(); 1288 1289 // ignore indent 1290 while (inindent(1)) 1291 if (inc_cursor() != 0) 1292 break; 1293 1294 if (in_html_tag(FALSE)) 1295 { 1296 // cursor on start tag, move to its '>' 1297 while (*ml_get_cursor() != '>') 1298 if (inc_cursor() < 0) 1299 break; 1300 } 1301 else if (in_html_tag(TRUE)) 1302 { 1303 // cursor on end tag, move to just before it 1304 while (*ml_get_cursor() != '<') 1305 if (dec_cursor() < 0) 1306 break; 1307 dec_cursor(); 1308 old_end = curwin->w_cursor; 1309 } 1310 } 1311 else if (LT_POS(VIsual, curwin->w_cursor)) 1312 { 1313 old_start = VIsual; 1314 curwin->w_cursor = VIsual; // cursor at low end of Visual 1315 } 1316 else 1317 old_end = VIsual; 1318 1319 again: 1320 /* 1321 * Search backwards for unclosed "<aaa>". 1322 * Put this position in start_pos. 1323 */ 1324 for (n = 0; n < count; ++n) 1325 { 1326 if (do_searchpair((char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", 1327 (char_u *)"", 1328 (char_u *)"</[^>]*>", BACKWARD, NULL, 0, 1329 NULL, (linenr_T)0, 0L) <= 0) 1330 { 1331 curwin->w_cursor = old_pos; 1332 goto theend; 1333 } 1334 } 1335 start_pos = curwin->w_cursor; 1336 1337 /* 1338 * Search for matching "</aaa>". First isolate the "aaa". 1339 */ 1340 inc_cursor(); 1341 p = ml_get_cursor(); 1342 for (cp = p; *cp != NUL && *cp != '>' && !VIM_ISWHITE(*cp); MB_PTR_ADV(cp)) 1343 ; 1344 len = (int)(cp - p); 1345 if (len == 0) 1346 { 1347 curwin->w_cursor = old_pos; 1348 goto theend; 1349 } 1350 spat = alloc(len + 39); 1351 epat = alloc(len + 9); 1352 if (spat == NULL || epat == NULL) 1353 { 1354 vim_free(spat); 1355 vim_free(epat); 1356 curwin->w_cursor = old_pos; 1357 goto theend; 1358 } 1359 sprintf((char *)spat, "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); 1360 sprintf((char *)epat, "</%.*s>\\c", len, p); 1361 1362 r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, 1363 0, NULL, (linenr_T)0, 0L); 1364 1365 vim_free(spat); 1366 vim_free(epat); 1367 1368 if (r < 1 || LT_POS(curwin->w_cursor, old_end)) 1369 { 1370 // Can't find other end or it's before the previous end. Could be a 1371 // HTML tag that doesn't have a matching end. Search backwards for 1372 // another starting tag. 1373 count = 1; 1374 curwin->w_cursor = start_pos; 1375 goto again; 1376 } 1377 1378 if (do_include) 1379 { 1380 // Include up to the '>'. 1381 while (*ml_get_cursor() != '>') 1382 if (inc_cursor() < 0) 1383 break; 1384 } 1385 else 1386 { 1387 char_u *c = ml_get_cursor(); 1388 1389 // Exclude the '<' of the end tag. 1390 // If the closing tag is on new line, do not decrement cursor, but 1391 // make operation exclusive, so that the linefeed will be selected 1392 if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) 1393 // do not decrement cursor 1394 is_inclusive = FALSE; 1395 else if (*c == '<') 1396 dec_cursor(); 1397 } 1398 end_pos = curwin->w_cursor; 1399 1400 if (!do_include) 1401 { 1402 // Exclude the start tag. 1403 curwin->w_cursor = start_pos; 1404 while (inc_cursor() >= 0) 1405 if (*ml_get_cursor() == '>') 1406 { 1407 inc_cursor(); 1408 start_pos = curwin->w_cursor; 1409 break; 1410 } 1411 curwin->w_cursor = end_pos; 1412 1413 // If we are in Visual mode and now have the same text as before set 1414 // "do_include" and try again. 1415 if (VIsual_active && EQUAL_POS(start_pos, old_start) 1416 && EQUAL_POS(end_pos, old_end)) 1417 { 1418 do_include = TRUE; 1419 curwin->w_cursor = old_start; 1420 count = count_arg; 1421 goto again; 1422 } 1423 } 1424 1425 if (VIsual_active) 1426 { 1427 // If the end is before the start there is no text between tags, select 1428 // the char under the cursor. 1429 if (LT_POS(end_pos, start_pos)) 1430 curwin->w_cursor = start_pos; 1431 else if (*p_sel == 'e') 1432 inc_cursor(); 1433 VIsual = start_pos; 1434 VIsual_mode = 'v'; 1435 redraw_curbuf_later(INVERTED); // update the inversion 1436 showmode(); 1437 } 1438 else 1439 { 1440 oap->start = start_pos; 1441 oap->motion_type = MCHAR; 1442 if (LT_POS(end_pos, start_pos)) 1443 { 1444 // End is before the start: there is no text between tags; operate 1445 // on an empty area. 1446 curwin->w_cursor = start_pos; 1447 oap->inclusive = FALSE; 1448 } 1449 else 1450 oap->inclusive = is_inclusive; 1451 } 1452 retval = OK; 1453 1454 theend: 1455 p_ws = save_p_ws; 1456 return retval; 1457 } 1458 1459 int 1460 current_par( 1461 oparg_T *oap, 1462 long count, 1463 int include, // TRUE == include white space 1464 int type) // 'p' for paragraph, 'S' for section 1465 { 1466 linenr_T start_lnum; 1467 linenr_T end_lnum; 1468 int white_in_front; 1469 int dir; 1470 int start_is_white; 1471 int prev_start_is_white; 1472 int retval = OK; 1473 int do_white = FALSE; 1474 int t; 1475 int i; 1476 1477 if (type == 'S') // not implemented yet 1478 return FAIL; 1479 1480 start_lnum = curwin->w_cursor.lnum; 1481 1482 /* 1483 * When visual area is more than one line: extend it. 1484 */ 1485 if (VIsual_active && start_lnum != VIsual.lnum) 1486 { 1487 extend: 1488 if (start_lnum < VIsual.lnum) 1489 dir = BACKWARD; 1490 else 1491 dir = FORWARD; 1492 for (i = count; --i >= 0; ) 1493 { 1494 if (start_lnum == 1495 (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) 1496 { 1497 retval = FAIL; 1498 break; 1499 } 1500 1501 prev_start_is_white = -1; 1502 for (t = 0; t < 2; ++t) 1503 { 1504 start_lnum += dir; 1505 start_is_white = linewhite(start_lnum); 1506 if (prev_start_is_white == start_is_white) 1507 { 1508 start_lnum -= dir; 1509 break; 1510 } 1511 for (;;) 1512 { 1513 if (start_lnum == (dir == BACKWARD 1514 ? 1 : curbuf->b_ml.ml_line_count)) 1515 break; 1516 if (start_is_white != linewhite(start_lnum + dir) 1517 || (!start_is_white 1518 && startPS(start_lnum + (dir > 0 1519 ? 1 : 0), 0, 0))) 1520 break; 1521 start_lnum += dir; 1522 } 1523 if (!include) 1524 break; 1525 if (start_lnum == (dir == BACKWARD 1526 ? 1 : curbuf->b_ml.ml_line_count)) 1527 break; 1528 prev_start_is_white = start_is_white; 1529 } 1530 } 1531 curwin->w_cursor.lnum = start_lnum; 1532 curwin->w_cursor.col = 0; 1533 return retval; 1534 } 1535 1536 /* 1537 * First move back to the start_lnum of the paragraph or white lines 1538 */ 1539 white_in_front = linewhite(start_lnum); 1540 while (start_lnum > 1) 1541 { 1542 if (white_in_front) // stop at first white line 1543 { 1544 if (!linewhite(start_lnum - 1)) 1545 break; 1546 } 1547 else // stop at first non-white line of start of paragraph 1548 { 1549 if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) 1550 break; 1551 } 1552 --start_lnum; 1553 } 1554 1555 /* 1556 * Move past the end of any white lines. 1557 */ 1558 end_lnum = start_lnum; 1559 while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) 1560 ++end_lnum; 1561 1562 --end_lnum; 1563 i = count; 1564 if (!include && white_in_front) 1565 --i; 1566 while (i--) 1567 { 1568 if (end_lnum == curbuf->b_ml.ml_line_count) 1569 return FAIL; 1570 1571 if (!include) 1572 do_white = linewhite(end_lnum + 1); 1573 1574 if (include || !do_white) 1575 { 1576 ++end_lnum; 1577 /* 1578 * skip to end of paragraph 1579 */ 1580 while (end_lnum < curbuf->b_ml.ml_line_count 1581 && !linewhite(end_lnum + 1) 1582 && !startPS(end_lnum + 1, 0, 0)) 1583 ++end_lnum; 1584 } 1585 1586 if (i == 0 && white_in_front && include) 1587 break; 1588 1589 /* 1590 * skip to end of white lines after paragraph 1591 */ 1592 if (include || do_white) 1593 while (end_lnum < curbuf->b_ml.ml_line_count 1594 && linewhite(end_lnum + 1)) 1595 ++end_lnum; 1596 } 1597 1598 /* 1599 * If there are no empty lines at the end, try to find some empty lines at 1600 * the start (unless that has been done already). 1601 */ 1602 if (!white_in_front && !linewhite(end_lnum) && include) 1603 while (start_lnum > 1 && linewhite(start_lnum - 1)) 1604 --start_lnum; 1605 1606 if (VIsual_active) 1607 { 1608 // Problem: when doing "Vipipip" nothing happens in a single white 1609 // line, we get stuck there. Trap this here. 1610 if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) 1611 goto extend; 1612 if (VIsual.lnum != start_lnum) 1613 { 1614 VIsual.lnum = start_lnum; 1615 VIsual.col = 0; 1616 } 1617 VIsual_mode = 'V'; 1618 redraw_curbuf_later(INVERTED); // update the inversion 1619 showmode(); 1620 } 1621 else 1622 { 1623 oap->start.lnum = start_lnum; 1624 oap->start.col = 0; 1625 oap->motion_type = MLINE; 1626 } 1627 curwin->w_cursor.lnum = end_lnum; 1628 curwin->w_cursor.col = 0; 1629 1630 return OK; 1631 } 1632 1633 /* 1634 * Search quote char from string line[col]. 1635 * Quote character escaped by one of the characters in "escape" is not counted 1636 * as a quote. 1637 * Returns column number of "quotechar" or -1 when not found. 1638 */ 1639 static int 1640 find_next_quote( 1641 char_u *line, 1642 int col, 1643 int quotechar, 1644 char_u *escape) // escape characters, can be NULL 1645 { 1646 int c; 1647 1648 for (;;) 1649 { 1650 c = line[col]; 1651 if (c == NUL) 1652 return -1; 1653 else if (escape != NULL && vim_strchr(escape, c)) 1654 ++col; 1655 else if (c == quotechar) 1656 break; 1657 if (has_mbyte) 1658 col += (*mb_ptr2len)(line + col); 1659 else 1660 ++col; 1661 } 1662 return col; 1663 } 1664 1665 /* 1666 * Search backwards in "line" from column "col_start" to find "quotechar". 1667 * Quote character escaped by one of the characters in "escape" is not counted 1668 * as a quote. 1669 * Return the found column or zero. 1670 */ 1671 static int 1672 find_prev_quote( 1673 char_u *line, 1674 int col_start, 1675 int quotechar, 1676 char_u *escape) // escape characters, can be NULL 1677 { 1678 int n; 1679 1680 while (col_start > 0) 1681 { 1682 --col_start; 1683 col_start -= (*mb_head_off)(line, line + col_start); 1684 n = 0; 1685 if (escape != NULL) 1686 while (col_start - n > 0 && vim_strchr(escape, 1687 line[col_start - n - 1]) != NULL) 1688 ++n; 1689 if (n & 1) 1690 col_start -= n; // uneven number of escape chars, skip it 1691 else if (line[col_start] == quotechar) 1692 break; 1693 } 1694 return col_start; 1695 } 1696 1697 /* 1698 * Find quote under the cursor, cursor at end. 1699 * Returns TRUE if found, else FALSE. 1700 */ 1701 int 1702 current_quote( 1703 oparg_T *oap, 1704 long count, 1705 int include, // TRUE == include quote char 1706 int quotechar) // Quote character 1707 { 1708 char_u *line = ml_get_curline(); 1709 int col_end; 1710 int col_start = curwin->w_cursor.col; 1711 int inclusive = FALSE; 1712 int vis_empty = TRUE; // Visual selection <= 1 char 1713 int vis_bef_curs = FALSE; // Visual starts before cursor 1714 int did_exclusive_adj = FALSE; // adjusted pos for 'selection' 1715 int inside_quotes = FALSE; // Looks like "i'" done before 1716 int selected_quote = FALSE; // Has quote inside selection 1717 int i; 1718 int restore_vis_bef = FALSE; // restore VIsual on abort 1719 1720 // When 'selection' is "exclusive" move the cursor to where it would be 1721 // with 'selection' "inclusive", so that the logic is the same for both. 1722 // The cursor then is moved forward after adjusting the area. 1723 if (VIsual_active) 1724 { 1725 // this only works within one line 1726 if (VIsual.lnum != curwin->w_cursor.lnum) 1727 return FALSE; 1728 1729 vis_bef_curs = LT_POS(VIsual, curwin->w_cursor); 1730 vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); 1731 if (*p_sel == 'e') 1732 { 1733 if (vis_bef_curs) 1734 { 1735 dec_cursor(); 1736 did_exclusive_adj = TRUE; 1737 } 1738 else if (!vis_empty) 1739 { 1740 dec(&VIsual); 1741 did_exclusive_adj = TRUE; 1742 } 1743 vis_empty = EQUAL_POS(VIsual, curwin->w_cursor); 1744 if (!vis_bef_curs && !vis_empty) 1745 { 1746 // VIsual needs to be the start of Visual selection. 1747 pos_T t = curwin->w_cursor; 1748 1749 curwin->w_cursor = VIsual; 1750 VIsual = t; 1751 vis_bef_curs = TRUE; 1752 restore_vis_bef = TRUE; 1753 } 1754 } 1755 } 1756 1757 if (!vis_empty) 1758 { 1759 // Check if the existing selection exactly spans the text inside 1760 // quotes. 1761 if (vis_bef_curs) 1762 { 1763 inside_quotes = VIsual.col > 0 1764 && line[VIsual.col - 1] == quotechar 1765 && line[curwin->w_cursor.col] != NUL 1766 && line[curwin->w_cursor.col + 1] == quotechar; 1767 i = VIsual.col; 1768 col_end = curwin->w_cursor.col; 1769 } 1770 else 1771 { 1772 inside_quotes = curwin->w_cursor.col > 0 1773 && line[curwin->w_cursor.col - 1] == quotechar 1774 && line[VIsual.col] != NUL 1775 && line[VIsual.col + 1] == quotechar; 1776 i = curwin->w_cursor.col; 1777 col_end = VIsual.col; 1778 } 1779 1780 // Find out if we have a quote in the selection. 1781 while (i <= col_end) 1782 if (line[i++] == quotechar) 1783 { 1784 selected_quote = TRUE; 1785 break; 1786 } 1787 } 1788 1789 if (!vis_empty && line[col_start] == quotechar) 1790 { 1791 // Already selecting something and on a quote character. Find the 1792 // next quoted string. 1793 if (vis_bef_curs) 1794 { 1795 // Assume we are on a closing quote: move to after the next 1796 // opening quote. 1797 col_start = find_next_quote(line, col_start + 1, quotechar, NULL); 1798 if (col_start < 0) 1799 goto abort_search; 1800 col_end = find_next_quote(line, col_start + 1, quotechar, 1801 curbuf->b_p_qe); 1802 if (col_end < 0) 1803 { 1804 // We were on a starting quote perhaps? 1805 col_end = col_start; 1806 col_start = curwin->w_cursor.col; 1807 } 1808 } 1809 else 1810 { 1811 col_end = find_prev_quote(line, col_start, quotechar, NULL); 1812 if (line[col_end] != quotechar) 1813 goto abort_search; 1814 col_start = find_prev_quote(line, col_end, quotechar, 1815 curbuf->b_p_qe); 1816 if (line[col_start] != quotechar) 1817 { 1818 // We were on an ending quote perhaps? 1819 col_start = col_end; 1820 col_end = curwin->w_cursor.col; 1821 } 1822 } 1823 } 1824 else 1825 1826 if (line[col_start] == quotechar || !vis_empty) 1827 { 1828 int first_col = col_start; 1829 1830 if (!vis_empty) 1831 { 1832 if (vis_bef_curs) 1833 first_col = find_next_quote(line, col_start, quotechar, NULL); 1834 else 1835 first_col = find_prev_quote(line, col_start, quotechar, NULL); 1836 } 1837 1838 // The cursor is on a quote, we don't know if it's the opening or 1839 // closing quote. Search from the start of the line to find out. 1840 // Also do this when there is a Visual area, a' may leave the cursor 1841 // in between two strings. 1842 col_start = 0; 1843 for (;;) 1844 { 1845 // Find open quote character. 1846 col_start = find_next_quote(line, col_start, quotechar, NULL); 1847 if (col_start < 0 || col_start > first_col) 1848 goto abort_search; 1849 // Find close quote character. 1850 col_end = find_next_quote(line, col_start + 1, quotechar, 1851 curbuf->b_p_qe); 1852 if (col_end < 0) 1853 goto abort_search; 1854 // If is cursor between start and end quote character, it is 1855 // target text object. 1856 if (col_start <= first_col && first_col <= col_end) 1857 break; 1858 col_start = col_end + 1; 1859 } 1860 } 1861 else 1862 { 1863 // Search backward for a starting quote. 1864 col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); 1865 if (line[col_start] != quotechar) 1866 { 1867 // No quote before the cursor, look after the cursor. 1868 col_start = find_next_quote(line, col_start, quotechar, NULL); 1869 if (col_start < 0) 1870 goto abort_search; 1871 } 1872 1873 // Find close quote character. 1874 col_end = find_next_quote(line, col_start + 1, quotechar, 1875 curbuf->b_p_qe); 1876 if (col_end < 0) 1877 goto abort_search; 1878 } 1879 1880 // When "include" is TRUE, include spaces after closing quote or before 1881 // the starting quote. 1882 if (include) 1883 { 1884 if (VIM_ISWHITE(line[col_end + 1])) 1885 while (VIM_ISWHITE(line[col_end + 1])) 1886 ++col_end; 1887 else 1888 while (col_start > 0 && VIM_ISWHITE(line[col_start - 1])) 1889 --col_start; 1890 } 1891 1892 // Set start position. After vi" another i" must include the ". 1893 // For v2i" include the quotes. 1894 if (!include && count < 2 && (vis_empty || !inside_quotes)) 1895 ++col_start; 1896 curwin->w_cursor.col = col_start; 1897 if (VIsual_active) 1898 { 1899 // Set the start of the Visual area when the Visual area was empty, we 1900 // were just inside quotes or the Visual area didn't start at a quote 1901 // and didn't include a quote. 1902 if (vis_empty 1903 || (vis_bef_curs 1904 && !selected_quote 1905 && (inside_quotes 1906 || (line[VIsual.col] != quotechar 1907 && (VIsual.col == 0 1908 || line[VIsual.col - 1] != quotechar))))) 1909 { 1910 VIsual = curwin->w_cursor; 1911 redraw_curbuf_later(INVERTED); 1912 } 1913 } 1914 else 1915 { 1916 oap->start = curwin->w_cursor; 1917 oap->motion_type = MCHAR; 1918 } 1919 1920 // Set end position. 1921 curwin->w_cursor.col = col_end; 1922 if ((include || count > 1 // After vi" another i" must include the ". 1923 || (!vis_empty && inside_quotes) 1924 ) && inc_cursor() == 2) 1925 inclusive = TRUE; 1926 if (VIsual_active) 1927 { 1928 if (vis_empty || vis_bef_curs) 1929 { 1930 // decrement cursor when 'selection' is not exclusive 1931 if (*p_sel != 'e') 1932 dec_cursor(); 1933 } 1934 else 1935 { 1936 // Cursor is at start of Visual area. Set the end of the Visual 1937 // area when it was just inside quotes or it didn't end at a 1938 // quote. 1939 if (inside_quotes 1940 || (!selected_quote 1941 && line[VIsual.col] != quotechar 1942 && (line[VIsual.col] == NUL 1943 || line[VIsual.col + 1] != quotechar))) 1944 { 1945 dec_cursor(); 1946 VIsual = curwin->w_cursor; 1947 } 1948 curwin->w_cursor.col = col_start; 1949 } 1950 if (VIsual_mode == 'V') 1951 { 1952 VIsual_mode = 'v'; 1953 redraw_cmdline = TRUE; // show mode later 1954 } 1955 } 1956 else 1957 { 1958 // Set inclusive and other oap's flags. 1959 oap->inclusive = inclusive; 1960 } 1961 1962 return OK; 1963 1964 abort_search: 1965 if (VIsual_active && *p_sel == 'e') 1966 { 1967 if (did_exclusive_adj) 1968 inc_cursor(); 1969 if (restore_vis_bef) 1970 { 1971 pos_T t = curwin->w_cursor; 1972 1973 curwin->w_cursor = VIsual; 1974 VIsual = t; 1975 } 1976 } 1977 return FALSE; 1978 } 1979 1980 #endif // FEAT_TEXTOBJ 1981