aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLewis Russell <lewis6991@gmail.com>2022-03-22 22:31:50 +0000
committerGitHub <noreply@github.com>2022-03-23 06:31:50 +0800
commit7863e6b709976d53d69b8495f1ab4417d965f4b3 (patch)
tree486780f066cc80cffb5daf95101871c586b6a2af
parentf63a52a0db936d04bdc65c121f7bd279709d4cf2 (diff)
downloadrneovim-7863e6b709976d53d69b8495f1ab4417d965f4b3.tar.gz
rneovim-7863e6b709976d53d69b8495f1ab4417d965f4b3.tar.bz2
rneovim-7863e6b709976d53d69b8495f1ab4417d965f4b3.zip
vim-patch:8.2.1078: highlight and match functionality together in one file (#17805)
Problem: Highlight and match functionality together in one file. Solution: Move match functionality to a separate file. (Yegappan Lakshmanan, closes vim/vim#6352) https://github.com/vim/vim/commit/06cf97e714fd8bf9b35ff5f8a6f2302c79acdd03
-rw-r--r--src/nvim/eval/funcs.c1
-rw-r--r--src/nvim/ex_docmd.c1
-rw-r--r--src/nvim/highlight_group.c1172
-rw-r--r--src/nvim/match.c1181
-rw-r--r--src/nvim/match.h12
-rw-r--r--src/nvim/screen.c1
-rw-r--r--src/nvim/window.c2
7 files changed, 1198 insertions, 1172 deletions
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 69055db9a6..41b419c150 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -39,6 +39,7 @@
#include "nvim/lua/executor.h"
#include "nvim/macros.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/math.h"
#include "nvim/memline.h"
#include "nvim/mouse.h"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a5732a006d..5ef9f6c05e 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -44,6 +44,7 @@
#include "nvim/keymap.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/match.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index ee78a79a97..d448f3a646 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -2,25 +2,18 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// highlight_group.c: code for managing highlight groups
-// Includes highlighting matches
#include <stdbool.h>
#include "nvim/autocmd.h"
#include "nvim/api/private/helpers.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
-#include "nvim/eval/funcs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
-#include "nvim/garray.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
-#include "nvim/memline.h"
+#include "nvim/match.h"
#include "nvim/option.h"
-#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/screen.h"
@@ -32,9 +25,6 @@
/// @}
#define MAX_SYN_NAME 200
-#define SEARCH_HL_PRIORITY 0
-
-static char *e_invalwindow = N_("E957: Invalid window number");
// builtin |highlight-groups|
static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
@@ -2807,1163 +2797,3 @@ int name_to_ctermcolor(const char *name)
TriState bold = kNone;
return lookup_color(i, false, &bold);
}
-
-/// Add match to the match list of window 'wp'. The pattern 'pat' will be
-/// highlighted with the group 'grp' with priority 'prio'.
-/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
-///
-/// @param[in] id a desired ID 'id' can be specified
-/// (greater than or equal to 1). -1 must be specified if no
-/// particular ID is desired
-/// @param[in] conceal_char pointer to conceal replacement char
-/// @return ID of added match, -1 on failure.
-static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id,
- list_T *pos_list, const char *const conceal_char)
- FUNC_ATTR_NONNULL_ARG(1, 2)
-{
- matchitem_T *cur;
- matchitem_T *prev;
- matchitem_T *m;
- int hlg_id;
- regprog_T *regprog = NULL;
- int rtype = SOME_VALID;
-
- if (*grp == NUL || (pat != NULL && *pat == NUL)) {
- return -1;
- }
- if (id < -1 || id == 0) {
- semsg(_("E799: Invalid ID: %" PRId64
- " (must be greater than or equal to 1)"),
- (int64_t)id);
- return -1;
- }
- if (id != -1) {
- cur = wp->w_match_head;
- while (cur != NULL) {
- if (cur->id == id) {
- semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
- return -1;
- }
- cur = cur->next;
- }
- }
- if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
- return -1;
- }
- if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) {
- semsg(_(e_invarg2), pat);
- return -1;
- }
-
- // Find available match ID.
- while (id == -1) {
- cur = wp->w_match_head;
- while (cur != NULL && cur->id != wp->w_next_match_id) {
- cur = cur->next;
- }
- if (cur == NULL) {
- id = wp->w_next_match_id;
- }
- wp->w_next_match_id++;
- }
-
- // Build new match.
- m = xcalloc(1, sizeof(matchitem_T));
- m->id = id;
- m->priority = prio;
- m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat);
- m->hlg_id = hlg_id;
- m->match.regprog = regprog;
- m->match.rmm_ic = false;
- m->match.rmm_maxcol = 0;
- m->conceal_char = 0;
- if (conceal_char != NULL) {
- m->conceal_char = utf_ptr2char((const char_u *)conceal_char);
- }
-
- // Set up position matches
- if (pos_list != NULL) {
- linenr_T toplnum = 0;
- linenr_T botlnum = 0;
-
- int i = 0;
- TV_LIST_ITER(pos_list, li, {
- linenr_T lnum = 0;
- colnr_T col = 0;
- int len = 1;
- bool error = false;
-
- if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
- const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list;
- const listitem_T *subli = tv_list_first(subl);
- if (subli == NULL) {
- semsg(_("E5030: Empty list at position %d"),
- (int)tv_list_idx_of_item(pos_list, li));
- goto fail;
- }
- lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (error) {
- goto fail;
- }
- if (lnum <= 0) {
- continue;
- }
- m->pos.pos[i].lnum = lnum;
- subli = TV_LIST_ITEM_NEXT(subl, subli);
- if (subli != NULL) {
- col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (error) {
- goto fail;
- }
- if (col < 0) {
- continue;
- }
- subli = TV_LIST_ITEM_NEXT(subl, subli);
- if (subli != NULL) {
- len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (len < 0) {
- continue;
- }
- if (error) {
- goto fail;
- }
- }
- }
- m->pos.pos[i].col = col;
- m->pos.pos[i].len = len;
- } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) {
- if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) {
- continue;
- }
- m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number;
- m->pos.pos[i].col = 0;
- m->pos.pos[i].len = 0;
- } else {
- semsg(_("E5031: List or number required at position %d"),
- (int)tv_list_idx_of_item(pos_list, li));
- goto fail;
- }
- if (toplnum == 0 || lnum < toplnum) {
- toplnum = lnum;
- }
- if (botlnum == 0 || lnum >= botlnum) {
- botlnum = lnum + 1;
- }
- i++;
- if (i >= MAXPOSMATCH) {
- break;
- }
- });
-
- // Calculate top and bottom lines for redrawing area
- if (toplnum != 0) {
- if (wp->w_buffer->b_mod_set) {
- if (wp->w_buffer->b_mod_top > toplnum) {
- wp->w_buffer->b_mod_top = toplnum;
- }
- if (wp->w_buffer->b_mod_bot < botlnum) {
- wp->w_buffer->b_mod_bot = botlnum;
- }
- } else {
- wp->w_buffer->b_mod_set = true;
- wp->w_buffer->b_mod_top = toplnum;
- wp->w_buffer->b_mod_bot = botlnum;
- wp->w_buffer->b_mod_xlines = 0;
- }
- m->pos.toplnum = toplnum;
- m->pos.botlnum = botlnum;
- rtype = VALID;
- }
- }
-
- // Insert new match. The match list is in ascending order with regard to
- // the match priorities.
- cur = wp->w_match_head;
- prev = cur;
- while (cur != NULL && prio >= cur->priority) {
- prev = cur;
- cur = cur->next;
- }
- if (cur == prev) {
- wp->w_match_head = m;
- } else {
- prev->next = m;
- }
- m->next = cur;
-
- redraw_later(wp, rtype);
- return id;
-
-fail:
- xfree(m);
- return -1;
-}
-
-/// Delete match with ID 'id' in the match list of window 'wp'.
-///
-/// @param perr print error messages if true.
-static int match_delete(win_T *wp, int id, bool perr)
-{
- matchitem_T *cur = wp->w_match_head;
- matchitem_T *prev = cur;
- int rtype = SOME_VALID;
-
- if (id < 1) {
- if (perr) {
- semsg(_("E802: Invalid ID: %" PRId64
- " (must be greater than or equal to 1)"),
- (int64_t)id);
- }
- return -1;
- }
- while (cur != NULL && cur->id != id) {
- prev = cur;
- cur = cur->next;
- }
- if (cur == NULL) {
- if (perr) {
- semsg(_("E803: ID not found: %" PRId64), (int64_t)id);
- }
- return -1;
- }
- if (cur == prev) {
- wp->w_match_head = cur->next;
- } else {
- prev->next = cur->next;
- }
- vim_regfree(cur->match.regprog);
- xfree(cur->pattern);
- if (cur->pos.toplnum != 0) {
- if (wp->w_buffer->b_mod_set) {
- if (wp->w_buffer->b_mod_top > cur->pos.toplnum) {
- wp->w_buffer->b_mod_top = cur->pos.toplnum;
- }
- if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) {
- wp->w_buffer->b_mod_bot = cur->pos.botlnum;
- }
- } else {
- wp->w_buffer->b_mod_set = true;
- wp->w_buffer->b_mod_top = cur->pos.toplnum;
- wp->w_buffer->b_mod_bot = cur->pos.botlnum;
- wp->w_buffer->b_mod_xlines = 0;
- }
- rtype = VALID;
- }
- xfree(cur);
- redraw_later(wp, rtype);
- return 0;
-}
-
-/// Delete all matches in the match list of window 'wp'.
-void clear_matches(win_T *wp)
-{
- matchitem_T *m;
-
- while (wp->w_match_head != NULL) {
- m = wp->w_match_head->next;
- vim_regfree(wp->w_match_head->match.regprog);
- xfree(wp->w_match_head->pattern);
- xfree(wp->w_match_head);
- wp->w_match_head = m;
- }
- redraw_later(wp, SOME_VALID);
-}
-
-/// Get match from ID 'id' in window 'wp'.
-/// Return NULL if match not found.
-matchitem_T *get_match(win_T *wp, int id)
-{
- matchitem_T *cur = wp->w_match_head;
-
- while (cur != NULL && cur->id != id) {
- cur = cur->next;
- }
- return cur;
-}
-
-/// Init for calling prepare_search_hl().
-void init_search_hl(win_T *wp, match_T *search_hl)
- FUNC_ATTR_NONNULL_ALL
-{
- // Setup for match and 'hlsearch' highlighting. Disable any previous
- // match
- matchitem_T *cur = wp->w_match_head;
- while (cur != NULL) {
- cur->hl.rm = cur->match;
- if (cur->hlg_id == 0) {
- cur->hl.attr = 0;
- } else {
- cur->hl.attr = syn_id2attr(cur->hlg_id);
- }
- cur->hl.buf = wp->w_buffer;
- cur->hl.lnum = 0;
- cur->hl.first_lnum = 0;
- // Set the time limit to 'redrawtime'.
- cur->hl.tm = profile_setlimit(p_rdt);
- cur = cur->next;
- }
- search_hl->buf = wp->w_buffer;
- search_hl->lnum = 0;
- search_hl->first_lnum = 0;
- search_hl->attr = win_hl_attr(wp, HLF_L);
-
- // time limit is set at the toplevel, for all windows
-}
-
-/// @param shl points to a match. Fill on match.
-/// @param posmatch match positions
-/// @param mincol minimal column for a match
-///
-/// @return one on match, otherwise return zero.
-static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol)
- FUNC_ATTR_NONNULL_ALL
-{
- int i;
- int found = -1;
-
- shl->lnum = 0;
- for (i = posmatch->cur; i < MAXPOSMATCH; i++) {
- llpos_T *pos = &posmatch->pos[i];
-
- if (pos->lnum == 0) {
- break;
- }
- if (pos->len == 0 && pos->col < mincol) {
- continue;
- }
- if (pos->lnum == lnum) {
- if (found >= 0) {
- // if this match comes before the one at "found" then swap
- // them
- if (pos->col < posmatch->pos[found].col) {
- llpos_T tmp = *pos;
-
- *pos = posmatch->pos[found];
- posmatch->pos[found] = tmp;
- }
- } else {
- found = i;
- }
- }
- }
- posmatch->cur = 0;
- if (found >= 0) {
- colnr_T start = posmatch->pos[found].col == 0
- ? 0: posmatch->pos[found].col - 1;
- colnr_T end = posmatch->pos[found].col == 0
- ? MAXCOL : start + posmatch->pos[found].len;
-
- shl->lnum = lnum;
- shl->rm.startpos[0].lnum = 0;
- shl->rm.startpos[0].col = start;
- shl->rm.endpos[0].lnum = 0;
- shl->rm.endpos[0].col = end;
- shl->is_addpos = true;
- posmatch->cur = found + 1;
- return 1;
- }
- return 0;
-}
-
-/// Search for a next 'hlsearch' or match.
-/// Uses shl->buf.
-/// Sets shl->lnum and shl->rm contents.
-/// Note: Assumes a previous match is always before "lnum", unless
-/// shl->lnum is zero.
-/// Careful: Any pointers for buffer lines will become invalid.
-///
-/// @param shl points to search_hl or a match
-/// @param mincol minimal column for a match
-/// @param cur to retrieve match positions if any
-static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum,
- colnr_T mincol, matchitem_T *cur)
- FUNC_ATTR_NONNULL_ARG(2)
-{
- linenr_T l;
- colnr_T matchcol;
- long nmatched = 0;
- int save_called_emsg = called_emsg;
-
- // for :{range}s/pat only highlight inside the range
- if (lnum < search_first_line || lnum > search_last_line) {
- shl->lnum = 0;
- return;
- }
-
- if (shl->lnum != 0) {
- // Check for three situations:
- // 1. If the "lnum" is below a previous match, start a new search.
- // 2. If the previous match includes "mincol", use it.
- // 3. Continue after the previous match.
- l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
- if (lnum > l) {
- shl->lnum = 0;
- } else if (lnum < l || shl->rm.endpos[0].col > mincol) {
- return;
- }
- }
-
- // Repeat searching for a match until one is found that includes "mincol"
- // or none is found in this line.
- called_emsg = false;
- for (;;) {
- // Stop searching after passing the time limit.
- if (profile_passed_limit(shl->tm)) {
- shl->lnum = 0; // no match found in time
- break;
- }
- // Three situations:
- // 1. No useful previous match: search from start of line.
- // 2. Not Vi compatible or empty match: continue at next character.
- // Break the loop if this is beyond the end of the line.
- // 3. Vi compatible searching: continue at end of previous match.
- if (shl->lnum == 0) {
- matchcol = 0;
- } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
- || (shl->rm.endpos[0].lnum == 0
- && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
- char_u *ml;
-
- matchcol = shl->rm.startpos[0].col;
- ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
- if (*ml == NUL) {
- matchcol++;
- shl->lnum = 0;
- break;
- }
- matchcol += utfc_ptr2len(ml);
- } else {
- matchcol = shl->rm.endpos[0].col;
- }
-
- shl->lnum = lnum;
- if (shl->rm.regprog != NULL) {
- // Remember whether shl->rm is using a copy of the regprog in
- // cur->match.
- bool regprog_is_copy = (shl != search_hl
- && cur != NULL
- && shl == &cur->hl
- && cur->match.regprog == cur->hl.rm.regprog);
- int timed_out = false;
-
- nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
- &(shl->tm), &timed_out);
- // Copy the regprog, in case it got freed and recompiled.
- if (regprog_is_copy) {
- cur->match.regprog = cur->hl.rm.regprog;
- }
- if (called_emsg || got_int || timed_out) {
- // Error while handling regexp: stop using this regexp.
- if (shl == search_hl) {
- // don't free regprog in the match list, it's a copy
- vim_regfree(shl->rm.regprog);
- set_no_hlsearch(true);
- }
- shl->rm.regprog = NULL;
- shl->lnum = 0;
- got_int = false; // avoid the "Type :quit to exit Vim" message
- break;
- }
- } else if (cur != NULL) {
- nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
- }
- if (nmatched == 0) {
- shl->lnum = 0; // no match found
- break;
- }
- if (shl->rm.startpos[0].lnum > 0
- || shl->rm.startpos[0].col >= mincol
- || nmatched > 1
- || shl->rm.endpos[0].col > mincol) {
- shl->lnum += shl->rm.startpos[0].lnum;
- break; // useful match found
- }
-
- // Restore called_emsg for assert_fails().
- called_emsg = save_called_emsg;
- }
-}
-
-/// Advance to the match in window "wp" line "lnum" or past it.
-void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
- FUNC_ATTR_NONNULL_ALL
-{
- matchitem_T *cur; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag; // flag to indicate whether search_hl
- // has been processed or not
-
- // When using a multi-line pattern, start searching at the top
- // of the window or just after a closed fold.
- // Do this both for search_hl and the match list.
- cur = wp->w_match_head;
- shl_flag = false;
- while (cur != NULL || shl_flag == false) {
- if (shl_flag == false) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl; // -V595
- }
- if (shl->rm.regprog != NULL
- && shl->lnum == 0
- && re_multiline(shl->rm.regprog)) {
- if (shl->first_lnum == 0) {
- for (shl->first_lnum = lnum;
- shl->first_lnum > wp->w_topline;
- shl->first_lnum--) {
- if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) {
- break;
- }
- }
- }
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- bool pos_inprogress = true; // mark that a position match search is
- // in progress
- int n = 0;
- while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
- || (cur != NULL && pos_inprogress))) {
- next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
- shl == search_hl ? NULL : cur);
- pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
- if (shl->lnum != 0) {
- shl->first_lnum = shl->lnum
- + shl->rm.endpos[0].lnum
- - shl->rm.startpos[0].lnum;
- n = shl->rm.endpos[0].col;
- } else {
- shl->first_lnum++;
- n = 0;
- }
- }
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-}
-
-/// Prepare for 'hlsearch' and match highlighting in one window line.
-/// Return true if there is such highlighting and set "search_attr" to the
-/// current highlight attribute.
-bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line,
- match_T *search_hl, int *search_attr, bool *search_attr_from_match)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
- bool area_highlighting = false;
-
- // Handle highlighting the last used search pattern and matches.
- // Do this for both search_hl and the match list.
- while (cur != NULL || !shl_flag) {
- if (!shl_flag) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl; // -V595
- }
- shl->startcol = MAXCOL;
- shl->endcol = MAXCOL;
- shl->attr_cur = 0;
- shl->is_addpos = false;
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- next_search_hl(wp, search_hl, shl, lnum, mincol,
- shl == search_hl ? NULL : cur);
-
- // Need to get the line again, a multi-line regexp may have made it
- // invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- if (shl->lnum != 0 && shl->lnum <= lnum) {
- if (shl->lnum == lnum) {
- shl->startcol = shl->rm.startpos[0].col;
- } else {
- shl->startcol = 0;
- }
- if (lnum == shl->lnum + shl->rm.endpos[0].lnum
- - shl->rm.startpos[0].lnum) {
- shl->endcol = shl->rm.endpos[0].col;
- } else {
- shl->endcol = MAXCOL;
- }
- // Highlight one character for an empty match.
- if (shl->startcol == shl->endcol) {
- if ((*line)[shl->endcol] != NUL) {
- shl->endcol += utfc_ptr2len(*line + shl->endcol);
- } else {
- shl->endcol++;
- }
- }
- if ((long)shl->startcol < mincol) { // match at leftcol
- shl->attr_cur = shl->attr;
- *search_attr = shl->attr;
- *search_attr_from_match = shl != search_hl;
- }
- area_highlighting = true;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
- return area_highlighting;
-}
-
-/// For a position in a line: Check for start/end of 'hlsearch' and other
-/// matches.
-/// After end, check for start/end of next match.
-/// When another match, have to check for start again.
-/// Watch out for matching an empty string!
-/// Return the updated search_attr.
-int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl,
- int *has_match_conc, int *match_conc, int lcs_eol_one,
- bool *search_attr_from_match)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
- int search_attr = 0;
-
- // Do this for 'search_hl' and the match list (ordered by priority).
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- bool pos_inprogress = true; // mark that a position match search is
- // in progress
- while (shl->rm.regprog != NULL
- || (cur != NULL && pos_inprogress)) {
- if (shl->startcol != MAXCOL
- && col >= shl->startcol
- && col < shl->endcol) {
- int next_col = col + utfc_ptr2len(*line + col);
-
- if (shl->endcol < next_col) {
- shl->endcol = next_col;
- }
- shl->attr_cur = shl->attr;
- // Match with the "Conceal" group results in hiding
- // the match.
- if (cur != NULL
- && shl != search_hl
- && syn_name2id("Conceal") == cur->hlg_id) {
- *has_match_conc = col == shl->startcol ? 2 : 1;
- *match_conc = cur->conceal_char;
- } else {
- *has_match_conc = 0;
- }
- } else if (col == shl->endcol) {
- shl->attr_cur = 0;
-
- next_search_hl(wp, search_hl, shl, lnum, col,
- shl == search_hl ? NULL : cur);
- pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
-
- // Need to get the line again, a multi-line regexp
- // may have made it invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- if (shl->lnum == lnum) {
- shl->startcol = shl->rm.startpos[0].col;
- if (shl->rm.endpos[0].lnum == 0) {
- shl->endcol = shl->rm.endpos[0].col;
- } else {
- shl->endcol = MAXCOL;
- }
-
- if (shl->startcol == shl->endcol) {
- // highlight empty match, try again after it
- shl->endcol += utfc_ptr2len(*line + shl->endcol);
- }
-
- // Loop to check if the match starts at the
- // current position
- continue;
- }
- }
- break;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-
- // Use attributes from match with highest priority among
- // 'search_hl' and the match list.
- *search_attr_from_match = false;
- search_attr = search_hl->attr_cur;
- cur = wp->w_match_head;
- shl_flag = false;
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (shl->attr_cur != 0) {
- search_attr = shl->attr_cur;
- *search_attr_from_match = shl != search_hl;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
- // Only highlight one character after the last column.
- if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) {
- search_attr = 0;
- }
- return search_attr;
-}
-
-bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
-{
- long prevcol = curcol;
- matchitem_T *cur; // points to the match list
-
- // we're not really at that column when skipping some text
- if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
- prevcol++;
- }
-
- if (!search_hl->is_addpos && prevcol == search_hl->startcol) {
- return true;
- } else {
- cur = wp->w_match_head;
- while (cur != NULL) {
- if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) {
- return true;
- }
- cur = cur->next;
- }
- }
- return false;
-}
-
-/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
-/// or match highlighting.
-void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
-
- *char_attr = search_hl->attr;
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (col - 1 == (long)shl->startcol
- && (shl == search_hl || !shl->is_addpos)) {
- *char_attr = shl->attr;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-}
-
-static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win)
-{
- dictitem_T *di;
-
- if (tv->v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return FAIL;
- }
-
- if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) {
- *conceal_char = tv_get_string(&di->di_tv);
- }
-
- if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) {
- *win = find_win_by_nr_or_id(&di->di_tv);
- if (*win == NULL) {
- emsg(_(e_invalwindow));
- return FAIL;
- }
- }
-
- return OK;
-}
-
-/// "clearmatches()" function
-void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win = get_optional_window(argvars, 0);
-
- if (win != NULL) {
- clear_matches(win);
- }
-}
-
-/// "getmatches()" function
-void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- matchitem_T *cur;
- int i;
- win_T *win = get_optional_window(argvars, 0);
-
- if (win == NULL) {
- return;
- }
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- cur = win->w_match_head;
- while (cur != NULL) {
- dict_T *dict = tv_dict_alloc();
- if (cur->match.regprog == NULL) {
- // match added with matchaddpos()
- for (i = 0; i < MAXPOSMATCH; i++) {
- llpos_T *llpos;
- char buf[30]; // use 30 to avoid compiler warning
-
- llpos = &cur->pos.pos[i];
- if (llpos->lnum == 0) {
- break;
- }
- list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
- tv_list_append_number(l, (varnumber_T)llpos->lnum);
- if (llpos->col > 0) {
- tv_list_append_number(l, (varnumber_T)llpos->col);
- tv_list_append_number(l, (varnumber_T)llpos->len);
- }
- int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
- assert((size_t)len < sizeof(buf));
- tv_dict_add_list(dict, buf, (size_t)len, l);
- }
- } else {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
- }
- tv_dict_add_str(dict, S_LEN("group"),
- (const char *)syn_id2name(cur->hlg_id));
- tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
- tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
-
- if (cur->conceal_char) {
- char buf[MB_MAXBYTES + 1];
-
- buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL;
- tv_dict_add_str(dict, S_LEN("conceal"), buf);
- }
-
- tv_list_append_dict(rettv->vval.v_list, dict);
- cur = cur->next;
- }
-}
-
-/// "setmatches()" function
-void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- list_T *s = NULL;
- win_T *win = get_optional_window(argvars, 1);
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- emsg(_(e_listreq));
- return;
- }
- if (win == NULL) {
- return;
- }
-
- list_T *const l = argvars[0].vval.v_list;
- // To some extent make sure that we are dealing with a list from
- // "getmatches()".
- int li_idx = 0;
- TV_LIST_ITER_CONST(l, li, {
- if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
- || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
- semsg(_("E474: List item %d is either not a dictionary "
- "or an empty one"), li_idx);
- return;
- }
- if (!(tv_dict_find(d, S_LEN("group")) != NULL
- && (tv_dict_find(d, S_LEN("pattern")) != NULL
- || tv_dict_find(d, S_LEN("pos1")) != NULL)
- && tv_dict_find(d, S_LEN("priority")) != NULL
- && tv_dict_find(d, S_LEN("id")) != NULL)) {
- semsg(_("E474: List item %d is missing one of the required keys"),
- li_idx);
- return;
- }
- li_idx++;
- });
-
- clear_matches(win);
- bool match_add_failed = false;
- TV_LIST_ITER_CONST(l, li, {
- int i = 0;
-
- d = TV_LIST_ITEM_TV(li)->vval.v_dict;
- dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
- if (di == NULL) {
- if (s == NULL) {
- s = tv_list_alloc(9);
- }
-
- // match from matchaddpos()
- for (i = 1; i < 9; i++) {
- char buf[30]; // use 30 to avoid compiler warning
- snprintf(buf, sizeof(buf), "pos%d", i);
- dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
- if (pos_di != NULL) {
- if (pos_di->di_tv.v_type != VAR_LIST) {
- return;
- }
-
- tv_list_append_tv(s, &pos_di->di_tv);
- tv_list_ref(s);
- } else {
- break;
- }
- }
- }
-
- // Note: there are three number buffers involved:
- // - group_buf below.
- // - numbuf in tv_dict_get_string().
- // - mybuf in tv_get_string().
- //
- // If you change this code make sure that buffers will not get
- // accidentally reused.
- char group_buf[NUMBUFLEN];
- const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
- const int priority = (int)tv_dict_get_number(d, "priority");
- const int id = (int)tv_dict_get_number(d, "id");
- dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
- const char *const conceal = (conceal_di != NULL
- ? tv_get_string(&conceal_di->di_tv)
- : NULL);
- if (i == 0) {
- if (match_add(win, group,
- tv_dict_get_string(d, "pattern", false),
- priority, id, NULL, conceal) != id) {
- match_add_failed = true;
- }
- } else {
- if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
- match_add_failed = true;
- }
- tv_list_unref(s);
- s = NULL;
- }
- });
- if (!match_add_failed) {
- rettv->vval.v_number = 0;
- }
-}
-
-
-/// "matchadd()" function
-void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char grpbuf[NUMBUFLEN];
- char patbuf[NUMBUFLEN];
- // group
- const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
- // pattern
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- // default priority
- int prio = 10;
- int id = -1;
- bool error = false;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- rettv->vval.v_number = -1;
-
- if (grp == NULL || pat == NULL) {
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = (int)tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = (int)tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error) {
- return;
- }
- if (id >= 1 && id <= 3) {
- semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
-}
-
-/// "matchaddpo()" function
-void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
- if (group == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_LIST) {
- semsg(_(e_listarg), "matchaddpos()");
- return;
- }
-
- list_T *l;
- l = argvars[1].vval.v_list;
- if (l == NULL) {
- return;
- }
-
- bool error = false;
- int prio = 10;
- int id = -1;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = (int)tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = (int)tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error == true) {
- return;
- }
-
- // id == 3 is ok because matchaddpos() is supposed to substitute :3match
- if (id == 1 || id == 2) {
- semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
-}
-
-/// "matcharg()" function
-void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = (int)tv_get_number(&argvars[0]);
-
- tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
- ? 2
- : 0));
-
- if (id >= 1 && id <= 3) {
- matchitem_T *const m = get_match(curwin, id);
-
- if (m != NULL) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)syn_id2name(m->hlg_id), -1);
- tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
- } else {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- }
- }
-}
-
-/// "matchdelete()" function
-void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win = get_optional_window(argvars, 1);
- if (win == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = match_delete(win,
- (int)tv_get_number(&argvars[0]), true);
- }
-}
-
-/// ":[N]match {group} {pattern}"
-/// Sets nextcmd to the start of the next command, if any. Also called when
-/// skipping commands to find the next command.
-void ex_match(exarg_T *eap)
-{
- char_u *p;
- char_u *g = NULL;
- char_u *end;
- int c;
- int id;
-
- if (eap->line2 <= 3) {
- id = (int)eap->line2;
- } else {
- emsg(e_invcmd);
- return;
- }
-
- // First clear any old pattern.
- if (!eap->skip) {
- match_delete(curwin, id, false);
- }
-
- if (ends_excmd(*eap->arg)) {
- end = eap->arg;
- } else if ((STRNICMP(eap->arg, "none", 4) == 0
- && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
- end = eap->arg + 4;
- } else {
- p = skiptowhite(eap->arg);
- if (!eap->skip) {
- g = vim_strnsave(eap->arg, (size_t)(p - eap->arg));
- }
- p = skipwhite(p);
- if (*p == NUL) {
- // There must be two arguments.
- xfree(g);
- semsg(_(e_invarg2), eap->arg);
- return;
- }
- end = skip_regexp(p + 1, *p, true, NULL);
- if (!eap->skip) {
- if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
- xfree(g);
- eap->errmsg = e_trailing;
- return;
- }
- if (*end != *p) {
- xfree(g);
- semsg(_(e_invarg2), p);
- return;
- }
-
- c = *end;
- *end = NUL;
- match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
- NULL, NULL);
- xfree(g);
- *end = (char_u)c;
- }
- }
- eap->nextcmd = find_nextcmd(end);
-}
-
diff --git a/src/nvim/match.c b/src/nvim/match.c
new file mode 100644
index 0000000000..af89319a09
--- /dev/null
+++ b/src/nvim/match.c
@@ -0,0 +1,1181 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// match.c: functions for highlighting matches
+
+#include <stdbool.h>
+#include "nvim/charset.h"
+#include "nvim/fold.h"
+#include "nvim/highlight_group.h"
+#include "nvim/match.h"
+#include "nvim/memline.h"
+#include "nvim/regexp.h"
+#include "nvim/runtime.h"
+#include "nvim/screen.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "match.c.generated.h"
+#endif
+
+static char *e_invalwindow = N_("E957: Invalid window number");
+
+#define SEARCH_HL_PRIORITY 0
+
+/// Add match to the match list of window 'wp'. The pattern 'pat' will be
+/// highlighted with the group 'grp' with priority 'prio'.
+/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
+///
+/// @param[in] id a desired ID 'id' can be specified
+/// (greater than or equal to 1). -1 must be specified if no
+/// particular ID is desired
+/// @param[in] conceal_char pointer to conceal replacement char
+/// @return ID of added match, -1 on failure.
+static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id,
+ list_T *pos_list, const char *const conceal_char)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ matchitem_T *cur;
+ matchitem_T *prev;
+ matchitem_T *m;
+ int hlg_id;
+ regprog_T *regprog = NULL;
+ int rtype = SOME_VALID;
+
+ if (*grp == NUL || (pat != NULL && *pat == NUL)) {
+ return -1;
+ }
+ if (id < -1 || id == 0) {
+ semsg(_("E799: Invalid ID: %" PRId64
+ " (must be greater than or equal to 1)"),
+ (int64_t)id);
+ return -1;
+ }
+ if (id != -1) {
+ cur = wp->w_match_head;
+ while (cur != NULL) {
+ if (cur->id == id) {
+ semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
+ return -1;
+ }
+ cur = cur->next;
+ }
+ }
+ if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
+ return -1;
+ }
+ if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) {
+ semsg(_(e_invarg2), pat);
+ return -1;
+ }
+
+ // Find available match ID.
+ while (id == -1) {
+ cur = wp->w_match_head;
+ while (cur != NULL && cur->id != wp->w_next_match_id) {
+ cur = cur->next;
+ }
+ if (cur == NULL) {
+ id = wp->w_next_match_id;
+ }
+ wp->w_next_match_id++;
+ }
+
+ // Build new match.
+ m = xcalloc(1, sizeof(matchitem_T));
+ m->id = id;
+ m->priority = prio;
+ m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat);
+ m->hlg_id = hlg_id;
+ m->match.regprog = regprog;
+ m->match.rmm_ic = false;
+ m->match.rmm_maxcol = 0;
+ m->conceal_char = 0;
+ if (conceal_char != NULL) {
+ m->conceal_char = utf_ptr2char((const char_u *)conceal_char);
+ }
+
+ // Set up position matches
+ if (pos_list != NULL) {
+ linenr_T toplnum = 0;
+ linenr_T botlnum = 0;
+
+ int i = 0;
+ TV_LIST_ITER(pos_list, li, {
+ linenr_T lnum = 0;
+ colnr_T col = 0;
+ int len = 1;
+ bool error = false;
+
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
+ const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list;
+ const listitem_T *subli = tv_list_first(subl);
+ if (subli == NULL) {
+ semsg(_("E5030: Empty list at position %d"),
+ (int)tv_list_idx_of_item(pos_list, li));
+ goto fail;
+ }
+ lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (error) {
+ goto fail;
+ }
+ if (lnum <= 0) {
+ continue;
+ }
+ m->pos.pos[i].lnum = lnum;
+ subli = TV_LIST_ITEM_NEXT(subl, subli);
+ if (subli != NULL) {
+ col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (error) {
+ goto fail;
+ }
+ if (col < 0) {
+ continue;
+ }
+ subli = TV_LIST_ITEM_NEXT(subl, subli);
+ if (subli != NULL) {
+ len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (len < 0) {
+ continue;
+ }
+ if (error) {
+ goto fail;
+ }
+ }
+ }
+ m->pos.pos[i].col = col;
+ m->pos.pos[i].len = len;
+ } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) {
+ if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) {
+ continue;
+ }
+ m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number;
+ m->pos.pos[i].col = 0;
+ m->pos.pos[i].len = 0;
+ } else {
+ semsg(_("E5031: List or number required at position %d"),
+ (int)tv_list_idx_of_item(pos_list, li));
+ goto fail;
+ }
+ if (toplnum == 0 || lnum < toplnum) {
+ toplnum = lnum;
+ }
+ if (botlnum == 0 || lnum >= botlnum) {
+ botlnum = lnum + 1;
+ }
+ i++;
+ if (i >= MAXPOSMATCH) {
+ break;
+ }
+ });
+
+ // Calculate top and bottom lines for redrawing area
+ if (toplnum != 0) {
+ if (wp->w_buffer->b_mod_set) {
+ if (wp->w_buffer->b_mod_top > toplnum) {
+ wp->w_buffer->b_mod_top = toplnum;
+ }
+ if (wp->w_buffer->b_mod_bot < botlnum) {
+ wp->w_buffer->b_mod_bot = botlnum;
+ }
+ } else {
+ wp->w_buffer->b_mod_set = true;
+ wp->w_buffer->b_mod_top = toplnum;
+ wp->w_buffer->b_mod_bot = botlnum;
+ wp->w_buffer->b_mod_xlines = 0;
+ }
+ m->pos.toplnum = toplnum;
+ m->pos.botlnum = botlnum;
+ rtype = VALID;
+ }
+ }
+
+ // Insert new match. The match list is in ascending order with regard to
+ // the match priorities.
+ cur = wp->w_match_head;
+ prev = cur;
+ while (cur != NULL && prio >= cur->priority) {
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur == prev) {
+ wp->w_match_head = m;
+ } else {
+ prev->next = m;
+ }
+ m->next = cur;
+
+ redraw_later(wp, rtype);
+ return id;
+
+fail:
+ xfree(m);
+ return -1;
+}
+
+/// Delete match with ID 'id' in the match list of window 'wp'.
+///
+/// @param perr print error messages if true.
+static int match_delete(win_T *wp, int id, bool perr)
+{
+ matchitem_T *cur = wp->w_match_head;
+ matchitem_T *prev = cur;
+ int rtype = SOME_VALID;
+
+ if (id < 1) {
+ if (perr) {
+ semsg(_("E802: Invalid ID: %" PRId64
+ " (must be greater than or equal to 1)"),
+ (int64_t)id);
+ }
+ return -1;
+ }
+ while (cur != NULL && cur->id != id) {
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur == NULL) {
+ if (perr) {
+ semsg(_("E803: ID not found: %" PRId64), (int64_t)id);
+ }
+ return -1;
+ }
+ if (cur == prev) {
+ wp->w_match_head = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ vim_regfree(cur->match.regprog);
+ xfree(cur->pattern);
+ if (cur->pos.toplnum != 0) {
+ if (wp->w_buffer->b_mod_set) {
+ if (wp->w_buffer->b_mod_top > cur->pos.toplnum) {
+ wp->w_buffer->b_mod_top = cur->pos.toplnum;
+ }
+ if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) {
+ wp->w_buffer->b_mod_bot = cur->pos.botlnum;
+ }
+ } else {
+ wp->w_buffer->b_mod_set = true;
+ wp->w_buffer->b_mod_top = cur->pos.toplnum;
+ wp->w_buffer->b_mod_bot = cur->pos.botlnum;
+ wp->w_buffer->b_mod_xlines = 0;
+ }
+ rtype = VALID;
+ }
+ xfree(cur);
+ redraw_later(wp, rtype);
+ return 0;
+}
+
+/// Delete all matches in the match list of window 'wp'.
+void clear_matches(win_T *wp)
+{
+ matchitem_T *m;
+
+ while (wp->w_match_head != NULL) {
+ m = wp->w_match_head->next;
+ vim_regfree(wp->w_match_head->match.regprog);
+ xfree(wp->w_match_head->pattern);
+ xfree(wp->w_match_head);
+ wp->w_match_head = m;
+ }
+ redraw_later(wp, SOME_VALID);
+}
+
+/// Get match from ID 'id' in window 'wp'.
+/// Return NULL if match not found.
+matchitem_T *get_match(win_T *wp, int id)
+{
+ matchitem_T *cur = wp->w_match_head;
+
+ while (cur != NULL && cur->id != id) {
+ cur = cur->next;
+ }
+ return cur;
+}
+
+/// Init for calling prepare_search_hl().
+void init_search_hl(win_T *wp, match_T *search_hl)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Setup for match and 'hlsearch' highlighting. Disable any previous
+ // match
+ matchitem_T *cur = wp->w_match_head;
+ while (cur != NULL) {
+ cur->hl.rm = cur->match;
+ if (cur->hlg_id == 0) {
+ cur->hl.attr = 0;
+ } else {
+ cur->hl.attr = syn_id2attr(cur->hlg_id);
+ }
+ cur->hl.buf = wp->w_buffer;
+ cur->hl.lnum = 0;
+ cur->hl.first_lnum = 0;
+ // Set the time limit to 'redrawtime'.
+ cur->hl.tm = profile_setlimit(p_rdt);
+ cur = cur->next;
+ }
+ search_hl->buf = wp->w_buffer;
+ search_hl->lnum = 0;
+ search_hl->first_lnum = 0;
+ search_hl->attr = win_hl_attr(wp, HLF_L);
+
+ // time limit is set at the toplevel, for all windows
+}
+
+/// @param shl points to a match. Fill on match.
+/// @param posmatch match positions
+/// @param mincol minimal column for a match
+///
+/// @return one on match, otherwise return zero.
+static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int i;
+ int found = -1;
+
+ shl->lnum = 0;
+ for (i = posmatch->cur; i < MAXPOSMATCH; i++) {
+ llpos_T *pos = &posmatch->pos[i];
+
+ if (pos->lnum == 0) {
+ break;
+ }
+ if (pos->len == 0 && pos->col < mincol) {
+ continue;
+ }
+ if (pos->lnum == lnum) {
+ if (found >= 0) {
+ // if this match comes before the one at "found" then swap
+ // them
+ if (pos->col < posmatch->pos[found].col) {
+ llpos_T tmp = *pos;
+
+ *pos = posmatch->pos[found];
+ posmatch->pos[found] = tmp;
+ }
+ } else {
+ found = i;
+ }
+ }
+ }
+ posmatch->cur = 0;
+ if (found >= 0) {
+ colnr_T start = posmatch->pos[found].col == 0
+ ? 0: posmatch->pos[found].col - 1;
+ colnr_T end = posmatch->pos[found].col == 0
+ ? MAXCOL : start + posmatch->pos[found].len;
+
+ shl->lnum = lnum;
+ shl->rm.startpos[0].lnum = 0;
+ shl->rm.startpos[0].col = start;
+ shl->rm.endpos[0].lnum = 0;
+ shl->rm.endpos[0].col = end;
+ shl->is_addpos = true;
+ posmatch->cur = found + 1;
+ return 1;
+ }
+ return 0;
+}
+
+/// Search for a next 'hlsearch' or match.
+/// Uses shl->buf.
+/// Sets shl->lnum and shl->rm contents.
+/// Note: Assumes a previous match is always before "lnum", unless
+/// shl->lnum is zero.
+/// Careful: Any pointers for buffer lines will become invalid.
+///
+/// @param shl points to search_hl or a match
+/// @param mincol minimal column for a match
+/// @param cur to retrieve match positions if any
+static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum,
+ colnr_T mincol, matchitem_T *cur)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
+ linenr_T l;
+ colnr_T matchcol;
+ long nmatched = 0;
+ int save_called_emsg = called_emsg;
+
+ // for :{range}s/pat only highlight inside the range
+ if (lnum < search_first_line || lnum > search_last_line) {
+ shl->lnum = 0;
+ return;
+ }
+
+ if (shl->lnum != 0) {
+ // Check for three situations:
+ // 1. If the "lnum" is below a previous match, start a new search.
+ // 2. If the previous match includes "mincol", use it.
+ // 3. Continue after the previous match.
+ l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
+ if (lnum > l) {
+ shl->lnum = 0;
+ } else if (lnum < l || shl->rm.endpos[0].col > mincol) {
+ return;
+ }
+ }
+
+ // Repeat searching for a match until one is found that includes "mincol"
+ // or none is found in this line.
+ called_emsg = false;
+ for (;;) {
+ // Stop searching after passing the time limit.
+ if (profile_passed_limit(shl->tm)) {
+ shl->lnum = 0; // no match found in time
+ break;
+ }
+ // Three situations:
+ // 1. No useful previous match: search from start of line.
+ // 2. Not Vi compatible or empty match: continue at next character.
+ // Break the loop if this is beyond the end of the line.
+ // 3. Vi compatible searching: continue at end of previous match.
+ if (shl->lnum == 0) {
+ matchcol = 0;
+ } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
+ || (shl->rm.endpos[0].lnum == 0
+ && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
+ char_u *ml;
+
+ matchcol = shl->rm.startpos[0].col;
+ ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
+ if (*ml == NUL) {
+ matchcol++;
+ shl->lnum = 0;
+ break;
+ }
+ matchcol += utfc_ptr2len(ml);
+ } else {
+ matchcol = shl->rm.endpos[0].col;
+ }
+
+ shl->lnum = lnum;
+ if (shl->rm.regprog != NULL) {
+ // Remember whether shl->rm is using a copy of the regprog in
+ // cur->match.
+ bool regprog_is_copy = (shl != search_hl
+ && cur != NULL
+ && shl == &cur->hl
+ && cur->match.regprog == cur->hl.rm.regprog);
+ int timed_out = false;
+
+ nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
+ &(shl->tm), &timed_out);
+ // Copy the regprog, in case it got freed and recompiled.
+ if (regprog_is_copy) {
+ cur->match.regprog = cur->hl.rm.regprog;
+ }
+ if (called_emsg || got_int || timed_out) {
+ // Error while handling regexp: stop using this regexp.
+ if (shl == search_hl) {
+ // don't free regprog in the match list, it's a copy
+ vim_regfree(shl->rm.regprog);
+ set_no_hlsearch(true);
+ }
+ shl->rm.regprog = NULL;
+ shl->lnum = 0;
+ got_int = false; // avoid the "Type :quit to exit Vim" message
+ break;
+ }
+ } else if (cur != NULL) {
+ nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
+ }
+ if (nmatched == 0) {
+ shl->lnum = 0; // no match found
+ break;
+ }
+ if (shl->rm.startpos[0].lnum > 0
+ || shl->rm.startpos[0].col >= mincol
+ || nmatched > 1
+ || shl->rm.endpos[0].col > mincol) {
+ shl->lnum += shl->rm.startpos[0].lnum;
+ break; // useful match found
+ }
+
+ // Restore called_emsg for assert_fails().
+ called_emsg = save_called_emsg;
+ }
+}
+
+/// Advance to the match in window "wp" line "lnum" or past it.
+void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
+ FUNC_ATTR_NONNULL_ALL
+{
+ matchitem_T *cur; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag; // flag to indicate whether search_hl
+ // has been processed or not
+
+ // When using a multi-line pattern, start searching at the top
+ // of the window or just after a closed fold.
+ // Do this both for search_hl and the match list.
+ cur = wp->w_match_head;
+ shl_flag = false;
+ while (cur != NULL || shl_flag == false) {
+ if (shl_flag == false) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl; // -V595
+ }
+ if (shl->rm.regprog != NULL
+ && shl->lnum == 0
+ && re_multiline(shl->rm.regprog)) {
+ if (shl->first_lnum == 0) {
+ for (shl->first_lnum = lnum;
+ shl->first_lnum > wp->w_topline;
+ shl->first_lnum--) {
+ if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) {
+ break;
+ }
+ }
+ }
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ bool pos_inprogress = true; // mark that a position match search is
+ // in progress
+ int n = 0;
+ while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
+ || (cur != NULL && pos_inprogress))) {
+ next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
+ shl == search_hl ? NULL : cur);
+ pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
+ if (shl->lnum != 0) {
+ shl->first_lnum = shl->lnum
+ + shl->rm.endpos[0].lnum
+ - shl->rm.startpos[0].lnum;
+ n = shl->rm.endpos[0].col;
+ } else {
+ shl->first_lnum++;
+ n = 0;
+ }
+ }
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+}
+
+/// Prepare for 'hlsearch' and match highlighting in one window line.
+/// Return true if there is such highlighting and set "search_attr" to the
+/// current highlight attribute.
+bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line,
+ match_T *search_hl, int *search_attr, bool *search_attr_from_match)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+ bool area_highlighting = false;
+
+ // Handle highlighting the last used search pattern and matches.
+ // Do this for both search_hl and the match list.
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl; // -V595
+ }
+ shl->startcol = MAXCOL;
+ shl->endcol = MAXCOL;
+ shl->attr_cur = 0;
+ shl->is_addpos = false;
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ next_search_hl(wp, search_hl, shl, lnum, mincol,
+ shl == search_hl ? NULL : cur);
+
+ // Need to get the line again, a multi-line regexp may have made it
+ // invalid.
+ *line = ml_get_buf(wp->w_buffer, lnum, false);
+
+ if (shl->lnum != 0 && shl->lnum <= lnum) {
+ if (shl->lnum == lnum) {
+ shl->startcol = shl->rm.startpos[0].col;
+ } else {
+ shl->startcol = 0;
+ }
+ if (lnum == shl->lnum + shl->rm.endpos[0].lnum
+ - shl->rm.startpos[0].lnum) {
+ shl->endcol = shl->rm.endpos[0].col;
+ } else {
+ shl->endcol = MAXCOL;
+ }
+ // Highlight one character for an empty match.
+ if (shl->startcol == shl->endcol) {
+ if ((*line)[shl->endcol] != NUL) {
+ shl->endcol += utfc_ptr2len(*line + shl->endcol);
+ } else {
+ shl->endcol++;
+ }
+ }
+ if ((long)shl->startcol < mincol) { // match at leftcol
+ shl->attr_cur = shl->attr;
+ *search_attr = shl->attr;
+ *search_attr_from_match = shl != search_hl;
+ }
+ area_highlighting = true;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+ return area_highlighting;
+}
+
+/// For a position in a line: Check for start/end of 'hlsearch' and other
+/// matches.
+/// After end, check for start/end of next match.
+/// When another match, have to check for start again.
+/// Watch out for matching an empty string!
+/// Return the updated search_attr.
+int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl,
+ int *has_match_conc, int *match_conc, int lcs_eol_one,
+ bool *search_attr_from_match)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+ int search_attr = 0;
+
+ // Do this for 'search_hl' and the match list (ordered by priority).
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ bool pos_inprogress = true; // mark that a position match search is
+ // in progress
+ while (shl->rm.regprog != NULL
+ || (cur != NULL && pos_inprogress)) {
+ if (shl->startcol != MAXCOL
+ && col >= shl->startcol
+ && col < shl->endcol) {
+ int next_col = col + utfc_ptr2len(*line + col);
+
+ if (shl->endcol < next_col) {
+ shl->endcol = next_col;
+ }
+ shl->attr_cur = shl->attr;
+ // Match with the "Conceal" group results in hiding
+ // the match.
+ if (cur != NULL
+ && shl != search_hl
+ && syn_name2id("Conceal") == cur->hlg_id) {
+ *has_match_conc = col == shl->startcol ? 2 : 1;
+ *match_conc = cur->conceal_char;
+ } else {
+ *has_match_conc = 0;
+ }
+ } else if (col == shl->endcol) {
+ shl->attr_cur = 0;
+
+ next_search_hl(wp, search_hl, shl, lnum, col,
+ shl == search_hl ? NULL : cur);
+ pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
+
+ // Need to get the line again, a multi-line regexp
+ // may have made it invalid.
+ *line = ml_get_buf(wp->w_buffer, lnum, false);
+
+ if (shl->lnum == lnum) {
+ shl->startcol = shl->rm.startpos[0].col;
+ if (shl->rm.endpos[0].lnum == 0) {
+ shl->endcol = shl->rm.endpos[0].col;
+ } else {
+ shl->endcol = MAXCOL;
+ }
+
+ if (shl->startcol == shl->endcol) {
+ // highlight empty match, try again after it
+ shl->endcol += utfc_ptr2len(*line + shl->endcol);
+ }
+
+ // Loop to check if the match starts at the
+ // current position
+ continue;
+ }
+ }
+ break;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+
+ // Use attributes from match with highest priority among
+ // 'search_hl' and the match list.
+ *search_attr_from_match = false;
+ search_attr = search_hl->attr_cur;
+ cur = wp->w_match_head;
+ shl_flag = false;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (shl->attr_cur != 0) {
+ search_attr = shl->attr_cur;
+ *search_attr_from_match = shl != search_hl;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+ // Only highlight one character after the last column.
+ if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) {
+ search_attr = 0;
+ }
+ return search_attr;
+}
+
+bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
+{
+ long prevcol = curcol;
+ matchitem_T *cur; // points to the match list
+
+ // we're not really at that column when skipping some text
+ if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
+ prevcol++;
+ }
+
+ if (!search_hl->is_addpos && prevcol == search_hl->startcol) {
+ return true;
+ } else {
+ cur = wp->w_match_head;
+ while (cur != NULL) {
+ if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) {
+ return true;
+ }
+ cur = cur->next;
+ }
+ }
+ return false;
+}
+
+/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
+/// or match highlighting.
+void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+
+ *char_attr = search_hl->attr;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (col - 1 == (long)shl->startcol
+ && (shl == search_hl || !shl->is_addpos)) {
+ *char_attr = shl->attr;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+}
+
+static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win)
+{
+ dictitem_T *di;
+
+ if (tv->v_type != VAR_DICT) {
+ emsg(_(e_dictreq));
+ return FAIL;
+ }
+
+ if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) {
+ *conceal_char = tv_get_string(&di->di_tv);
+ }
+
+ if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) {
+ *win = find_win_by_nr_or_id(&di->di_tv);
+ if (*win == NULL) {
+ emsg(_(e_invalwindow));
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/// "clearmatches()" function
+void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win != NULL) {
+ clear_matches(win);
+ }
+}
+
+/// "getmatches()" function
+void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ matchitem_T *cur;
+ int i;
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win == NULL) {
+ return;
+ }
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ cur = win->w_match_head;
+ while (cur != NULL) {
+ dict_T *dict = tv_dict_alloc();
+ if (cur->match.regprog == NULL) {
+ // match added with matchaddpos()
+ for (i = 0; i < MAXPOSMATCH; i++) {
+ llpos_T *llpos;
+ char buf[30]; // use 30 to avoid compiler warning
+
+ llpos = &cur->pos.pos[i];
+ if (llpos->lnum == 0) {
+ break;
+ }
+ list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
+ tv_list_append_number(l, (varnumber_T)llpos->lnum);
+ if (llpos->col > 0) {
+ tv_list_append_number(l, (varnumber_T)llpos->col);
+ tv_list_append_number(l, (varnumber_T)llpos->len);
+ }
+ int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
+ assert((size_t)len < sizeof(buf));
+ tv_dict_add_list(dict, buf, (size_t)len, l);
+ }
+ } else {
+ tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
+ }
+ tv_dict_add_str(dict, S_LEN("group"),
+ (const char *)syn_id2name(cur->hlg_id));
+ tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
+ tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
+
+ if (cur->conceal_char) {
+ char buf[MB_MAXBYTES + 1];
+
+ buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL;
+ tv_dict_add_str(dict, S_LEN("conceal"), buf);
+ }
+
+ tv_list_append_dict(rettv->vval.v_list, dict);
+ cur = cur->next;
+ }
+}
+
+/// "setmatches()" function
+void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ list_T *s = NULL;
+ win_T *win = get_optional_window(argvars, 1);
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ emsg(_(e_listreq));
+ return;
+ }
+ if (win == NULL) {
+ return;
+ }
+
+ list_T *const l = argvars[0].vval.v_list;
+ // To some extent make sure that we are dealing with a list from
+ // "getmatches()".
+ int li_idx = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
+ || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
+ semsg(_("E474: List item %d is either not a dictionary "
+ "or an empty one"), li_idx);
+ return;
+ }
+ if (!(tv_dict_find(d, S_LEN("group")) != NULL
+ && (tv_dict_find(d, S_LEN("pattern")) != NULL
+ || tv_dict_find(d, S_LEN("pos1")) != NULL)
+ && tv_dict_find(d, S_LEN("priority")) != NULL
+ && tv_dict_find(d, S_LEN("id")) != NULL)) {
+ semsg(_("E474: List item %d is missing one of the required keys"),
+ li_idx);
+ return;
+ }
+ li_idx++;
+ });
+
+ clear_matches(win);
+ bool match_add_failed = false;
+ TV_LIST_ITER_CONST(l, li, {
+ int i = 0;
+
+ d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
+ if (di == NULL) {
+ if (s == NULL) {
+ s = tv_list_alloc(9);
+ }
+
+ // match from matchaddpos()
+ for (i = 1; i < 9; i++) {
+ char buf[30]; // use 30 to avoid compiler warning
+ snprintf(buf, sizeof(buf), "pos%d", i);
+ dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
+ if (pos_di != NULL) {
+ if (pos_di->di_tv.v_type != VAR_LIST) {
+ return;
+ }
+
+ tv_list_append_tv(s, &pos_di->di_tv);
+ tv_list_ref(s);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // Note: there are three number buffers involved:
+ // - group_buf below.
+ // - numbuf in tv_dict_get_string().
+ // - mybuf in tv_get_string().
+ //
+ // If you change this code make sure that buffers will not get
+ // accidentally reused.
+ char group_buf[NUMBUFLEN];
+ const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
+ const int priority = (int)tv_dict_get_number(d, "priority");
+ const int id = (int)tv_dict_get_number(d, "id");
+ dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
+ const char *const conceal = (conceal_di != NULL
+ ? tv_get_string(&conceal_di->di_tv)
+ : NULL);
+ if (i == 0) {
+ if (match_add(win, group,
+ tv_dict_get_string(d, "pattern", false),
+ priority, id, NULL, conceal) != id) {
+ match_add_failed = true;
+ }
+ } else {
+ if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
+ match_add_failed = true;
+ }
+ tv_list_unref(s);
+ s = NULL;
+ }
+ });
+ if (!match_add_failed) {
+ rettv->vval.v_number = 0;
+ }
+}
+
+/// "matchadd()" function
+void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char grpbuf[NUMBUFLEN];
+ char patbuf[NUMBUFLEN];
+ // group
+ const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
+ // pattern
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ // default priority
+ int prio = 10;
+ int id = -1;
+ bool error = false;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ rettv->vval.v_number = -1;
+
+ if (grp == NULL || pat == NULL) {
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = (int)tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = (int)tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error) {
+ return;
+ }
+ if (id >= 1 && id <= 3) {
+ semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
+}
+
+/// "matchaddpo()" function
+void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
+ if (group == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_LIST) {
+ semsg(_(e_listarg), "matchaddpos()");
+ return;
+ }
+
+ list_T *l;
+ l = argvars[1].vval.v_list;
+ if (l == NULL) {
+ return;
+ }
+
+ bool error = false;
+ int prio = 10;
+ int id = -1;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = (int)tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = (int)tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error == true) {
+ return;
+ }
+
+ // id == 3 is ok because matchaddpos() is supposed to substitute :3match
+ if (id == 1 || id == 2) {
+ semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
+}
+
+/// "matcharg()" function
+void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = (int)tv_get_number(&argvars[0]);
+
+ tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
+ ? 2
+ : 0));
+
+ if (id >= 1 && id <= 3) {
+ matchitem_T *const m = get_match(curwin, id);
+
+ if (m != NULL) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)syn_id2name(m->hlg_id), -1);
+ tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
+ } else {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ }
+ }
+}
+
+/// "matchdelete()" function
+void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 1);
+ if (win == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = match_delete(win,
+ (int)tv_get_number(&argvars[0]), true);
+ }
+}
+
+/// ":[N]match {group} {pattern}"
+/// Sets nextcmd to the start of the next command, if any. Also called when
+/// skipping commands to find the next command.
+void ex_match(exarg_T *eap)
+{
+ char_u *p;
+ char_u *g = NULL;
+ char_u *end;
+ int c;
+ int id;
+
+ if (eap->line2 <= 3) {
+ id = (int)eap->line2;
+ } else {
+ emsg(e_invcmd);
+ return;
+ }
+
+ // First clear any old pattern.
+ if (!eap->skip) {
+ match_delete(curwin, id, false);
+ }
+
+ if (ends_excmd(*eap->arg)) {
+ end = eap->arg;
+ } else if ((STRNICMP(eap->arg, "none", 4) == 0
+ && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
+ end = eap->arg + 4;
+ } else {
+ p = skiptowhite(eap->arg);
+ if (!eap->skip) {
+ g = vim_strnsave(eap->arg, (size_t)(p - eap->arg));
+ }
+ p = skipwhite(p);
+ if (*p == NUL) {
+ // There must be two arguments.
+ xfree(g);
+ semsg(_(e_invarg2), eap->arg);
+ return;
+ }
+ end = skip_regexp(p + 1, *p, true, NULL);
+ if (!eap->skip) {
+ if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
+ xfree(g);
+ eap->errmsg = e_trailing;
+ return;
+ }
+ if (*end != *p) {
+ xfree(g);
+ semsg(_(e_invarg2), p);
+ return;
+ }
+
+ c = *end;
+ *end = NUL;
+ match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
+ NULL, NULL);
+ xfree(g);
+ *end = (char_u)c;
+ }
+ }
+ eap->nextcmd = find_nextcmd(end);
+}
+
diff --git a/src/nvim/match.h b/src/nvim/match.h
new file mode 100644
index 0000000000..fdcec0ae05
--- /dev/null
+++ b/src/nvim/match.h
@@ -0,0 +1,12 @@
+#ifndef NVIM_MATCH_H
+#define NVIM_MATCH_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "match.h.generated.h"
+#endif
+
+#endif // NVIM_MATCH_H
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 83d555e584..96bc5180e2 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -95,6 +95,7 @@
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 50a56056bf..e0d657af45 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -25,9 +25,9 @@
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_group.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"