blob: 40f426ec699ded34b3d02c3aec86e6e56542b3b3 [file] [log] [blame]
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001/* 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
28match_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 {
Bram Moolenaare29a27f2021-07-20 21:07:36 +020067 semsg(_(e_no_such_highlight_group_name_str), grp);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +020068 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
218fail:
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
228match_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
285clear_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 *
305get_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
318init_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
352next_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
416next_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
551prepare_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
627prepare_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!
Bram Moolenaar0c359af2021-11-29 19:18:57 +0000706 * "on_last_col" is set to TRUE with non-zero search_attr and the next column
707 * is endcol.
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200708 * Return the updated search_attr.
709 */
710 int
711update_search_hl(
712 win_T *wp,
713 linenr_T lnum,
714 colnr_T col,
715 char_u **line,
716 match_T *search_hl,
717 int *has_match_conc UNUSED,
718 int *match_conc UNUSED,
719 int did_line_attr,
Bram Moolenaar0c359af2021-11-29 19:18:57 +0000720 int lcs_eol_one,
721 int *on_last_col)
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200722{
723 matchitem_T *cur; // points to the match list
724 match_T *shl; // points to search_hl or a match
725 int shl_flag; // flag to indicate whether search_hl
726 // has been processed or not
727 int pos_inprogress; // marks that position match search is in
728 // progress
729 int search_attr = 0;
730
731
732 // Do this for 'search_hl' and the match list (ordered by priority).
733 cur = wp->w_match_head;
734 shl_flag = WIN_IS_POPUP(wp);
735 while (cur != NULL || shl_flag == FALSE)
736 {
737 if (shl_flag == FALSE
738 && (cur == NULL
739 || cur->priority > SEARCH_HL_PRIORITY))
740 {
741 shl = search_hl;
742 shl_flag = TRUE;
743 }
744 else
745 shl = &cur->hl;
746 if (cur != NULL)
747 cur->pos.cur = 0;
748 pos_inprogress = TRUE;
749 while (shl->rm.regprog != NULL || (cur != NULL && pos_inprogress))
750 {
751 if (shl->startcol != MAXCOL
752 && col >= shl->startcol
753 && col < shl->endcol)
754 {
755 int next_col = col + mb_ptr2len(*line + col);
756
757 if (shl->endcol < next_col)
758 shl->endcol = next_col;
759 shl->attr_cur = shl->attr;
760# ifdef FEAT_CONCEAL
761 // Match with the "Conceal" group results in hiding
762 // the match.
763 if (cur != NULL
764 && shl != search_hl
765 && syn_name2id((char_u *)"Conceal") == cur->hlg_id)
766 {
767 *has_match_conc = col == shl->startcol ? 2 : 1;
768 *match_conc = cur->conceal_char;
769 }
770 else
771 *has_match_conc = 0;
772# endif
773 }
774 else if (col == shl->endcol)
775 {
776 shl->attr_cur = 0;
777 next_search_hl(wp, search_hl, shl, lnum, col,
778 shl == search_hl ? NULL : cur);
779 pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
780
781 // Need to get the line again, a multi-line regexp may have
782 // made it invalid.
783 *line = ml_get_buf(wp->w_buffer, lnum, FALSE);
784
785 if (shl->lnum == lnum)
786 {
787 shl->startcol = shl->rm.startpos[0].col;
788 if (shl->rm.endpos[0].lnum == 0)
789 shl->endcol = shl->rm.endpos[0].col;
790 else
791 shl->endcol = MAXCOL;
792
793 if (shl->startcol == shl->endcol)
794 {
795 // highlight empty match, try again after
796 // it
797 if (has_mbyte)
Bram Moolenaar41f08952021-02-22 22:13:49 +0100798 {
799 char_u *p = *line + shl->endcol;
800
801 if (*p == NUL)
802 // consistent with non-mbyte
803 ++shl->endcol;
804 else
805 shl->endcol += (*mb_ptr2len)(p);
806 }
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200807 else
808 ++shl->endcol;
809 }
810
811 // Loop to check if the match starts at the
812 // current position
813 continue;
814 }
815 }
816 break;
817 }
818 if (shl != search_hl && cur != NULL)
819 cur = cur->next;
820 }
821
822 // Use attributes from match with highest priority among 'search_hl' and
823 // the match list.
824 cur = wp->w_match_head;
825 shl_flag = WIN_IS_POPUP(wp);
826 while (cur != NULL || shl_flag == FALSE)
827 {
828 if (shl_flag == FALSE
829 && (cur == NULL ||
830 cur->priority > SEARCH_HL_PRIORITY))
831 {
832 shl = search_hl;
833 shl_flag = TRUE;
834 }
835 else
836 shl = &cur->hl;
837 if (shl->attr_cur != 0)
Bram Moolenaar0c359af2021-11-29 19:18:57 +0000838 {
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200839 search_attr = shl->attr_cur;
Bram Moolenaar0c359af2021-11-29 19:18:57 +0000840 *on_last_col = col + 1 >= shl->endcol;
841 }
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200842 if (shl != search_hl && cur != NULL)
843 cur = cur->next;
844 }
845 // Only highlight one character after the last column.
846 if (*(*line + col) == NUL && (did_line_attr >= 1
847 || (wp->w_p_list && lcs_eol_one == -1)))
848 search_attr = 0;
849 return search_attr;
850}
851
852 int
853get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
854{
855 long prevcol = curcol;
856 int prevcol_hl_flag = FALSE;
857 matchitem_T *cur; // points to the match list
858
Bram Moolenaar41f08952021-02-22 22:13:49 +0100859#if defined(FEAT_PROP_POPUP)
860 // don't do this in a popup window
861 if (popup_is_popup(wp))
862 return FALSE;
863#endif
864
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200865 // we're not really at that column when skipping some text
866 if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol)
867 ++prevcol;
868
Bram Moolenaar41f08952021-02-22 22:13:49 +0100869 // Highlight a character after the end of the line if the match started
870 // at the end of the line or when the match continues in the next line
871 // (match includes the line break).
872 if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol
873 || (prevcol > (long)search_hl->startcol
874 && search_hl->endcol == MAXCOL)))
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200875 prevcol_hl_flag = TRUE;
876 else
877 {
878 cur = wp->w_match_head;
879 while (cur != NULL)
880 {
Bram Moolenaar41f08952021-02-22 22:13:49 +0100881 if (!cur->hl.is_addpos && (prevcol == (long)cur->hl.startcol
882 || (prevcol > (long)cur->hl.startcol
883 && cur->hl.endcol == MAXCOL)))
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200884 {
885 prevcol_hl_flag = TRUE;
886 break;
887 }
888 cur = cur->next;
889 }
890 }
891 return prevcol_hl_flag;
892}
893
894/*
895 * Get highlighting for the char after the text in "char_attr" from 'hlsearch'
896 * or match highlighting.
897 */
898 void
899get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
900{
901 matchitem_T *cur; // points to the match list
902 match_T *shl; // points to search_hl or a match
903 int shl_flag; // flag to indicate whether search_hl
904 // has been processed or not
905
906 cur = wp->w_match_head;
907 shl_flag = WIN_IS_POPUP(wp);
908 while (cur != NULL || shl_flag == FALSE)
909 {
910 if (shl_flag == FALSE
911 && ((cur != NULL
912 && cur->priority > SEARCH_HL_PRIORITY)
913 || cur == NULL))
914 {
915 shl = search_hl;
916 shl_flag = TRUE;
917 }
918 else
919 shl = &cur->hl;
920 if (col - 1 == (long)shl->startcol
921 && (shl == search_hl || !shl->is_addpos))
922 *char_attr = shl->attr;
923 if (shl != search_hl && cur != NULL)
924 cur = cur->next;
925 }
926}
927
928#endif // FEAT_SEARCH_EXTRA
929
930#if defined(FEAT_EVAL) || defined(PROTO)
931# ifdef FEAT_SEARCH_EXTRA
932 static int
933matchadd_dict_arg(typval_T *tv, char_u **conceal_char, win_T **win)
934{
935 dictitem_T *di;
936
937 if (tv->v_type != VAR_DICT)
938 {
939 emsg(_(e_dictreq));
940 return FAIL;
941 }
942
943 if (dict_find(tv->vval.v_dict, (char_u *)"conceal", -1) != NULL)
944 *conceal_char = dict_get_string(tv->vval.v_dict,
945 (char_u *)"conceal", FALSE);
946
947 if ((di = dict_find(tv->vval.v_dict, (char_u *)"window", -1)) != NULL)
948 {
949 *win = find_win_by_nr_or_id(&di->di_tv);
950 if (*win == NULL)
951 {
952 emsg(_(e_invalwindow));
953 return FAIL;
954 }
955 }
956
957 return OK;
958}
959#endif
960
961/*
962 * "clearmatches()" function
963 */
964 void
965f_clearmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
966{
967#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200968 win_T *win;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200969
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200970 if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
971 return;
972
973 win = get_optional_window(argvars, 0);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200974 if (win != NULL)
975 clear_matches(win);
976#endif
977}
978
979/*
980 * "getmatches()" function
981 */
982 void
983f_getmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
984{
985# ifdef FEAT_SEARCH_EXTRA
986 dict_T *dict;
987 matchitem_T *cur;
988 int i;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200989 win_T *win;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200990
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +0200991 if (in_vim9script() && check_for_opt_number_arg(argvars, 0) == FAIL)
992 return;
993
994 win = get_optional_window(argvars, 0);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200995 if (rettv_list_alloc(rettv) == FAIL || win == NULL)
996 return;
997
998 cur = win->w_match_head;
999 while (cur != NULL)
1000 {
1001 dict = dict_alloc();
1002 if (dict == NULL)
1003 return;
1004 if (cur->match.regprog == NULL)
1005 {
1006 // match added with matchaddpos()
1007 for (i = 0; i < MAXPOSMATCH; ++i)
1008 {
1009 llpos_T *llpos;
1010 char buf[30]; // use 30 to avoid compiler warning
1011 list_T *l;
1012
1013 llpos = &cur->pos.pos[i];
1014 if (llpos->lnum == 0)
1015 break;
1016 l = list_alloc();
1017 if (l == NULL)
1018 break;
1019 list_append_number(l, (varnumber_T)llpos->lnum);
1020 if (llpos->col > 0)
1021 {
1022 list_append_number(l, (varnumber_T)llpos->col);
1023 list_append_number(l, (varnumber_T)llpos->len);
1024 }
1025 sprintf(buf, "pos%d", i + 1);
1026 dict_add_list(dict, buf, l);
1027 }
1028 }
1029 else
1030 {
1031 dict_add_string(dict, "pattern", cur->pattern);
1032 }
1033 dict_add_string(dict, "group", syn_id2name(cur->hlg_id));
1034 dict_add_number(dict, "priority", (long)cur->priority);
1035 dict_add_number(dict, "id", (long)cur->id);
1036# if defined(FEAT_CONCEAL)
1037 if (cur->conceal_char)
1038 {
1039 char_u buf[MB_MAXBYTES + 1];
1040
1041 buf[(*mb_char2bytes)((int)cur->conceal_char, buf)] = NUL;
1042 dict_add_string(dict, "conceal", (char_u *)&buf);
1043 }
1044# endif
1045 list_append_dict(rettv->vval.v_list, dict);
1046 cur = cur->next;
1047 }
1048# endif
1049}
1050
1051/*
1052 * "setmatches()" function
1053 */
1054 void
1055f_setmatches(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1056{
1057#ifdef FEAT_SEARCH_EXTRA
1058 list_T *l;
1059 listitem_T *li;
1060 dict_T *d;
1061 list_T *s = NULL;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001062 win_T *win;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001063
1064 rettv->vval.v_number = -1;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001065
1066 if (in_vim9script()
1067 && (check_for_list_arg(argvars, 0) == FAIL
1068 || check_for_opt_number_arg(argvars, 1) == FAIL))
1069 return;
1070
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001071 if (argvars[0].v_type != VAR_LIST)
1072 {
1073 emsg(_(e_listreq));
1074 return;
1075 }
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001076 win = get_optional_window(argvars, 1);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001077 if (win == NULL)
1078 return;
1079
1080 if ((l = argvars[0].vval.v_list) != NULL)
1081 {
1082 // To some extent make sure that we are dealing with a list from
1083 // "getmatches()".
1084 li = l->lv_first;
1085 while (li != NULL)
1086 {
1087 if (li->li_tv.v_type != VAR_DICT
1088 || (d = li->li_tv.vval.v_dict) == NULL)
1089 {
1090 emsg(_(e_invarg));
1091 return;
1092 }
1093 if (!(dict_find(d, (char_u *)"group", -1) != NULL
1094 && (dict_find(d, (char_u *)"pattern", -1) != NULL
1095 || dict_find(d, (char_u *)"pos1", -1) != NULL)
1096 && dict_find(d, (char_u *)"priority", -1) != NULL
1097 && dict_find(d, (char_u *)"id", -1) != NULL))
1098 {
1099 emsg(_(e_invarg));
1100 return;
1101 }
1102 li = li->li_next;
1103 }
1104
1105 clear_matches(win);
1106 li = l->lv_first;
1107 while (li != NULL)
1108 {
1109 int i = 0;
1110 char buf[30]; // use 30 to avoid compiler warning
1111 dictitem_T *di;
1112 char_u *group;
1113 int priority;
1114 int id;
1115 char_u *conceal;
1116
1117 d = li->li_tv.vval.v_dict;
1118 if (dict_find(d, (char_u *)"pattern", -1) == NULL)
1119 {
1120 if (s == NULL)
1121 {
1122 s = list_alloc();
1123 if (s == NULL)
1124 return;
1125 }
1126
1127 // match from matchaddpos()
1128 for (i = 1; i < 9; i++)
1129 {
1130 sprintf((char *)buf, (char *)"pos%d", i);
1131 if ((di = dict_find(d, (char_u *)buf, -1)) != NULL)
1132 {
1133 if (di->di_tv.v_type != VAR_LIST)
1134 return;
1135
1136 list_append_tv(s, &di->di_tv);
1137 s->lv_refcount++;
1138 }
1139 else
1140 break;
1141 }
1142 }
1143
1144 group = dict_get_string(d, (char_u *)"group", TRUE);
1145 priority = (int)dict_get_number(d, (char_u *)"priority");
1146 id = (int)dict_get_number(d, (char_u *)"id");
1147 conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
1148 ? dict_get_string(d, (char_u *)"conceal", TRUE)
1149 : NULL;
1150 if (i == 0)
1151 {
1152 match_add(win, group,
1153 dict_get_string(d, (char_u *)"pattern", FALSE),
1154 priority, id, NULL, conceal);
1155 }
1156 else
1157 {
1158 match_add(win, group, NULL, priority, id, s, conceal);
1159 list_unref(s);
1160 s = NULL;
1161 }
1162 vim_free(group);
1163 vim_free(conceal);
1164
1165 li = li->li_next;
1166 }
1167 rettv->vval.v_number = 0;
1168 }
1169#endif
1170}
1171
1172/*
1173 * "matchadd()" function
1174 */
1175 void
1176f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1177{
1178# ifdef FEAT_SEARCH_EXTRA
1179 char_u buf[NUMBUFLEN];
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02001180 char_u *grp; // group
1181 char_u *pat; // pattern
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001182 int prio = 10; // default priority
1183 int id = -1;
1184 int error = FALSE;
1185 char_u *conceal_char = NULL;
1186 win_T *win = curwin;
1187
1188 rettv->vval.v_number = -1;
1189
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02001190 if (in_vim9script()
1191 && (check_for_string_arg(argvars, 0) == FAIL
1192 || check_for_string_arg(argvars, 1) == FAIL
1193 || check_for_opt_number_arg(argvars, 2) == FAIL
1194 || (argvars[2].v_type != VAR_UNKNOWN
1195 && (check_for_opt_number_arg(argvars, 3) == FAIL
1196 || (argvars[3].v_type != VAR_UNKNOWN
1197 && check_for_opt_dict_arg(argvars, 4) == FAIL)))))
1198 return;
1199
1200 grp = tv_get_string_buf_chk(&argvars[0], buf); // group
1201 pat = tv_get_string_buf_chk(&argvars[1], buf); // pattern
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001202 if (grp == NULL || pat == NULL)
1203 return;
1204 if (argvars[2].v_type != VAR_UNKNOWN)
1205 {
1206 prio = (int)tv_get_number_chk(&argvars[2], &error);
1207 if (argvars[3].v_type != VAR_UNKNOWN)
1208 {
1209 id = (int)tv_get_number_chk(&argvars[3], &error);
1210 if (argvars[4].v_type != VAR_UNKNOWN
1211 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
1212 return;
1213 }
1214 }
1215 if (error == TRUE)
1216 return;
1217 if (id >= 1 && id <= 3)
1218 {
1219 semsg(_("E798: ID is reserved for \":match\": %d"), id);
1220 return;
1221 }
1222
1223 rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL,
1224 conceal_char);
1225# endif
1226}
1227
1228/*
1229 * "matchaddpos()" function
1230 */
1231 void
1232f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1233{
1234# ifdef FEAT_SEARCH_EXTRA
1235 char_u buf[NUMBUFLEN];
1236 char_u *group;
1237 int prio = 10;
1238 int id = -1;
1239 int error = FALSE;
1240 list_T *l;
1241 char_u *conceal_char = NULL;
1242 win_T *win = curwin;
1243
1244 rettv->vval.v_number = -1;
1245
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02001246 if (in_vim9script()
1247 && (check_for_string_arg(argvars, 0) == FAIL
1248 || check_for_list_arg(argvars, 1) == FAIL
1249 || check_for_opt_number_arg(argvars, 2) == FAIL
1250 || (argvars[2].v_type != VAR_UNKNOWN
1251 && (check_for_opt_number_arg(argvars, 3) == FAIL
1252 || (argvars[3].v_type != VAR_UNKNOWN
1253 && check_for_opt_dict_arg(argvars, 4) == FAIL)))))
1254 return;
1255
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001256 group = tv_get_string_buf_chk(&argvars[0], buf);
1257 if (group == NULL)
1258 return;
1259
1260 if (argvars[1].v_type != VAR_LIST)
1261 {
1262 semsg(_(e_listarg), "matchaddpos()");
1263 return;
1264 }
1265 l = argvars[1].vval.v_list;
1266 if (l == NULL)
1267 return;
1268
1269 if (argvars[2].v_type != VAR_UNKNOWN)
1270 {
1271 prio = (int)tv_get_number_chk(&argvars[2], &error);
1272 if (argvars[3].v_type != VAR_UNKNOWN)
1273 {
1274 id = (int)tv_get_number_chk(&argvars[3], &error);
1275
1276 if (argvars[4].v_type != VAR_UNKNOWN
1277 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
1278 return;
1279 }
1280 }
1281 if (error == TRUE)
1282 return;
1283
1284 // id == 3 is ok because matchaddpos() is supposed to substitute :3match
1285 if (id == 1 || id == 2)
1286 {
1287 semsg(_("E798: ID is reserved for \":match\": %d"), id);
1288 return;
1289 }
1290
1291 rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
1292 conceal_char);
1293# endif
1294}
1295
1296/*
1297 * "matcharg()" function
1298 */
1299 void
1300f_matcharg(typval_T *argvars UNUSED, typval_T *rettv)
1301{
1302 if (rettv_list_alloc(rettv) == OK)
1303 {
1304# ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001305 int id;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001306 matchitem_T *m;
1307
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001308 if (in_vim9script() && check_for_number_arg(argvars, 0) == FAIL)
1309 return;
1310
1311 id = (int)tv_get_number(&argvars[0]);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001312 if (id >= 1 && id <= 3)
1313 {
1314 if ((m = (matchitem_T *)get_match(curwin, id)) != NULL)
1315 {
1316 list_append_string(rettv->vval.v_list,
1317 syn_id2name(m->hlg_id), -1);
1318 list_append_string(rettv->vval.v_list, m->pattern, -1);
1319 }
1320 else
1321 {
1322 list_append_string(rettv->vval.v_list, NULL, -1);
1323 list_append_string(rettv->vval.v_list, NULL, -1);
1324 }
1325 }
1326# endif
1327 }
1328}
1329
1330/*
1331 * "matchdelete()" function
1332 */
1333 void
1334f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1335{
1336# ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001337 win_T *win;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001338
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02001339 if (in_vim9script()
1340 && (check_for_number_arg(argvars, 0) == FAIL
1341 || check_for_opt_number_arg(argvars, 1) == FAIL))
1342 return;
1343
1344 win = get_optional_window(argvars, 1);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001345 if (win == NULL)
1346 rettv->vval.v_number = -1;
1347 else
1348 rettv->vval.v_number = match_delete(win,
1349 (int)tv_get_number(&argvars[0]), TRUE);
1350# endif
1351}
1352#endif
1353
1354#if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
1355/*
1356 * ":[N]match {group} {pattern}"
1357 * Sets nextcmd to the start of the next command, if any. Also called when
1358 * skipping commands to find the next command.
1359 */
1360 void
1361ex_match(exarg_T *eap)
1362{
1363 char_u *p;
1364 char_u *g = NULL;
1365 char_u *end;
1366 int c;
1367 int id;
1368
1369 if (eap->line2 <= 3)
1370 id = eap->line2;
1371 else
1372 {
Bram Moolenaar451c2e32020-08-15 16:33:28 +02001373 emsg(_(e_invalid_command));
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001374 return;
1375 }
1376
1377 // First clear any old pattern.
1378 if (!eap->skip)
1379 match_delete(curwin, id, FALSE);
1380
1381 if (ends_excmd2(eap->cmd, eap->arg))
1382 end = eap->arg;
1383 else if ((STRNICMP(eap->arg, "none", 4) == 0
1384 && (VIM_ISWHITE(eap->arg[4])
1385 || ends_excmd2(eap->arg, eap->arg + 4))))
1386 end = eap->arg + 4;
1387 else
1388 {
1389 p = skiptowhite(eap->arg);
1390 if (!eap->skip)
1391 g = vim_strnsave(eap->arg, p - eap->arg);
1392 p = skipwhite(p);
1393 if (*p == NUL)
1394 {
1395 // There must be two arguments.
1396 vim_free(g);
1397 semsg(_(e_invarg2), eap->arg);
1398 return;
1399 }
1400 end = skip_regexp(p + 1, *p, TRUE);
1401 if (!eap->skip)
1402 {
1403 if (*end != NUL && !ends_excmd2(end, skipwhite(end + 1)))
1404 {
1405 vim_free(g);
Bram Moolenaar8930caa2020-07-23 16:37:03 +02001406 eap->errmsg = ex_errmsg(e_trailing_arg, end);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001407 return;
1408 }
1409 if (*end != *p)
1410 {
1411 vim_free(g);
1412 semsg(_(e_invarg2), p);
1413 return;
1414 }
1415
1416 c = *end;
1417 *end = NUL;
1418 match_add(curwin, g, p + 1, 10, id, NULL, NULL);
1419 vim_free(g);
1420 *end = c;
1421 }
1422 }
1423 eap->nextcmd = find_nextcmd(end);
1424}
1425#endif