neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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 }