neovim

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

match.c (36249B)


      1 // match.c: functions for highlighting matches
      2 
      3 #include <assert.h>
      4 #include <inttypes.h>
      5 #include <stdbool.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 
      9 #include "nvim/ascii_defs.h"
     10 #include "nvim/buffer_defs.h"
     11 #include "nvim/charset.h"
     12 #include "nvim/drawscreen.h"
     13 #include "nvim/errors.h"
     14 #include "nvim/eval/funcs.h"
     15 #include "nvim/eval/typval.h"
     16 #include "nvim/eval/window.h"
     17 #include "nvim/ex_cmds_defs.h"
     18 #include "nvim/ex_docmd.h"
     19 #include "nvim/fold.h"
     20 #include "nvim/gettext_defs.h"
     21 #include "nvim/globals.h"
     22 #include "nvim/highlight.h"
     23 #include "nvim/highlight_defs.h"
     24 #include "nvim/highlight_group.h"
     25 #include "nvim/macros_defs.h"
     26 #include "nvim/match.h"
     27 #include "nvim/mbyte.h"
     28 #include "nvim/mbyte_defs.h"
     29 #include "nvim/memline.h"
     30 #include "nvim/memory.h"
     31 #include "nvim/message.h"
     32 #include "nvim/option_vars.h"
     33 #include "nvim/pos_defs.h"
     34 #include "nvim/profile.h"
     35 #include "nvim/regexp.h"
     36 #include "nvim/strings.h"
     37 #include "nvim/types_defs.h"
     38 #include "nvim/vim_defs.h"
     39 
     40 #include "match.c.generated.h"
     41 
     42 static const char *e_invalwindow = N_("E957: Invalid window number");
     43 
     44 #define SEARCH_HL_PRIORITY 0
     45 
     46 /// Add match to the match list of window "wp".
     47 /// If "pat" is not NULL the pattern will be highlighted with the group "grp"
     48 /// with priority "prio".
     49 /// If "pos_list" is not NULL the list of positions defines the highlights.
     50 /// Optionally, a desired ID "id" can be specified (greater than or equal to 1).
     51 /// If no particular ID is desired, -1 must be specified for "id".
     52 ///
     53 /// @param[in] conceal_char pointer to conceal replacement char
     54 /// @return ID of added match, -1 on failure.
     55 static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id,
     56                     list_T *pos_list, const char *const conceal_char)
     57  FUNC_ATTR_NONNULL_ARG(1, 2)
     58 {
     59  int hlg_id;
     60  regprog_T *regprog = NULL;
     61  int rtype = UPD_SOME_VALID;
     62 
     63  if (*grp == NUL || (pat != NULL && *pat == NUL)) {
     64    return -1;
     65  }
     66  if (id < -1 || id == 0) {
     67    semsg(_("E799: Invalid ID: %" PRId64
     68            " (must be greater than or equal to 1)"),
     69          (int64_t)id);
     70    return -1;
     71  }
     72  if (id == -1) {
     73    // use the next available match ID
     74    id = wp->w_next_match_id++;
     75  } else {
     76    // check the given ID is not already in use
     77    for (matchitem_T *cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) {
     78      if (cur->mit_id == id) {
     79        semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
     80        return -1;
     81      }
     82    }
     83 
     84    // Make sure the next match ID is always higher than the highest
     85    // manually selected ID.  Add some extra in case a few more IDs are
     86    // added soon.
     87    if (wp->w_next_match_id < id + 100) {
     88      wp->w_next_match_id = id + 100;
     89    }
     90  }
     91 
     92  if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
     93    return -1;
     94  }
     95  if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) {
     96    semsg(_(e_invarg2), pat);
     97    return -1;
     98  }
     99 
    100  // Build new match.
    101  matchitem_T *m = xcalloc(1, sizeof(matchitem_T));
    102  if (tv_list_len(pos_list) > 0) {
    103    m->mit_pos_array = xcalloc((size_t)tv_list_len(pos_list), sizeof(llpos_T));
    104    m->mit_pos_count = tv_list_len(pos_list);
    105  }
    106  m->mit_id = id;
    107  m->mit_priority = prio;
    108  m->mit_pattern = pat == NULL ? NULL : xstrdup(pat);
    109  m->mit_hlg_id = hlg_id;
    110  m->mit_match.regprog = regprog;
    111  m->mit_match.rmm_ic = false;
    112  m->mit_match.rmm_maxcol = 0;
    113  m->mit_conceal_char = 0;
    114  if (conceal_char != NULL) {
    115    m->mit_conceal_char = utf_ptr2char(conceal_char);
    116  }
    117 
    118  // Set up position matches
    119  if (pos_list != NULL) {
    120    linenr_T toplnum = 0;
    121    linenr_T botlnum = 0;
    122 
    123    int i = 0;
    124    TV_LIST_ITER(pos_list, li, {
    125      linenr_T lnum = 0;
    126      colnr_T col = 0;
    127      int len = 1;
    128      bool error = false;
    129 
    130      if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
    131        const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list;
    132        const listitem_T *subli = tv_list_first(subl);
    133        if (subli == NULL) {
    134          semsg(_("E5030: Empty list at position %d"),
    135                (int)tv_list_idx_of_item(pos_list, li));
    136          goto fail;
    137        }
    138        lnum = (linenr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
    139        if (error) {
    140          goto fail;
    141        }
    142        if (lnum <= 0) {
    143          continue;
    144        }
    145        m->mit_pos_array[i].lnum = lnum;
    146        subli = TV_LIST_ITEM_NEXT(subl, subli);
    147        if (subli != NULL) {
    148          col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
    149          if (error) {
    150            goto fail;
    151          }
    152          if (col < 0) {
    153            continue;
    154          }
    155          subli = TV_LIST_ITEM_NEXT(subl, subli);
    156          if (subli != NULL) {
    157            len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
    158            if (len < 0) {
    159              continue;
    160            }
    161            if (error) {
    162              goto fail;
    163            }
    164          }
    165        }
    166        m->mit_pos_array[i].col = col;
    167        m->mit_pos_array[i].len = len;
    168      } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) {
    169        if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) {
    170          continue;
    171        }
    172        m->mit_pos_array[i].lnum = (linenr_T)TV_LIST_ITEM_TV(li)->vval.v_number;
    173        m->mit_pos_array[i].col = 0;
    174        m->mit_pos_array[i].len = 0;
    175      } else {
    176        semsg(_("E5031: List or number required at position %d"),
    177              (int)tv_list_idx_of_item(pos_list, li));
    178        goto fail;
    179      }
    180      if (toplnum == 0 || lnum < toplnum) {
    181        toplnum = lnum;
    182      }
    183      if (botlnum == 0 || lnum >= botlnum) {
    184        botlnum = lnum + 1;
    185      }
    186      i++;
    187    });
    188 
    189    // Calculate top and bottom lines for redrawing area
    190    if (toplnum != 0) {
    191      redraw_win_range_later(wp, toplnum, botlnum);
    192      m->mit_toplnum = toplnum;
    193      m->mit_botlnum = botlnum;
    194      rtype = UPD_VALID;
    195    }
    196  }
    197 
    198  // Insert new match.  The match list is in ascending order with regard to
    199  // the match priorities.
    200  matchitem_T *cur = wp->w_match_head;
    201  matchitem_T *prev = cur;
    202  while (cur != NULL && prio >= cur->mit_priority) {
    203    prev = cur;
    204    cur = cur->mit_next;
    205  }
    206  if (cur == prev) {
    207    wp->w_match_head = m;
    208  } else {
    209    prev->mit_next = m;
    210  }
    211  m->mit_next = cur;
    212 
    213  redraw_later(wp, rtype);
    214  return id;
    215 
    216 fail:
    217  vim_regfree(regprog);
    218  xfree(m->mit_pattern);
    219  xfree(m->mit_pos_array);
    220  xfree(m);
    221  return -1;
    222 }
    223 
    224 /// Delete match with ID 'id' in the match list of window 'wp'.
    225 ///
    226 /// @param perr  print error messages if true.
    227 static int match_delete(win_T *wp, int id, bool perr)
    228 {
    229  matchitem_T *cur = wp->w_match_head;
    230  matchitem_T *prev = cur;
    231  int rtype = UPD_SOME_VALID;
    232 
    233  if (id < 1) {
    234    if (perr) {
    235      semsg(_("E802: Invalid ID: %" PRId64 " (must be greater than or equal to 1)"),
    236            (int64_t)id);
    237    }
    238    return -1;
    239  }
    240  while (cur != NULL && cur->mit_id != id) {
    241    prev = cur;
    242    cur = cur->mit_next;
    243  }
    244  if (cur == NULL) {
    245    if (perr) {
    246      semsg(_("E803: ID not found: %" PRId64), (int64_t)id);
    247    }
    248    return -1;
    249  }
    250  if (cur == prev) {
    251    wp->w_match_head = cur->mit_next;
    252  } else {
    253    prev->mit_next = cur->mit_next;
    254  }
    255  vim_regfree(cur->mit_match.regprog);
    256  xfree(cur->mit_pattern);
    257  if (cur->mit_toplnum != 0) {
    258    redraw_win_range_later(wp, cur->mit_toplnum, cur->mit_botlnum);
    259    rtype = UPD_VALID;
    260  }
    261  xfree(cur->mit_pos_array);
    262  xfree(cur);
    263  redraw_later(wp, rtype);
    264  return 0;
    265 }
    266 
    267 /// Delete all matches in the match list of window 'wp'.
    268 void clear_matches(win_T *wp)
    269 {
    270  while (wp->w_match_head != NULL) {
    271    matchitem_T *m = wp->w_match_head->mit_next;
    272    vim_regfree(wp->w_match_head->mit_match.regprog);
    273    xfree(wp->w_match_head->mit_pattern);
    274    xfree(wp->w_match_head->mit_pos_array);
    275    xfree(wp->w_match_head);
    276    wp->w_match_head = m;
    277  }
    278  redraw_later(wp, UPD_SOME_VALID);
    279 }
    280 
    281 /// Get match from ID 'id' in window 'wp'.
    282 /// Return NULL if match not found.
    283 static matchitem_T *get_match(win_T *wp, int id)
    284 {
    285  matchitem_T *cur = wp->w_match_head;
    286 
    287  while (cur != NULL && cur->mit_id != id) {
    288    cur = cur->mit_next;
    289  }
    290  return cur;
    291 }
    292 
    293 /// Init for calling prepare_search_hl().
    294 void init_search_hl(win_T *wp, match_T *search_hl)
    295  FUNC_ATTR_NONNULL_ALL
    296 {
    297  // Setup for match and 'hlsearch' highlighting.  Disable any previous
    298  // match
    299  matchitem_T *cur = wp->w_match_head;
    300  while (cur != NULL) {
    301    cur->mit_hl.rm = cur->mit_match;
    302    if (cur->mit_hlg_id == 0) {
    303      cur->mit_hl.attr = 0;
    304    } else {
    305      cur->mit_hl.attr = syn_id2attr(cur->mit_hlg_id);
    306    }
    307    cur->mit_hl.buf = wp->w_buffer;
    308    cur->mit_hl.lnum = 0;
    309    cur->mit_hl.first_lnum = 0;
    310    // Set the time limit to 'redrawtime'.
    311    cur->mit_hl.tm = profile_setlimit(p_rdt);
    312    cur = cur->mit_next;
    313  }
    314  search_hl->buf = wp->w_buffer;
    315  search_hl->lnum = 0;
    316  search_hl->first_lnum = 0;
    317  search_hl->attr = win_hl_attr(wp, HLF_L);
    318 
    319  // time limit is set at the toplevel, for all windows
    320 }
    321 
    322 /// @param shl       points to a match. Fill on match.
    323 /// @param posmatch  match item with positions
    324 /// @param mincol    minimal column for a match
    325 ///
    326 /// @return one on match, otherwise return zero.
    327 static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, colnr_T mincol)
    328  FUNC_ATTR_NONNULL_ALL
    329 {
    330  int found = -1;
    331 
    332  shl->lnum = 0;
    333  for (int i = match->mit_pos_cur; i < match->mit_pos_count; i++) {
    334    llpos_T *pos = &match->mit_pos_array[i];
    335 
    336    if (pos->lnum == 0) {
    337      break;
    338    }
    339    if (pos->len == 0 && pos->col < mincol) {
    340      continue;
    341    }
    342    if (pos->lnum == lnum) {
    343      if (found >= 0) {
    344        // if this match comes before the one at "found" then swap them
    345        if (pos->col < match->mit_pos_array[found].col) {
    346          llpos_T tmp = *pos;
    347 
    348          *pos = match->mit_pos_array[found];
    349          match->mit_pos_array[found] = tmp;
    350        }
    351      } else {
    352        found = i;
    353      }
    354    }
    355  }
    356  match->mit_pos_cur = 0;
    357  if (found >= 0) {
    358    colnr_T start = match->mit_pos_array[found].col == 0
    359                    ? 0 : match->mit_pos_array[found].col - 1;
    360    colnr_T end = match->mit_pos_array[found].col == 0
    361                  ? MAXCOL : start + match->mit_pos_array[found].len;
    362 
    363    shl->lnum = lnum;
    364    shl->rm.startpos[0].lnum = 0;
    365    shl->rm.startpos[0].col = start;
    366    shl->rm.endpos[0].lnum = 0;
    367    shl->rm.endpos[0].col = end;
    368    shl->is_addpos = true;
    369    shl->has_cursor = false;
    370    match->mit_pos_cur = found + 1;
    371    return 1;
    372  }
    373  return 0;
    374 }
    375 
    376 /// Search for a next 'hlsearch' or match.
    377 /// Uses shl->buf.
    378 /// Sets shl->lnum and shl->rm contents.
    379 /// Note: Assumes a previous match is always before "lnum", unless
    380 /// shl->lnum is zero.
    381 /// Careful: Any pointers for buffer lines will become invalid.
    382 ///
    383 /// @param shl     points to search_hl or a match
    384 /// @param mincol  minimal column for a match
    385 /// @param cur     to retrieve match positions if any
    386 static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum,
    387                           colnr_T mincol, matchitem_T *cur)
    388  FUNC_ATTR_NONNULL_ARG(2)
    389 {
    390  colnr_T matchcol;
    391  int nmatched = 0;
    392  const int called_emsg_before = called_emsg;
    393 
    394  // for :{range}s/pat only highlight inside the range
    395  if ((lnum < search_first_line || lnum > search_last_line) && cur == NULL) {
    396    shl->lnum = 0;
    397    return;
    398  }
    399 
    400  if (shl->lnum != 0) {
    401    // Check for three situations:
    402    // 1. If the "lnum" is below a previous match, start a new search.
    403    // 2. If the previous match includes "mincol", use it.
    404    // 3. Continue after the previous match.
    405    linenr_T l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
    406    if (lnum > l) {
    407      shl->lnum = 0;
    408    } else if (lnum < l || shl->rm.endpos[0].col > mincol) {
    409      return;
    410    }
    411  }
    412 
    413  // Repeat searching for a match until one is found that includes "mincol"
    414  // or none is found in this line.
    415  while (true) {
    416    // Stop searching after passing the time limit.
    417    if (profile_passed_limit(shl->tm)) {
    418      shl->lnum = 0;                    // no match found in time
    419      break;
    420    }
    421    // Three situations:
    422    // 1. No useful previous match: search from start of line.
    423    // 2. Not Vi compatible or empty match: continue at next character.
    424    //    Break the loop if this is beyond the end of the line.
    425    // 3. Vi compatible searching: continue at end of previous match.
    426    if (shl->lnum == 0) {
    427      matchcol = 0;
    428    } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
    429               || (shl->rm.endpos[0].lnum == 0
    430                   && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
    431      matchcol = shl->rm.startpos[0].col;
    432      char *ml = ml_get_buf(shl->buf, lnum) + matchcol;
    433      if (*ml == NUL) {
    434        matchcol++;
    435        shl->lnum = 0;
    436        break;
    437      }
    438      matchcol += utfc_ptr2len(ml);
    439    } else {
    440      matchcol = shl->rm.endpos[0].col;
    441    }
    442 
    443    shl->lnum = lnum;
    444    if (shl->rm.regprog != NULL) {
    445      // Remember whether shl->rm is using a copy of the regprog in
    446      // cur->mit_match.
    447      bool regprog_is_copy = (shl != search_hl && cur != NULL
    448                              && shl == &cur->mit_hl
    449                              && cur->mit_match.regprog == cur->mit_hl.rm.regprog);
    450      int timed_out = false;
    451 
    452      nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
    453                                   &(shl->tm), &timed_out);
    454      // Copy the regprog, in case it got freed and recompiled.
    455      if (regprog_is_copy) {
    456        cur->mit_match.regprog = cur->mit_hl.rm.regprog;
    457      }
    458      if (called_emsg > called_emsg_before || got_int || timed_out) {
    459        // Error while handling regexp: stop using this regexp.
    460        if (shl == search_hl) {
    461          // don't free regprog in the match list, it's a copy
    462          vim_regfree(shl->rm.regprog);
    463          set_no_hlsearch(true);
    464        }
    465        shl->rm.regprog = NULL;
    466        shl->lnum = 0;
    467        got_int = false;  // avoid the "Type :quit to exit Vim" message
    468        break;
    469      }
    470    } else if (cur != NULL) {
    471      nmatched = next_search_hl_pos(shl, lnum, cur, matchcol);
    472    }
    473    if (nmatched == 0) {
    474      shl->lnum = 0;                    // no match found
    475      break;
    476    }
    477    if (shl->rm.startpos[0].lnum > 0
    478        || shl->rm.startpos[0].col >= mincol
    479        || nmatched > 1
    480        || shl->rm.endpos[0].col > mincol) {
    481      shl->lnum += shl->rm.startpos[0].lnum;
    482      break;                            // useful match found
    483    }
    484  }
    485 }
    486 
    487 /// Advance to the match in window "wp" line "lnum" or past it.
    488 void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
    489  FUNC_ATTR_NONNULL_ALL
    490 {
    491  matchitem_T *cur = wp->w_match_head;  // points to the match list
    492  match_T *shl;       // points to search_hl or a match
    493  bool shl_flag = false;  // flag to indicate whether search_hl has been processed or not
    494 
    495  // When using a multi-line pattern, start searching at the top
    496  // of the window or just after a closed fold.
    497  // Do this both for search_hl and the match list.
    498  while (cur != NULL || shl_flag == false) {
    499    if (shl_flag == false) {
    500      shl = search_hl;
    501      shl_flag = true;
    502    } else {
    503      shl = &cur->mit_hl;
    504    }
    505    if (shl->rm.regprog != NULL
    506        && shl->lnum == 0
    507        && re_multiline(shl->rm.regprog)) {
    508      if (shl->first_lnum == 0) {
    509        for (shl->first_lnum = lnum;
    510             shl->first_lnum > wp->w_topline;
    511             shl->first_lnum--) {
    512          if (hasFolding(wp, shl->first_lnum - 1, NULL, NULL)) {
    513            break;
    514          }
    515        }
    516      }
    517      if (cur != NULL) {
    518        cur->mit_pos_cur = 0;
    519      }
    520      bool pos_inprogress = true;  // mark that a position match search is
    521                                   // in progress
    522      int n = 0;
    523      while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
    524                                        || (cur != NULL && pos_inprogress))) {
    525        next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
    526                       shl == search_hl ? NULL : cur);
    527        pos_inprogress = !(cur == NULL || cur->mit_pos_cur == 0);
    528        if (shl->lnum != 0) {
    529          shl->first_lnum = shl->lnum
    530                            + shl->rm.endpos[0].lnum
    531                            - shl->rm.startpos[0].lnum;
    532          n = shl->rm.endpos[0].col;
    533        } else {
    534          shl->first_lnum++;
    535          n = 0;
    536        }
    537      }
    538    }
    539    if (shl != search_hl && cur != NULL) {
    540      cur = cur->mit_next;
    541    }
    542  }
    543 }
    544 
    545 /// Update "shl->has_cursor" based on the match in "shl" and the cursor
    546 /// position.
    547 static void check_cur_search_hl(win_T *wp, match_T *shl)
    548 {
    549  linenr_T linecount = shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
    550 
    551  if (wp->w_cursor.lnum >= shl->lnum
    552      && wp->w_cursor.lnum <= shl->lnum + linecount
    553      && (wp->w_cursor.lnum > shl->lnum || wp->w_cursor.col >= shl->rm.startpos[0].col)
    554      && (wp->w_cursor.lnum < shl->lnum + linecount || wp->w_cursor.col < shl->rm.endpos[0].col)) {
    555    shl->has_cursor = true;
    556  } else {
    557    shl->has_cursor = false;
    558  }
    559 }
    560 
    561 /// Prepare for 'hlsearch' and match highlighting in one window line.
    562 ///
    563 /// @return  true if there is such highlighting and set "search_attr" to the
    564 ///          current highlight attribute.
    565 bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **line,
    566                            match_T *search_hl, int *search_attr, bool *search_attr_from_match)
    567 {
    568  matchitem_T *cur = wp->w_match_head;  // points to the match list
    569  match_T *shl;                     // points to search_hl or a match
    570  bool shl_flag = false;        // flag to indicate whether search_hl
    571                                // has been processed or not
    572  bool area_highlighting = false;
    573 
    574  // Handle highlighting the last used search pattern and matches.
    575  // Do this for both search_hl and the match list.
    576  while (cur != NULL || !shl_flag) {
    577    if (!shl_flag) {
    578      shl = search_hl;
    579      shl_flag = true;
    580    } else {
    581      shl = &cur->mit_hl;
    582    }
    583    shl->startcol = MAXCOL;
    584    shl->endcol = MAXCOL;
    585    shl->attr_cur = 0;
    586    shl->is_addpos = false;
    587    shl->has_cursor = false;
    588    if (cur != NULL) {
    589      cur->mit_pos_cur = 0;
    590    }
    591    next_search_hl(wp, search_hl, shl, lnum, mincol,
    592                   shl == search_hl ? NULL : cur);
    593 
    594    // Need to get the line again, a multi-line regexp may have made it
    595    // invalid.
    596    *line = ml_get_buf(wp->w_buffer, lnum);
    597 
    598    if (shl->lnum != 0 && shl->lnum <= lnum) {
    599      if (shl->lnum == lnum) {
    600        shl->startcol = shl->rm.startpos[0].col;
    601      } else {
    602        shl->startcol = 0;
    603      }
    604      if (lnum == shl->lnum + shl->rm.endpos[0].lnum
    605          - shl->rm.startpos[0].lnum) {
    606        shl->endcol = shl->rm.endpos[0].col;
    607      } else {
    608        shl->endcol = MAXCOL;
    609      }
    610 
    611      // check if the cursor is in the match before changing the columns
    612      if (shl == search_hl) {
    613        check_cur_search_hl(wp, shl);
    614      }
    615 
    616      // Highlight one character for an empty match.
    617      if (shl->startcol == shl->endcol) {
    618        if ((*line)[shl->endcol] != NUL) {
    619          shl->endcol += utfc_ptr2len(*line + shl->endcol);
    620        } else {
    621          shl->endcol++;
    622        }
    623      }
    624      if (shl->startcol < mincol) {   // match at leftcol
    625        shl->attr_cur = shl->attr;
    626        *search_attr = shl->attr;
    627        *search_attr_from_match = shl != search_hl;
    628      }
    629      area_highlighting = true;
    630    }
    631    if (shl != search_hl && cur != NULL) {
    632      cur = cur->mit_next;
    633    }
    634  }
    635  return area_highlighting;
    636 }
    637 
    638 /// For a position in a line: Check for start/end of 'hlsearch' and other
    639 /// matches.
    640 /// After end, check for start/end of next match.
    641 /// When another match, have to check for start again.
    642 /// Watch out for matching an empty string!
    643 /// "on_last_col" is set to true with non-zero search_attr and the next column
    644 /// is endcol.
    645 /// Return the updated search_attr.
    646 int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T *search_hl,
    647                     int *has_match_conc, int *match_conc, bool lcs_eol_todo, bool *on_last_col,
    648                     bool *search_attr_from_match)
    649 {
    650  matchitem_T *cur = wp->w_match_head;  // points to the match list
    651  match_T *shl;                     // points to search_hl or a match
    652  bool shl_flag = false;        // flag to indicate whether search_hl
    653                                // has been processed or not
    654  int search_attr = 0;
    655 
    656  // Do this for 'search_hl' and the match list (ordered by priority).
    657  while (cur != NULL || !shl_flag) {
    658    if (!shl_flag
    659        && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) {
    660      shl = search_hl;
    661      shl_flag = true;
    662    } else {
    663      shl = &cur->mit_hl;
    664    }
    665    if (cur != NULL) {
    666      cur->mit_pos_cur = 0;
    667    }
    668    bool pos_inprogress = true;  // mark that a position match search is
    669                                 // in progress
    670    while (shl->rm.regprog != NULL
    671           || (cur != NULL && pos_inprogress)) {
    672      if (shl->startcol != MAXCOL
    673          && col >= shl->startcol
    674          && col < shl->endcol) {
    675        int next_col = col + utfc_ptr2len(*line + col);
    676 
    677        if (shl->endcol < next_col) {
    678          shl->endcol = next_col;
    679        }
    680        // Highlight the match were the cursor is using the CurSearch
    681        // group.
    682        if (shl == search_hl && shl->has_cursor) {
    683          shl->attr_cur = win_hl_attr(wp, HLF_LC);
    684          if (shl->attr_cur != shl->attr) {
    685            search_hl_has_cursor_lnum = lnum;
    686          }
    687        } else {
    688          shl->attr_cur = shl->attr;
    689        }
    690        // Match with the "Conceal" group results in hiding
    691        // the match.
    692        if (cur != NULL
    693            && shl != search_hl
    694            && syn_name2id("Conceal") == cur->mit_hlg_id) {
    695          *has_match_conc = col == shl->startcol ? 2 : 1;
    696          *match_conc = cur->mit_conceal_char;
    697        } else {
    698          *has_match_conc = 0;
    699        }
    700      } else if (col == shl->endcol) {
    701        shl->attr_cur = 0;
    702 
    703        next_search_hl(wp, search_hl, shl, lnum, col,
    704                       shl == search_hl ? NULL : cur);
    705        pos_inprogress = !(cur == NULL || cur->mit_pos_cur == 0);
    706 
    707        // Need to get the line again, a multi-line regexp
    708        // may have made it invalid.
    709        *line = ml_get_buf(wp->w_buffer, lnum);
    710 
    711        if (shl->lnum == lnum) {
    712          shl->startcol = shl->rm.startpos[0].col;
    713          if (shl->rm.endpos[0].lnum == 0) {
    714            shl->endcol = shl->rm.endpos[0].col;
    715          } else {
    716            shl->endcol = MAXCOL;
    717          }
    718 
    719          // check if the cursor is in the match
    720          if (shl == search_hl) {
    721            check_cur_search_hl(wp, shl);
    722          }
    723 
    724          if (shl->startcol == shl->endcol) {
    725            // highlight empty match, try again after it
    726            char *p = *line + shl->endcol;
    727 
    728            if (*p == NUL) {
    729              shl->endcol++;
    730            } else {
    731              shl->endcol += utfc_ptr2len(p);
    732            }
    733          }
    734 
    735          // Loop to check if the match starts at the
    736          // current position
    737          continue;
    738        }
    739      }
    740      break;
    741    }
    742    if (shl != search_hl && cur != NULL) {
    743      cur = cur->mit_next;
    744    }
    745  }
    746 
    747  // Use attributes from match with highest priority among 'search_hl' and
    748  // the match list.
    749  *search_attr_from_match = false;
    750  search_attr = search_hl->attr_cur;
    751  cur = wp->w_match_head;
    752  shl_flag = false;
    753  while (cur != NULL || !shl_flag) {
    754    if (!shl_flag
    755        && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) {
    756      shl = search_hl;
    757      shl_flag = true;
    758    } else {
    759      shl = &cur->mit_hl;
    760    }
    761    if (shl->attr_cur != 0) {
    762      search_attr = shl->attr_cur;
    763      *on_last_col = col + 1 >= shl->endcol;
    764      *search_attr_from_match = shl != search_hl;
    765    }
    766    if (shl != search_hl && cur != NULL) {
    767      cur = cur->mit_next;
    768    }
    769  }
    770  // Only highlight one character after the last column.
    771  if (*(*line + col) == NUL && (wp->w_p_list && !lcs_eol_todo)) {
    772    search_attr = 0;
    773  }
    774  return search_attr;
    775 }
    776 
    777 bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, colnr_T curcol)
    778 {
    779  colnr_T prevcol = curcol;
    780 
    781  // we're not really at that column when skipping some text
    782  if ((wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
    783    prevcol++;
    784  }
    785 
    786  // Highlight a character after the end of the line if the match started
    787  // at the end of the line or when the match continues in the next line
    788  // (match includes the line break).
    789  if (!search_hl->is_addpos && (prevcol == search_hl->startcol
    790                                || (prevcol > search_hl->startcol
    791                                    && search_hl->endcol == MAXCOL))) {
    792    return true;
    793  }
    794  matchitem_T *cur = wp->w_match_head;  // points to the match list
    795  while (cur != NULL) {
    796    if (!cur->mit_hl.is_addpos && (prevcol == cur->mit_hl.startcol
    797                                   || (prevcol > cur->mit_hl.startcol
    798                                       && cur->mit_hl.endcol == MAXCOL))) {
    799      return true;
    800    }
    801    cur = cur->mit_next;
    802  }
    803 
    804  return false;
    805 }
    806 
    807 /// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
    808 /// or match highlighting.
    809 void get_search_match_hl(win_T *wp, match_T *search_hl, colnr_T col, int *char_attr)
    810 {
    811  matchitem_T *cur = wp->w_match_head;  // points to the match list
    812  match_T *shl;                     // points to search_hl or a match
    813  bool shl_flag = false;        // flag to indicate whether search_hl
    814                                // has been processed or not
    815 
    816  while (cur != NULL || !shl_flag) {
    817    if (!shl_flag
    818        && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) {
    819      shl = search_hl;
    820      shl_flag = true;
    821    } else {
    822      shl = &cur->mit_hl;
    823    }
    824    if (col - 1 == shl->startcol
    825        && (shl == search_hl || !shl->is_addpos)) {
    826      *char_attr = shl->attr;
    827    }
    828    if (shl != search_hl && cur != NULL) {
    829      cur = cur->mit_next;
    830    }
    831  }
    832 }
    833 
    834 static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win)
    835 {
    836  dictitem_T *di;
    837 
    838  if (tv->v_type != VAR_DICT) {
    839    emsg(_(e_dictreq));
    840    return FAIL;
    841  }
    842 
    843  if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) {
    844    *conceal_char = tv_get_string(&di->di_tv);
    845  }
    846 
    847  if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) == NULL) {
    848    return OK;
    849  }
    850 
    851  *win = find_win_by_nr_or_id(&di->di_tv);
    852  if (*win == NULL) {
    853    emsg(_(e_invalwindow));
    854    return FAIL;
    855  }
    856 
    857  return OK;
    858 }
    859 
    860 /// "clearmatches()" function
    861 void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    862 {
    863  win_T *win = get_optional_window(argvars, 0);
    864 
    865  if (win != NULL) {
    866    clear_matches(win);
    867  }
    868 }
    869 
    870 /// "getmatches()" function
    871 void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    872 {
    873  win_T *win = get_optional_window(argvars, 0);
    874 
    875  tv_list_alloc_ret(rettv, kListLenMayKnow);
    876  if (win == NULL) {
    877    return;
    878  }
    879 
    880  matchitem_T *cur = win->w_match_head;
    881  while (cur != NULL) {
    882    dict_T *dict = tv_dict_alloc();
    883    if (cur->mit_match.regprog == NULL) {
    884      // match added with matchaddpos()
    885      for (int i = 0; i < cur->mit_pos_count; i++) {
    886        llpos_T *llpos;
    887        char buf[30];  // use 30 to avoid compiler warning
    888 
    889        llpos = &cur->mit_pos_array[i];
    890        if (llpos->lnum == 0) {
    891          break;
    892        }
    893        list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
    894        tv_list_append_number(l, (varnumber_T)llpos->lnum);
    895        if (llpos->col > 0) {
    896          tv_list_append_number(l, (varnumber_T)llpos->col);
    897          tv_list_append_number(l, (varnumber_T)llpos->len);
    898        }
    899        int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
    900        assert((size_t)len < sizeof(buf));
    901        tv_dict_add_list(dict, buf, (size_t)len, l);
    902      }
    903    } else {
    904      tv_dict_add_str(dict, S_LEN("pattern"), cur->mit_pattern);
    905    }
    906    tv_dict_add_str(dict, S_LEN("group"), syn_id2name(cur->mit_hlg_id));
    907    tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->mit_priority);
    908    tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id);
    909 
    910    if (cur->mit_conceal_char) {
    911      char buf[MB_MAXCHAR + 1];
    912 
    913      buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL;
    914      tv_dict_add_str(dict, S_LEN("conceal"), buf);
    915    }
    916 
    917    tv_list_append_dict(rettv->vval.v_list, dict);
    918    cur = cur->mit_next;
    919  }
    920 }
    921 
    922 /// "setmatches()" function
    923 void f_setmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    924 {
    925  dict_T *d;
    926  list_T *s = NULL;
    927  win_T *win = get_optional_window(argvars, 1);
    928 
    929  rettv->vval.v_number = -1;
    930  if (argvars[0].v_type != VAR_LIST) {
    931    emsg(_(e_listreq));
    932    return;
    933  }
    934  if (win == NULL) {
    935    return;
    936  }
    937 
    938  list_T *const l = argvars[0].vval.v_list;
    939  // To some extent make sure that we are dealing with a list from
    940  // "getmatches()".
    941  int li_idx = 0;
    942  TV_LIST_ITER_CONST(l, li, {
    943    if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
    944        || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
    945      semsg(_("E474: List item %d is either not a dictionary "
    946              "or an empty one"), li_idx);
    947      return;
    948    }
    949    if (!(tv_dict_find(d, S_LEN("group")) != NULL
    950          && (tv_dict_find(d, S_LEN("pattern")) != NULL
    951              || tv_dict_find(d, S_LEN("pos1")) != NULL)
    952          && tv_dict_find(d, S_LEN("priority")) != NULL
    953          && tv_dict_find(d, S_LEN("id")) != NULL)) {
    954      semsg(_("E474: List item %d is missing one of the required keys"),
    955            li_idx);
    956      return;
    957    }
    958    li_idx++;
    959  });
    960 
    961  clear_matches(win);
    962  bool match_add_failed = false;
    963  TV_LIST_ITER_CONST(l, li, {
    964    int i = 0;
    965 
    966    d = TV_LIST_ITEM_TV(li)->vval.v_dict;
    967    dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
    968    if (di == NULL) {
    969      if (s == NULL) {
    970        s = tv_list_alloc(9);
    971      }
    972 
    973      // match from matchaddpos()
    974      for (i = 1; i < 9; i++) {
    975        char buf[30];  // use 30 to avoid compiler warning
    976        snprintf(buf, sizeof(buf), "pos%d", i);
    977        dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
    978        if (pos_di != NULL) {
    979          if (pos_di->di_tv.v_type != VAR_LIST) {
    980            return;
    981          }
    982 
    983          tv_list_append_tv(s, &pos_di->di_tv);
    984          tv_list_ref(s);
    985        } else {
    986          break;
    987        }
    988      }
    989    }
    990 
    991    // Note: there are three number buffers involved:
    992    // - group_buf below.
    993    // - numbuf in tv_dict_get_string().
    994    // - mybuf in tv_get_string().
    995    //
    996    // If you change this code make sure that buffers will not get
    997    // accidentally reused.
    998    char group_buf[NUMBUFLEN];
    999    const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
   1000    const int priority = (int)tv_dict_get_number(d, "priority");
   1001    const int id = (int)tv_dict_get_number(d, "id");
   1002    dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
   1003    const char *const conceal = (conceal_di != NULL
   1004                                 ? tv_get_string(&conceal_di->di_tv)
   1005                                 : NULL);
   1006    if (i == 0) {
   1007      if (match_add(win, group,
   1008                    tv_dict_get_string(d, "pattern", false),
   1009                    priority, id, NULL, conceal) != id) {
   1010        match_add_failed = true;
   1011      }
   1012    } else {
   1013      if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
   1014        match_add_failed = true;
   1015      }
   1016      tv_list_unref(s);
   1017      s = NULL;
   1018    }
   1019  });
   1020  if (!match_add_failed) {
   1021    rettv->vval.v_number = 0;
   1022  }
   1023 }
   1024 
   1025 /// "matchadd()" function
   1026 void f_matchadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1027 {
   1028  char grpbuf[NUMBUFLEN];
   1029  char patbuf[NUMBUFLEN];
   1030  // group
   1031  const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
   1032  // pattern
   1033  const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
   1034  // default priority
   1035  int prio = 10;
   1036  int id = -1;
   1037  bool error = false;
   1038  const char *conceal_char = NULL;
   1039  win_T *win = curwin;
   1040 
   1041  rettv->vval.v_number = -1;
   1042 
   1043  if (grp == NULL || pat == NULL) {
   1044    return;
   1045  }
   1046  if (argvars[2].v_type != VAR_UNKNOWN) {
   1047    prio = (int)tv_get_number_chk(&argvars[2], &error);
   1048    if (argvars[3].v_type != VAR_UNKNOWN) {
   1049      id = (int)tv_get_number_chk(&argvars[3], &error);
   1050      if (argvars[4].v_type != VAR_UNKNOWN
   1051          && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
   1052        return;
   1053      }
   1054    }
   1055  }
   1056  if (error) {
   1057    return;
   1058  }
   1059  if (id >= 1 && id <= 3) {
   1060    semsg(_("E798: ID is reserved for \":match\": %d"), id);
   1061    return;
   1062  }
   1063 
   1064  rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
   1065 }
   1066 
   1067 /// "matchaddpo()" function
   1068 void f_matchaddpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1069 {
   1070  rettv->vval.v_number = -1;
   1071 
   1072  char buf[NUMBUFLEN];
   1073  const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
   1074  if (group == NULL) {
   1075    return;
   1076  }
   1077 
   1078  if (argvars[1].v_type != VAR_LIST) {
   1079    semsg(_(e_listarg), "matchaddpos()");
   1080    return;
   1081  }
   1082 
   1083  list_T *l;
   1084  l = argvars[1].vval.v_list;
   1085  if (tv_list_len(l) == 0) {
   1086    return;
   1087  }
   1088 
   1089  bool error = false;
   1090  int prio = 10;
   1091  int id = -1;
   1092  const char *conceal_char = NULL;
   1093  win_T *win = curwin;
   1094 
   1095  if (argvars[2].v_type != VAR_UNKNOWN) {
   1096    prio = (int)tv_get_number_chk(&argvars[2], &error);
   1097    if (argvars[3].v_type != VAR_UNKNOWN) {
   1098      id = (int)tv_get_number_chk(&argvars[3], &error);
   1099      if (argvars[4].v_type != VAR_UNKNOWN
   1100          && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
   1101        return;
   1102      }
   1103    }
   1104  }
   1105  if (error == true) {
   1106    return;
   1107  }
   1108 
   1109  // id == 3 is ok because matchaddpos() is supposed to substitute :3match
   1110  if (id == 1 || id == 2) {
   1111    semsg(_("E798: ID is reserved for \"match\": %d"), id);
   1112    return;
   1113  }
   1114 
   1115  rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
   1116 }
   1117 
   1118 /// "matcharg()" function
   1119 void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1120 {
   1121  const int id = (int)tv_get_number(&argvars[0]);
   1122 
   1123  tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
   1124                            ? 2
   1125                            : 0));
   1126 
   1127  if (id >= 1 && id <= 3) {
   1128    matchitem_T *const m = get_match(curwin, id);
   1129 
   1130    if (m != NULL) {
   1131      tv_list_append_string(rettv->vval.v_list, syn_id2name(m->mit_hlg_id), -1);
   1132      tv_list_append_string(rettv->vval.v_list, m->mit_pattern, -1);
   1133    } else {
   1134      tv_list_append_string(rettv->vval.v_list, NULL, 0);
   1135      tv_list_append_string(rettv->vval.v_list, NULL, 0);
   1136    }
   1137  }
   1138 }
   1139 
   1140 /// "matchdelete()" function
   1141 void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
   1142 {
   1143  win_T *win = get_optional_window(argvars, 1);
   1144  if (win == NULL) {
   1145    rettv->vval.v_number = -1;
   1146  } else {
   1147    rettv->vval.v_number = match_delete(win,
   1148                                        (int)tv_get_number(&argvars[0]), true);
   1149  }
   1150 }
   1151 
   1152 /// ":[N]match {group} {pattern}"
   1153 /// Sets nextcmd to the start of the next command, if any.  Also called when
   1154 /// skipping commands to find the next command.
   1155 void ex_match(exarg_T *eap)
   1156 {
   1157  char *g = NULL;
   1158  char *end;
   1159  int id;
   1160 
   1161  if (eap->line2 <= 3) {
   1162    id = (int)eap->line2;
   1163  } else {
   1164    emsg(e_invcmd);
   1165    return;
   1166  }
   1167 
   1168  // First clear any old pattern.
   1169  if (!eap->skip) {
   1170    match_delete(curwin, id, false);
   1171  }
   1172 
   1173  if (ends_excmd(*eap->arg)) {
   1174    end = eap->arg;
   1175  } else if ((STRNICMP(eap->arg, "none", 4) == 0
   1176              && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
   1177    end = eap->arg + 4;
   1178  } else {
   1179    char *p = skiptowhite(eap->arg);
   1180    if (!eap->skip) {
   1181      g = xmemdupz(eap->arg, (size_t)(p - eap->arg));
   1182    }
   1183    p = skipwhite(p);
   1184    if (*p == NUL) {
   1185      // There must be two arguments.
   1186      xfree(g);
   1187      semsg(_(e_invarg2), eap->arg);
   1188      return;
   1189    }
   1190    end = skip_regexp(p + 1, *p, true);
   1191    if (!eap->skip) {
   1192      if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
   1193        xfree(g);
   1194        eap->errmsg = ex_errmsg(e_trailing_arg, end);
   1195        return;
   1196      }
   1197      if (*end != *p) {
   1198        xfree(g);
   1199        semsg(_(e_invarg2), p);
   1200        return;
   1201      }
   1202 
   1203      int c = (uint8_t)(*end);
   1204      *end = NUL;
   1205      match_add(curwin, g, p + 1, 10, id, NULL, NULL);
   1206      xfree(g);
   1207      *end = (char)c;
   1208    }
   1209  }
   1210  eap->nextcmd = find_nextcmd(end);
   1211 }