1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * match.c: functions for highlighting matches 12 */ 13 14 #include "vim.h" 15 16 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO) 17 18 # define SEARCH_HL_PRIORITY 0 19 20 /* 21 * Add match to the match list of window 'wp'. The pattern 'pat' will be 22 * highlighted with the group 'grp' with priority 'prio'. 23 * Optionally, a desired ID 'id' can be specified (greater than or equal to 1). 24 * If no particular ID is desired, -1 must be specified for 'id'. 25 * Return ID of added match, -1 on failure. 26 */ 27 static int 28 match_add( 29 win_T *wp, 30 char_u *grp, 31 char_u *pat, 32 int prio, 33 int id, 34 list_T *pos_list, 35 char_u *conceal_char UNUSED) // pointer to conceal replacement char 36 { 37 matchitem_T *cur; 38 matchitem_T *prev; 39 matchitem_T *m; 40 int hlg_id; 41 regprog_T *regprog = NULL; 42 int rtype = SOME_VALID; 43 44 if (*grp == NUL || (pat != NULL && *pat == NUL)) 45 return -1; 46 if (id < -1 || id == 0) 47 { 48 semsg(_("E799: Invalid ID: %d (must be greater than or equal to 1)"), 49 id); 50 return -1; 51 } 52 if (id != -1) 53 { 54 cur = wp->w_match_head; 55 while (cur != NULL) 56 { 57 if (cur->id == id) 58 { 59 semsg(_("E801: ID already taken: %d"), id); 60 return -1; 61 } 62 cur = cur->next; 63 } 64 } 65 if ((hlg_id = syn_namen2id(grp, (int)STRLEN(grp))) == 0) 66 { 67 semsg(_(e_nogroup), grp); 68 return -1; 69 } 70 if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) 71 { 72 semsg(_(e_invarg2), pat); 73 return -1; 74 } 75 76 // Find available match ID. 77 while (id == -1) 78 { 79 cur = wp->w_match_head; 80 while (cur != NULL && cur->id != wp->w_next_match_id) 81 cur = cur->next; 82 if (cur == NULL) 83 id = wp->w_next_match_id; 84 wp->w_next_match_id++; 85 } 86 87 // Build new match. 88 m = ALLOC_CLEAR_ONE(matchitem_T); 89 m->id = id; 90 m->priority = prio; 91 m->pattern = pat == NULL ? NULL : vim_strsave(pat); 92 m->hlg_id = hlg_id; 93 m->match.regprog = regprog; 94 m->match.rmm_ic = FALSE; 95 m->match.rmm_maxcol = 0; 96 # if defined(FEAT_CONCEAL) 97 m->conceal_char = 0; 98 if (conceal_char != NULL) 99 m->conceal_char = (*mb_ptr2char)(conceal_char); 100 # endif 101 102 // Set up position matches 103 if (pos_list != NULL) 104 { 105 linenr_T toplnum = 0; 106 linenr_T botlnum = 0; 107 listitem_T *li; 108 int i; 109 110 CHECK_LIST_MATERIALIZE(pos_list); 111 for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; 112 i++, li = li->li_next) 113 { 114 linenr_T lnum = 0; 115 colnr_T col = 0; 116 int len = 1; 117 list_T *subl; 118 listitem_T *subli; 119 int error = FALSE; 120 121 if (li->li_tv.v_type == VAR_LIST) 122 { 123 subl = li->li_tv.vval.v_list; 124 if (subl == NULL) 125 goto fail; 126 subli = subl->lv_first; 127 if (subli == NULL) 128 goto fail; 129 lnum = tv_get_number_chk(&subli->li_tv, &error); 130 if (error == TRUE) 131 goto fail; 132 if (lnum == 0) 133 { 134 --i; 135 continue; 136 } 137 m->pos.pos[i].lnum = lnum; 138 subli = subli->li_next; 139 if (subli != NULL) 140 { 141 col = tv_get_number_chk(&subli->li_tv, &error); 142 if (error == TRUE) 143 goto fail; 144 subli = subli->li_next; 145 if (subli != NULL) 146 { 147 len = tv_get_number_chk(&subli->li_tv, &error); 148 if (error == TRUE) 149 goto fail; 150 } 151 } 152 m->pos.pos[i].col = col; 153 m->pos.pos[i].len = len; 154 } 155 else if (li->li_tv.v_type == VAR_NUMBER) 156 { 157 if (li->li_tv.vval.v_number == 0) 158 { 159 --i; 160 continue; 161 } 162 m->pos.pos[i].lnum = li->li_tv.vval.v_number; 163 m->pos.pos[i].col = 0; 164 m->pos.pos[i].len = 0; 165 } 166 else 167 { 168 emsg(_("E290: List or number required")); 169 goto fail; 170 } 171 if (toplnum == 0 || lnum < toplnum) 172 toplnum = lnum; 173 if (botlnum == 0 || lnum >= botlnum) 174 botlnum = lnum + 1; 175 } 176 177 // Calculate top and bottom lines for redrawing area 178 if (toplnum != 0) 179 { 180 if (wp->w_buffer->b_mod_set) 181 { 182 if (wp->w_buffer->b_mod_top > toplnum) 183 wp->w_buffer->b_mod_top = toplnum; 184 if (wp->w_buffer->b_mod_bot < botlnum) 185 wp->w_buffer->b_mod_bot = botlnum; 186 } 187 else 188 { 189 wp->w_buffer->b_mod_set = TRUE; 190 wp->w_buffer->b_mod_top = toplnum; 191 wp->w_buffer->b_mod_bot = botlnum; 192 wp->w_buffer->b_mod_xlines = 0; 193 } 194 m->pos.toplnum = toplnum; 195 m->pos.botlnum = botlnum; 196 rtype = VALID; 197 } 198 } 199 200 // Insert new match. The match list is in ascending order with regard to 201 // the match priorities. 202 cur = wp->w_match_head; 203 prev = cur; 204 while (cur != NULL && prio >= cur->priority) 205 { 206 prev = cur; 207 cur = cur->next; 208 } 209 if (cur == prev) 210 wp->w_match_head = m; 211 else 212 prev->next = m; 213 m->next = cur; 214 215 redraw_win_later(wp, rtype); 216 return id; 217 218 fail: 219 vim_free(m); 220 return -1; 221 } 222 223 /* 224 * Delete match with ID 'id' in the match list of window 'wp'. 225 * Print error messages if 'perr' is TRUE. 226 */ 227 static int 228 match_delete(win_T *wp, int id, int perr) 229 { 230 matchitem_T *cur = wp->w_match_head; 231 matchitem_T *prev = cur; 232 int rtype = SOME_VALID; 233 234 if (id < 1) 235 { 236 if (perr == TRUE) 237 semsg(_("E802: Invalid ID: %d (must be greater than or equal to 1)"), 238 id); 239 return -1; 240 } 241 while (cur != NULL && cur->id != id) 242 { 243 prev = cur; 244 cur = cur->next; 245 } 246 if (cur == NULL) 247 { 248 if (perr == TRUE) 249 semsg(_("E803: ID not found: %d"), id); 250 return -1; 251 } 252 if (cur == prev) 253 wp->w_match_head = cur->next; 254 else 255 prev->next = cur->next; 256 vim_regfree(cur->match.regprog); 257 vim_free(cur->pattern); 258 if (cur->pos.toplnum != 0) 259 { 260 if (wp->w_buffer->b_mod_set) 261 { 262 if (wp->w_buffer->b_mod_top > cur->pos.toplnum) 263 wp->w_buffer->b_mod_top = cur->pos.toplnum; 264 if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) 265 wp->w_buffer->b_mod_bot = cur->pos.botlnum; 266 } 267 else 268 { 269 wp->w_buffer->b_mod_set = TRUE; 270 wp->w_buffer->b_mod_top = cur->pos.toplnum; 271 wp->w_buffer->b_mod_bot = cur->pos.botlnum; 272 wp->w_buffer->b_mod_xlines = 0; 273 } 274 rtype = VALID; 275 } 276 vim_free(cur); 277 redraw_win_later(wp, rtype); 278 return 0; 279 } 280 281 /* 282 * Delete all matches in the match list of window 'wp'. 283 */ 284 void 285 clear_matches(win_T *wp) 286 { 287 matchitem_T *m; 288 289 while (wp->w_match_head != NULL) 290 { 291 m = wp->w_match_head->next; 292 vim_regfree(wp->w_match_head->match.regprog); 293 vim_free(wp->w_match_head->pattern); 294 vim_free(wp->w_match_head); 295 wp->w_match_head = m; 296 } 297 redraw_win_later(wp, SOME_VALID); 298 } 299 300 /* 301 * Get match from ID 'id' in window 'wp'. 302 * Return NULL if match not found. 303 */ 304 static matchitem_T * 305 get_match(win_T *wp, int id) 306 { 307 matchitem_T *cur = wp->w_match_head; 308 309 while (cur != NULL && cur->id != id) 310 cur = cur->next; 311 return cur; 312 } 313 314 /* 315 * Init for calling prepare_search_hl(). 316 */ 317 void 318 init_search_hl(win_T *wp, match_T *search_hl) 319 { 320 matchitem_T *cur; 321 322 // Setup for match and 'hlsearch' highlighting. Disable any previous 323 // match 324 cur = wp->w_match_head; 325 while (cur != NULL) 326 { 327 cur->hl.rm = cur->match; 328 if (cur->hlg_id == 0) 329 cur->hl.attr = 0; 330 else 331 cur->hl.attr = syn_id2attr(cur->hlg_id); 332 cur->hl.buf = wp->w_buffer; 333 cur->hl.lnum = 0; 334 cur->hl.first_lnum = 0; 335 # ifdef FEAT_RELTIME 336 // Set the time limit to 'redrawtime'. 337 profile_setlimit(p_rdt, &(cur->hl.tm)); 338 # endif 339 cur = cur->next; 340 } 341 search_hl->buf = wp->w_buffer; 342 search_hl->lnum = 0; 343 search_hl->first_lnum = 0; 344 // time limit is set at the toplevel, for all windows 345 } 346 347 /* 348 * If there is a match fill "shl" and return one. 349 * Return zero otherwise. 350 */ 351 static int 352 next_search_hl_pos( 353 match_T *shl, // points to a match 354 linenr_T lnum, 355 posmatch_T *posmatch, // match positions 356 colnr_T mincol) // minimal column for a match 357 { 358 int i; 359 int found = -1; 360 361 for (i = posmatch->cur; i < MAXPOSMATCH; i++) 362 { 363 llpos_T *pos = &posmatch->pos[i]; 364 365 if (pos->lnum == 0) 366 break; 367 if (pos->len == 0 && pos->col < mincol) 368 continue; 369 if (pos->lnum == lnum) 370 { 371 if (found >= 0) 372 { 373 // if this match comes before the one at "found" then swap 374 // them 375 if (pos->col < posmatch->pos[found].col) 376 { 377 llpos_T tmp = *pos; 378 379 *pos = posmatch->pos[found]; 380 posmatch->pos[found] = tmp; 381 } 382 } 383 else 384 found = i; 385 } 386 } 387 posmatch->cur = 0; 388 if (found >= 0) 389 { 390 colnr_T start = posmatch->pos[found].col == 0 391 ? 0 : posmatch->pos[found].col - 1; 392 colnr_T end = posmatch->pos[found].col == 0 393 ? MAXCOL : start + posmatch->pos[found].len; 394 395 shl->lnum = lnum; 396 shl->rm.startpos[0].lnum = 0; 397 shl->rm.startpos[0].col = start; 398 shl->rm.endpos[0].lnum = 0; 399 shl->rm.endpos[0].col = end; 400 shl->is_addpos = TRUE; 401 posmatch->cur = found + 1; 402 return 1; 403 } 404 return 0; 405 } 406 407 /* 408 * Search for a next 'hlsearch' or match. 409 * Uses shl->buf. 410 * Sets shl->lnum and shl->rm contents. 411 * Note: Assumes a previous match is always before "lnum", unless 412 * shl->lnum is zero. 413 * Careful: Any pointers for buffer lines will become invalid. 414 */ 415 static void 416 next_search_hl( 417 win_T *win, 418 match_T *search_hl, 419 match_T *shl, // points to search_hl or a match 420 linenr_T lnum, 421 colnr_T mincol, // minimal column for a match 422 matchitem_T *cur) // to retrieve match positions if any 423 { 424 linenr_T l; 425 colnr_T matchcol; 426 long nmatched; 427 int called_emsg_before = called_emsg; 428 429 // for :{range}s/pat only highlight inside the range 430 if (lnum < search_first_line || lnum > search_last_line) 431 { 432 shl->lnum = 0; 433 return; 434 } 435 436 if (shl->lnum != 0) 437 { 438 // Check for three situations: 439 // 1. If the "lnum" is below a previous match, start a new search. 440 // 2. If the previous match includes "mincol", use it. 441 // 3. Continue after the previous match. 442 l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; 443 if (lnum > l) 444 shl->lnum = 0; 445 else if (lnum < l || shl->rm.endpos[0].col > mincol) 446 return; 447 } 448 449 // Repeat searching for a match until one is found that includes "mincol" 450 // or none is found in this line. 451 for (;;) 452 { 453 # ifdef FEAT_RELTIME 454 // Stop searching after passing the time limit. 455 if (profile_passed_limit(&(shl->tm))) 456 { 457 shl->lnum = 0; // no match found in time 458 break; 459 } 460 # endif 461 // Three situations: 462 // 1. No useful previous match: search from start of line. 463 // 2. Not Vi compatible or empty match: continue at next character. 464 // Break the loop if this is beyond the end of the line. 465 // 3. Vi compatible searching: continue at end of previous match. 466 if (shl->lnum == 0) 467 matchcol = 0; 468 else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL 469 || (shl->rm.endpos[0].lnum == 0 470 && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) 471 { 472 char_u *ml; 473 474 matchcol = shl->rm.startpos[0].col; 475 ml = ml_get_buf(shl->buf, lnum, FALSE) + matchcol; 476 if (*ml == NUL) 477 { 478 ++matchcol; 479 shl->lnum = 0; 480 break; 481 } 482 if (has_mbyte) 483 matchcol += mb_ptr2len(ml); 484 else 485 ++matchcol; 486 } 487 else 488 matchcol = shl->rm.endpos[0].col; 489 490 shl->lnum = lnum; 491 if (shl->rm.regprog != NULL) 492 { 493 // Remember whether shl->rm is using a copy of the regprog in 494 // cur->match. 495 int regprog_is_copy = (shl != search_hl && cur != NULL 496 && shl == &cur->hl 497 && cur->match.regprog == cur->hl.rm.regprog); 498 int timed_out = FALSE; 499 500 nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, 501 matchcol, 502 #ifdef FEAT_RELTIME 503 &(shl->tm), &timed_out 504 #else 505 NULL, NULL 506 #endif 507 ); 508 // Copy the regprog, in case it got freed and recompiled. 509 if (regprog_is_copy) 510 cur->match.regprog = cur->hl.rm.regprog; 511 512 if (called_emsg > called_emsg_before || got_int || timed_out) 513 { 514 // Error while handling regexp: stop using this regexp. 515 if (shl == search_hl) 516 { 517 // don't free regprog in the match list, it's a copy 518 vim_regfree(shl->rm.regprog); 519 set_no_hlsearch(TRUE); 520 } 521 shl->rm.regprog = NULL; 522 shl->lnum = 0; 523 got_int = FALSE; // avoid the "Type :quit to exit Vim" message 524 break; 525 } 526 } 527 else if (cur != NULL) 528 nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); 529 else 530 nmatched = 0; 531 if (nmatched == 0) 532 { 533 shl->lnum = 0; // no match found 534 break; 535 } 536 if (shl->rm.startpos[0].lnum > 0 537 || shl->rm.startpos[0].col >= mincol 538 || nmatched > 1 539 || shl->rm.endpos[0].col > mincol) 540 { 541 shl->lnum += shl->rm.startpos[0].lnum; 542 break; // useful match found 543 } 544 } 545 } 546 547 /* 548 * Advance to the match in window "wp" line "lnum" or past it. 549 */ 550 void 551 prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) 552 { 553 matchitem_T *cur; // points to the match list 554 match_T *shl; // points to search_hl or a match 555 int shl_flag; // flag to indicate whether search_hl 556 // has been processed or not 557 int pos_inprogress; // marks that position match search is 558 // in progress 559 int n; 560 561 // When using a multi-line pattern, start searching at the top 562 // of the window or just after a closed fold. 563 // Do this both for search_hl and the match list. 564 cur = wp->w_match_head; 565 shl_flag = WIN_IS_POPUP(wp); // skip search_hl in a popup window 566 while (cur != NULL || shl_flag == FALSE) 567 { 568 if (shl_flag == FALSE) 569 { 570 shl = search_hl; 571 shl_flag = TRUE; 572 } 573 else 574 shl = &cur->hl; 575 if (shl->rm.regprog != NULL 576 && shl->lnum == 0 577 && re_multiline(shl->rm.regprog)) 578 { 579 if (shl->first_lnum == 0) 580 { 581 # ifdef FEAT_FOLDING 582 for (shl->first_lnum = lnum; 583 shl->first_lnum > wp->w_topline; --shl->first_lnum) 584 if (hasFoldingWin(wp, shl->first_lnum - 1, 585 NULL, NULL, TRUE, NULL)) 586 break; 587 # else 588 shl->first_lnum = wp->w_topline; 589 # endif 590 } 591 if (cur != NULL) 592 cur->pos.cur = 0; 593 pos_inprogress = TRUE; 594 n = 0; 595 while (shl->first_lnum < lnum && (shl->rm.regprog != NULL 596 || (cur != NULL && pos_inprogress))) 597 { 598 next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n, 599 shl == search_hl ? NULL : cur); 600 pos_inprogress = cur == NULL || cur->pos.cur == 0 601 ? FALSE : TRUE; 602 if (shl->lnum != 0) 603 { 604 shl->first_lnum = shl->lnum 605 + shl->rm.endpos[0].lnum 606 - shl->rm.startpos[0].lnum; 607 n = shl->rm.endpos[0].col; 608 } 609 else 610 { 611 ++shl->first_lnum; 612 n = 0; 613 } 614 } 615 } 616 if (shl != search_hl && cur != NULL) 617 cur = cur->next; 618 } 619 } 620 621 /* 622 * Prepare for 'hlsearch' and match highlighting in one window line. 623 * Return TRUE if there is such highlighting and set "search_attr" to the 624 * current highlight attribute. 625 */ 626 int 627 prepare_search_hl_line( 628 win_T *wp, 629 linenr_T lnum, 630 colnr_T mincol, 631 char_u **line, 632 match_T *search_hl, 633 int *search_attr) 634 { 635 matchitem_T *cur; // points to the match list 636 match_T *shl; // points to search_hl or a match 637 int shl_flag; // flag to indicate whether search_hl 638 // has been processed or not 639 int area_highlighting = FALSE; 640 641 // Handle highlighting the last used search pattern and matches. 642 // Do this for both search_hl and the match list. 643 // Do not use search_hl in a popup window. 644 cur = wp->w_match_head; 645 shl_flag = WIN_IS_POPUP(wp); 646 while (cur != NULL || shl_flag == FALSE) 647 { 648 if (shl_flag == FALSE) 649 { 650 shl = search_hl; 651 shl_flag = TRUE; 652 } 653 else 654 shl = &cur->hl; 655 shl->startcol = MAXCOL; 656 shl->endcol = MAXCOL; 657 shl->attr_cur = 0; 658 shl->is_addpos = FALSE; 659 if (cur != NULL) 660 cur->pos.cur = 0; 661 next_search_hl(wp, search_hl, shl, lnum, mincol, 662 shl == search_hl ? NULL : cur); 663 664 // Need to get the line again, a multi-line regexp may have made it 665 // invalid. 666 *line = ml_get_buf(wp->w_buffer, lnum, FALSE); 667 668 if (shl->lnum != 0 && shl->lnum <= lnum) 669 { 670 if (shl->lnum == lnum) 671 shl->startcol = shl->rm.startpos[0].col; 672 else 673 shl->startcol = 0; 674 if (lnum == shl->lnum + shl->rm.endpos[0].lnum 675 - shl->rm.startpos[0].lnum) 676 shl->endcol = shl->rm.endpos[0].col; 677 else 678 shl->endcol = MAXCOL; 679 // Highlight one character for an empty match. 680 if (shl->startcol == shl->endcol) 681 { 682 if (has_mbyte && (*line)[shl->endcol] != NUL) 683 shl->endcol += (*mb_ptr2len)((*line) + shl->endcol); 684 else 685 ++shl->endcol; 686 } 687 if ((long)shl->startcol < mincol) // match at leftcol 688 { 689 shl->attr_cur = shl->attr; 690 *search_attr = shl->attr; 691 } 692 area_highlighting = TRUE; 693 } 694 if (shl != search_hl && cur != NULL) 695 cur = cur->next; 696 } 697 return area_highlighting; 698 } 699 700 /* 701 * For a position in a line: Check for start/end of 'hlsearch' and other 702 * matches. 703 * After end, check for start/end of next match. 704 * When another match, have to check for start again. 705 * Watch out for matching an empty string! 706 * Return the updated search_attr. 707 */ 708 int 709 update_search_hl( 710 win_T *wp, 711 linenr_T lnum, 712 colnr_T col, 713 char_u **line, 714 match_T *search_hl, 715 int *has_match_conc UNUSED, 716 int *match_conc UNUSED, 717 int did_line_attr, 718 int lcs_eol_one) 719 { 720 matchitem_T *cur; // points to the match list 721 match_T *shl; // points to search_hl or a match 722 int shl_flag; // flag to indicate whether search_hl 723 // has been processed or not 724 int pos_inprogress; // marks that position match search is in 725 // progress 726 int search_attr = 0; 727 728 729 // Do this for 'search_hl' and the match list (ordered by priority). 730 cur = wp->w_match_head; 731 shl_flag = WIN_IS_POPUP(wp); 732 while (cur != NULL || shl_flag == FALSE) 733 { 734 if (shl_flag == FALSE 735 && (cur == NULL 736 || cur->priority > SEARCH_HL_PRIORITY)) 737 { 738 shl = search_hl; 739 shl_flag = TRUE; 740 } 741 else 742 shl = &cur->hl; 743 if (cur != NULL) 744 cur->pos.cur = 0; 745 pos_inprogress = TRUE; 746 while (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress)) 747 { 748 if (shl->startcol != MAXCOL 749 && col >= shl->startcol 750 && col < shl->endcol) 751 { 752 int next_col = col + mb_ptr2len(*line + col); 753 754 if (shl->endcol < next_col) 755 shl->endcol = next_col; 756 shl->attr_cur = shl->attr; 757 # ifdef FEAT_CONCEAL 758 // Match with the "Conceal" group results in hiding 759 // the match. 760 if (cur != NULL 761 && shl != search_hl 762 && syn_name2id((char_u *)"Conceal") == cur->hlg_id) 763 { 764 *has_match_conc = col == shl->startcol ? 2 : 1; 765 *match_conc = cur->conceal_char; 766 } 767 else 768 *has_match_conc = 0; 769 # endif 770 } 771 else if (col == shl->endcol) 772 { 773 shl->attr_cur = 0; 774 next_search_hl(wp, search_hl, shl, lnum, col, 775 shl == search_hl ? NULL : cur); 776 pos_inprogress = !(cur == NULL || cur->pos.cur == 0); 777 778 // Need to get the line again, a multi-line regexp may have 779 // made it invalid. 780 *line = ml_get_buf(wp->w_buffer, lnum, FALSE); 781 782 if (shl->lnum == lnum) 783 { 784 shl->startcol = shl->rm.startpos[0].col; 785 if (shl->rm.endpos[0].lnum == 0) 786 shl->endcol = shl->rm.endpos[0].col; 787 else 788 shl->endcol = MAXCOL; 789 790 if (shl->startcol == shl->endcol) 791 { 792 // highlight empty match, try again after 793 // it 794 if (has_mbyte) 795 { 796 char_u *p = *line + shl->endcol; 797 798 if (*p == NUL) 799 // consistent with non-mbyte 800 ++shl->endcol; 801 else 802 shl->endcol += (*mb_ptr2len)(p); 803 } 804 else 805 ++shl->endcol; 806 } 807 808 // Loop to check if the match starts at the 809 // current position 810 continue; 811 } 812 } 813 break; 814 } 815 if (shl != search_hl && cur != NULL) 816 cur = cur->next; 817 } 818 819 // Use attributes from match with highest priority among 'search_hl' and 820 // the match list. 821 cur = wp->w_match_head; 822 shl_flag = WIN_IS_POPUP(wp); 823 while (cur != NULL || shl_flag == FALSE) 824 { 825 if (shl_flag == FALSE 826 && (cur == NULL || 827 cur->priority > SEARCH_HL_PRIORITY)) 828 { 829 shl = search_hl; 830 shl_flag = TRUE; 831 } 832 else 833 shl = &cur->hl; 834 if (shl->attr_cur != 0) 835 search_attr = shl->attr_cur; 836 if (shl != search_hl && cur != NULL) 837 cur = cur->next; 838 } 839 // Only highlight one character after the last column. 840 if (*(*line + col) == NUL && (did_line_attr >= 1 841 || (wp->w_p_list && lcs_eol_one == -1))) 842 search_attr = 0; 843 return search_attr; 844 } 845 846 int 847 get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) 848 { 849 long prevcol = curcol; 850 int prevcol_hl_flag = FALSE; 851 matchitem_T *cur; // points to the match list 852 853 #if defined(FEAT_PROP_POPUP) 854 // don't do this in a popup window 855 if (popup_is_popup(wp)) 856 return FALSE; 857 #endif 858 859 // we're not really at that column when skipping some text 860 if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) 861 ++prevcol; 862 863 // Highlight a character after the end of the line if the match started 864 // at the end of the line or when the match continues in the next line 865 // (match includes the line break). 866 if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol 867 || (prevcol > (long)search_hl->startcol 868 && search_hl->endcol == MAXCOL))) 869 prevcol_hl_flag = TRUE; 870 else 871 { 872 cur = wp->w_match_head; 873 while (cur != NULL) 874 { 875 if (!cur->hl.is_addpos && (prevcol == (long)cur->hl.startcol 876 || (prevcol > (long)cur->hl.startcol 877 && cur->hl.endcol == MAXCOL))) 878 { 879 prevcol_hl_flag = TRUE; 880 break; 881 } 882 cur = cur->next; 883 } 884 } 885 return prevcol_hl_flag; 886 } 887 888 /* 889 * Get highlighting for the char after the text in "char_attr" from 'hlsearch' 890 * or match highlighting. 891 */ 892 void 893 get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr) 894 { 895 matchitem_T *cur; // points to the match list 896 match_T *shl; // points to search_hl or a match 897 int shl_flag; // flag to indicate whether search_hl 898 // has been processed or not 899 900 cur = wp->w_match_head; 901 shl_flag = WIN_IS_POPUP(wp); 902 while (cur != NULL || shl_flag == FALSE) 903 { 904 if (shl_flag == FALSE 905 && ((cur != NULL 906 && cur->priority > SEARCH_HL_PRIORITY) 907 || cur == NULL)) 908 { 909 shl = search_hl; 910 shl_flag = TRUE; 911 } 912 else 913 shl = &cur->hl; 914 if (col - 1 == (long)shl->startcol 915 && (shl == search_hl || !shl->is_addpos)) 916 *char_attr = shl->attr; 917 if (shl != search_hl && cur != NULL) 918 cur = cur->next; 919 } 920 } 921 922 #endif // FEAT_SEARCH_EXTRA 923 924 #if defined(FEAT_EVAL) || defined(PROTO) 925 # ifdef FEAT_SEARCH_EXTRA 926 static int 927 matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win) 928 { 929 dictitem_T *di; 930 931 if (tv->v_type != VAR_DICT) 932 { 933 emsg(_(e_dictreq)); 934 return FAIL; 935 } 936 937 if (dict_find(tv->vval.v_dict, (char_u *)"conceal", -1) != NULL) 938 *conceal_char = dict_get_string(tv->vval.v_dict, 939 (char_u *)"conceal", FALSE); 940 941 if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL) 942 { 943 *win = find_win_by_nr_or_id(&di->di_tv); 944 if (*win == NULL) 945 { 946 emsg(_(e_invalwindow)); 947 return FAIL; 948 } 949 } 950 951 return OK; 952 } 953 #endif 954 955 /* 956 * "clearmatches()" function 957 */ 958 void 959 f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 960 { 961 #ifdef FEAT_SEARCH_EXTRA 962 win_T *win = get_optional_window(argvars, 0); 963 964 if (win != NULL) 965 clear_matches(win); 966 #endif 967 } 968 969 /* 970 * "getmatches()" function 971 */ 972 void 973 f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 974 { 975 # ifdef FEAT_SEARCH_EXTRA 976 dict_T *dict; 977 matchitem_T *cur; 978 int i; 979 win_T *win = get_optional_window(argvars, 0); 980 981 if (rettv_list_alloc(rettv) == FAIL || win == NULL) 982 return; 983 984 cur = win->w_match_head; 985 while (cur != NULL) 986 { 987 dict = dict_alloc(); 988 if (dict == NULL) 989 return; 990 if (cur->match.regprog == NULL) 991 { 992 // match added with matchaddpos() 993 for (i = 0; i < MAXPOSMATCH; ++i) 994 { 995 llpos_T *llpos; 996 char buf[30]; // use 30 to avoid compiler warning 997 list_T *l; 998 999 llpos = &cur->pos.pos[i]; 1000 if (llpos->lnum == 0) 1001 break; 1002 l = list_alloc(); 1003 if (l == NULL) 1004 break; 1005 list_append_number(l, (varnumber_T)llpos->lnum); 1006 if (llpos->col > 0) 1007 { 1008 list_append_number(l, (varnumber_T)llpos->col); 1009 list_append_number(l, (varnumber_T)llpos->len); 1010 } 1011 sprintf(buf, "pos%d", i + 1); 1012 dict_add_list(dict, buf, l); 1013 } 1014 } 1015 else 1016 { 1017 dict_add_string(dict, "pattern", cur->pattern); 1018 } 1019 dict_add_string(dict, "group", syn_id2name(cur->hlg_id)); 1020 dict_add_number(dict, "priority", (long)cur->priority); 1021 dict_add_number(dict, "id", (long)cur->id); 1022 # if defined(FEAT_CONCEAL) 1023 if (cur->conceal_char) 1024 { 1025 char_u buf[MB_MAXBYTES + 1]; 1026 1027 buf[(*mb_char2bytes)((int)cur->conceal_char, buf)] = NUL; 1028 dict_add_string(dict, "conceal", (char_u *)&buf); 1029 } 1030 # endif 1031 list_append_dict(rettv->vval.v_list, dict); 1032 cur = cur->next; 1033 } 1034 # endif 1035 } 1036 1037 /* 1038 * "setmatches()" function 1039 */ 1040 void 1041 f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 1042 { 1043 #ifdef FEAT_SEARCH_EXTRA 1044 list_T *l; 1045 listitem_T *li; 1046 dict_T *d; 1047 list_T *s = NULL; 1048 win_T *win = get_optional_window(argvars, 1); 1049 1050 rettv->vval.v_number = -1; 1051 if (argvars[0].v_type != VAR_LIST) 1052 { 1053 emsg(_(e_listreq)); 1054 return; 1055 } 1056 if (win == NULL) 1057 return; 1058 1059 if ((l = argvars[0].vval.v_list) != NULL) 1060 { 1061 // To some extent make sure that we are dealing with a list from 1062 // "getmatches()". 1063 li = l->lv_first; 1064 while (li != NULL) 1065 { 1066 if (li->li_tv.v_type != VAR_DICT 1067 || (d = li->li_tv.vval.v_dict) == NULL) 1068 { 1069 emsg(_(e_invarg)); 1070 return; 1071 } 1072 if (!(dict_find(d, (char_u *)"group", -1) != NULL 1073 && (dict_find(d, (char_u *)"pattern", -1) != NULL 1074 || dict_find(d, (char_u *)"pos1", -1) != NULL) 1075 && dict_find(d, (char_u *)"priority", -1) != NULL 1076 && dict_find(d, (char_u *)"id", -1) != NULL)) 1077 { 1078 emsg(_(e_invarg)); 1079 return; 1080 } 1081 li = li->li_next; 1082 } 1083 1084 clear_matches(win); 1085 li = l->lv_first; 1086 while (li != NULL) 1087 { 1088 int i = 0; 1089 char buf[30]; // use 30 to avoid compiler warning 1090 dictitem_T *di; 1091 char_u *group; 1092 int priority; 1093 int id; 1094 char_u *conceal; 1095 1096 d = li->li_tv.vval.v_dict; 1097 if (dict_find(d, (char_u *)"pattern", -1) == NULL) 1098 { 1099 if (s == NULL) 1100 { 1101 s = list_alloc(); 1102 if (s == NULL) 1103 return; 1104 } 1105 1106 // match from matchaddpos() 1107 for (i = 1; i < 9; i++) 1108 { 1109 sprintf((char *)buf, (char *)"pos%d", i); 1110 if ((di = dict_find(d, (char_u *)buf, -1)) != NULL) 1111 { 1112 if (di->di_tv.v_type != VAR_LIST) 1113 return; 1114 1115 list_append_tv(s, &di->di_tv); 1116 s->lv_refcount++; 1117 } 1118 else 1119 break; 1120 } 1121 } 1122 1123 group = dict_get_string(d, (char_u *)"group", TRUE); 1124 priority = (int)dict_get_number(d, (char_u *)"priority"); 1125 id = (int)dict_get_number(d, (char_u *)"id"); 1126 conceal = dict_find(d, (char_u *)"conceal", -1) != NULL 1127 ? dict_get_string(d, (char_u *)"conceal", TRUE) 1128 : NULL; 1129 if (i == 0) 1130 { 1131 match_add(win, group, 1132 dict_get_string(d, (char_u *)"pattern", FALSE), 1133 priority, id, NULL, conceal); 1134 } 1135 else 1136 { 1137 match_add(win, group, NULL, priority, id, s, conceal); 1138 list_unref(s); 1139 s = NULL; 1140 } 1141 vim_free(group); 1142 vim_free(conceal); 1143 1144 li = li->li_next; 1145 } 1146 rettv->vval.v_number = 0; 1147 } 1148 #endif 1149 } 1150 1151 /* 1152 * "matchadd()" function 1153 */ 1154 void 1155 f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 1156 { 1157 # ifdef FEAT_SEARCH_EXTRA 1158 char_u buf[NUMBUFLEN]; 1159 char_u *grp = tv_get_string_buf_chk(&argvars[0], buf); // group 1160 char_u *pat = tv_get_string_buf_chk(&argvars[1], buf); // pattern 1161 int prio = 10; // default priority 1162 int id = -1; 1163 int error = FALSE; 1164 char_u *conceal_char = NULL; 1165 win_T *win = curwin; 1166 1167 rettv->vval.v_number = -1; 1168 1169 if (grp == NULL || pat == NULL) 1170 return; 1171 if (argvars[2].v_type != VAR_UNKNOWN) 1172 { 1173 prio = (int)tv_get_number_chk(&argvars[2], &error); 1174 if (argvars[3].v_type != VAR_UNKNOWN) 1175 { 1176 id = (int)tv_get_number_chk(&argvars[3], &error); 1177 if (argvars[4].v_type != VAR_UNKNOWN 1178 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) 1179 return; 1180 } 1181 } 1182 if (error == TRUE) 1183 return; 1184 if (id >= 1 && id <= 3) 1185 { 1186 semsg(_("E798: ID is reserved for \":match\": %d"), id); 1187 return; 1188 } 1189 1190 rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, 1191 conceal_char); 1192 # endif 1193 } 1194 1195 /* 1196 * "matchaddpos()" function 1197 */ 1198 void 1199 f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 1200 { 1201 # ifdef FEAT_SEARCH_EXTRA 1202 char_u buf[NUMBUFLEN]; 1203 char_u *group; 1204 int prio = 10; 1205 int id = -1; 1206 int error = FALSE; 1207 list_T *l; 1208 char_u *conceal_char = NULL; 1209 win_T *win = curwin; 1210 1211 rettv->vval.v_number = -1; 1212 1213 group = tv_get_string_buf_chk(&argvars[0], buf); 1214 if (group == NULL) 1215 return; 1216 1217 if (argvars[1].v_type != VAR_LIST) 1218 { 1219 semsg(_(e_listarg), "matchaddpos()"); 1220 return; 1221 } 1222 l = argvars[1].vval.v_list; 1223 if (l == NULL) 1224 return; 1225 1226 if (argvars[2].v_type != VAR_UNKNOWN) 1227 { 1228 prio = (int)tv_get_number_chk(&argvars[2], &error); 1229 if (argvars[3].v_type != VAR_UNKNOWN) 1230 { 1231 id = (int)tv_get_number_chk(&argvars[3], &error); 1232 1233 if (argvars[4].v_type != VAR_UNKNOWN 1234 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) 1235 return; 1236 } 1237 } 1238 if (error == TRUE) 1239 return; 1240 1241 // id == 3 is ok because matchaddpos() is supposed to substitute :3match 1242 if (id == 1 || id == 2) 1243 { 1244 semsg(_("E798: ID is reserved for \":match\": %d"), id); 1245 return; 1246 } 1247 1248 rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, 1249 conceal_char); 1250 # endif 1251 } 1252 1253 /* 1254 * "matcharg()" function 1255 */ 1256 void 1257 f_matcharg(typval_T *argvars UNUSED, typval_T *rettv) 1258 { 1259 if (rettv_list_alloc(rettv) == OK) 1260 { 1261 # ifdef FEAT_SEARCH_EXTRA 1262 int id = (int)tv_get_number(&argvars[0]); 1263 matchitem_T *m; 1264 1265 if (id >= 1 && id <= 3) 1266 { 1267 if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) 1268 { 1269 list_append_string(rettv->vval.v_list, 1270 syn_id2name(m->hlg_id), -1); 1271 list_append_string(rettv->vval.v_list, m->pattern, -1); 1272 } 1273 else 1274 { 1275 list_append_string(rettv->vval.v_list, NULL, -1); 1276 list_append_string(rettv->vval.v_list, NULL, -1); 1277 } 1278 } 1279 # endif 1280 } 1281 } 1282 1283 /* 1284 * "matchdelete()" function 1285 */ 1286 void 1287 f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 1288 { 1289 # ifdef FEAT_SEARCH_EXTRA 1290 win_T *win = get_optional_window(argvars, 1); 1291 1292 if (win == NULL) 1293 rettv->vval.v_number = -1; 1294 else 1295 rettv->vval.v_number = match_delete(win, 1296 (int)tv_get_number(&argvars[0]), TRUE); 1297 # endif 1298 } 1299 #endif 1300 1301 #if defined(FEAT_SEARCH_EXTRA) || defined(PROTO) 1302 /* 1303 * ":[N]match {group} {pattern}" 1304 * Sets nextcmd to the start of the next command, if any. Also called when 1305 * skipping commands to find the next command. 1306 */ 1307 void 1308 ex_match(exarg_T *eap) 1309 { 1310 char_u *p; 1311 char_u *g = NULL; 1312 char_u *end; 1313 int c; 1314 int id; 1315 1316 if (eap->line2 <= 3) 1317 id = eap->line2; 1318 else 1319 { 1320 emsg(_(e_invalid_command)); 1321 return; 1322 } 1323 1324 // First clear any old pattern. 1325 if (!eap->skip) 1326 match_delete(curwin, id, FALSE); 1327 1328 if (ends_excmd2(eap->cmd, eap->arg)) 1329 end = eap->arg; 1330 else if ((STRNICMP(eap->arg, "none", 4) == 0 1331 && (VIM_ISWHITE(eap->arg[4]) 1332 || ends_excmd2(eap->arg, eap->arg + 4)))) 1333 end = eap->arg + 4; 1334 else 1335 { 1336 p = skiptowhite(eap->arg); 1337 if (!eap->skip) 1338 g = vim_strnsave(eap->arg, p - eap->arg); 1339 p = skipwhite(p); 1340 if (*p == NUL) 1341 { 1342 // There must be two arguments. 1343 vim_free(g); 1344 semsg(_(e_invarg2), eap->arg); 1345 return; 1346 } 1347 end = skip_regexp(p + 1, *p, TRUE); 1348 if (!eap->skip) 1349 { 1350 if (*end != NUL && !ends_excmd2(end, skipwhite(end + 1))) 1351 { 1352 vim_free(g); 1353 eap->errmsg = ex_errmsg(e_trailing_arg, end); 1354 return; 1355 } 1356 if (*end != *p) 1357 { 1358 vim_free(g); 1359 semsg(_(e_invarg2), p); 1360 return; 1361 } 1362 1363 c = *end; 1364 *end = NUL; 1365 match_add(curwin, g, p + 1, 10, id, NULL, NULL); 1366 vim_free(g); 1367 *end = c; 1368 } 1369 } 1370 eap->nextcmd = find_nextcmd(end); 1371 } 1372 #endif 1373