diff options
Diffstat (limited to 'src/nvim/match.c')
-rw-r--r-- | src/nvim/match.c | 1181 |
1 files changed, 1181 insertions, 0 deletions
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); +} + |