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