// 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 #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/match.h" #include "nvim/memline.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/vim.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". /// If "pat" is not NULL the pattern will be highlighted with the group "grp" /// with priority "prio". /// If "pos_list" is not NULL the list of posisions defines the highlights. /// Optionally, a desired ID "id" can be specified (greater than or equal to 1). /// If no particular ID is desired, -1 must be specified for "id". /// /// @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 = UPD_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) { // use the next available match ID id = wp->w_next_match_id++; } else { // check the given ID is not already in use for (cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) { if (cur->mit_id == id) { semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); return -1; } } // Make sure the next match ID is always higher than the highest // manually selected ID. Add some extra in case a few more IDs are // added soon. if (wp->w_next_match_id < id + 100) { wp->w_next_match_id = id + 100; } } if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { return -1; } if (pat != NULL && (regprog = vim_regcomp((char *)pat, RE_MAGIC)) == NULL) { semsg(_(e_invarg2), pat); return -1; } // Build new match. m = xcalloc(1, sizeof(matchitem_T)); if (pos_list != NULL) { m->mit_pos_array = xcalloc((size_t)tv_list_len(pos_list), sizeof(llpos_T)); m->mit_pos_count = tv_list_len(pos_list); } m->mit_id = id; m->mit_priority = prio; m->mit_pattern = pat == NULL ? NULL: xstrdup(pat); m->mit_hlg_id = hlg_id; m->mit_match.regprog = regprog; m->mit_match.rmm_ic = false; m->mit_match.rmm_maxcol = 0; m->mit_conceal_char = 0; if (conceal_char != NULL) { m->mit_conceal_char = utf_ptr2char(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 = (linenr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } if (lnum <= 0) { continue; } m->mit_pos_array[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->mit_pos_array[i].col = col; m->mit_pos_array[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->mit_pos_array[i].lnum = (linenr_T)TV_LIST_ITEM_TV(li)->vval.v_number; m->mit_pos_array[i].col = 0; m->mit_pos_array[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++; }); // 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->mit_toplnum = toplnum; m->mit_botlnum = botlnum; rtype = UPD_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->mit_priority) { prev = cur; cur = cur->mit_next; } if (cur == prev) { wp->w_match_head = m; } else { prev->mit_next = m; } m->mit_next = cur; redraw_later(wp, rtype); return id; fail: xfree(m->mit_pattern); xfree(m->mit_pos_array); 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 = UPD_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->mit_id != id) { prev = cur; cur = cur->mit_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->mit_next; } else { prev->mit_next = cur->mit_next; } vim_regfree(cur->mit_match.regprog); xfree(cur->mit_pattern); if (cur->mit_toplnum != 0) { if (wp->w_buffer->b_mod_set) { if (wp->w_buffer->b_mod_top > cur->mit_toplnum) { wp->w_buffer->b_mod_top = cur->mit_toplnum; } if (wp->w_buffer->b_mod_bot < cur->mit_botlnum) { wp->w_buffer->b_mod_bot = cur->mit_botlnum; } } else { wp->w_buffer->b_mod_set = true; wp->w_buffer->b_mod_top = cur->mit_toplnum; wp->w_buffer->b_mod_bot = cur->mit_botlnum; wp->w_buffer->b_mod_xlines = 0; } rtype = UPD_VALID; } xfree(cur->mit_pos_array); 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->mit_next; vim_regfree(wp->w_match_head->mit_match.regprog); xfree(wp->w_match_head->mit_pattern); xfree(wp->w_match_head->mit_pos_array); xfree(wp->w_match_head); wp->w_match_head = m; } redraw_later(wp, UPD_SOME_VALID); } /// Get match from ID 'id' in window 'wp'. /// Return NULL if match not found. static matchitem_T *get_match(win_T *wp, int id) { matchitem_T *cur = wp->w_match_head; while (cur != NULL && cur->mit_id != id) { cur = cur->mit_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->mit_hl.rm = cur->mit_match; if (cur->mit_hlg_id == 0) { cur->mit_hl.attr = 0; } else { cur->mit_hl.attr = syn_id2attr(cur->mit_hlg_id); } cur->mit_hl.buf = wp->w_buffer; cur->mit_hl.lnum = 0; cur->mit_hl.first_lnum = 0; // Set the time limit to 'redrawtime'. cur->mit_hl.tm = profile_setlimit(p_rdt); cur = cur->mit_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 item with 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, matchitem_T *match, colnr_T mincol) FUNC_ATTR_NONNULL_ALL { int i; int found = -1; shl->lnum = 0; for (i = match->mit_pos_cur; i < match->mit_pos_count; i++) { llpos_T *pos = &match->mit_pos_array[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 < match->mit_pos_array[found].col) { llpos_T tmp = *pos; *pos = match->mit_pos_array[found]; match->mit_pos_array[found] = tmp; } } else { found = i; } } } match->mit_pos_cur = 0; if (found >= 0) { colnr_T start = match->mit_pos_array[found].col == 0 ? 0: match->mit_pos_array[found].col - 1; colnr_T end = match->mit_pos_array[found].col == 0 ? MAXCOL : start + match->mit_pos_array[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; shl->has_cursor = false; match->mit_pos_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; const int called_emsg_before = 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. 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 = (char_u *)ml_get_buf(shl->buf, lnum, false) + matchcol; if (*ml == NUL) { matchcol++; shl->lnum = 0; break; } matchcol += utfc_ptr2len((char *)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->mit_match. bool regprog_is_copy = (shl != search_hl && cur != NULL && shl == &cur->mit_hl && cur->mit_match.regprog == cur->mit_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->mit_match.regprog = cur->mit_hl.rm.regprog; } if (called_emsg > called_emsg_before || 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, 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 } } } /// 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->mit_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->mit_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->mit_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->mit_next; } } } /// Update "shl->has_cursor" based on the match in "shl" and the cursor /// position. static void check_cur_search_hl(win_T *wp, match_T *shl) { linenr_T linecount = shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; if (wp->w_cursor.lnum >= shl->lnum && wp->w_cursor.lnum <= shl->lnum + linecount && (wp->w_cursor.lnum > shl->lnum || wp->w_cursor.col >= shl->rm.startpos[0].col) && (wp->w_cursor.lnum < shl->lnum + linecount || wp->w_cursor.col < shl->rm.endpos[0].col)) { shl->has_cursor = true; } else { shl->has_cursor = false; } } /// 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->mit_hl; // -V595 } shl->startcol = MAXCOL; shl->endcol = MAXCOL; shl->attr_cur = 0; shl->is_addpos = false; shl->has_cursor = false; if (cur != NULL) { cur->mit_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 = (char_u *)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; } // check if the cursor is in the match before changing the columns if (shl == search_hl) { check_cur_search_hl(wp, shl); } // Highlight one character for an empty match. if (shl->startcol == shl->endcol) { if ((*line)[shl->endcol] != NUL) { shl->endcol += utfc_ptr2len((char *)(*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->mit_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->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { shl = &cur->mit_hl; } if (cur != NULL) { cur->mit_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((char *)(*line) + col); if (shl->endcol < next_col) { shl->endcol = next_col; } // Highlight the match were the cursor is using the CurSearch // group. if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) { shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC); } else { 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->mit_hlg_id) { *has_match_conc = col == shl->startcol ? 2 : 1; *match_conc = cur->mit_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->mit_pos_cur == 0); // Need to get the line again, a multi-line regexp // may have made it invalid. *line = (char_u *)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; } // check if the cursor is in the match if (shl == search_hl) { check_cur_search_hl(wp, shl); } if (shl->startcol == shl->endcol) { // highlight empty match, try again after it char *p = (char *)(*line) + shl->endcol; if (*p == NUL) { shl->endcol++; } else { shl->endcol += utfc_ptr2len(p); } } // Loop to check if the match starts at the // current position continue; } } break; } if (shl != search_hl && cur != NULL) { cur = cur->mit_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->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { shl = &cur->mit_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->mit_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++; } // Highlight a character after the end of the line if the match started // at the end of the line or when the match continues in the next line // (match includes the line break). if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol || (prevcol > (long)search_hl->startcol && search_hl->endcol == MAXCOL))) { return true; } else { cur = wp->w_match_head; while (cur != NULL) { if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol || (prevcol > (long)cur->mit_hl.startcol && cur->mit_hl.endcol == MAXCOL))) { return true; } cur = cur->mit_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 while (cur != NULL || !shl_flag) { if (!shl_flag && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { shl = &cur->mit_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->mit_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, EvalFuncData 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, EvalFuncData fptr) { matchitem_T *cur; int i; win_T *win = get_optional_window(argvars, 0); tv_list_alloc_ret(rettv, kListLenMayKnow); if (win == NULL) { return; } cur = win->w_match_head; while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->mit_match.regprog == NULL) { // match added with matchaddpos() for (i = 0; i < cur->mit_pos_count; i++) { llpos_T *llpos; char buf[30]; // use 30 to avoid compiler warning llpos = &cur->mit_pos_array[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->mit_pattern); } tv_dict_add_str(dict, S_LEN("group"), (const char *)syn_id2name(cur->mit_hlg_id)); tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->mit_priority); tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id); if (cur->mit_conceal_char) { char buf[MB_MAXBYTES + 1]; buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL; tv_dict_add_str(dict, S_LEN("conceal"), buf); } tv_list_append_dict(rettv->vval.v_list, dict); cur = cur->mit_next; } } /// "setmatches()" function void f_setmatches(typval_T *argvars, typval_T *rettv, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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, EvalFuncData 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->mit_hlg_id), -1); tv_list_append_string(rettv->vval.v_list, (const char *)m->mit_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, EvalFuncData 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 *p; char *g = NULL; char *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 = xstrnsave(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 = ex_errmsg(e_trailing_arg, (const char *)end); return; } if (*end != *p) { xfree(g); semsg(_(e_invarg2), p); return; } c = (uint8_t)(*end); *end = NUL; match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, NULL, NULL); xfree(g); *end = (char)c; } } eap->nextcmd = find_nextcmd(end); }