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