neovim

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

search.c (116759B)


      1 // search.c: code for normal mode searching commands
      2 
      3 #include <assert.h>
      4 #include <inttypes.h>
      5 #include <limits.h>
      6 #include <stdbool.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "nvim/ascii_defs.h"
     12 #include "nvim/autocmd.h"
     13 #include "nvim/autocmd_defs.h"
     14 #include "nvim/buffer.h"
     15 #include "nvim/buffer_defs.h"
     16 #include "nvim/change.h"
     17 #include "nvim/charset.h"
     18 #include "nvim/cmdhist.h"
     19 #include "nvim/cursor.h"
     20 #include "nvim/drawscreen.h"
     21 #include "nvim/errors.h"
     22 #include "nvim/eval/typval.h"
     23 #include "nvim/eval/typval_defs.h"
     24 #include "nvim/eval/vars.h"
     25 #include "nvim/ex_cmds.h"
     26 #include "nvim/ex_cmds_defs.h"
     27 #include "nvim/ex_docmd.h"
     28 #include "nvim/ex_getln.h"
     29 #include "nvim/file_search.h"
     30 #include "nvim/fileio.h"
     31 #include "nvim/fold.h"
     32 #include "nvim/getchar.h"
     33 #include "nvim/gettext_defs.h"
     34 #include "nvim/globals.h"
     35 #include "nvim/highlight_defs.h"
     36 #include "nvim/indent_c.h"
     37 #include "nvim/insexpand.h"
     38 #include "nvim/macros_defs.h"
     39 #include "nvim/mark.h"
     40 #include "nvim/mark_defs.h"
     41 #include "nvim/mbyte.h"
     42 #include "nvim/memline.h"
     43 #include "nvim/memory.h"
     44 #include "nvim/message.h"
     45 #include "nvim/mouse.h"
     46 #include "nvim/move.h"
     47 #include "nvim/normal.h"
     48 #include "nvim/option.h"
     49 #include "nvim/option_vars.h"
     50 #include "nvim/os/fs.h"
     51 #include "nvim/os/input.h"
     52 #include "nvim/os/time.h"
     53 #include "nvim/path.h"
     54 #include "nvim/plines.h"
     55 #include "nvim/profile.h"
     56 #include "nvim/regexp.h"
     57 #include "nvim/search.h"
     58 #include "nvim/state_defs.h"
     59 #include "nvim/strings.h"
     60 #include "nvim/tag.h"
     61 #include "nvim/ui.h"
     62 #include "nvim/ui_defs.h"
     63 #include "nvim/vim_defs.h"
     64 #include "nvim/window.h"
     65 
     66 #include "search.c.generated.h"
     67 
     68 static const char e_search_hit_top_without_match_for_str[]
     69  = N_("E384: Search hit TOP without match for: %s");
     70 static const char e_search_hit_bottom_without_match_for_str[]
     71  = N_("E385: Search hit BOTTOM without match for: %s");
     72 
     73 //  This file contains various searching-related routines. These fall into
     74 //  three groups:
     75 //  1. string searches (for /, ?, n, and N)
     76 //  2. character searches within a single line (for f, F, t, T, etc)
     77 //  3. "other" kinds of searches like the '%' command, and 'word' searches.
     78 //
     79 //
     80 //  String searches
     81 //
     82 //  The string search functions are divided into two levels:
     83 //  lowest:  searchit(); uses a pos_T for starting position and found match.
     84 //  Highest: do_search(); uses curwin->w_cursor; calls searchit().
     85 //
     86 //  The last search pattern is remembered for repeating the same search.
     87 //  This pattern is shared between the :g, :s, ? and / commands.
     88 //  This is in search_regcomp().
     89 //
     90 //  The actual string matching is done using a heavily modified version of
     91 //  Henry Spencer's regular expression library.  See regexp.c.
     92 //
     93 //
     94 //
     95 // Two search patterns are remembered: One for the :substitute command and
     96 // one for other searches.  last_idx points to the one that was used the last
     97 // time.
     98 
     99 static SearchPattern spats[2] = {
    100  // Last used search pattern
    101  [0] = { NULL, 0, true, false, 0, { '/', false, false, 0 }, NULL },
    102  // Last used substitute pattern
    103  [1] = { NULL, 0, true, false, 0, { '/', false, false, 0 }, NULL }
    104 };
    105 
    106 static int last_idx = 0;        // index in spats[] for RE_LAST
    107 
    108 static uint8_t lastc[2] = { NUL, NUL };   // last character searched for
    109 static Direction lastcdir = FORWARD;      // last direction of character search
    110 static bool last_t_cmd = true;            // last search t_cmd
    111 static char lastc_bytes[MAX_SCHAR_SIZE + 1];
    112 static int lastc_bytelen = 1;             // >1 for multi-byte char
    113 
    114 // copy of spats[], for keeping the search patterns while executing autocmds
    115 static SearchPattern saved_spats[ARRAY_SIZE(spats)];
    116 static char *saved_mr_pattern = NULL;
    117 static size_t saved_mr_patternlen = 0;
    118 static int saved_spats_last_idx = 0;
    119 static bool saved_spats_no_hlsearch = false;
    120 
    121 // allocated copy of pattern used by search_regcomp()
    122 static char *mr_pattern = NULL;
    123 static size_t mr_patternlen = 0;
    124 
    125 // Type used by find_pattern_in_path() to remember which included files have
    126 // been searched already.
    127 typedef struct {
    128  FILE *fp;              // File pointer
    129  char *name;            // Full name of file
    130  linenr_T lnum;                // Line we were up to in file
    131  int matched;                  // Found a match in this file
    132 } SearchedFile;
    133 
    134 /// translate search pattern for vim_regcomp()
    135 ///
    136 /// pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
    137 /// pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
    138 /// pat_save == RE_BOTH: save pat in both patterns (:global command)
    139 /// pat_use  == RE_SEARCH: use previous search pattern if "pat" is NULL
    140 /// pat_use  == RE_SUBST: use previous substitute pattern if "pat" is NULL
    141 /// pat_use  == RE_LAST: use last used pattern if "pat" is NULL
    142 /// options & SEARCH_HIS: put search string in history
    143 /// options & SEARCH_KEEP: keep previous search pattern
    144 ///
    145 /// @param regmatch  return: pattern and ignore-case flag
    146 ///
    147 /// @return          FAIL if failed, OK otherwise.
    148 int search_regcomp(char *pat, size_t patlen, char **used_pat, int pat_save, int pat_use,
    149                   int options, regmmatch_T *regmatch)
    150 {
    151  rc_did_emsg = false;
    152  int magic = magic_isset();
    153 
    154  // If no pattern given, use a previously defined pattern.
    155  if (pat == NULL || *pat == NUL) {
    156    int i;
    157    if (pat_use == RE_LAST) {
    158      i = last_idx;
    159    } else {
    160      i = pat_use;
    161    }
    162    if (spats[i].pat == NULL) {         // pattern was never defined
    163      if (pat_use == RE_SUBST) {
    164        emsg(_(e_nopresub));
    165      } else {
    166        emsg(_(e_noprevre));
    167      }
    168      rc_did_emsg = true;
    169      return FAIL;
    170    }
    171    pat = spats[i].pat;
    172    patlen = spats[i].patlen;
    173    magic = spats[i].magic;
    174    no_smartcase = spats[i].no_scs;
    175  } else if (options & SEARCH_HIS) {      // put new pattern in history
    176    add_to_history(HIST_SEARCH, pat, patlen, true, NUL);
    177  }
    178 
    179  if (used_pat) {
    180    *used_pat = pat;
    181  }
    182 
    183  xfree(mr_pattern);
    184  if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
    185    mr_pattern = reverse_text(pat);
    186  } else {
    187    mr_pattern = xstrnsave(pat, patlen);
    188  }
    189  mr_patternlen = patlen;
    190 
    191  // Save the currently used pattern in the appropriate place,
    192  // unless the pattern should not be remembered.
    193  if (!(options & SEARCH_KEEP) && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
    194    // search or global command
    195    if (pat_save == RE_SEARCH || pat_save == RE_BOTH) {
    196      save_re_pat(RE_SEARCH, pat, patlen, magic);
    197    }
    198    // substitute or global command
    199    if (pat_save == RE_SUBST || pat_save == RE_BOTH) {
    200      save_re_pat(RE_SUBST, pat, patlen, magic);
    201    }
    202  }
    203 
    204  regmatch->rmm_ic = ignorecase(pat);
    205  regmatch->rmm_maxcol = 0;
    206  regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
    207  if (regmatch->regprog == NULL) {
    208    return FAIL;
    209  }
    210  return OK;
    211 }
    212 
    213 /// Get search pattern used by search_regcomp().
    214 char *get_search_pat(void)
    215 {
    216  return mr_pattern;
    217 }
    218 
    219 void save_re_pat(int idx, char *pat, size_t patlen, int magic)
    220 {
    221  if (spats[idx].pat == pat) {
    222    return;
    223  }
    224 
    225  free_spat(&spats[idx]);
    226  spats[idx].pat = xstrnsave(pat, patlen);
    227  spats[idx].patlen = patlen;
    228  spats[idx].magic = magic;
    229  spats[idx].no_scs = no_smartcase;
    230  spats[idx].timestamp = os_time();
    231  spats[idx].additional_data = NULL;
    232  last_idx = idx;
    233  // If 'hlsearch' set and search pat changed: need redraw.
    234  if (p_hls) {
    235    redraw_all_later(UPD_SOME_VALID);
    236  }
    237  set_no_hlsearch(false);
    238 }
    239 
    240 // Save the search patterns, so they can be restored later.
    241 // Used before/after executing autocommands and user functions.
    242 static int save_level = 0;
    243 
    244 void save_search_patterns(void)
    245 {
    246  if (save_level++ != 0) {
    247    return;
    248  }
    249 
    250  for (size_t i = 0; i < ARRAY_SIZE(spats); i++) {
    251    saved_spats[i] = spats[i];
    252    if (spats[i].pat != NULL) {
    253      saved_spats[i].pat = xstrnsave(spats[i].pat, spats[i].patlen);
    254      saved_spats[i].patlen = spats[i].patlen;
    255    }
    256  }
    257  if (mr_pattern == NULL) {
    258    saved_mr_pattern = NULL;
    259    saved_mr_patternlen = 0;
    260  } else {
    261    saved_mr_pattern = xstrnsave(mr_pattern, mr_patternlen);
    262    saved_mr_patternlen = mr_patternlen;
    263  }
    264  saved_spats_last_idx = last_idx;
    265  saved_spats_no_hlsearch = no_hlsearch;
    266 }
    267 
    268 void restore_search_patterns(void)
    269 {
    270  if (--save_level != 0) {
    271    return;
    272  }
    273 
    274  for (size_t i = 0; i < ARRAY_SIZE(spats); i++) {
    275    free_spat(&spats[i]);
    276    spats[i] = saved_spats[i];
    277  }
    278  set_vv_searchforward();
    279  xfree(mr_pattern);
    280  mr_pattern = saved_mr_pattern;
    281  mr_patternlen = saved_mr_patternlen;
    282  last_idx = saved_spats_last_idx;
    283  set_no_hlsearch(saved_spats_no_hlsearch);
    284 }
    285 
    286 static inline void free_spat(SearchPattern *const spat)
    287 {
    288  xfree(spat->pat);
    289  xfree(spat->additional_data);
    290 }
    291 
    292 #if defined(EXITFREE)
    293 void free_search_patterns(void)
    294 {
    295  for (size_t i = 0; i < ARRAY_SIZE(spats); i++) {
    296    free_spat(&spats[i]);
    297  }
    298  CLEAR_FIELD(spats);
    299 
    300  XFREE_CLEAR(mr_pattern);
    301  mr_patternlen = 0;
    302 }
    303 
    304 #endif
    305 
    306 // copy of spats[RE_SEARCH], for keeping the search patterns while incremental
    307 // searching
    308 static SearchPattern saved_last_search_spat;
    309 static int did_save_last_search_spat = 0;
    310 static int saved_last_idx = 0;
    311 static bool saved_no_hlsearch = false;
    312 static colnr_T saved_search_match_endcol;
    313 static linenr_T saved_search_match_lines;
    314 
    315 /// Save and restore the search pattern for incremental highlight search
    316 /// feature.
    317 ///
    318 /// It's similar to but different from save_search_patterns() and
    319 /// restore_search_patterns(), because the search pattern must be restored when
    320 /// cancelling incremental searching even if it's called inside user functions.
    321 void save_last_search_pattern(void)
    322 {
    323  if (++did_save_last_search_spat != 1) {
    324    // nested call, nothing to do
    325    return;
    326  }
    327 
    328  saved_last_search_spat = spats[RE_SEARCH];
    329  if (spats[RE_SEARCH].pat != NULL) {
    330    saved_last_search_spat.pat = xstrnsave(spats[RE_SEARCH].pat, spats[RE_SEARCH].patlen);
    331    saved_last_search_spat.patlen = spats[RE_SEARCH].patlen;
    332  }
    333  saved_last_idx = last_idx;
    334  saved_no_hlsearch = no_hlsearch;
    335 }
    336 
    337 void restore_last_search_pattern(void)
    338 {
    339  if (--did_save_last_search_spat > 0) {
    340    // nested call, nothing to do
    341    return;
    342  }
    343  if (did_save_last_search_spat != 0) {
    344    iemsg("restore_last_search_pattern() called more often than"
    345          " save_last_search_pattern()");
    346    return;
    347  }
    348 
    349  xfree(spats[RE_SEARCH].pat);
    350  spats[RE_SEARCH] = saved_last_search_spat;
    351  saved_last_search_spat.pat = NULL;
    352  saved_last_search_spat.patlen = 0;
    353  set_vv_searchforward();
    354  last_idx = saved_last_idx;
    355  set_no_hlsearch(saved_no_hlsearch);
    356 }
    357 
    358 /// Save and restore the incsearch highlighting variables.
    359 /// This is required so that calling searchcount() at does not invalidate the
    360 /// incsearch highlighting.
    361 static void save_incsearch_state(void)
    362 {
    363  saved_search_match_endcol = search_match_endcol;
    364  saved_search_match_lines = search_match_lines;
    365 }
    366 
    367 static void restore_incsearch_state(void)
    368 {
    369  search_match_endcol = saved_search_match_endcol;
    370  search_match_lines = saved_search_match_lines;
    371 }
    372 
    373 char *last_search_pattern(void)
    374 {
    375  return spats[RE_SEARCH].pat;
    376 }
    377 
    378 size_t last_search_pattern_len(void)
    379 {
    380  return spats[RE_SEARCH].patlen;
    381 }
    382 
    383 /// Return true when case should be ignored for search pattern "pat".
    384 /// Uses the 'ignorecase' and 'smartcase' options.
    385 int ignorecase(char *pat)
    386 {
    387  return ignorecase_opt(pat, p_ic, p_scs);
    388 }
    389 
    390 /// As ignorecase() but pass the "ic" and "scs" flags.
    391 int ignorecase_opt(char *pat, int ic_in, int scs)
    392 {
    393  int ic = ic_in;
    394  if (ic && !no_smartcase && scs
    395      && !(ctrl_x_mode_not_default()
    396           && curbuf->b_p_inf)) {
    397    ic = !pat_has_uppercase(pat);
    398  }
    399  no_smartcase = false;
    400 
    401  return ic;
    402 }
    403 
    404 /// Returns true if pattern `pat` has an uppercase character.
    405 bool pat_has_uppercase(char *pat)
    406  FUNC_ATTR_NONNULL_ALL
    407 {
    408  char *p = pat;
    409  magic_T magic_val = MAGIC_ON;
    410 
    411  // get the magicness of the pattern
    412  skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
    413 
    414  while (*p != NUL) {
    415    const int l = utfc_ptr2len(p);
    416 
    417    if (l > 1) {
    418      if (mb_isupper(utf_ptr2char(p))) {
    419        return true;
    420      }
    421      p += l;
    422    } else if (*p == '\\' && magic_val <= MAGIC_ON) {
    423      if (p[1] == '_' && p[2] != NUL) {  // skip "\_X"
    424        p += 3;
    425      } else if (p[1] == '%' && p[2] != NUL) {  // skip "\%X"
    426        p += 3;
    427      } else if (p[1] != NUL) {  // skip "\X"
    428        p += 2;
    429      } else {
    430        p += 1;
    431      }
    432    } else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL) {
    433      if (p[1] != NUL) {  // skip "_X" and %X
    434        p += 2;
    435      } else {
    436        p++;
    437      }
    438    } else if (mb_isupper((uint8_t)(*p))) {
    439      return true;
    440    } else {
    441      p++;
    442    }
    443  }
    444  return false;
    445 }
    446 
    447 const char *last_csearch(void)
    448  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    449 {
    450  return lastc_bytes;
    451 }
    452 
    453 int last_csearch_forward(void)
    454 {
    455  return lastcdir == FORWARD;
    456 }
    457 
    458 int last_csearch_until(void)
    459 {
    460  return last_t_cmd;
    461 }
    462 
    463 void set_last_csearch(int c, char *s, int len)
    464 {
    465  *lastc = (uint8_t)c;
    466  lastc_bytelen = len;
    467  if (len) {
    468    memcpy(lastc_bytes, s, (size_t)len);
    469  } else {
    470    CLEAR_FIELD(lastc_bytes);
    471  }
    472 }
    473 
    474 void set_csearch_direction(Direction cdir)
    475 {
    476  lastcdir = cdir;
    477 }
    478 
    479 void set_csearch_until(int t_cmd)
    480 {
    481  last_t_cmd = t_cmd;
    482 }
    483 
    484 char *last_search_pat(void)
    485 {
    486  return spats[last_idx].pat;
    487 }
    488 
    489 // Reset search direction to forward.  For "gd" and "gD" commands.
    490 void reset_search_dir(void)
    491 {
    492  spats[0].off.dir = '/';
    493  set_vv_searchforward();
    494 }
    495 
    496 // Set the last search pattern.  For ":let @/ =" and ShaDa file.
    497 // Also set the saved search pattern, so that this works in an autocommand.
    498 void set_last_search_pat(const char *s, int idx, int magic, bool setlast)
    499 {
    500  free_spat(&spats[idx]);
    501  // An empty string means that nothing should be matched.
    502  if (*s == NUL) {
    503    spats[idx].pat = NULL;
    504    spats[idx].patlen = 0;
    505  } else {
    506    spats[idx].patlen = strlen(s);
    507    spats[idx].pat = xstrnsave(s, spats[idx].patlen);
    508  }
    509  spats[idx].timestamp = os_time();
    510  spats[idx].additional_data = NULL;
    511  spats[idx].magic = magic;
    512  spats[idx].no_scs = false;
    513  spats[idx].off.dir = '/';
    514  set_vv_searchforward();
    515  spats[idx].off.line = false;
    516  spats[idx].off.end = false;
    517  spats[idx].off.off = 0;
    518  if (setlast) {
    519    last_idx = idx;
    520  }
    521  if (save_level) {
    522    free_spat(&saved_spats[idx]);
    523    saved_spats[idx] = spats[0];
    524    if (spats[idx].pat == NULL) {
    525      saved_spats[idx].pat = NULL;
    526      saved_spats[idx].patlen = 0;
    527    } else {
    528      saved_spats[idx].pat = xstrnsave(spats[idx].pat, spats[idx].patlen);
    529      saved_spats[idx].patlen = spats[idx].patlen;
    530    }
    531    saved_spats_last_idx = last_idx;
    532  }
    533  // If 'hlsearch' set and search pat changed: need redraw.
    534  if (p_hls && idx == last_idx && !no_hlsearch) {
    535    redraw_all_later(UPD_SOME_VALID);
    536  }
    537 }
    538 
    539 // Get a regexp program for the last used search pattern.
    540 // This is used for highlighting all matches in a window.
    541 // Values returned in regmatch->regprog and regmatch->rmm_ic.
    542 void last_pat_prog(regmmatch_T *regmatch)
    543 {
    544  if (spats[last_idx].pat == NULL) {
    545    regmatch->regprog = NULL;
    546    return;
    547  }
    548  emsg_off++;           // So it doesn't beep if bad expr
    549  search_regcomp("", 0, NULL, 0, last_idx, SEARCH_KEEP, regmatch);
    550  emsg_off--;
    551 }
    552 
    553 /// Lowest level search function.
    554 /// Search for 'count'th occurrence of pattern "pat" in direction "dir".
    555 /// Start at position "pos" and return the found position in "pos".
    556 ///
    557 /// if (options & SEARCH_MSG) == 0 don't give any messages
    558 /// if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
    559 /// if (options & SEARCH_MSG) == SEARCH_MSG give all messages
    560 /// if (options & SEARCH_HIS) put search pattern in history
    561 /// if (options & SEARCH_END) return position at end of match
    562 /// if (options & SEARCH_START) accept match at pos itself
    563 /// if (options & SEARCH_KEEP) keep previous search pattern
    564 /// if (options & SEARCH_FOLD) match only once in a closed fold
    565 /// if (options & SEARCH_PEEK) check for typed char, cancel search
    566 /// if (options & SEARCH_COL) start at pos->col instead of zero
    567 ///
    568 /// @param win        window to search in; can be NULL for a buffer without a window!
    569 /// @param end_pos    set to end of the match, unless NULL
    570 /// @param pat_use    which pattern to use when "pat" is empty
    571 /// @param extra_arg  optional extra arguments, can be NULL
    572 ///
    573 /// @returns          FAIL (zero) for failure, non-zero for success.
    574 ///                   the index of the first matching
    575 ///                   subpattern plus one; one if there was none.
    576 int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char *pat,
    577             size_t patlen, int count, int options, int pat_use, searchit_arg_T *extra_arg)
    578 {
    579  int found;
    580  linenr_T lnum;                // no init to shut up Apollo cc
    581  regmmatch_T regmatch;
    582  char *ptr;
    583  colnr_T matchcol;
    584  lpos_T endpos;
    585  lpos_T matchpos;
    586  int loop;
    587  int extra_col;
    588  int start_char_len;
    589  bool match_ok;
    590  int nmatched;
    591  int submatch = 0;
    592  bool first_match = true;
    593  const int called_emsg_before = called_emsg;
    594  bool break_loop = false;
    595  linenr_T stop_lnum = 0;  // stop after this line number when != 0
    596  proftime_T *tm = NULL;   // timeout limit or NULL
    597  int *timed_out = NULL;   // set when timed out or NULL
    598 
    599  if (extra_arg != NULL) {
    600    stop_lnum = extra_arg->sa_stop_lnum;
    601    tm = extra_arg->sa_tm;
    602    timed_out = &extra_arg->sa_timed_out;
    603  }
    604 
    605  if (search_regcomp(pat, patlen, NULL, RE_SEARCH, pat_use,
    606                     (options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL) {
    607    if ((options & SEARCH_MSG) && !rc_did_emsg) {
    608      semsg(_("E383: Invalid search string: %s"), mr_pattern);
    609    }
    610    return FAIL;
    611  }
    612 
    613  const bool search_from_match_end = vim_strchr(p_cpo, CPO_SEARCH) != NULL;
    614 
    615  // find the string
    616  do {  // loop for count
    617    // When not accepting a match at the start position set "extra_col" to a
    618    // non-zero value.  Don't do that when starting at MAXCOL, since MAXCOL + 1
    619    // is zero.
    620    if (pos->col == MAXCOL) {
    621      start_char_len = 0;
    622    } else if (pos->lnum >= 1
    623               && pos->lnum <= buf->b_ml.ml_line_count
    624               && pos->col < MAXCOL - 2) {
    625      // Watch out for the "col" being MAXCOL - 2, used in a closed fold.
    626      ptr = ml_get_buf(buf, pos->lnum);
    627      if (ml_get_buf_len(buf, pos->lnum) <= pos->col) {
    628        start_char_len = 1;
    629      } else {
    630        start_char_len = utfc_ptr2len(ptr + pos->col);
    631      }
    632    } else {
    633      start_char_len = 1;
    634    }
    635    if (dir == FORWARD) {
    636      extra_col = (options & SEARCH_START) ? 0 : start_char_len;
    637    } else {
    638      extra_col = (options & SEARCH_START) ? start_char_len : 0;
    639    }
    640 
    641    pos_T start_pos = *pos;           // remember start pos for detecting no match
    642    found = 0;                  // default: not found
    643    int at_first_line = true;       // default: start in first line
    644    if (pos->lnum == 0) {       // correct lnum for when starting in line 0
    645      pos->lnum = 1;
    646      pos->col = 0;
    647      at_first_line = false;        // not in first line now
    648    }
    649 
    650    // Start searching in current line, unless searching backwards and
    651    // we're in column 0.
    652    // If we are searching backwards, in column 0, and not including the
    653    // current position, gain some efficiency by skipping back a line.
    654    // Otherwise begin the search in the current line.
    655    if (dir == BACKWARD && start_pos.col == 0
    656        && (options & SEARCH_START) == 0) {
    657      lnum = pos->lnum - 1;
    658      at_first_line = false;
    659    } else {
    660      lnum = pos->lnum;
    661    }
    662 
    663    for (loop = 0; loop <= 1; loop++) {     // loop twice if 'wrapscan' set
    664      for (; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
    665           lnum += dir, at_first_line = false) {
    666        // Stop after checking "stop_lnum", if it's set.
    667        if (stop_lnum != 0 && (dir == FORWARD
    668                               ? lnum > stop_lnum : lnum < stop_lnum)) {
    669          break;
    670        }
    671        // Stop after passing the "tm" time limit.
    672        if (tm != NULL && profile_passed_limit(*tm)) {
    673          break;
    674        }
    675 
    676        // Look for a match somewhere in line "lnum".
    677        colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0;
    678        nmatched = vim_regexec_multi(&regmatch, win, buf,
    679                                     lnum, col, tm, timed_out);
    680        // vim_regexec_multi() may clear "regprog"
    681        if (regmatch.regprog == NULL) {
    682          break;
    683        }
    684        // Abort searching on an error (e.g., out of stack).
    685        if (called_emsg > called_emsg_before || (timed_out != NULL && *timed_out)) {
    686          break;
    687        }
    688        if (nmatched > 0) {
    689          // match may actually be in another line when using \zs
    690          matchpos = regmatch.startpos[0];
    691          endpos = regmatch.endpos[0];
    692          submatch = first_submatch(&regmatch);
    693          // "lnum" may be past end of buffer for "\n\zs".
    694          if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) {
    695            ptr = "";
    696          } else {
    697            ptr = ml_get_buf(buf, lnum + matchpos.lnum);
    698          }
    699 
    700          // Forward search in the first line: match should be after
    701          // the start position. If not, continue at the end of the
    702          // match (this is vi compatible) or on the next char.
    703          if (dir == FORWARD && at_first_line) {
    704            match_ok = true;
    705 
    706            // When the match starts in a next line it's certainly
    707            // past the start position.
    708            // When match lands on a NUL the cursor will be put
    709            // one back afterwards, compare with that position,
    710            // otherwise "/$" will get stuck on end of line.
    711            while (matchpos.lnum == 0
    712                   && (((options & SEARCH_END) && first_match)
    713                       ? (nmatched == 1
    714                          && (int)endpos.col - 1
    715                          < (int)start_pos.col + extra_col)
    716                       : ((int)matchpos.col
    717                          - (ptr[matchpos.col] == NUL)
    718                          < (int)start_pos.col + extra_col))) {
    719              // If vi-compatible searching, continue at the end
    720              // of the match, otherwise continue one position
    721              // forward.
    722              if (search_from_match_end) {
    723                if (nmatched > 1) {
    724                  // end is in next line, thus no match in
    725                  // this line
    726                  match_ok = false;
    727                  break;
    728                }
    729                matchcol = endpos.col;
    730                // for empty match: advance one char
    731                if (matchcol == matchpos.col && ptr[matchcol] != NUL) {
    732                  matchcol += utfc_ptr2len(ptr + matchcol);
    733                }
    734              } else {
    735                // Advance "matchcol" to the next character.
    736                // This uses rmm_matchcol, the actual start of
    737                // the match, ignoring "\zs".
    738                matchcol = regmatch.rmm_matchcol;
    739                if (ptr[matchcol] != NUL) {
    740                  matchcol += utfc_ptr2len(ptr + matchcol);
    741                }
    742              }
    743              if (matchcol == 0 && (options & SEARCH_START)) {
    744                break;
    745              }
    746              if (ptr[matchcol] == NUL
    747                  || (nmatched = vim_regexec_multi(&regmatch, win, buf,
    748                                                   lnum, matchcol, tm,
    749                                                   timed_out)) == 0) {
    750                match_ok = false;
    751                break;
    752              }
    753              // vim_regexec_multi() may clear "regprog"
    754              if (regmatch.regprog == NULL) {
    755                break;
    756              }
    757              matchpos = regmatch.startpos[0];
    758              endpos = regmatch.endpos[0];
    759              submatch = first_submatch(&regmatch);
    760 
    761              // This while-loop only works with matchpos.lnum == 0.
    762              // For bigger values the next line pointer ptr might not be a
    763              // buffer line.
    764              if (matchpos.lnum != 0) {
    765                break;
    766              }
    767              // Need to get the line pointer again, a multi-line search may
    768              // have made it invalid.
    769              ptr = ml_get_buf(buf, lnum);
    770            }
    771            if (!match_ok) {
    772              continue;
    773            }
    774          }
    775          if (dir == BACKWARD) {
    776            // Now, if there are multiple matches on this line,
    777            // we have to get the last one. Or the last one before
    778            // the cursor, if we're on that line.
    779            // When putting the new cursor at the end, compare
    780            // relative to the end of the match.
    781            match_ok = false;
    782            while (true) {
    783              // Remember a position that is before the start
    784              // position, we use it if it's the last match in
    785              // the line.  Always accept a position after
    786              // wrapping around.
    787              if (loop
    788                  || ((options & SEARCH_END)
    789                      ? (lnum + regmatch.endpos[0].lnum
    790                         < start_pos.lnum
    791                         || (lnum + regmatch.endpos[0].lnum
    792                             == start_pos.lnum
    793                             && (int)regmatch.endpos[0].col - 1
    794                             < (int)start_pos.col + extra_col))
    795                      : (lnum + regmatch.startpos[0].lnum
    796                         < start_pos.lnum
    797                         || (lnum + regmatch.startpos[0].lnum
    798                             == start_pos.lnum
    799                             && (int)regmatch.startpos[0].col
    800                             < (int)start_pos.col + extra_col)))) {
    801                match_ok = true;
    802                matchpos = regmatch.startpos[0];
    803                endpos = regmatch.endpos[0];
    804                submatch = first_submatch(&regmatch);
    805              } else {
    806                break;
    807              }
    808 
    809              // We found a valid match, now check if there is
    810              // another one after it.
    811              // If vi-compatible searching, continue at the end
    812              // of the match, otherwise continue one position
    813              // forward.
    814              if (search_from_match_end) {
    815                if (nmatched > 1) {
    816                  break;
    817                }
    818                matchcol = endpos.col;
    819                // for empty match: advance one char
    820                if (matchcol == matchpos.col
    821                    && ptr[matchcol] != NUL) {
    822                  matchcol += utfc_ptr2len(ptr + matchcol);
    823                }
    824              } else {
    825                // Stop when the match is in a next line.
    826                if (matchpos.lnum > 0) {
    827                  break;
    828                }
    829                matchcol = matchpos.col;
    830                if (ptr[matchcol] != NUL) {
    831                  matchcol += utfc_ptr2len(ptr + matchcol);
    832                }
    833              }
    834              if (ptr[matchcol] == NUL
    835                  || (nmatched = vim_regexec_multi(&regmatch, win, buf, lnum + matchpos.lnum,
    836                                                   matchcol, tm, timed_out)) == 0) {
    837                // If the search timed out, we did find a match
    838                // but it might be the wrong one, so that's not
    839                // OK.
    840                if (tm != NULL && profile_passed_limit(*tm)) {
    841                  match_ok = false;
    842                }
    843                break;
    844              }
    845              // vim_regexec_multi() may clear "regprog"
    846              if (regmatch.regprog == NULL) {
    847                break;
    848              }
    849              // Need to get the line pointer again, a
    850              // multi-line search may have made it invalid.
    851              ptr = ml_get_buf(buf, lnum + matchpos.lnum);
    852            }
    853 
    854            // If there is only a match after the cursor, skip
    855            // this match.
    856            if (!match_ok) {
    857              continue;
    858            }
    859          }
    860 
    861          // With the SEARCH_END option move to the last character
    862          // of the match.  Don't do it for an empty match, end
    863          // should be same as start then.
    864          if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
    865              && !(matchpos.lnum == endpos.lnum
    866                   && matchpos.col == endpos.col)) {
    867            // For a match in the first column, set the position
    868            // on the NUL in the previous line.
    869            pos->lnum = lnum + endpos.lnum;
    870            pos->col = endpos.col;
    871            if (endpos.col == 0) {
    872              if (pos->lnum > 1) {              // just in case
    873                pos->lnum--;
    874                pos->col = ml_get_buf_len(buf, pos->lnum);
    875              }
    876            } else {
    877              pos->col--;
    878              if (pos->lnum <= buf->b_ml.ml_line_count) {
    879                ptr = ml_get_buf(buf, pos->lnum);
    880                pos->col -= utf_head_off(ptr, ptr + pos->col);
    881              }
    882            }
    883            if (end_pos != NULL) {
    884              end_pos->lnum = lnum + matchpos.lnum;
    885              end_pos->col = matchpos.col;
    886            }
    887          } else {
    888            pos->lnum = lnum + matchpos.lnum;
    889            pos->col = matchpos.col;
    890            if (end_pos != NULL) {
    891              end_pos->lnum = lnum + endpos.lnum;
    892              end_pos->col = endpos.col;
    893            }
    894          }
    895          pos->coladd = 0;
    896          if (end_pos != NULL) {
    897            end_pos->coladd = 0;
    898          }
    899          found = 1;
    900          first_match = false;
    901 
    902          // Set variables used for 'incsearch' highlighting.
    903          search_match_lines = endpos.lnum - matchpos.lnum;
    904          search_match_endcol = endpos.col;
    905          break;
    906        }
    907        line_breakcheck();              // stop if ctrl-C typed
    908        if (got_int) {
    909          break;
    910        }
    911 
    912        // Cancel searching if a character was typed.  Used for
    913        // 'incsearch'.  Don't check too often, that would slowdown
    914        // searching too much.
    915        if ((options & SEARCH_PEEK)
    916            && ((lnum - pos->lnum) & 0x3f) == 0
    917            && char_avail()) {
    918          break_loop = true;
    919          break;
    920        }
    921 
    922        if (loop && lnum == start_pos.lnum) {
    923          break;                    // if second loop, stop where started
    924        }
    925      }
    926      at_first_line = false;
    927 
    928      // vim_regexec_multi() may clear "regprog"
    929      if (regmatch.regprog == NULL) {
    930        break;
    931      }
    932 
    933      // Stop the search if wrapscan isn't set, "stop_lnum" is
    934      // specified, after an interrupt, after a match and after looping
    935      // twice.
    936      if (!p_ws || stop_lnum != 0 || got_int
    937          || called_emsg > called_emsg_before
    938          || (timed_out != NULL && *timed_out)
    939          || break_loop
    940          || found || loop) {
    941        break;
    942      }
    943 
    944      // If 'wrapscan' is set we continue at the other end of the file.
    945      // If 'shortmess' does not contain 's', we give a message, but
    946      // only, if we won't show the search stat later anyhow,
    947      // (so SEARCH_COUNT must be absent).
    948      // This message is also remembered in keep_msg for when the screen
    949      // is redrawn. The keep_msg is cleared whenever another message is
    950      // written.
    951      lnum = dir == BACKWARD  // start second loop at the other end
    952             ? buf->b_ml.ml_line_count
    953             : 1;
    954      if (!shortmess(SHM_SEARCH)
    955          && shortmess(SHM_SEARCHCOUNT)
    956          && (options & SEARCH_MSG)) {
    957        give_warning(_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true, false);
    958      }
    959      if (extra_arg != NULL) {
    960        extra_arg->sa_wrapped = true;
    961      }
    962    }
    963    if (got_int || called_emsg > called_emsg_before
    964        || (timed_out != NULL && *timed_out)
    965        || break_loop) {
    966      break;
    967    }
    968  } while (--count > 0 && found);   // stop after count matches or no match
    969 
    970  vim_regfree(regmatch.regprog);
    971 
    972  if (!found) {             // did not find it
    973    if (got_int) {
    974      emsg(_(e_interr));
    975    } else if ((options & SEARCH_MSG) == SEARCH_MSG) {
    976      if (p_ws) {
    977        semsg(_(e_patnotf2), mr_pattern);
    978      } else if (lnum == 0) {
    979        semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
    980      } else {
    981        semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
    982      }
    983    }
    984    return FAIL;
    985  }
    986 
    987  // A pattern like "\n\zs" may go past the last line.
    988  if (pos->lnum > buf->b_ml.ml_line_count) {
    989    pos->lnum = buf->b_ml.ml_line_count;
    990    pos->col = ml_get_buf_len(buf, pos->lnum);
    991    if (pos->col > 0) {
    992      pos->col--;
    993    }
    994  }
    995 
    996  return submatch + 1;
    997 }
    998 
    999 void set_search_direction(int cdir)
   1000 {
   1001  spats[0].off.dir = (char)cdir;
   1002 }
   1003 
   1004 static void set_vv_searchforward(void)
   1005 {
   1006  set_vim_var_nr(VV_SEARCHFORWARD, spats[0].off.dir == '/');
   1007 }
   1008 
   1009 // Return the number of the first subpat that matched.
   1010 // Return zero if none of them matched.
   1011 static int first_submatch(regmmatch_T *rp)
   1012 {
   1013  int submatch;
   1014 
   1015  for (submatch = 1;; submatch++) {
   1016    if (rp->startpos[submatch].lnum >= 0) {
   1017      break;
   1018    }
   1019    if (submatch == 9) {
   1020      submatch = 0;
   1021      break;
   1022    }
   1023  }
   1024  return submatch;
   1025 }
   1026 
   1027 /// Highest level string search function.
   1028 /// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
   1029 ///
   1030 /// Careful: If spats[0].off.line == true and spats[0].off.off == 0 this
   1031 /// makes the movement linewise without moving the match position.
   1032 ///
   1033 /// @param dirc          if 0: use previous dir.
   1034 /// @param pat           NULL or empty : use previous string.
   1035 /// @param options       if true and
   1036 ///                      SEARCH_REV   == true : go in reverse of previous dir.
   1037 ///                      SEARCH_ECHO  == true : echo the search command and handle options
   1038 ///                      SEARCH_MSG   == true : may give error message
   1039 ///                      SEARCH_OPT   == true : interpret optional flags
   1040 ///                      SEARCH_HIS   == true : put search pattern in history
   1041 ///                      SEARCH_NOOF  == true : don't add offset to position
   1042 ///                      SEARCH_MARK  == true : set previous context mark
   1043 ///                      SEARCH_KEEP  == true : keep previous search pattern
   1044 ///                      SEARCH_START == true : accept match at curpos itself
   1045 ///                      SEARCH_PEEK  == true : check for typed char, cancel search
   1046 /// @param oap           can be NULL
   1047 /// @param dirc          '/' or '?'
   1048 /// @param search_delim  delimiter for search, e.g. '%' in s%regex%replacement
   1049 /// @param sia           optional arguments or NULL
   1050 ///
   1051 /// @return              0 for failure, 1 for found, 2 for found and line offset added.
   1052 int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen, int count,
   1053              int options, searchit_arg_T *sia)
   1054 {
   1055  char *searchstr;
   1056  size_t searchstrlen;
   1057  int retval;                   // Return value
   1058  char *p;
   1059  int64_t c;
   1060  char *dircp;
   1061  char *strcopy = NULL;
   1062  char *ps;
   1063  char *msgbuf = NULL;
   1064  size_t msgbuflen = 0;
   1065  bool has_offset = false;
   1066 
   1067  searchcmdlen = 0;
   1068 
   1069  // A line offset is not remembered, this is vi compatible.
   1070  if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) {
   1071    spats[0].off.line = false;
   1072    spats[0].off.off = 0;
   1073  }
   1074 
   1075  // Save the values for when (options & SEARCH_KEEP) is used.
   1076  // (there is no "if ()" around this because gcc wants them initialized)
   1077  SearchOffset old_off = spats[0].off;
   1078 
   1079  pos_T pos = curwin->w_cursor;  // Position of the last match.
   1080                                 // Start searching at the cursor position.
   1081 
   1082  // Find out the direction of the search.
   1083  if (dirc == 0) {
   1084    dirc = (uint8_t)spats[0].off.dir;
   1085  } else {
   1086    spats[0].off.dir = (char)dirc;
   1087    set_vv_searchforward();
   1088  }
   1089  if (options & SEARCH_REV) {
   1090    dirc = dirc == '/' ? '?' : '/';
   1091  }
   1092 
   1093  // If the cursor is in a closed fold, don't find another match in the same
   1094  // fold.
   1095  if (dirc == '/') {
   1096    if (hasFolding(curwin, pos.lnum, NULL, &pos.lnum)) {
   1097      pos.col = MAXCOL - 2;             // avoid overflow when adding 1
   1098    }
   1099  } else {
   1100    if (hasFolding(curwin, pos.lnum, &pos.lnum, NULL)) {
   1101      pos.col = 0;
   1102    }
   1103  }
   1104 
   1105  // Turn 'hlsearch' highlighting back on.
   1106  if (no_hlsearch && !(options & SEARCH_KEEP)) {
   1107    redraw_all_later(UPD_SOME_VALID);
   1108    set_no_hlsearch(false);
   1109  }
   1110 
   1111  // Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
   1112  while (true) {
   1113    bool show_top_bot_msg = false;
   1114 
   1115    searchstr = pat;
   1116    searchstrlen = patlen;
   1117 
   1118    dircp = NULL;
   1119    // use previous pattern
   1120    if (pat == NULL || *pat == NUL || *pat == search_delim) {
   1121      if (spats[RE_SEARCH].pat == NULL) {           // no previous pattern
   1122        if (spats[RE_SUBST].pat == NULL) {
   1123          emsg(_(e_noprevre));
   1124          retval = 0;
   1125          goto end_do_search;
   1126        }
   1127        searchstr = spats[RE_SUBST].pat;
   1128        searchstrlen = spats[RE_SUBST].patlen;
   1129      } else {
   1130        // make search_regcomp() use spats[RE_SEARCH].pat
   1131        searchstr = "";
   1132        searchstrlen = 0;
   1133      }
   1134    }
   1135 
   1136    if (pat != NULL && *pat != NUL) {   // look for (new) offset
   1137      // Find end of regular expression.
   1138      // If there is a matching '/' or '?', toss it.
   1139      ps = strcopy;
   1140      p = skip_regexp_ex(pat, search_delim, magic_isset(), &strcopy, NULL, NULL);
   1141      if (strcopy != ps) {
   1142        size_t len = strlen(strcopy);
   1143        // made a copy of "pat" to change "\?" to "?"
   1144        searchcmdlen += (int)(patlen - len);
   1145        pat = strcopy;
   1146        patlen = len;
   1147        searchstr = strcopy;
   1148        searchstrlen = len;
   1149      }
   1150      if (*p == search_delim) {
   1151        searchstrlen = (size_t)(p - pat);
   1152        dircp = p;              // remember where we put the NUL
   1153        *p++ = NUL;
   1154      }
   1155      spats[0].off.line = false;
   1156      spats[0].off.end = false;
   1157      spats[0].off.off = 0;
   1158      // Check for a line offset or a character offset.
   1159      // For get_address (echo off) we don't check for a character
   1160      // offset, because it is meaningless and the 's' could be a
   1161      // substitute command.
   1162      if (*p == '+' || *p == '-' || ascii_isdigit(*p)) {
   1163        spats[0].off.line = true;
   1164      } else if ((options & SEARCH_OPT)
   1165                 && (*p == 'e' || *p == 's' || *p == 'b')) {
   1166        if (*p == 'e') {  // end
   1167          spats[0].off.end = true;
   1168        }
   1169        p++;
   1170      }
   1171      if (ascii_isdigit(*p) || *p == '+' || *p == '-') {      // got an offset
   1172        // 'nr' or '+nr' or '-nr'
   1173        if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) {
   1174          spats[0].off.off = atol(p);
   1175        } else if (*p == '-') {                      // single '-'
   1176          spats[0].off.off = -1;
   1177        } else {  // single '+'
   1178          spats[0].off.off = 1;
   1179        }
   1180        p++;
   1181        while (ascii_isdigit(*p)) {  // skip number
   1182          p++;
   1183        }
   1184      }
   1185 
   1186      // compute length of search command for get_address()
   1187      searchcmdlen += (int)(p - pat);
   1188 
   1189      patlen -= (size_t)(p - pat);
   1190      pat = p;                              // put pat after search command
   1191    }
   1192 
   1193    bool show_search_stats = false;
   1194    if ((options & SEARCH_ECHO) && messaging() && !msg_silent
   1195        && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) {
   1196      char off_buf[40];
   1197      size_t off_len = 0;
   1198 
   1199      // Compute msg_row early.
   1200      msg_start();
   1201      msg_ext_set_kind("search_cmd");
   1202 
   1203      // Get the offset, so we know how long it is.
   1204      if (!cmd_silent
   1205          && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) {
   1206        off_buf[off_len++] = (char)dirc;
   1207        if (spats[0].off.end) {
   1208          off_buf[off_len++] = 'e';
   1209        } else if (!spats[0].off.line) {
   1210          off_buf[off_len++] = 's';
   1211        }
   1212        off_buf[off_len] = NUL;
   1213        if (spats[0].off.off != 0 || spats[0].off.line) {
   1214          off_len += (size_t)snprintf(off_buf + off_len, sizeof(off_buf) - off_len,
   1215                                      "%+" PRId64, spats[0].off.off);
   1216        }
   1217      }
   1218 
   1219      size_t plen;
   1220      if (*searchstr == NUL) {
   1221        p = spats[0].pat;
   1222        plen = spats[0].patlen;
   1223      } else {
   1224        p = searchstr;
   1225        plen = searchstrlen;
   1226      }
   1227 
   1228      size_t msgbufsize;
   1229      if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) {
   1230        // Reserve enough space for the search pattern + offset +
   1231        // search stat.  Use all the space available, so that the
   1232        // search state is right aligned.  If there is not enough space
   1233        // msg_strtrunc() will shorten in the middle.
   1234        if (ui_has(kUIMessages)) {
   1235          msgbufsize = 0;  // adjusted below
   1236        } else if (msg_scrolled != 0 && !cmd_silent) {
   1237          // Use all the columns.
   1238          msgbufsize = (size_t)((Rows - msg_row) * Columns - 1);
   1239        } else {
   1240          // Use up to 'showcmd' column.
   1241          msgbufsize = (size_t)((Rows - msg_row - 1) * Columns + sc_col - 1);
   1242        }
   1243        if (msgbufsize < plen + off_len + SEARCH_STAT_BUF_LEN + 3) {
   1244          msgbufsize = plen + off_len + SEARCH_STAT_BUF_LEN + 3;
   1245        }
   1246      } else {
   1247        // Reserve enough space for the search pattern + offset.
   1248        msgbufsize = plen + off_len + 3;
   1249      }
   1250 
   1251      xfree(msgbuf);
   1252      msgbuf = xmalloc(msgbufsize);
   1253      memset(msgbuf, ' ', msgbufsize);
   1254      msgbuflen = msgbufsize - 1;
   1255      msgbuf[msgbuflen] = NUL;
   1256 
   1257      // do not fill the msgbuf buffer, if cmd_silent is set, leave it
   1258      // empty for the search_stat feature.
   1259      if (!cmd_silent) {
   1260        ui_busy_start();
   1261        msgbuf[0] = (char)dirc;
   1262        if (utf_iscomposing_first(utf_ptr2char(p))) {
   1263          // Use a space to draw the composing char on.
   1264          msgbuf[1] = ' ';
   1265          memmove(msgbuf + 2, p, plen);
   1266        } else {
   1267          memmove(msgbuf + 1, p, plen);
   1268        }
   1269        if (off_len > 0) {
   1270          memmove(msgbuf + plen + 1, off_buf, off_len);
   1271        }
   1272 
   1273        char *trunc = msg_strtrunc(msgbuf, true);
   1274        if (trunc != NULL) {
   1275          xfree(msgbuf);
   1276          msgbuf = trunc;
   1277          msgbuflen = strlen(msgbuf);
   1278        }
   1279 
   1280        // The search pattern could be shown on the right in rightleft
   1281        // mode, but the 'ruler' and 'showcmd' area use it too, thus
   1282        // it would be blanked out again very soon.  Show it on the
   1283        // left, but do reverse the text.
   1284        if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
   1285          char *r = reverse_text(msgbuf);
   1286          xfree(msgbuf);
   1287          msgbuf = r;
   1288          msgbuflen = strlen(msgbuf);
   1289          // move reversed text to beginning of buffer
   1290          while (*r == ' ') {
   1291            r++;
   1292          }
   1293          size_t pat_len = (size_t)(msgbuf + msgbuflen - r);
   1294          memmove(msgbuf, r, pat_len);
   1295          // overwrite old text
   1296          if ((size_t)(r - msgbuf) >= pat_len) {
   1297            memset(r, ' ', pat_len);
   1298          } else {
   1299            memset(msgbuf + pat_len, ' ', (size_t)(r - msgbuf));
   1300          }
   1301        }
   1302        msg_outtrans(msgbuf, 0, false);
   1303        msg_clr_eos();
   1304        msg_check();
   1305 
   1306        gotocmdline(false);
   1307        ui_flush();
   1308        ui_busy_stop();
   1309        msg_nowait = true;  // don't wait for this message
   1310      }
   1311 
   1312      if (!shortmess(SHM_SEARCHCOUNT)) {
   1313        show_search_stats = true;
   1314      }
   1315    }
   1316 
   1317    // If there is a character offset, subtract it from the current
   1318    // position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
   1319    // Skip this if pos.col is near MAXCOL (closed fold).
   1320    // This is not done for a line offset, because then we would not be vi
   1321    // compatible.
   1322    if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) {
   1323      if (spats[0].off.off > 0) {
   1324        for (c = spats[0].off.off; c; c--) {
   1325          if (decl(&pos) == -1) {
   1326            break;
   1327          }
   1328        }
   1329        if (c) {                        // at start of buffer
   1330          pos.lnum = 0;                 // allow lnum == 0 here
   1331          pos.col = MAXCOL;
   1332        }
   1333      } else {
   1334        for (c = spats[0].off.off; c; c++) {
   1335          if (incl(&pos) == -1) {
   1336            break;
   1337          }
   1338        }
   1339        if (c) {                        // at end of buffer
   1340          pos.lnum = curbuf->b_ml.ml_line_count + 1;
   1341          pos.col = 0;
   1342        }
   1343      }
   1344    }
   1345 
   1346    c = searchit(curwin, curbuf, &pos, NULL, dirc == '/' ? FORWARD : BACKWARD,
   1347                 searchstr, searchstrlen, count,
   1348                 (spats[0].off.end * SEARCH_END
   1349                  + (options
   1350                     & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG
   1351                        + SEARCH_START
   1352                        + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))),
   1353                 RE_LAST, sia);
   1354 
   1355    if (dircp != NULL) {
   1356      *dircp = (char)search_delim;  // restore second '/' or '?' for normal_cmd()
   1357    }
   1358 
   1359    if (!shortmess(SHM_SEARCH) && sia && sia->sa_wrapped) {
   1360      show_top_bot_msg = true;
   1361    }
   1362 
   1363    if (c == FAIL) {
   1364      retval = 0;
   1365      goto end_do_search;
   1366    }
   1367    if (spats[0].off.end && oap != NULL) {
   1368      oap->inclusive = true;        // 'e' includes last character
   1369    }
   1370    retval = 1;                     // pattern found
   1371 
   1372    if (sia && sia->sa_wrapped) {
   1373      apply_autocmds(EVENT_SEARCHWRAPPED, NULL, NULL, false, NULL);
   1374    }
   1375 
   1376    // Add character and/or line offset
   1377    if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';')) {
   1378      pos_T org_pos = pos;
   1379 
   1380      if (spats[0].off.line) {  // Add the offset to the line number.
   1381        c = pos.lnum + spats[0].off.off;
   1382        if (c < 1) {
   1383          pos.lnum = 1;
   1384        } else if (c > curbuf->b_ml.ml_line_count) {
   1385          pos.lnum = curbuf->b_ml.ml_line_count;
   1386        } else {
   1387          pos.lnum = (linenr_T)c;
   1388        }
   1389        pos.col = 0;
   1390 
   1391        retval = 2;                 // pattern found, line offset added
   1392      } else if (pos.col < MAXCOL - 2) {      // just in case
   1393        // to the right, check for end of file
   1394        c = spats[0].off.off;
   1395        if (c > 0) {
   1396          while (c-- > 0) {
   1397            if (incl(&pos) == -1) {
   1398              break;
   1399            }
   1400          }
   1401        } else {  // to the left, check for start of file
   1402          while (c++ < 0) {
   1403            if (decl(&pos) == -1) {
   1404              break;
   1405            }
   1406          }
   1407        }
   1408      }
   1409      if (!equalpos(pos, org_pos)) {
   1410        has_offset = true;
   1411      }
   1412    }
   1413 
   1414    // Show [1/15] if 'S' is not in 'shortmess'.
   1415    if (show_search_stats) {
   1416      cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
   1417                          show_top_bot_msg, msgbuf, msgbuflen,
   1418                          (count != 1 || has_offset
   1419                           || (!(fdo_flags & kOptFdoFlagSearch)
   1420                               && hasFolding(curwin, curwin->w_cursor.lnum, NULL,
   1421                                             NULL))),
   1422                          (int)p_msc,
   1423                          SEARCH_STAT_DEF_TIMEOUT);
   1424    }
   1425 
   1426    // The search command can be followed by a ';' to do another search.
   1427    // For example: "/pat/;/foo/+3;?bar"
   1428    // This is like doing another search command, except:
   1429    // - The remembered direction '/' or '?' is from the first search.
   1430    // - When an error happens the cursor isn't moved at all.
   1431    // Don't do this when called by get_address() (it handles ';' itself).
   1432    if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';') {
   1433      break;
   1434    }
   1435 
   1436    dirc = (uint8_t)(*++pat);
   1437    search_delim = dirc;
   1438    if (dirc != '?' && dirc != '/') {
   1439      retval = 0;
   1440      emsg(_("E386: Expected '?' or '/'  after ';'"));
   1441      goto end_do_search;
   1442    }
   1443    pat++;
   1444    patlen--;
   1445  }
   1446 
   1447  if (options & SEARCH_MARK) {
   1448    setpcmark();
   1449  }
   1450  curwin->w_cursor = pos;
   1451  curwin->w_set_curswant = true;
   1452 
   1453 end_do_search:
   1454  if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS)) {
   1455    spats[0].off = old_off;
   1456  }
   1457  xfree(strcopy);
   1458  xfree(msgbuf);
   1459 
   1460  return retval;
   1461 }
   1462 
   1463 // search_for_exact_line(buf, pos, dir, pat)
   1464 //
   1465 // Search for a line starting with the given pattern (ignoring leading
   1466 // white-space), starting from pos and going in direction "dir". "pos" will
   1467 // contain the position of the match found.    Blank lines match only if
   1468 // ADDING is set.  If p_ic is set then the pattern must be in lowercase.
   1469 // Return OK for success, or FAIL if no line found.
   1470 int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
   1471 {
   1472  linenr_T start = 0;
   1473 
   1474  if (buf->b_ml.ml_line_count == 0) {
   1475    return FAIL;
   1476  }
   1477  while (true) {
   1478    pos->lnum += dir;
   1479    if (pos->lnum < 1) {
   1480      if (p_ws) {
   1481        pos->lnum = buf->b_ml.ml_line_count;
   1482        if (!shortmess(SHM_SEARCH)) {
   1483          give_warning(_(top_bot_msg), true, false);
   1484        }
   1485      } else {
   1486        pos->lnum = 1;
   1487        break;
   1488      }
   1489    } else if (pos->lnum > buf->b_ml.ml_line_count) {
   1490      if (p_ws) {
   1491        pos->lnum = 1;
   1492        if (!shortmess(SHM_SEARCH)) {
   1493          give_warning(_(bot_top_msg), true, false);
   1494        }
   1495      } else {
   1496        pos->lnum = 1;
   1497        break;
   1498      }
   1499    }
   1500    if (pos->lnum == start) {
   1501      break;
   1502    }
   1503    if (start == 0) {
   1504      start = pos->lnum;
   1505    }
   1506    char *ptr = ml_get_buf(buf, pos->lnum);
   1507    char *p = skipwhite(ptr);
   1508    pos->col = (colnr_T)(p - ptr);
   1509 
   1510    // when adding lines the matching line may be empty but it is not
   1511    // ignored because we are interested in the next line -- Acevedo
   1512    if (compl_status_adding() && !compl_status_sol()) {
   1513      if (mb_strcmp_ic((bool)p_ic, p, pat) == 0) {
   1514        return OK;
   1515      }
   1516    } else if (*p != NUL) {  // Ignore empty lines.
   1517      // Expanding lines or words.
   1518      assert(ins_compl_len() >= 0);
   1519      if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len())
   1520                : strncmp(p, pat, (size_t)ins_compl_len())) == 0) {
   1521        return OK;
   1522      }
   1523    }
   1524  }
   1525  return FAIL;
   1526 }
   1527 
   1528 // Character Searches
   1529 
   1530 /// Search for a character in a line.  If "t_cmd" is false, move to the
   1531 /// position of the character, otherwise move to just before the char.
   1532 /// Do this "cap->count1" times.
   1533 /// Return FAIL or OK.
   1534 int searchc(cmdarg_T *cap, bool t_cmd)
   1535  FUNC_ATTR_NONNULL_ALL
   1536 {
   1537  int c = cap->nchar;                   // char to search for
   1538  int dir = cap->arg;                   // true for searching forward
   1539  int count = cap->count1;              // repeat count
   1540  bool stop = true;
   1541 
   1542  if (c != NUL) {       // normal search: remember args for repeat
   1543    if (!KeyStuffed) {      // don't remember when redoing
   1544      *lastc = (uint8_t)c;
   1545      set_csearch_direction(dir);
   1546      set_csearch_until(t_cmd);
   1547      if (cap->nchar_len) {
   1548        lastc_bytelen = cap->nchar_len;
   1549        memcpy(lastc_bytes, cap->nchar_composing, (size_t)cap->nchar_len);
   1550      } else {
   1551        lastc_bytelen = utf_char2bytes(c, lastc_bytes);
   1552      }
   1553    }
   1554  } else {            // repeat previous search
   1555    if (*lastc == NUL && lastc_bytelen <= 1) {
   1556      return FAIL;
   1557    }
   1558    dir = dir  // repeat in opposite direction
   1559          ? -lastcdir
   1560          : lastcdir;
   1561    t_cmd = last_t_cmd;
   1562    c = *lastc;
   1563    // For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
   1564 
   1565    // Force a move of at least one char, so ";" and "," will move the
   1566    // cursor, even if the cursor is right in front of char we are looking
   1567    // at.
   1568    if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd) {
   1569      stop = false;
   1570    }
   1571  }
   1572 
   1573  cap->oap->inclusive = dir != BACKWARD;
   1574 
   1575  char *p = get_cursor_line_ptr();
   1576  int col = curwin->w_cursor.col;
   1577  int len = get_cursor_line_len();
   1578 
   1579  while (count--) {
   1580    while (true) {
   1581      if (dir > 0) {
   1582        col += utfc_ptr2len(p + col);
   1583        if (col >= len) {
   1584          return FAIL;
   1585        }
   1586      } else {
   1587        if (col == 0) {
   1588          return FAIL;
   1589        }
   1590        col -= utf_head_off(p, p + col - 1) + 1;
   1591      }
   1592      if (lastc_bytelen <= 1) {
   1593        if (p[col] == c && stop) {
   1594          break;
   1595        }
   1596      } else if (strncmp(p + col, lastc_bytes, (size_t)lastc_bytelen) == 0 && stop) {
   1597        break;
   1598      }
   1599      stop = true;
   1600    }
   1601  }
   1602 
   1603  if (t_cmd) {
   1604    // Backup to before the character (possibly double-byte).
   1605    col -= dir;
   1606    if (dir < 0) {
   1607      // Landed on the search char which is lastc_bytelen long.
   1608      col += lastc_bytelen - 1;
   1609    } else {
   1610      // To previous char, which may be multi-byte.
   1611      col -= utf_head_off(p, p + col);
   1612    }
   1613  }
   1614  curwin->w_cursor.col = col;
   1615 
   1616  return OK;
   1617 }
   1618 
   1619 // "Other" Searches
   1620 
   1621 // findmatch - find the matching paren or brace
   1622 //
   1623 // Improvement over vi: Braces inside quotes are ignored.
   1624 pos_T *findmatch(oparg_T *oap, int initc)
   1625 {
   1626  return findmatchlimit(oap, initc, 0, 0);
   1627 }
   1628 
   1629 // Return true if the character before "linep[col]" equals "ch".
   1630 // Return false if "col" is zero.
   1631 // Update "*prevcol" to the column of the previous character, unless "prevcol"
   1632 // is NULL.
   1633 // Handles multibyte string correctly.
   1634 static bool check_prevcol(char *linep, int col, int ch, int *prevcol)
   1635 {
   1636  col--;
   1637  if (col > 0) {
   1638    col -= utf_head_off(linep, linep + col);
   1639  }
   1640  if (prevcol) {
   1641    *prevcol = col;
   1642  }
   1643  return col >= 0 && (uint8_t)linep[col] == ch;
   1644 }
   1645 
   1646 /// Raw string start is found at linep[startpos.col - 1].
   1647 ///
   1648 /// @return  true if the matching end can be found between startpos and endpos.
   1649 static bool find_rawstring_end(char *linep, pos_T *startpos, pos_T *endpos)
   1650 {
   1651  char *p;
   1652  linenr_T lnum;
   1653 
   1654  for (p = linep + startpos->col + 1; *p && *p != '('; p++) {}
   1655 
   1656  size_t delim_len = (size_t)((p - linep) - startpos->col - 1);
   1657  char *delim_copy = xmemdupz(linep + startpos->col + 1, delim_len);
   1658  bool found = false;
   1659  for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) {
   1660    char *line = ml_get(lnum);
   1661 
   1662    for (p = line + (lnum == startpos->lnum ? startpos->col + 1 : 0); *p; p++) {
   1663      if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) {
   1664        break;
   1665      }
   1666      if (*p == ')'
   1667          && strncmp(delim_copy, p + 1, delim_len) == 0
   1668          && p[delim_len + 1] == '"') {
   1669        found = true;
   1670        break;
   1671      }
   1672    }
   1673    if (found) {
   1674      break;
   1675    }
   1676  }
   1677  xfree(delim_copy);
   1678  return found;
   1679 }
   1680 
   1681 /// Check matchpairs option for "*initc".
   1682 /// If there is a match set "*initc" to the matching character and "*findc" to
   1683 /// the opposite character.  Set "*backwards" to the direction.
   1684 /// When "switchit" is true swap the direction.
   1685 static void find_mps_values(int *initc, int *findc, bool *backwards, bool switchit)
   1686  FUNC_ATTR_NONNULL_ALL
   1687 {
   1688  char *ptr = curbuf->b_p_mps;
   1689 
   1690  while (*ptr != NUL) {
   1691    if (utf_ptr2char(ptr) == *initc) {
   1692      if (switchit) {
   1693        *findc = *initc;
   1694        *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1);
   1695        *backwards = true;
   1696      } else {
   1697        *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1);
   1698        *backwards = false;
   1699      }
   1700      return;
   1701    }
   1702    char *prev = ptr;
   1703    ptr += utfc_ptr2len(ptr) + 1;
   1704    if (utf_ptr2char(ptr) == *initc) {
   1705      if (switchit) {
   1706        *findc = *initc;
   1707        *initc = utf_ptr2char(prev);
   1708        *backwards = false;
   1709      } else {
   1710        *findc = utf_ptr2char(prev);
   1711        *backwards = true;
   1712      }
   1713      return;
   1714    }
   1715    ptr += utfc_ptr2len(ptr);
   1716    if (*ptr == ',') {
   1717      ptr++;
   1718    }
   1719  }
   1720 }
   1721 
   1722 // findmatchlimit -- find the matching paren or brace, if it exists within
   1723 // maxtravel lines of the cursor.  A maxtravel of 0 means search until falling
   1724 // off the edge of the file.
   1725 //
   1726 // "initc" is the character to find a match for.  NUL means to find the
   1727 // character at or after the cursor. Special values:
   1728 // '*'  look for C-style comment / *
   1729 // '/'  look for C-style comment / *, ignoring comment-end
   1730 // '#'  look for preprocessor directives
   1731 // 'R'  look for raw string start: R"delim(text)delim" (only backwards)
   1732 //
   1733 // flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
   1734 //    FM_FORWARD  search forwards (when initc is '/', '*' or '#')
   1735 //    FM_BLOCKSTOP  stop at start/end of block ({ or } in column 0)
   1736 //    FM_SKIPCOMM skip comments (not implemented yet!)
   1737 //
   1738 // "oap" is only used to set oap->motion_type for a linewise motion, it can be
   1739 // NULL
   1740 pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
   1741 {
   1742  static pos_T pos;                     // current search position
   1743  int findc = 0;                        // matching brace
   1744  int count = 0;                        // cumulative number of braces
   1745  bool backwards = false;               // init for gcc
   1746  bool raw_string = false;              // search for raw string
   1747  bool inquote = false;                 // true when inside quotes
   1748  char *ptr;
   1749  int hash_dir = 0;                     // Direction searched for # things
   1750  int comment_dir = 0;                  // Direction searched for comments
   1751  int traveled = 0;                     // how far we've searched so far
   1752  bool ignore_cend = false;             // ignore comment end
   1753  int match_escaped = 0;                // search for escaped match
   1754  int dir;                              // Direction to search
   1755  int comment_col = MAXCOL;             // start of / / comment
   1756  bool lispcomm = false;                // inside of Lisp-style comment
   1757  bool lisp = curbuf->b_p_lisp;         // engage Lisp-specific hacks ;)
   1758 
   1759  pos = curwin->w_cursor;
   1760  pos.coladd = 0;
   1761  char *linep = ml_get(pos.lnum);     // pointer to current line
   1762 
   1763  // vi compatible matching
   1764  bool cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
   1765  // don't recognize backslashes
   1766  bool cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
   1767 
   1768  // Direction to search when initc is '/', '*' or '#'
   1769  if (flags & FM_BACKWARD) {
   1770    dir = BACKWARD;
   1771  } else if (flags & FM_FORWARD) {
   1772    dir = FORWARD;
   1773  } else {
   1774    dir = 0;
   1775  }
   1776 
   1777  // if initc given, look in the table for the matching character
   1778  // '/' and '*' are special cases: look for start or end of comment.
   1779  // When '/' is used, we ignore running backwards into a star-slash, for
   1780  // "[*" command, we just want to find any comment.
   1781  if (initc == '/' || initc == '*' || initc == 'R') {
   1782    comment_dir = dir;
   1783    if (initc == '/') {
   1784      ignore_cend = true;
   1785    }
   1786    backwards = (dir == FORWARD) ? false : true;
   1787    raw_string = (initc == 'R');
   1788    initc = NUL;
   1789  } else if (initc != '#' && initc != NUL) {
   1790    find_mps_values(&initc, &findc, &backwards, true);
   1791    if (dir) {
   1792      backwards = (dir == FORWARD) ? false : true;
   1793    }
   1794    if (findc == NUL) {
   1795      return NULL;
   1796    }
   1797  } else {
   1798    // Either initc is '#', or no initc was given and we need to look
   1799    // under the cursor.
   1800    if (initc == '#') {
   1801      hash_dir = dir;
   1802    } else {
   1803      // initc was not given, must look for something to match under
   1804      // or near the cursor.
   1805      // Only check for special things when 'cpo' doesn't have '%'.
   1806      if (!cpo_match) {
   1807        // Are we before or at #if, #else etc.?
   1808        ptr = skipwhite(linep);
   1809        if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep)) {
   1810          ptr = skipwhite(ptr + 1);
   1811          if (strncmp(ptr, "if", 2) == 0
   1812              || strncmp(ptr, "endif", 5) == 0
   1813              || strncmp(ptr, "el", 2) == 0) {
   1814            hash_dir = 1;
   1815          }
   1816        } else if (linep[pos.col] == '/') {  // Are we on a comment?
   1817          if (linep[pos.col + 1] == '*') {
   1818            comment_dir = FORWARD;
   1819            backwards = false;
   1820            pos.col++;
   1821          } else if (pos.col > 0 && linep[pos.col - 1] == '*') {
   1822            comment_dir = BACKWARD;
   1823            backwards = true;
   1824            pos.col--;
   1825          }
   1826        } else if (linep[pos.col] == '*') {
   1827          if (linep[pos.col + 1] == '/') {
   1828            comment_dir = BACKWARD;
   1829            backwards = true;
   1830          } else if (pos.col > 0 && linep[pos.col - 1] == '/') {
   1831            comment_dir = FORWARD;
   1832            backwards = false;
   1833          }
   1834        }
   1835      }
   1836 
   1837      // If we are not on a comment or the # at the start of a line, then
   1838      // look for brace anywhere on this line after the cursor.
   1839      if (!hash_dir && !comment_dir) {
   1840        // Find the brace under or after the cursor.
   1841        // If beyond the end of the line, use the last character in
   1842        // the line.
   1843        if (linep[pos.col] == NUL && pos.col) {
   1844          pos.col--;
   1845        }
   1846        while (true) {
   1847          initc = utf_ptr2char(linep + pos.col);
   1848          if (initc == NUL) {
   1849            break;
   1850          }
   1851 
   1852          find_mps_values(&initc, &findc, &backwards, false);
   1853          if (findc) {
   1854            break;
   1855          }
   1856          pos.col += utfc_ptr2len(linep + pos.col);
   1857        }
   1858        if (!findc) {
   1859          // no brace in the line, maybe use "  #if" then
   1860          if (!cpo_match && *skipwhite(linep) == '#') {
   1861            hash_dir = 1;
   1862          } else {
   1863            return NULL;
   1864          }
   1865        } else if (!cpo_bsl) {
   1866          int bslcnt = 0;
   1867 
   1868          // Set "match_escaped" if there are an odd number of
   1869          // backslashes.
   1870          for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
   1871            bslcnt++;
   1872          }
   1873          match_escaped = (bslcnt & 1);
   1874        }
   1875      }
   1876    }
   1877    if (hash_dir) {
   1878      // Look for matching #if, #else, #elif, or #endif
   1879      if (oap != NULL) {
   1880        oap->motion_type = kMTLineWise;  // Linewise for this case only
   1881      }
   1882      if (initc != '#') {
   1883        ptr = skipwhite(skipwhite(linep) + 1);
   1884        if (strncmp(ptr, "if", 2) == 0 || strncmp(ptr, "el", 2) == 0) {
   1885          hash_dir = 1;
   1886        } else if (strncmp(ptr, "endif", 5) == 0) {
   1887          hash_dir = -1;
   1888        } else {
   1889          return NULL;
   1890        }
   1891      }
   1892      pos.col = 0;
   1893      while (!got_int) {
   1894        if (hash_dir > 0) {
   1895          if (pos.lnum == curbuf->b_ml.ml_line_count) {
   1896            break;
   1897          }
   1898        } else if (pos.lnum == 1) {
   1899          break;
   1900        }
   1901        pos.lnum += hash_dir;
   1902        linep = ml_get(pos.lnum);
   1903        line_breakcheck();              // check for CTRL-C typed
   1904        ptr = skipwhite(linep);
   1905        if (*ptr != '#') {
   1906          continue;
   1907        }
   1908        pos.col = (colnr_T)(ptr - linep);
   1909        ptr = skipwhite(ptr + 1);
   1910        if (hash_dir > 0) {
   1911          if (strncmp(ptr, "if", 2) == 0) {
   1912            count++;
   1913          } else if (strncmp(ptr, "el", 2) == 0) {
   1914            if (count == 0) {
   1915              return &pos;
   1916            }
   1917          } else if (strncmp(ptr, "endif", 5) == 0) {
   1918            if (count == 0) {
   1919              return &pos;
   1920            }
   1921            count--;
   1922          }
   1923        } else {
   1924          if (strncmp(ptr, "if", 2) == 0) {
   1925            if (count == 0) {
   1926              return &pos;
   1927            }
   1928            count--;
   1929          } else if (initc == '#' && strncmp(ptr, "el", 2) == 0) {
   1930            if (count == 0) {
   1931              return &pos;
   1932            }
   1933          } else if (strncmp(ptr, "endif", 5) == 0) {
   1934            count++;
   1935          }
   1936        }
   1937      }
   1938      return NULL;
   1939    }
   1940  }
   1941 
   1942  // This is just guessing: when 'rightleft' is set, search for a matching
   1943  // paren/brace in the other direction.
   1944  if (curwin->w_p_rl && vim_strchr("()[]{}<>", initc) != NULL) {
   1945    backwards = !backwards;
   1946  }
   1947 
   1948  int do_quotes = -1;                 // check for quotes in current line
   1949  int at_start;                       // do_quotes value at start position
   1950  TriState start_in_quotes = kNone;   // start position is in quotes
   1951  pos_T match_pos;                    // Where last slash-star was found
   1952  clearpos(&match_pos);
   1953 
   1954  // backward search: Check if this line contains a single-line comment
   1955  if ((backwards && comment_dir) || lisp) {
   1956    comment_col = check_linecomment(linep);
   1957  }
   1958  if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col) {
   1959    lispcomm = true;        // find match inside this comment
   1960  }
   1961 
   1962  while (!got_int) {
   1963    // Go to the next position, forward or backward. We could use
   1964    // inc() and dec() here, but that is much slower
   1965    if (backwards) {
   1966      // char to match is inside of comment, don't search outside
   1967      if (lispcomm && pos.col < (colnr_T)comment_col) {
   1968        break;
   1969      }
   1970      if (pos.col == 0) {               // at start of line, go to prev. one
   1971        if (pos.lnum == 1) {            // start of file
   1972          break;
   1973        }
   1974        pos.lnum--;
   1975 
   1976        if (maxtravel > 0 && ++traveled > maxtravel) {
   1977          break;
   1978        }
   1979 
   1980        linep = ml_get(pos.lnum);
   1981        pos.col = ml_get_len(pos.lnum);  // pos.col on trailing NUL
   1982        do_quotes = -1;
   1983        line_breakcheck();
   1984 
   1985        // Check if this line contains a single-line comment
   1986        if (comment_dir || lisp) {
   1987          comment_col = check_linecomment(linep);
   1988        }
   1989        // skip comment
   1990        if (lisp && comment_col != MAXCOL) {
   1991          pos.col = comment_col;
   1992        }
   1993      } else {
   1994        pos.col--;
   1995        pos.col -= utf_head_off(linep, linep + pos.col);
   1996      }
   1997    } else {                          // forward search
   1998      if (linep[pos.col] == NUL
   1999          // at end of line, go to next one
   2000          // For lisp don't search for match in comment
   2001          || (lisp && comment_col != MAXCOL
   2002              && pos.col == (colnr_T)comment_col)) {
   2003        if (pos.lnum == curbuf->b_ml.ml_line_count          // end of file
   2004            // line is exhausted and comment with it,
   2005            // don't search for match in code
   2006            || lispcomm) {
   2007          break;
   2008        }
   2009        pos.lnum++;
   2010 
   2011        if (maxtravel && traveled++ > maxtravel) {
   2012          break;
   2013        }
   2014 
   2015        linep = ml_get(pos.lnum);
   2016        pos.col = 0;
   2017        do_quotes = -1;
   2018        line_breakcheck();
   2019        if (lisp) {         // find comment pos in new line
   2020          comment_col = check_linecomment(linep);
   2021        }
   2022      } else {
   2023        pos.col += utfc_ptr2len(linep + pos.col);
   2024      }
   2025    }
   2026 
   2027    // If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
   2028    if (pos.col == 0 && (flags & FM_BLOCKSTOP)
   2029        && (linep[0] == '{' || linep[0] == '}')) {
   2030      if (linep[0] == findc && count == 0) {  // match!
   2031        return &pos;
   2032      }
   2033      break;  // out of scope
   2034    }
   2035 
   2036    if (comment_dir) {
   2037      // Note: comments do not nest, and we ignore quotes in them
   2038      // TODO(vim): ignore comment brackets inside strings
   2039      if (comment_dir == FORWARD) {
   2040        if (linep[pos.col] == '*' && linep[pos.col + 1] == '/') {
   2041          pos.col++;
   2042          return &pos;
   2043        }
   2044      } else {    // Searching backwards
   2045        // A comment may contain / * or / /, it may also start or end
   2046        // with / * /. Ignore a / * after / / and after *.
   2047        if (pos.col == 0) {
   2048          continue;
   2049        } else if (raw_string) {
   2050          if (linep[pos.col - 1] == 'R'
   2051              && linep[pos.col] == '"'
   2052              && vim_strchr(linep + pos.col + 1, '(') != NULL) {
   2053            // Possible start of raw string. Now that we have the
   2054            // delimiter we can check if it ends before where we
   2055            // started searching, or before the previously found
   2056            // raw string start.
   2057            if (!find_rawstring_end(linep, &pos,
   2058                                    count > 0 ? &match_pos : &curwin->w_cursor)) {
   2059              count++;
   2060              match_pos = pos;
   2061              match_pos.col--;
   2062            }
   2063            linep = ml_get(pos.lnum);  // may have been released
   2064          }
   2065        } else if (linep[pos.col - 1] == '/'
   2066                   && linep[pos.col] == '*'
   2067                   && (pos.col == 1 || linep[pos.col - 2] != '*')
   2068                   && (int)pos.col < comment_col) {
   2069          count++;
   2070          match_pos = pos;
   2071          match_pos.col--;
   2072        } else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/') {
   2073          if (count > 0) {
   2074            pos = match_pos;
   2075          } else if (pos.col > 1 && linep[pos.col - 2] == '/'
   2076                     && (int)pos.col <= comment_col) {
   2077            pos.col -= 2;
   2078          } else if (ignore_cend) {
   2079            continue;
   2080          } else {
   2081            return NULL;
   2082          }
   2083          return &pos;
   2084        }
   2085      }
   2086      continue;
   2087    }
   2088 
   2089    // If smart matching ('cpoptions' does not contain '%'), braces inside
   2090    // of quotes are ignored, but only if there is an even number of
   2091    // quotes in the line.
   2092    if (cpo_match) {
   2093      do_quotes = 0;
   2094    } else if (do_quotes == -1) {
   2095      // Count the number of quotes in the line, skipping \" and '"'.
   2096      // Watch out for "\\".
   2097      at_start = do_quotes;
   2098      for (ptr = linep; *ptr; ptr++) {
   2099        if (ptr == linep + pos.col + backwards) {
   2100          at_start = (do_quotes & 1);
   2101        }
   2102        if (*ptr == '"'
   2103            && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\'')) {
   2104          do_quotes++;
   2105        }
   2106        if (*ptr == '\\' && ptr[1] != NUL) {
   2107          ptr++;
   2108        }
   2109      }
   2110      do_quotes &= 1;               // result is 1 with even number of quotes
   2111 
   2112      // If we find an uneven count, check current line and previous
   2113      // one for a '\' at the end.
   2114      if (!do_quotes) {
   2115        inquote = false;
   2116        if (ptr[-1] == '\\') {
   2117          do_quotes = 1;
   2118          if (start_in_quotes == kNone) {
   2119            // Do we need to use at_start here?
   2120            inquote = true;
   2121            start_in_quotes = kTrue;
   2122          } else if (backwards) {
   2123            inquote = true;
   2124          }
   2125        }
   2126        if (pos.lnum > 1) {
   2127          ptr = ml_get(pos.lnum - 1);
   2128          if (*ptr && *(ptr + ml_get_len(pos.lnum - 1) - 1) == '\\') {
   2129            do_quotes = 1;
   2130            if (start_in_quotes == kNone) {
   2131              inquote = at_start;
   2132              if (inquote) {
   2133                start_in_quotes = kTrue;
   2134              }
   2135            } else if (!backwards) {
   2136              inquote = true;
   2137            }
   2138          }
   2139 
   2140          // ml_get() only keeps one line, need to get linep again
   2141          linep = ml_get(pos.lnum);
   2142        }
   2143      }
   2144    }
   2145    if (start_in_quotes == kNone) {
   2146      start_in_quotes = kFalse;
   2147    }
   2148 
   2149    // If 'smartmatch' is set:
   2150    //   Things inside quotes are ignored by setting 'inquote'.  If we
   2151    //   find a quote without a preceding '\' invert 'inquote'.  At the
   2152    //   end of a line not ending in '\' we reset 'inquote'.
   2153    //
   2154    //   In lines with an uneven number of quotes (without preceding '\')
   2155    //   we do not know which part to ignore. Therefore we only set
   2156    //   inquote if the number of quotes in a line is even, unless this
   2157    //   line or the previous one ends in a '\'.  Complicated, isn't it?
   2158    const int c = utf_ptr2char(linep + pos.col);
   2159    switch (c) {
   2160    case NUL:
   2161      // at end of line without trailing backslash, reset inquote
   2162      if (pos.col == 0 || linep[pos.col - 1] != '\\') {
   2163        inquote = false;
   2164        start_in_quotes = kFalse;
   2165      }
   2166      break;
   2167 
   2168    case '"':
   2169      // a quote that is preceded with an odd number of backslashes is
   2170      // ignored
   2171      if (do_quotes) {
   2172        int col;
   2173 
   2174        for (col = pos.col - 1; col >= 0; col--) {
   2175          if (linep[col] != '\\') {
   2176            break;
   2177          }
   2178        }
   2179        if ((((int)pos.col - 1 - col) & 1) == 0) {
   2180          inquote = !inquote;
   2181          start_in_quotes = kFalse;
   2182        }
   2183      }
   2184      break;
   2185 
   2186    // If smart matching ('cpoptions' does not contain '%'):
   2187    //   Skip things in single quotes: 'x' or '\x'.  Be careful for single
   2188    //   single quotes, eg jon's.  Things like '\233' or '\x3f' are not
   2189    //   skipped, there is never a brace in them.
   2190    //   Ignore this when finding matches for `'.
   2191    case '\'':
   2192      if (!cpo_match && initc != '\'' && findc != '\'') {
   2193        if (backwards) {
   2194          if (pos.col > 1) {
   2195            if (linep[pos.col - 2] == '\'') {
   2196              pos.col -= 2;
   2197              break;
   2198            } else if (linep[pos.col - 2] == '\\'
   2199                       && pos.col > 2 && linep[pos.col - 3] == '\'') {
   2200              pos.col -= 3;
   2201              break;
   2202            }
   2203          }
   2204        } else if (linep[pos.col + 1]) {  // forward search
   2205          if (linep[pos.col + 1] == '\\'
   2206              && linep[pos.col + 2] && linep[pos.col + 3] == '\'') {
   2207            pos.col += 3;
   2208            break;
   2209          } else if (linep[pos.col + 2] == '\'') {
   2210            pos.col += 2;
   2211            break;
   2212          }
   2213        }
   2214      }
   2215      FALLTHROUGH;
   2216 
   2217    default:
   2218      // For Lisp skip over backslashed (), {} and [].
   2219      // (actually, we skip #\( et al)
   2220      if (curbuf->b_p_lisp
   2221          && vim_strchr("(){}[]", c) != NULL
   2222          && pos.col > 1
   2223          && check_prevcol(linep, pos.col, '\\', NULL)
   2224          && check_prevcol(linep, pos.col - 1, '#', NULL)) {
   2225        break;
   2226      }
   2227 
   2228      // Check for match outside of quotes, and inside of
   2229      // quotes when the start is also inside of quotes.
   2230      if ((!inquote || start_in_quotes == kTrue)
   2231          && (c == initc || c == findc)) {
   2232        int bslcnt = 0;
   2233 
   2234        if (!cpo_bsl) {
   2235          for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
   2236            bslcnt++;
   2237          }
   2238        }
   2239        // Only accept a match when 'M' is in 'cpo' or when escaping
   2240        // is what we expect.
   2241        if (cpo_bsl || (bslcnt & 1) == match_escaped) {
   2242          if (c == initc) {
   2243            count++;
   2244          } else {
   2245            if (count == 0) {
   2246              return &pos;
   2247            }
   2248            count--;
   2249          }
   2250        }
   2251      }
   2252    }
   2253  }
   2254 
   2255  if (comment_dir == BACKWARD && count > 0) {
   2256    pos = match_pos;
   2257    return &pos;
   2258  }
   2259  return (pos_T *)NULL;         // never found it
   2260 }
   2261 
   2262 /// Check if line[] contains a / / comment.
   2263 /// @returns MAXCOL if not, otherwise return the column.
   2264 int check_linecomment(const char *line)
   2265 {
   2266  const char *p = line;  // scan from start
   2267  // skip Lispish one-line comments
   2268  if (curbuf->b_p_lisp) {
   2269    if (vim_strchr(p, ';') != NULL) {   // there may be comments
   2270      bool in_str = false;       // inside of string
   2271 
   2272      while ((p = strpbrk(p, "\";")) != NULL) {
   2273        if (*p == '"') {
   2274          if (in_str) {
   2275            if (*(p - 1) != '\\') {             // skip escaped quote
   2276              in_str = false;
   2277            }
   2278          } else if (p == line || ((p - line) >= 2
   2279                                   // skip #\" form
   2280                                   && *(p - 1) != '\\' && *(p - 2) != '#')) {
   2281            in_str = true;
   2282          }
   2283        } else if (!in_str && ((p - line) < 2
   2284                               || (*(p - 1) != '\\' && *(p - 2) != '#'))
   2285                   && !is_pos_in_string(line, (colnr_T)(p - line))) {
   2286          break;                // found!
   2287        }
   2288        p++;
   2289      }
   2290    } else {
   2291      p = NULL;
   2292    }
   2293  } else {
   2294    while ((p = vim_strchr(p, '/')) != NULL) {
   2295      // Accept a double /, unless it's preceded with * and followed by *,
   2296      // because * / / * is an end and start of a C comment.  Only
   2297      // accept the position if it is not inside a string.
   2298      if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
   2299          && !is_pos_in_string(line, (colnr_T)(p - line))) {
   2300        break;
   2301      }
   2302      p++;
   2303    }
   2304  }
   2305 
   2306  if (p == NULL) {
   2307    return MAXCOL;
   2308  }
   2309  return (int)(p - line);
   2310 }
   2311 
   2312 /// Move cursor briefly to character matching the one under the cursor.
   2313 /// Used for Insert mode and "r" command.
   2314 /// Show the match only if it is visible on the screen.
   2315 /// If there isn't a match, then beep.
   2316 ///
   2317 /// @param c  char to show match for
   2318 void showmatch(int c)
   2319 {
   2320  pos_T *lpos;
   2321  colnr_T vcol;
   2322  OptInt *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
   2323  OptInt *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
   2324  char *p;
   2325 
   2326  // Only show match for chars in the 'matchpairs' option.
   2327  // 'matchpairs' is "x:y,x:y"
   2328  for (p = curbuf->b_p_mps; *p != NUL; p++) {
   2329    if (utf_ptr2char(p) == c && (curwin->w_p_rl ^ p_ri)) {
   2330      break;
   2331    }
   2332    p += utfc_ptr2len(p) + 1;
   2333    if (utf_ptr2char(p) == c && !(curwin->w_p_rl ^ p_ri)) {
   2334      break;
   2335    }
   2336    p += utfc_ptr2len(p);
   2337    if (*p == NUL) {
   2338      return;
   2339    }
   2340  }
   2341  if (*p == NUL) {
   2342    return;
   2343  }
   2344 
   2345  if ((lpos = findmatch(NULL, NUL)) == NULL) {  // no match, so beep
   2346    vim_beep(kOptBoFlagShowmatch);
   2347    return;
   2348  }
   2349 
   2350  if (lpos->lnum < curwin->w_topline || lpos->lnum >= curwin->w_botline) {
   2351    return;
   2352  }
   2353 
   2354  if (!curwin->w_p_wrap) {
   2355    getvcol(curwin, lpos, NULL, &vcol, NULL);
   2356  }
   2357 
   2358  bool col_visible = curwin->w_p_wrap
   2359                     || (vcol >= curwin->w_leftcol
   2360                         && vcol < curwin->w_leftcol + curwin->w_view_width);
   2361  if (!col_visible) {
   2362    return;
   2363  }
   2364 
   2365  pos_T mpos = *lpos;  // save the pos, update_screen() may change it
   2366  pos_T save_cursor = curwin->w_cursor;
   2367  OptInt save_so = *so;
   2368  OptInt save_siso = *siso;
   2369  // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
   2370  // stop displaying the "$".
   2371  if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) {
   2372    dollar_vcol = -1;
   2373  }
   2374  curwin->w_virtcol++;              // do display ')' just before "$"
   2375 
   2376  colnr_T save_dollar_vcol = dollar_vcol;
   2377  int save_state = State;
   2378  State = MODE_SHOWMATCH;
   2379  ui_cursor_shape();                // may show different cursor shape
   2380  curwin->w_cursor = mpos;          // move to matching char
   2381  *so = 0;                          // don't use 'scrolloff' here
   2382  *siso = 0;                        // don't use 'sidescrolloff' here
   2383  show_cursor_info_later(false);
   2384  update_screen();                  // show the new char
   2385  setcursor();
   2386  ui_flush();
   2387  // Restore dollar_vcol(), because setcursor() may call curs_rows()
   2388  // which resets it if the matching position is in a previous line
   2389  // and has a higher column number.
   2390  dollar_vcol = save_dollar_vcol;
   2391 
   2392  // brief pause, unless 'm' is present in 'cpo' and a character is
   2393  // available.
   2394  if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
   2395    os_delay((uint64_t)p_mat * 100 + 8, true);
   2396  } else if (!char_avail()) {
   2397    os_delay((uint64_t)p_mat * 100 + 9, false);
   2398  }
   2399  curwin->w_cursor = save_cursor;           // restore cursor position
   2400  *so = save_so;
   2401  *siso = save_siso;
   2402  State = save_state;
   2403  ui_cursor_shape();                // may show different cursor shape
   2404 }
   2405 
   2406 /// Find next search match under cursor, cursor at end.
   2407 /// Used while an operator is pending, and in Visual mode.
   2408 ///
   2409 /// @param forward  true for forward, false for backward
   2410 int current_search(int count, bool forward)
   2411 {
   2412  bool old_p_ws = p_ws;
   2413  pos_T save_VIsual = VIsual;
   2414 
   2415  // Correct cursor when 'selection' is exclusive
   2416  if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) {
   2417    dec_cursor();
   2418  }
   2419 
   2420  // When searching forward and the cursor is at the start of the Visual
   2421  // area, skip the first search backward, otherwise it doesn't move.
   2422  const bool skip_first_backward = forward && VIsual_active
   2423                                   && lt(curwin->w_cursor, VIsual);
   2424 
   2425  pos_T pos = curwin->w_cursor;       // position after the pattern
   2426  pos_T orig_pos = curwin->w_cursor;  // position of the cursor at beginning
   2427  if (VIsual_active) {
   2428    // Searching further will extend the match.
   2429    if (forward) {
   2430      incl(&pos);
   2431    } else {
   2432      decl(&pos);
   2433    }
   2434  }
   2435 
   2436  // Is the pattern is zero-width?, this time, don't care about the direction
   2437  int zero_width = is_zero_width(spats[last_idx].pat, spats[last_idx].patlen,
   2438                                 true, &curwin->w_cursor, FORWARD);
   2439  if (zero_width == -1) {
   2440    return FAIL;  // pattern not found
   2441  }
   2442 
   2443  pos_T end_pos;  // end position of the pattern match
   2444  int result;     // result of various function calls
   2445 
   2446  // The trick is to first search backwards and then search forward again,
   2447  // so that a match at the current cursor position will be correctly
   2448  // captured.  When "forward" is false do it the other way around.
   2449  for (int i = 0; i < 2; i++) {
   2450    int dir;
   2451    if (forward) {
   2452      if (i == 0 && skip_first_backward) {
   2453        continue;
   2454      }
   2455      dir = i;
   2456    } else {
   2457      dir = !i;
   2458    }
   2459 
   2460    int flags = 0;
   2461 
   2462    if (!dir && !zero_width) {
   2463      flags = SEARCH_END;
   2464    }
   2465    end_pos = pos;
   2466 
   2467    // wrapping should not occur in the first round
   2468    if (i == 0) {
   2469      p_ws = false;
   2470    }
   2471 
   2472    result = searchit(curwin, curbuf, &pos, &end_pos,
   2473                      (dir ? FORWARD : BACKWARD),
   2474                      spats[last_idx].pat, spats[last_idx].patlen, i ? count : 1,
   2475                      SEARCH_KEEP | flags, RE_SEARCH, NULL);
   2476 
   2477    p_ws = old_p_ws;
   2478 
   2479    // First search may fail, but then start searching from the
   2480    // beginning of the file (cursor might be on the search match)
   2481    // except when Visual mode is active, so that extending the visual
   2482    // selection works.
   2483    if (i == 1 && !result) {  // not found, abort
   2484      curwin->w_cursor = orig_pos;
   2485      if (VIsual_active) {
   2486        VIsual = save_VIsual;
   2487      }
   2488      return FAIL;
   2489    } else if (i == 0 && !result) {
   2490      if (forward) {  // try again from start of buffer
   2491        clearpos(&pos);
   2492      } else {  // try again from end of buffer
   2493                // searching backwards, so set pos to last line and col
   2494        pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
   2495        pos.col = ml_get_len(curwin->w_buffer->b_ml.ml_line_count);
   2496      }
   2497    }
   2498  }
   2499 
   2500  pos_T start_pos = pos;
   2501 
   2502  if (!VIsual_active) {
   2503    VIsual = start_pos;
   2504  }
   2505 
   2506  // put the cursor after the match
   2507  curwin->w_cursor = end_pos;
   2508  if (lt(VIsual, end_pos) && forward) {
   2509    if (skip_first_backward) {
   2510      // put the cursor on the start of the match
   2511      curwin->w_cursor = pos;
   2512    } else {
   2513      // put the cursor on last character of match
   2514      dec_cursor();
   2515    }
   2516  } else if (VIsual_active && lt(curwin->w_cursor, VIsual) && forward) {
   2517    curwin->w_cursor = pos;   // put the cursor on the start of the match
   2518  }
   2519  VIsual_active = true;
   2520  VIsual_mode = 'v';
   2521 
   2522  if (*p_sel == 'e') {
   2523    // Correction for exclusive selection depends on the direction.
   2524    if (forward && ltoreq(VIsual, curwin->w_cursor)) {
   2525      inc_cursor();
   2526    } else if (!forward && ltoreq(curwin->w_cursor, VIsual)) {
   2527      inc(&VIsual);
   2528    }
   2529  }
   2530 
   2531  if (fdo_flags & kOptFdoFlagSearch && KeyTyped) {
   2532    foldOpenCursor();
   2533  }
   2534 
   2535  may_start_select('c');
   2536  setmouse();
   2537  redraw_curbuf_later(UPD_INVERTED);
   2538  showmode();
   2539 
   2540  return OK;
   2541 }
   2542 
   2543 /// Check if the pattern is zero-width.
   2544 /// If move is true, check from the beginning of the buffer,
   2545 /// else from position "cur".
   2546 /// "direction" is FORWARD or BACKWARD.
   2547 /// Returns true, false or -1 for failure.
   2548 static int is_zero_width(char *pattern, size_t patternlen, bool move, pos_T *cur,
   2549                         Direction direction)
   2550 {
   2551  regmmatch_T regmatch;
   2552  int result = -1;
   2553  pos_T pos;
   2554  const int called_emsg_before = called_emsg;
   2555  int flag = 0;
   2556 
   2557  if (pattern == NULL) {
   2558    pattern = spats[last_idx].pat;
   2559    patternlen = spats[last_idx].patlen;
   2560  }
   2561 
   2562  if (search_regcomp(pattern, patternlen, NULL, RE_SEARCH, RE_SEARCH,
   2563                     SEARCH_KEEP, &regmatch) == FAIL) {
   2564    return -1;
   2565  }
   2566 
   2567  // init startcol correctly
   2568  regmatch.startpos[0].col = -1;
   2569  // move to match
   2570  if (move) {
   2571    clearpos(&pos);
   2572  } else {
   2573    pos = *cur;
   2574    // accept a match at the cursor position
   2575    flag = SEARCH_START;
   2576  }
   2577  if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, patternlen, 1,
   2578               SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) {
   2579    int nmatched = 0;
   2580    // Zero-width pattern should match somewhere, then we can check if
   2581    // start and end are in the same position.
   2582    do {
   2583      regmatch.startpos[0].col++;
   2584      nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
   2585                                   pos.lnum, regmatch.startpos[0].col,
   2586                                   NULL, NULL);
   2587      if (nmatched != 0) {
   2588        break;
   2589      }
   2590    } while (regmatch.regprog != NULL
   2591             && direction == FORWARD
   2592             ? regmatch.startpos[0].col < pos.col
   2593             : regmatch.startpos[0].col > pos.col);
   2594 
   2595    if (called_emsg == called_emsg_before) {
   2596      result = (nmatched != 0
   2597                && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
   2598                && regmatch.startpos[0].col == regmatch.endpos[0].col);
   2599    }
   2600  }
   2601 
   2602  vim_regfree(regmatch.regprog);
   2603  return result;
   2604 }
   2605 
   2606 /// @return  true if line 'lnum' is empty or has white chars only.
   2607 bool linewhite(linenr_T lnum)
   2608 {
   2609  char *p = skipwhite(ml_get(lnum));
   2610  return *p == NUL;
   2611 }
   2612 
   2613 /// Add the search count "[3/19]" to "msgbuf".
   2614 /// See update_search_stat() for other arguments.
   2615 static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool show_top_bot_msg,
   2616                                char *msgbuf, size_t msgbuflen, bool recompute, int maxcount,
   2617                                int timeout)
   2618 {
   2619  searchstat_T stat;
   2620 
   2621  update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
   2622                     timeout);
   2623  if (stat.cur <= 0) {
   2624    return;
   2625  }
   2626 
   2627  char t[SEARCH_STAT_BUF_LEN];
   2628  size_t len;
   2629 
   2630  if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
   2631    if (stat.incomplete == 1) {
   2632      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
   2633    } else if (stat.cnt > maxcount && stat.cur > maxcount) {
   2634      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
   2635                                 maxcount, maxcount);
   2636    } else if (stat.cnt > maxcount) {
   2637      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
   2638                                 maxcount, stat.cur);
   2639    } else {
   2640      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
   2641                                 stat.cnt, stat.cur);
   2642    }
   2643  } else {
   2644    if (stat.incomplete == 1) {
   2645      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
   2646    } else if (stat.cnt > maxcount && stat.cur > maxcount) {
   2647      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
   2648                                 maxcount, maxcount);
   2649    } else if (stat.cnt > maxcount) {
   2650      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
   2651                                 stat.cur, maxcount);
   2652    } else {
   2653      len = (size_t)vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
   2654                                 stat.cur, stat.cnt);
   2655    }
   2656  }
   2657 
   2658  if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
   2659    memmove(t + 2, t, len);
   2660    t[0] = 'W';
   2661    t[1] = ' ';
   2662    len += 2;
   2663  }
   2664 
   2665  if (len > msgbuflen) {
   2666    len = msgbuflen;
   2667  }
   2668  memmove(msgbuf + msgbuflen - len, t, len);
   2669 
   2670  if (dirc == '?' && stat.cur == maxcount + 1) {
   2671    stat.cur = -1;
   2672  }
   2673 
   2674  // keep the message even after redraw, but don't put in history
   2675  msg_ext_overwrite = true;
   2676  msg_ext_set_kind("search_count");
   2677  give_warning(msgbuf, false, false);
   2678 }
   2679 
   2680 // Add the search count information to "stat".
   2681 // "stat" must not be NULL.
   2682 // When "recompute" is true always recompute the numbers.
   2683 // dirc == 0: don't find the next/previous match (only set the result to "stat")
   2684 // dirc == '/': find the next match
   2685 // dirc == '?': find the previous match
   2686 static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat,
   2687                               bool recompute, int maxcount, int timeout)
   2688 {
   2689  int save_ws = p_ws;
   2690  bool wraparound = false;
   2691  pos_T p = (*pos);
   2692  static pos_T lastpos = { 0, 0, 0 };
   2693  static int cur = 0;
   2694  static int cnt = 0;
   2695  static bool exact_match = false;
   2696  static int incomplete = 0;
   2697  static int last_maxcount = 0;
   2698  static int chgtick = 0;
   2699  static char *lastpat = NULL;
   2700  static size_t lastpatlen = 0;
   2701  static buf_T *lbuf = NULL;
   2702 
   2703  CLEAR_POINTER(stat);
   2704 
   2705  if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) {
   2706    stat->cur = cur;
   2707    stat->cnt = cnt;
   2708    stat->exact_match = exact_match;
   2709    stat->incomplete = incomplete;
   2710    stat->last_maxcount = (int)p_msc;
   2711    return;
   2712  }
   2713  last_maxcount = maxcount;
   2714  wraparound = ((dirc == '?' && lt(lastpos, p))
   2715                || (dirc == '/' && lt(p, lastpos)));
   2716 
   2717  // If anything relevant changed the count has to be recomputed.
   2718  if (!(chgtick == buf_get_changedtick(curbuf)
   2719        && (lastpat != NULL  // suppress clang/NULL passed as nonnull parameter
   2720            && strncmp(lastpat, spats[last_idx].pat, lastpatlen) == 0
   2721            && lastpatlen == spats[last_idx].patlen)
   2722        && equalpos(lastpos, *cursor_pos)
   2723        && lbuf == curbuf)
   2724      || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount)
   2725      || recompute) {
   2726    cur = 0;
   2727    cnt = 0;
   2728    exact_match = false;
   2729    incomplete = 0;
   2730    clearpos(&lastpos);
   2731    lbuf = curbuf;
   2732  }
   2733 
   2734  // when searching backwards and having jumped to the first occurrence,
   2735  // cur must remain greater than 1
   2736  if (equalpos(lastpos, *cursor_pos) && !wraparound
   2737      && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 1)) {
   2738    cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
   2739  } else {
   2740    proftime_T start;
   2741    bool done_search = false;
   2742    pos_T endpos = { 0, 0, 0 };
   2743    p_ws = false;
   2744    if (timeout > 0) {
   2745      start = profile_setlimit(timeout);
   2746    }
   2747    while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
   2748                                FORWARD, NULL, 0, 1, SEARCH_KEEP, RE_LAST,
   2749                                NULL) != FAIL) {
   2750      done_search = true;
   2751      // Stop after passing the time limit.
   2752      if (timeout > 0 && profile_passed_limit(start)) {
   2753        incomplete = 1;
   2754        break;
   2755      }
   2756      cnt++;
   2757      if (ltoreq(lastpos, p)) {
   2758        cur = cnt;
   2759        if (lt(p, endpos)) {
   2760          exact_match = true;
   2761        }
   2762      }
   2763      fast_breakcheck();
   2764      if (maxcount > 0 && cnt > maxcount) {
   2765        incomplete = 2;    // max count exceeded
   2766        break;
   2767      }
   2768    }
   2769    if (got_int) {
   2770      cur = -1;  // abort
   2771    }
   2772    if (done_search) {
   2773      xfree(lastpat);
   2774      lastpat = xstrnsave(spats[last_idx].pat, spats[last_idx].patlen);
   2775      lastpatlen = spats[last_idx].patlen;
   2776      chgtick = (int)buf_get_changedtick(curbuf);
   2777      lbuf = curbuf;
   2778      lastpos = p;
   2779    }
   2780  }
   2781  stat->cur = cur;
   2782  stat->cnt = cnt;
   2783  stat->exact_match = exact_match;
   2784  stat->incomplete = incomplete;
   2785  stat->last_maxcount = last_maxcount;
   2786  p_ws = save_ws;
   2787 }
   2788 
   2789 // "searchcount()" function
   2790 void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   2791 {
   2792  pos_T pos = curwin->w_cursor;
   2793  char *pattern = NULL;
   2794  int maxcount = (int)p_msc;
   2795  int timeout = SEARCH_STAT_DEF_TIMEOUT;
   2796  bool recompute = true;
   2797  searchstat_T stat;
   2798 
   2799  tv_dict_alloc_ret(rettv);
   2800 
   2801  if (shortmess(SHM_SEARCHCOUNT)) {  // 'shortmess' contains 'S' flag
   2802    recompute = true;
   2803  }
   2804 
   2805  if (argvars[0].v_type != VAR_UNKNOWN) {
   2806    dict_T *dict;
   2807    dictitem_T *di;
   2808    bool error = false;
   2809 
   2810    if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
   2811      return;
   2812    }
   2813    dict = argvars[0].vval.v_dict;
   2814    di = tv_dict_find(dict, "timeout", -1);
   2815    if (di != NULL) {
   2816      timeout = (int)tv_get_number_chk(&di->di_tv, &error);
   2817      if (error) {
   2818        return;
   2819      }
   2820    }
   2821    di = tv_dict_find(dict, "maxcount", -1);
   2822    if (di != NULL) {
   2823      maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
   2824      if (error) {
   2825        return;
   2826      }
   2827    }
   2828    di = tv_dict_find(dict, "recompute", -1);
   2829    if (di != NULL) {
   2830      recompute = tv_get_number_chk(&di->di_tv, &error);
   2831      if (error) {
   2832        return;
   2833      }
   2834    }
   2835    di = tv_dict_find(dict, "pattern", -1);
   2836    if (di != NULL) {
   2837      pattern = (char *)tv_get_string_chk(&di->di_tv);
   2838      if (pattern == NULL) {
   2839        return;
   2840      }
   2841    }
   2842    di = tv_dict_find(dict, "pos", -1);
   2843    if (di != NULL) {
   2844      if (di->di_tv.v_type != VAR_LIST) {
   2845        semsg(_(e_invarg2), "pos");
   2846        return;
   2847      }
   2848      if (tv_list_len(di->di_tv.vval.v_list) != 3) {
   2849        semsg(_(e_invarg2), "List format should be [lnum, col, off]");
   2850        return;
   2851      }
   2852      listitem_T *li = tv_list_find(di->di_tv.vval.v_list, 0);
   2853      if (li != NULL) {
   2854        pos.lnum = (linenr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
   2855        if (error) {
   2856          return;
   2857        }
   2858      }
   2859      li = tv_list_find(di->di_tv.vval.v_list, 1);
   2860      if (li != NULL) {
   2861        pos.col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
   2862        if (error) {
   2863          return;
   2864        }
   2865      }
   2866      li = tv_list_find(di->di_tv.vval.v_list, 2);
   2867      if (li != NULL) {
   2868        pos.coladd = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
   2869        if (error) {
   2870          return;
   2871        }
   2872      }
   2873    }
   2874  }
   2875 
   2876  save_last_search_pattern();
   2877  save_incsearch_state();
   2878  if (pattern != NULL) {
   2879    if (*pattern == NUL) {
   2880      goto the_end;
   2881    }
   2882    xfree(spats[last_idx].pat);
   2883    spats[last_idx].patlen = strlen(pattern);
   2884    spats[last_idx].pat = xstrnsave(pattern, spats[last_idx].patlen);
   2885  }
   2886  if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) {
   2887    goto the_end;  // the previous pattern was never defined
   2888  }
   2889 
   2890  update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
   2891 
   2892  tv_dict_add_nr(rettv->vval.v_dict, S_LEN("current"), stat.cur);
   2893  tv_dict_add_nr(rettv->vval.v_dict, S_LEN("total"), stat.cnt);
   2894  tv_dict_add_nr(rettv->vval.v_dict, S_LEN("exact_match"), stat.exact_match);
   2895  tv_dict_add_nr(rettv->vval.v_dict, S_LEN("incomplete"), stat.incomplete);
   2896  tv_dict_add_nr(rettv->vval.v_dict, S_LEN("maxcount"), stat.last_maxcount);
   2897 
   2898 the_end:
   2899  restore_last_search_pattern();
   2900  restore_incsearch_state();
   2901 }
   2902 
   2903 /// Get line "lnum" and copy it into "buf[LSIZE]".
   2904 /// The copy is made because the regexp may make the line invalid when using a
   2905 /// mark.
   2906 static char *get_line_and_copy(linenr_T lnum, char *buf)
   2907 {
   2908  char *line = ml_get(lnum);
   2909  xstrlcpy(buf, line, LSIZE);
   2910  return buf;
   2911 }
   2912 
   2913 /// Find identifiers or defines in included files.
   2914 /// If p_ic && compl_status_sol() then ptr must be in lowercase.
   2915 ///
   2916 /// @param ptr            pointer to search pattern
   2917 /// @param dir            direction of expansion
   2918 /// @param len            length of search pattern
   2919 /// @param whole          match whole words only
   2920 /// @param skip_comments  don't match inside comments
   2921 /// @param type           Type of search; are we looking for a type? a macro?
   2922 /// @param action         What to do when we find it
   2923 /// @param start_lnum     first line to start searching
   2924 /// @param end_lnum       last line for searching
   2925 /// @param forceit        If true, always switch to the found path
   2926 /// @param silent         Do not print messages when ACTION_EXPAND
   2927 void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments,
   2928                          int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum,
   2929                          bool forceit, bool silent)
   2930 {
   2931  SearchedFile *files;                  // Stack of included files
   2932  SearchedFile *bigger;                 // When we need more space
   2933  int max_path_depth = 50;
   2934  int match_count = 1;
   2935 
   2936  char *new_fname;
   2937  char *curr_fname = curbuf->b_fname;
   2938  char *prev_fname = NULL;
   2939  int depth_displayed;                  // For type==CHECK_PATH
   2940  char *p;
   2941  bool define_matched;
   2942  regmatch_T regmatch;
   2943  regmatch_T incl_regmatch;
   2944  regmatch_T def_regmatch;
   2945  bool matched = false;
   2946  bool did_show = false;
   2947  bool found = false;
   2948  int i;
   2949  char *already = NULL;
   2950  char *startp = NULL;
   2951  win_T *curwin_save = NULL;
   2952  const int l_g_do_tagpreview = g_do_tagpreview;
   2953 
   2954  regmatch.regprog = NULL;
   2955  incl_regmatch.regprog = NULL;
   2956  def_regmatch.regprog = NULL;
   2957 
   2958  char *file_line = xmalloc(LSIZE);
   2959 
   2960  if (type != CHECK_PATH && type != FIND_DEFINE
   2961      // when CONT_SOL is set compare "ptr" with the beginning of the
   2962      // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
   2963      && !compl_status_sol()) {
   2964    size_t patsize = len + 5;
   2965    char *pat = xmalloc(patsize);
   2966    assert(len <= INT_MAX);
   2967    snprintf(pat, patsize, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr);
   2968    // ignore case according to p_ic, p_scs and pat
   2969    regmatch.rm_ic = ignorecase(pat);
   2970    regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
   2971    xfree(pat);
   2972    if (regmatch.regprog == NULL) {
   2973      goto fpip_end;
   2974    }
   2975  }
   2976  char *inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
   2977  if (*inc_opt != NUL) {
   2978    incl_regmatch.regprog = vim_regcomp(inc_opt, magic_isset() ? RE_MAGIC : 0);
   2979    if (incl_regmatch.regprog == NULL) {
   2980      goto fpip_end;
   2981    }
   2982    incl_regmatch.rm_ic = false;        // don't ignore case in incl. pat.
   2983  }
   2984  if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) {
   2985    def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL ? p_def : curbuf->b_p_def,
   2986                                       magic_isset() ? RE_MAGIC : 0);
   2987    if (def_regmatch.regprog == NULL) {
   2988      goto fpip_end;
   2989    }
   2990    def_regmatch.rm_ic = false;         // don't ignore case in define pat.
   2991  }
   2992  files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile));
   2993  int old_files = max_path_depth;
   2994  int depth = depth_displayed = -1;
   2995 
   2996  end_lnum = MIN(end_lnum, curbuf->b_ml.ml_line_count);
   2997  linenr_T lnum = MIN(start_lnum, end_lnum);  // do at least one line
   2998  char *line = get_line_and_copy(lnum, file_line);
   2999 
   3000  while (true) {
   3001    if (incl_regmatch.regprog != NULL
   3002        && vim_regexec(&incl_regmatch, line, 0)) {
   3003      char *p_fname = (curr_fname == curbuf->b_fname)
   3004                      ? curbuf->b_ffname : curr_fname;
   3005 
   3006      if (strstr(inc_opt, "\\zs") != NULL) {
   3007        // Use text from '\zs' to '\ze' (or end) of 'include'.
   3008        new_fname = find_file_name_in_path(incl_regmatch.startp[0],
   3009                                           (size_t)(incl_regmatch.endp[0]
   3010                                                    - incl_regmatch.startp[0]),
   3011                                           FNAME_EXP|FNAME_INCL|FNAME_REL,
   3012                                           1, p_fname);
   3013      } else {
   3014        // Use text after match with 'include'.
   3015        new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
   3016                                      FNAME_EXP|FNAME_INCL|FNAME_REL, 1, p_fname,
   3017                                      NULL);
   3018      }
   3019      bool already_searched = false;
   3020      if (new_fname != NULL) {
   3021        // Check whether we have already searched in this file
   3022        for (i = 0;; i++) {
   3023          if (i == depth + 1) {
   3024            i = old_files;
   3025          }
   3026          if (i == max_path_depth) {
   3027            break;
   3028          }
   3029          if (path_full_compare(new_fname, files[i].name, true,
   3030                                true) & kEqualFiles) {
   3031            if (type != CHECK_PATH
   3032                && action == ACTION_SHOW_ALL && files[i].matched) {
   3033              msg_putchar('\n');  // cursor below last one
   3034              if (!got_int) {  // don't display if 'q' typed at "--more--" message
   3035                msg_home_replace(new_fname);
   3036                msg_puts(_(" (includes previously listed match)"));
   3037                prev_fname = NULL;
   3038              }
   3039            }
   3040            XFREE_CLEAR(new_fname);
   3041            already_searched = true;
   3042            break;
   3043          }
   3044        }
   3045      }
   3046 
   3047      if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
   3048                                 || (new_fname == NULL && !already_searched))) {
   3049        if (did_show) {
   3050          msg_putchar('\n');  // cursor below last one
   3051        } else {
   3052          gotocmdline(true);  // cursor at status line
   3053          msg_puts_title(_("--- Included files "));
   3054          if (action != ACTION_SHOW_ALL) {
   3055            msg_puts_title(_("not found "));
   3056          }
   3057          msg_puts_title(_("in path ---\n"));
   3058        }
   3059        did_show = true;
   3060        while (depth_displayed < depth && !got_int) {
   3061          depth_displayed++;
   3062          for (i = 0; i < depth_displayed; i++) {
   3063            msg_puts("  ");
   3064          }
   3065          msg_home_replace(files[depth_displayed].name);
   3066          msg_puts(" -->\n");
   3067        }
   3068        if (!got_int) {                     // don't display if 'q' typed
   3069                                            // for "--more--" message
   3070          for (i = 0; i <= depth_displayed; i++) {
   3071            msg_puts("  ");
   3072          }
   3073          if (new_fname != NULL) {
   3074            // using "new_fname" is more reliable, e.g., when
   3075            // 'includeexpr' is set.
   3076            msg_outtrans(new_fname, HLF_D, false);
   3077          } else {
   3078            // Isolate the file name.
   3079            // Include the surrounding "" or <> if present.
   3080            if (strstr(inc_opt, "\\zs") != NULL) {
   3081              // pattern contains \zs, use the match
   3082              p = incl_regmatch.startp[0];
   3083              i = (int)(incl_regmatch.endp[0]
   3084                        - incl_regmatch.startp[0]);
   3085            } else {
   3086              // find the file name after the end of the match
   3087              for (p = incl_regmatch.endp[0];
   3088                   *p && !vim_isfilec((uint8_t)(*p)); p++) {}
   3089              for (i = 0; vim_isfilec((uint8_t)p[i]); i++) {}
   3090            }
   3091 
   3092            if (i == 0) {
   3093              // Nothing found, use the rest of the line.
   3094              p = incl_regmatch.endp[0];
   3095              i = (int)strlen(p);
   3096            } else if (p > line) {
   3097              // Avoid checking before the start of the line, can
   3098              // happen if \zs appears in the regexp.
   3099              if (p[-1] == '"' || p[-1] == '<') {
   3100                p--;
   3101                i++;
   3102              }
   3103              if (p[i] == '"' || p[i] == '>') {
   3104                i++;
   3105              }
   3106            }
   3107            char save_char = p[i];
   3108            p[i] = NUL;
   3109            msg_outtrans(p, HLF_D, false);
   3110            p[i] = save_char;
   3111          }
   3112 
   3113          if (new_fname == NULL && action == ACTION_SHOW_ALL) {
   3114            if (already_searched) {
   3115              msg_puts(_("  (Already listed)"));
   3116            } else {
   3117              msg_puts(_("  NOT FOUND"));
   3118            }
   3119          }
   3120        }
   3121      }
   3122 
   3123      if (new_fname != NULL) {
   3124        // Push the new file onto the file stack
   3125        if (depth + 1 == old_files) {
   3126          bigger = xmalloc((size_t)max_path_depth * 2 * sizeof(SearchedFile));
   3127          for (i = 0; i <= depth; i++) {
   3128            bigger[i] = files[i];
   3129          }
   3130          for (i = depth + 1; i < old_files + max_path_depth; i++) {
   3131            bigger[i].fp = NULL;
   3132            bigger[i].name = NULL;
   3133            bigger[i].lnum = 0;
   3134            bigger[i].matched = false;
   3135          }
   3136          for (i = old_files; i < max_path_depth; i++) {
   3137            bigger[i + max_path_depth] = files[i];
   3138          }
   3139          old_files += max_path_depth;
   3140          max_path_depth *= 2;
   3141          xfree(files);
   3142          files = bigger;
   3143        }
   3144        if ((files[depth + 1].fp = os_fopen(new_fname, "r")) == NULL) {
   3145          xfree(new_fname);
   3146        } else {
   3147          if (++depth == old_files) {
   3148            // Something wrong. We will forget one of our already visited files
   3149            // now.
   3150            xfree(files[old_files].name);
   3151            old_files++;
   3152          }
   3153          files[depth].name = curr_fname = new_fname;
   3154          files[depth].lnum = 0;
   3155          files[depth].matched = false;
   3156          if (action == ACTION_EXPAND && !shortmess(SHM_COMPLETIONSCAN) && !silent) {
   3157            msg_hist_off = true;                // reset in msg_trunc()
   3158            vim_snprintf(IObuff, IOSIZE,
   3159                         _("Scanning included file: %s"),
   3160                         new_fname);
   3161            msg_trunc(IObuff, true, HLF_R);
   3162          } else if (p_verbose >= 5) {
   3163            verbose_enter();
   3164            smsg(0, _("Searching included file %s"), new_fname);
   3165            verbose_leave();
   3166          }
   3167        }
   3168      }
   3169    } else {
   3170      // Check if the line is a define (type == FIND_DEFINE)
   3171      p = line;
   3172 search_line:
   3173      define_matched = false;
   3174      if (def_regmatch.regprog != NULL
   3175          && vim_regexec(&def_regmatch, line, 0)) {
   3176        // Pattern must be first identifier after 'define', so skip
   3177        // to that position before checking for match of pattern.  Also
   3178        // don't let it match beyond the end of this identifier.
   3179        p = def_regmatch.endp[0];
   3180        while (*p && !vim_iswordc((uint8_t)(*p))) {
   3181          p++;
   3182        }
   3183        define_matched = true;
   3184      }
   3185 
   3186      // Look for a match.  Don't do this if we are looking for a
   3187      // define and this line didn't match define_prog above.
   3188      if (def_regmatch.regprog == NULL || define_matched) {
   3189        if (define_matched || compl_status_sol()) {
   3190          // compare the first "len" chars from "ptr"
   3191          startp = skipwhite(p);
   3192          if (p_ic) {
   3193            matched = !mb_strnicmp(startp, ptr, len);
   3194          } else {
   3195            matched = !strncmp(startp, ptr, len);
   3196          }
   3197          if (matched && define_matched && whole
   3198              && vim_iswordc((uint8_t)startp[len])) {
   3199            matched = false;
   3200          }
   3201        } else if (regmatch.regprog != NULL
   3202                   && vim_regexec(&regmatch, line, (colnr_T)(p - line))) {
   3203          matched = true;
   3204          startp = regmatch.startp[0];
   3205          // Check if the line is not a comment line (unless we are
   3206          // looking for a define).  A line starting with "# define"
   3207          // is not considered to be a comment line.
   3208          if (skip_comments) {
   3209            if ((*line != '#'
   3210                 || strncmp(skipwhite(line + 1), "define", 6) != 0)
   3211                && get_leader_len(line, NULL, false, true)) {
   3212              matched = false;
   3213            }
   3214 
   3215            // Also check for a "/ *" or "/ /" before the match.
   3216            // Skips lines like "int backwards;  / * normal index
   3217            // * /" when looking for "normal".
   3218            // Note: Doesn't skip "/ *" in comments.
   3219            p = skipwhite(line);
   3220            if (matched
   3221                || (p[0] == '/' && p[1] == '*') || p[0] == '*') {
   3222              for (p = line; *p && p < startp; p++) {
   3223                if (matched
   3224                    && p[0] == '/'
   3225                    && (p[1] == '*' || p[1] == '/')) {
   3226                  matched = false;
   3227                  // After "//" all text is comment
   3228                  if (p[1] == '/') {
   3229                    break;
   3230                  }
   3231                  p++;
   3232                } else if (!matched && p[0] == '*' && p[1] == '/') {
   3233                  // Can find match after "* /".
   3234                  matched = true;
   3235                  p++;
   3236                }
   3237              }
   3238            }
   3239          }
   3240        }
   3241      }
   3242    }
   3243    if (matched) {
   3244      if (action == ACTION_EXPAND) {
   3245        bool cont_s_ipos = false;
   3246 
   3247        if (depth == -1 && lnum == curwin->w_cursor.lnum) {
   3248          break;
   3249        }
   3250        found = true;
   3251        char *aux = p = startp;
   3252        if (compl_status_adding() && (int)strlen(p) >= ins_compl_len()) {
   3253          p += ins_compl_len();
   3254          if (vim_iswordp(p)) {
   3255            goto exit_matched;
   3256          }
   3257          p = find_word_start(p);
   3258        }
   3259        p = find_word_end(p);
   3260        i = (int)(p - aux);
   3261 
   3262        if (compl_status_adding() && i == ins_compl_len()) {
   3263          // IOSIZE > compl_length, so the strncpy works
   3264          strncpy(IObuff, aux, (size_t)i);  // NOLINT(runtime/printf)
   3265 
   3266          // Get the next line: when "depth" < 0  from the current
   3267          // buffer, otherwise from the included file.  Jump to
   3268          // exit_matched when past the last line.
   3269          if (depth < 0) {
   3270            if (lnum >= end_lnum) {
   3271              goto exit_matched;
   3272            }
   3273            line = get_line_and_copy(++lnum, file_line);
   3274          } else if (vim_fgets(line = file_line,
   3275                               LSIZE, files[depth].fp)) {
   3276            goto exit_matched;
   3277          }
   3278 
   3279          // we read a line, set "already" to check this "line" later
   3280          // if depth >= 0 we'll increase files[depth].lnum far
   3281          // below  -- Acevedo
   3282          already = aux = p = skipwhite(line);
   3283          p = find_word_start(p);
   3284          p = find_word_end(p);
   3285          if (p > aux) {
   3286            if (*aux != ')' && IObuff[i - 1] != TAB) {
   3287              if (IObuff[i - 1] != ' ') {
   3288                IObuff[i++] = ' ';
   3289              }
   3290              // IObuf =~ "\(\k\|\i\).* ", thus i >= 2
   3291              if (p_js
   3292                  && (IObuff[i - 2] == '.'
   3293                      || IObuff[i - 2] == '?'
   3294                      || IObuff[i - 2] == '!')) {
   3295                IObuff[i++] = ' ';
   3296              }
   3297            }
   3298            // copy as much as possible of the new word
   3299            if (p - aux >= IOSIZE - i) {
   3300              p = aux + IOSIZE - i - 1;
   3301            }
   3302            strncpy(IObuff + i, aux, (size_t)(p - aux));  // NOLINT(runtime/printf)
   3303            i += (int)(p - aux);
   3304            cont_s_ipos = true;
   3305          }
   3306          IObuff[i] = NUL;
   3307          aux = IObuff;
   3308 
   3309          if (i == ins_compl_len()) {
   3310            goto exit_matched;
   3311          }
   3312        }
   3313 
   3314        const int add_r = ins_compl_add_infercase(aux, i, p_ic,
   3315                                                  curr_fname == curbuf->b_fname
   3316                                                  ? NULL : curr_fname,
   3317                                                  dir, cont_s_ipos, 0);
   3318        if (add_r == OK) {
   3319          // if dir was BACKWARD then honor it just once
   3320          dir = FORWARD;
   3321        } else if (add_r == FAIL) {
   3322          break;
   3323        }
   3324      } else if (action == ACTION_SHOW_ALL) {
   3325        found = true;
   3326        if (!did_show) {
   3327          gotocmdline(true);                    // cursor at status line
   3328        }
   3329        if (curr_fname != prev_fname) {
   3330          if (did_show) {
   3331            msg_putchar('\n');                  // cursor below last one
   3332          }
   3333          if (!got_int) {             // don't display if 'q' typed
   3334                                      // at "--more--" message
   3335            msg_home_replace(curr_fname);
   3336          }
   3337          prev_fname = curr_fname;
   3338        }
   3339        did_show = true;
   3340        if (!got_int) {
   3341          show_pat_in_path(line, type, true, action,
   3342                           (depth == -1) ? NULL : files[depth].fp,
   3343                           (depth == -1) ? &lnum : &files[depth].lnum,
   3344                           match_count++);
   3345        }
   3346 
   3347        // Set matched flag for this file and all the ones that
   3348        // include it
   3349        for (i = 0; i <= depth; i++) {
   3350          files[i].matched = true;
   3351        }
   3352      } else if (--count <= 0) {
   3353        found = true;
   3354        if (depth == -1 && lnum == curwin->w_cursor.lnum
   3355            && l_g_do_tagpreview == 0) {
   3356          emsg(_("E387: Match is on current line"));
   3357        } else if (action == ACTION_SHOW) {
   3358          show_pat_in_path(line, type, did_show, action,
   3359                           (depth == -1) ? NULL : files[depth].fp,
   3360                           (depth == -1) ? &lnum : &files[depth].lnum, 1);
   3361          did_show = true;
   3362        } else {
   3363          // ":psearch" uses the preview window
   3364          if (l_g_do_tagpreview != 0) {
   3365            curwin_save = curwin;
   3366            prepare_tagpreview(true);
   3367          }
   3368          if (action == ACTION_SPLIT) {
   3369            if (win_split(0, 0) == FAIL) {
   3370              break;
   3371            }
   3372            RESET_BINDING(curwin);
   3373          }
   3374          if (depth == -1) {
   3375            // match in current file
   3376            if (l_g_do_tagpreview != 0) {
   3377              if (!win_valid(curwin_save)) {
   3378                break;
   3379              }
   3380              if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL,
   3381                                           NULL, true, lnum, forceit))) {
   3382                break;    // failed to jump to file
   3383              }
   3384            } else {
   3385              setpcmark();
   3386            }
   3387            curwin->w_cursor.lnum = lnum;
   3388            check_cursor(curwin);
   3389          } else {
   3390            if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true,
   3391                                         files[depth].lnum, forceit))) {
   3392              break;    // failed to jump to file
   3393            }
   3394            // autocommands may have changed the lnum, we don't
   3395            // want that here
   3396            curwin->w_cursor.lnum = files[depth].lnum;
   3397          }
   3398        }
   3399        if (action != ACTION_SHOW) {
   3400          curwin->w_cursor.col = (colnr_T)(startp - line);
   3401          curwin->w_set_curswant = true;
   3402        }
   3403 
   3404        if (l_g_do_tagpreview != 0
   3405            && curwin != curwin_save && win_valid(curwin_save)) {
   3406          // Return cursor to where we were
   3407          validate_cursor(curwin);
   3408          redraw_later(curwin, UPD_VALID);
   3409          win_enter(curwin_save, true);
   3410        }
   3411        break;
   3412      }
   3413 exit_matched:
   3414      matched = false;
   3415      // look for other matches in the rest of the line if we
   3416      // are not at the end of it already
   3417      if (def_regmatch.regprog == NULL
   3418          && action == ACTION_EXPAND
   3419          && !compl_status_sol()
   3420          && *startp != NUL
   3421          && *(startp + utfc_ptr2len(startp)) != NUL) {
   3422        goto search_line;
   3423      }
   3424    }
   3425    line_breakcheck();
   3426    if (action == ACTION_EXPAND) {
   3427      ins_compl_check_keys(30, false);
   3428    }
   3429    if (got_int || ins_compl_interrupted()) {
   3430      break;
   3431    }
   3432 
   3433    // Read the next line.  When reading an included file and encountering
   3434    // end-of-file, close the file and continue in the file that included
   3435    // it.
   3436    while (depth >= 0 && !already
   3437           && vim_fgets(line = file_line, LSIZE, files[depth].fp)) {
   3438      fclose(files[depth].fp);
   3439      old_files--;
   3440      files[old_files].name = files[depth].name;
   3441      files[old_files].matched = files[depth].matched;
   3442      depth--;
   3443      curr_fname = (depth == -1) ? curbuf->b_fname
   3444                                 : files[depth].name;
   3445      depth_displayed = MIN(depth_displayed, depth);
   3446    }
   3447    if (depth >= 0) {           // we could read the line
   3448      files[depth].lnum++;
   3449      // Remove any CR and LF from the line.
   3450      i = (int)strlen(line);
   3451      if (i > 0 && line[i - 1] == '\n') {
   3452        line[--i] = NUL;
   3453      }
   3454      if (i > 0 && line[i - 1] == '\r') {
   3455        line[--i] = NUL;
   3456      }
   3457    } else if (!already) {
   3458      if (++lnum > end_lnum) {
   3459        break;
   3460      }
   3461      line = get_line_and_copy(lnum, file_line);
   3462    }
   3463    already = NULL;
   3464  }
   3465  // End of big while (true) loop.
   3466 
   3467  // Close any files that are still open.
   3468  for (i = 0; i <= depth; i++) {
   3469    fclose(files[i].fp);
   3470    xfree(files[i].name);
   3471  }
   3472  for (i = old_files; i < max_path_depth; i++) {
   3473    xfree(files[i].name);
   3474  }
   3475  xfree(files);
   3476 
   3477  if (type == CHECK_PATH) {
   3478    if (!did_show) {
   3479      if (action != ACTION_SHOW_ALL) {
   3480        msg(_("All included files were found"), 0);
   3481      } else {
   3482        msg(_("No included files"), 0);
   3483      }
   3484    }
   3485  } else if (!found && action != ACTION_EXPAND && !silent) {
   3486    if (got_int || ins_compl_interrupted()) {
   3487      emsg(_(e_interr));
   3488    } else if (type == FIND_DEFINE) {
   3489      emsg(_("E388: Couldn't find definition"));
   3490    } else {
   3491      emsg(_("E389: Couldn't find pattern"));
   3492    }
   3493  }
   3494  if (action == ACTION_SHOW || action == ACTION_SHOW_ALL) {
   3495    msg_end();
   3496  }
   3497 
   3498 fpip_end:
   3499  xfree(file_line);
   3500  vim_regfree(regmatch.regprog);
   3501  vim_regfree(incl_regmatch.regprog);
   3502  vim_regfree(def_regmatch.regprog);
   3503 }
   3504 
   3505 static void show_pat_in_path(char *line, int type, bool did_show, int action, FILE *fp,
   3506                             linenr_T *lnum, int count)
   3507  FUNC_ATTR_NONNULL_ARG(1, 6)
   3508 {
   3509  if (did_show) {
   3510    msg_putchar('\n');          // cursor below last one
   3511  } else if (!msg_silent) {
   3512    gotocmdline(true);          // cursor at status line
   3513  }
   3514  if (got_int) {                // 'q' typed at "--more--" message
   3515    return;
   3516  }
   3517  size_t linelen = strlen(line);
   3518  while (true) {
   3519    char *p = line + linelen - 1;
   3520    if (fp != NULL) {
   3521      // We used fgets(), so get rid of newline at end
   3522      if (p >= line && *p == '\n') {
   3523        p--;
   3524      }
   3525      if (p >= line && *p == '\r') {
   3526        p--;
   3527      }
   3528      *(p + 1) = NUL;
   3529    }
   3530    if (action == ACTION_SHOW_ALL) {
   3531      snprintf(IObuff, IOSIZE, "%3d: ", count);  // Show match nr.
   3532      msg_puts(IObuff);
   3533      snprintf(IObuff, IOSIZE, "%4" PRIdLINENR, *lnum);  // Show line nr.
   3534      // Highlight line numbers.
   3535      msg_puts_hl(IObuff, HLF_N, false);
   3536      msg_puts(" ");
   3537    }
   3538    msg_prt_line(line, false);
   3539 
   3540    // Definition continues until line that doesn't end with '\'
   3541    if (got_int || type != FIND_DEFINE || p < line || *p != '\\') {
   3542      break;
   3543    }
   3544 
   3545    if (fp != NULL) {
   3546      if (vim_fgets(line, LSIZE, fp)) {     // end of file
   3547        break;
   3548      }
   3549      linelen = strlen(line);
   3550      (*lnum)++;
   3551    } else {
   3552      if (++*lnum > curbuf->b_ml.ml_line_count) {
   3553        break;
   3554      }
   3555      line = ml_get(*lnum);
   3556      linelen = (size_t)ml_get_len(*lnum);
   3557    }
   3558    msg_putchar('\n');
   3559  }
   3560 }
   3561 
   3562 /// Get last search pattern
   3563 void get_search_pattern(SearchPattern *const pat)
   3564 {
   3565  memcpy(pat, &(spats[0]), sizeof(spats[0]));
   3566 }
   3567 
   3568 /// Get last substitute pattern
   3569 void get_substitute_pattern(SearchPattern *const pat)
   3570 {
   3571  memcpy(pat, &(spats[1]), sizeof(spats[1]));
   3572  CLEAR_FIELD(pat->off);
   3573 }
   3574 
   3575 /// Set last search pattern
   3576 void set_search_pattern(const SearchPattern pat)
   3577 {
   3578  free_spat(&spats[0]);
   3579  memcpy(&(spats[0]), &pat, sizeof(spats[0]));
   3580  set_vv_searchforward();
   3581 }
   3582 
   3583 /// Set last substitute pattern
   3584 void set_substitute_pattern(const SearchPattern pat)
   3585 {
   3586  free_spat(&spats[1]);
   3587  memcpy(&(spats[1]), &pat, sizeof(spats[1]));
   3588  CLEAR_FIELD(spats[1].off);
   3589 }
   3590 
   3591 /// Set last used search pattern
   3592 ///
   3593 /// @param[in]  is_substitute_pattern  If true set substitute pattern as last
   3594 ///                                    used. Otherwise sets search pattern.
   3595 void set_last_used_pattern(const bool is_substitute_pattern)
   3596 {
   3597  last_idx = (is_substitute_pattern ? 1 : 0);
   3598 }
   3599 
   3600 /// Returns true if search pattern was the last used one
   3601 bool search_was_last_used(void)
   3602 {
   3603  return last_idx == 0;
   3604 }