Mercurial > vim
view src/match.c @ 29583:32aee589fc9a v9.0.0132
patch 9.0.0132: multi-byte characters in virtual text not handled correctly
Commit: https://github.com/vim/vim/commit/09ff4b54fb86a64390ba9c609853c6410ea6197c
Author: Bram Moolenaar <Bram@vim.org>
Date: Mon Aug 1 16:51:02 2022 +0100
patch 9.0.0132: multi-byte characters in virtual text not handled correctly
Problem: Multi-byte characters in virtual text not handled correctly.
Solution: Count screen cells instead of bytes.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Mon, 01 Aug 2022 18:00:05 +0200 |
parents | 827d9f2b7a71 |
children | 89e1d67814a9 |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * match.c: functions for highlighting matches */ #include "vim.h" #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO) # 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). * If no particular ID is desired, -1 must be specified for 'id'. * Return ID of added match, -1 on failure. */ static int match_add( win_T *wp, char_u *grp, char_u *pat, int prio, int id, list_T *pos_list, char_u *conceal_char UNUSED) // pointer to conceal replacement char { 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(_(e_invalid_id_nr_must_be_greater_than_or_equal_to_one_1), id); return -1; } if (id != -1) { cur = wp->w_match_head; while (cur != NULL) { if (cur->id == id) { semsg(_(e_id_already_taken_nr), id); return -1; } cur = cur->next; } } if ((hlg_id = syn_namen2id(grp, (int)STRLEN(grp))) == 0) { semsg(_(e_no_such_highlight_group_name_str), grp); return -1; } if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) { semsg(_(e_invalid_argument_str), 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 = ALLOC_CLEAR_ONE(matchitem_T); m->id = id; m->priority = prio; m->pattern = pat == NULL ? NULL : vim_strsave(pat); m->hlg_id = hlg_id; m->match.regprog = regprog; m->match.rmm_ic = FALSE; m->match.rmm_maxcol = 0; # if defined(FEAT_CONCEAL) m->conceal_char = 0; if (conceal_char != NULL) m->conceal_char = (*mb_ptr2char)(conceal_char); # endif // Set up position matches if (pos_list != NULL) { linenr_T toplnum = 0; linenr_T botlnum = 0; listitem_T *li; int i; CHECK_LIST_MATERIALIZE(pos_list); for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; i++, li = li->li_next) { linenr_T lnum = 0; colnr_T col = 0; int len = 1; list_T *subl; listitem_T *subli; int error = FALSE; if (li->li_tv.v_type == VAR_LIST) { subl = li->li_tv.vval.v_list; if (subl == NULL) goto fail; subli = subl->lv_first; if (subli == NULL) goto fail; lnum = tv_get_number_chk(&subli->li_tv, &error); if (error == TRUE) goto fail; if (lnum == 0) { --i; continue; } m->pos.pos[i].lnum = lnum; subli = subli->li_next; if (subli != NULL) { col = tv_get_number_chk(&subli->li_tv, &error); if (error == TRUE) goto fail; subli = subli->li_next; if (subli != NULL) { len = tv_get_number_chk(&subli->li_tv, &error); if (error == TRUE) goto fail; } } m->pos.pos[i].col = col; m->pos.pos[i].len = len; } else if (li->li_tv.v_type == VAR_NUMBER) { if (li->li_tv.vval.v_number == 0) { --i; continue; } m->pos.pos[i].lnum = li->li_tv.vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { emsg(_(e_list_or_number_required)); goto fail; } if (toplnum == 0 || lnum < toplnum) toplnum = lnum; if (botlnum == 0 || lnum >= botlnum) botlnum = lnum + 1; } // 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_win_later(wp, rtype); return id; fail: vim_free(m); return -1; } /* * Delete match with ID 'id' in the match list of window 'wp'. * Print error messages if 'perr' is TRUE. */ static int match_delete(win_T *wp, int id, int perr) { matchitem_T *cur = wp->w_match_head; matchitem_T *prev = cur; int rtype = SOME_VALID; if (id < 1) { if (perr == TRUE) semsg(_(e_invalid_id_nr_must_be_greater_than_or_equal_to_one_2), id); return -1; } while (cur != NULL && cur->id != id) { prev = cur; cur = cur->next; } if (cur == NULL) { if (perr == TRUE) semsg(_(e_id_not_found_nr), id); return -1; } if (cur == prev) wp->w_match_head = cur->next; else prev->next = cur->next; vim_regfree(cur->match.regprog); vim_free(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; } vim_free(cur); redraw_win_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); vim_free(wp->w_match_head->pattern); vim_free(wp->w_match_head); wp->w_match_head = m; } redraw_win_later(wp, 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->id != id) cur = cur->next; return cur; } /* * Init for calling prepare_search_hl(). */ void init_search_hl(win_T *wp, match_T *search_hl) { matchitem_T *cur; // Setup for match and 'hlsearch' highlighting. Disable any previous // match 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; cur = cur->next; } search_hl->buf = wp->w_buffer; search_hl->lnum = 0; search_hl->first_lnum = 0; // time limit is set at the toplevel, for all windows } /* * If there is a match fill "shl" and return one. * Return zero otherwise. */ static int next_search_hl_pos( match_T *shl, // points to a match linenr_T lnum, posmatch_T *posmatch, // match positions colnr_T mincol) // minimal column for a match { int i; int found = -1; 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; shl->has_cursor = FALSE; 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. */ static void next_search_hl( win_T *win, match_T *search_hl, match_T *shl, // points to search_hl or a match linenr_T lnum, colnr_T mincol, // minimal column for a match matchitem_T *cur) // to retrieve match positions if any { linenr_T l; colnr_T matchcol; long nmatched; int called_emsg_before = called_emsg; int timed_out = FALSE; // for :{range}s/pat only highlight inside the range if ((lnum < search_first_line || lnum > search_last_line) && cur == NULL) { 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 (;;) { // 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; } if (has_mbyte) matchcol += mb_ptr2len(ml); else ++matchcol; } 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. int regprog_is_copy = (shl != search_hl && cur != NULL && shl == &cur->hl && cur->match.regprog == cur->hl.rm.regprog); nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, &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 > 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->pos), matchcol); else nmatched = 0; 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) { matchitem_T *cur; // points to the match list match_T *shl; // points to search_hl or a match int shl_flag; // flag to indicate whether search_hl // has been processed or not int pos_inprogress; // marks that position match search is // in progress int n; // 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 = WIN_IS_POPUP(wp); // skip search_hl in a popup window while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE) { shl = search_hl; shl_flag = TRUE; } else shl = &cur->hl; if (shl->rm.regprog != NULL && shl->lnum == 0 && re_multiline(shl->rm.regprog)) { if (shl->first_lnum == 0) { # ifdef FEAT_FOLDING 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; # else shl->first_lnum = wp->w_topline; # endif } if (cur != NULL) cur->pos.cur = 0; pos_inprogress = TRUE; 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 ? FALSE : TRUE; 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; } } /* * 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. */ int prepare_search_hl_line( win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line, match_T *search_hl, int *search_attr) { matchitem_T *cur; // points to the match list match_T *shl; // points to search_hl or a match int shl_flag; // flag to indicate whether search_hl // has been processed or not int area_highlighting = FALSE; // Handle highlighting the last used search pattern and matches. // Do this for both search_hl and the match list. // Do not use search_hl in a popup window. cur = wp->w_match_head; shl_flag = WIN_IS_POPUP(wp); while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE) { shl = search_hl; shl_flag = TRUE; } else shl = &cur->hl; shl->startcol = MAXCOL; shl->endcol = MAXCOL; shl->attr_cur = 0; shl->is_addpos = FALSE; shl->has_cursor = 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; // 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 (has_mbyte && (*line)[shl->endcol] != NUL) shl->endcol += (*mb_ptr2len)((*line) + shl->endcol); else ++shl->endcol; } if ((long)shl->startcol < mincol) // match at leftcol { shl->attr_cur = shl->attr; *search_attr = shl->attr; } 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! * "on_last_col" is set to TRUE with non-zero search_attr and the next column * is endcol. * 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 UNUSED, int *match_conc UNUSED, int did_line_attr, int lcs_eol_one, int *on_last_col) { matchitem_T *cur; // points to the match list match_T *shl; // points to search_hl or a match int shl_flag; // flag to indicate whether search_hl // has been processed or not int pos_inprogress; // marks that position match search is in // progress int search_attr = 0; // Do this for 'search_hl' and the match list (ordered by priority). cur = wp->w_match_head; shl_flag = WIN_IS_POPUP(wp); while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = TRUE; } else shl = &cur->hl; if (cur != NULL) cur->pos.cur = 0; pos_inprogress = TRUE; while (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress)) { if (shl->startcol != MAXCOL && col >= shl->startcol && col < shl->endcol) { int next_col = col + mb_ptr2len(*line + col); if (shl->endcol < next_col) shl->endcol = next_col; shl->attr_cur = shl->attr; # ifdef FEAT_CONCEAL // Match with the "Conceal" group results in hiding // the match. if (cur != NULL && shl != search_hl && syn_name2id((char_u *)"Conceal") == cur->hlg_id) { *has_match_conc = col == shl->startcol ? 2 : 1; *match_conc = cur->conceal_char; } else *has_match_conc = 0; # endif // Highlight the match were the cursor is using the CurSearch // group. if (shl == search_hl && shl->has_cursor) { shl->attr_cur = HL_ATTR(HLF_LC); if (shl->attr_cur != shl->attr) search_hl_has_cursor_lnum = lnum; } } 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; // 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 if (has_mbyte) { char_u *p = *line + shl->endcol; if (*p == NUL) // consistent with non-mbyte ++shl->endcol; else shl->endcol += (*mb_ptr2len)(p); } else ++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. cur = wp->w_match_head; shl_flag = WIN_IS_POPUP(wp); while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE && (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; *on_last_col = col + 1 >= shl->endcol; } if (shl != search_hl && cur != NULL) cur = cur->next; } // Only highlight one character after the last column. if (*(*line + col) == NUL && (did_line_attr >= 1 || (wp->w_p_list && lcs_eol_one == -1))) search_attr = 0; return search_attr; } int get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) { long prevcol = curcol; int prevcol_hl_flag = FALSE; matchitem_T *cur; // points to the match list #if defined(FEAT_PROP_POPUP) // don't do this in a popup window if (popup_is_popup(wp)) return FALSE; #endif // 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))) prevcol_hl_flag = TRUE; else { cur = wp->w_match_head; while (cur != NULL) { if (!cur->hl.is_addpos && (prevcol == (long)cur->hl.startcol || (prevcol > (long)cur->hl.startcol && cur->hl.endcol == MAXCOL))) { prevcol_hl_flag = TRUE; break; } cur = cur->next; } } return prevcol_hl_flag; } /* * 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; // points to the match list match_T *shl; // points to search_hl or a match int shl_flag; // flag to indicate whether search_hl // has been processed or not cur = wp->w_match_head; shl_flag = WIN_IS_POPUP(wp); while (cur != NULL || shl_flag == FALSE) { if (shl_flag == FALSE && ((cur != NULL && cur->priority > SEARCH_HL_PRIORITY) || cur == NULL)) { 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; } } #endif // FEAT_SEARCH_EXTRA #if defined(FEAT_EVAL) || defined(PROTO) # ifdef FEAT_SEARCH_EXTRA static int matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win) { dictitem_T *di; if (tv->v_type != VAR_DICT) { emsg(_(e_dictionary_required)); return FAIL; } if (dict_has_key(tv->vval.v_dict, "conceal")) *conceal_char = dict_get_string(tv->vval.v_dict, "conceal", FALSE); if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL) { *win = find_win_by_nr_or_id(&di->di_tv); if (*win == NULL) { emsg(_(e_invalid_window_number)); return FAIL; } } return OK; } #endif /* * "clearmatches()" function */ void f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { #ifdef FEAT_SEARCH_EXTRA win_T *win; if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL) return; win = get_optional_window(argvars, 0); if (win != NULL) clear_matches(win); #endif } /* * "getmatches()" function */ void f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { # ifdef FEAT_SEARCH_EXTRA dict_T *dict; matchitem_T *cur; int i; win_T *win; if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL) return; win = get_optional_window(argvars, 0); if (rettv_list_alloc(rettv) == FAIL || win == NULL) return; cur = win->w_match_head; while (cur != NULL) { dict = dict_alloc(); if (dict == NULL) return; 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 list_T *l; llpos = &cur->pos.pos[i]; if (llpos->lnum == 0) break; l = list_alloc(); if (l == NULL) break; list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { list_append_number(l, (varnumber_T)llpos->col); list_append_number(l, (varnumber_T)llpos->len); } sprintf(buf, "pos%d", i + 1); dict_add_list(dict, buf, l); } } else { dict_add_string(dict, "pattern", cur->pattern); } dict_add_string(dict, "group", syn_id2name(cur->hlg_id)); dict_add_number(dict, "priority", (long)cur->priority); dict_add_number(dict, "id", (long)cur->id); # if defined(FEAT_CONCEAL) if (cur->conceal_char) { char_u buf[MB_MAXBYTES + 1]; buf[(*mb_char2bytes)(cur->conceal_char, buf)] = NUL; dict_add_string(dict, "conceal", (char_u *)&buf); } # endif list_append_dict(rettv->vval.v_list, dict); cur = cur->next; } # endif } /* * "setmatches()" function */ void f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { #ifdef FEAT_SEARCH_EXTRA list_T *l; listitem_T *li; dict_T *d; list_T *s = NULL; win_T *win; rettv->vval.v_number = -1; if (in_vim9script() && (check_for_list_arg(argvars, 0) == FAIL || check_for_opt_number_arg(argvars, 1) == FAIL)) return; if (argvars[0].v_type != VAR_LIST) { emsg(_(e_list_required)); return; } win = get_optional_window(argvars, 1); if (win == NULL) return; if ((l = argvars[0].vval.v_list) != NULL) { // To some extent make sure that we are dealing with a list from // "getmatches()". li = l->lv_first; while (li != NULL) { if (li->li_tv.v_type != VAR_DICT || (d = li->li_tv.vval.v_dict) == NULL) { emsg(_(e_invalid_argument)); return; } if (!(dict_has_key(d, "group") && (dict_has_key(d, "pattern") || dict_has_key(d, "pos1")) && dict_has_key(d, "priority") && dict_has_key(d, "id"))) { emsg(_(e_invalid_argument)); return; } li = li->li_next; } clear_matches(win); li = l->lv_first; while (li != NULL) { int i = 0; char buf[30]; // use 30 to avoid compiler warning dictitem_T *di; char_u *group; int priority; int id; char_u *conceal; d = li->li_tv.vval.v_dict; if (!dict_has_key(d, "pattern")) { if (s == NULL) { s = list_alloc(); if (s == NULL) return; } // match from matchaddpos() for (i = 1; i < 9; i++) { sprintf((char *)buf, (char *)"pos%d", i); if ((di = dict_find(d, (char_u *)buf, -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) return; list_append_tv(s, &di->di_tv); s->lv_refcount++; } else break; } } group = dict_get_string(d, "group", TRUE); priority = (int)dict_get_number(d, "priority"); id = (int)dict_get_number(d, "id"); conceal = dict_has_key(d, "conceal") ? dict_get_string(d, "conceal", TRUE) : NULL; if (i == 0) { match_add(win, group, dict_get_string(d, "pattern", FALSE), priority, id, NULL, conceal); } else { match_add(win, group, NULL, priority, id, s, conceal); list_unref(s); s = NULL; } vim_free(group); vim_free(conceal); li = li->li_next; } rettv->vval.v_number = 0; } #endif } /* * "matchadd()" function */ void f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { # ifdef FEAT_SEARCH_EXTRA char_u buf[NUMBUFLEN]; char_u *grp; // group char_u *pat; // pattern int prio = 10; // default priority int id = -1; int error = FALSE; char_u *conceal_char = NULL; win_T *win = curwin; rettv->vval.v_number = -1; if (in_vim9script() && (check_for_string_arg(argvars, 0) == FAIL || check_for_string_arg(argvars, 1) == FAIL || check_for_opt_number_arg(argvars, 2) == FAIL || (argvars[2].v_type != VAR_UNKNOWN && (check_for_opt_number_arg(argvars, 3) == FAIL || (argvars[3].v_type != VAR_UNKNOWN && check_for_opt_dict_arg(argvars, 4) == FAIL))))) return; grp = tv_get_string_buf_chk(&argvars[0], buf); // group pat = tv_get_string_buf_chk(&argvars[1], buf); // pattern 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 == TRUE) return; if (id >= 1 && id <= 3) { semsg(_(e_id_is_reserved_for_match_nr), id); return; } rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); # endif } /* * "matchaddpos()" function */ void f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { # ifdef FEAT_SEARCH_EXTRA char_u buf[NUMBUFLEN]; char_u *group; int prio = 10; int id = -1; int error = FALSE; list_T *l; char_u *conceal_char = NULL; win_T *win = curwin; rettv->vval.v_number = -1; if (in_vim9script() && (check_for_string_arg(argvars, 0) == FAIL || check_for_list_arg(argvars, 1) == FAIL || check_for_opt_number_arg(argvars, 2) == FAIL || (argvars[2].v_type != VAR_UNKNOWN && (check_for_opt_number_arg(argvars, 3) == FAIL || (argvars[3].v_type != VAR_UNKNOWN && check_for_opt_dict_arg(argvars, 4) == FAIL))))) return; group = tv_get_string_buf_chk(&argvars[0], buf); if (group == NULL) return; if (argvars[1].v_type != VAR_LIST) { semsg(_(e_argument_of_str_must_be_list), "matchaddpos()"); return; } l = argvars[1].vval.v_list; if (l == 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 == TRUE) return; // id == 3 is ok because matchaddpos() is supposed to substitute :3match if (id == 1 || id == 2) { semsg(_(e_id_is_reserved_for_match_nr), id); return; } rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); # endif } /* * "matcharg()" function */ void f_matcharg(typval_T *argvars UNUSED, typval_T *rettv) { if (rettv_list_alloc(rettv) == OK) { # ifdef FEAT_SEARCH_EXTRA int id; matchitem_T *m; if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL) return; id = (int)tv_get_number(&argvars[0]); if (id >= 1 && id <= 3) { if ((m = get_match(curwin, id)) != NULL) { list_append_string(rettv->vval.v_list, syn_id2name(m->hlg_id), -1); list_append_string(rettv->vval.v_list, m->pattern, -1); } else { list_append_string(rettv->vval.v_list, NULL, -1); list_append_string(rettv->vval.v_list, NULL, -1); } } # endif } } /* * "matchdelete()" function */ void f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED) { # ifdef FEAT_SEARCH_EXTRA win_T *win; if (in_vim9script() && (check_for_number_arg(argvars, 0) == FAIL || check_for_opt_number_arg(argvars, 1) == FAIL)) return; 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); # endif } #endif #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO) /* * ":[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 = eap->line2; else { emsg(_(e_invalid_command)); return; } // First clear any old pattern. if (!eap->skip) match_delete(curwin, id, FALSE); if (ends_excmd2(eap->cmd, eap->arg)) end = eap->arg; else if ((STRNICMP(eap->arg, "none", 4) == 0 && (VIM_ISWHITE(eap->arg[4]) || ends_excmd2(eap->arg, eap->arg + 4)))) end = eap->arg + 4; else { p = skiptowhite(eap->arg); if (!eap->skip) g = vim_strnsave(eap->arg, p - eap->arg); p = skipwhite(p); if (*p == NUL) { // There must be two arguments. vim_free(g); semsg(_(e_invalid_argument_str), eap->arg); return; } end = skip_regexp(p + 1, *p, TRUE); if (!eap->skip) { if (*end != NUL && !ends_excmd2(end, skipwhite(end + 1))) { vim_free(g); eap->errmsg = ex_errmsg(e_trailing_characters_str, end); return; } if (*end != *p) { vim_free(g); semsg(_(e_invalid_argument_str), p); return; } c = *end; *end = NUL; match_add(curwin, g, p + 1, 10, id, NULL, NULL); vim_free(g); *end = c; } } eap->nextcmd = find_nextcmd(end); } #endif