blob: 741488a08d6368d0bb414c2cb99a6e14e2220dd1 [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 {
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
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!
706 * Return the updated search_attr.
707 */
708 int
709update_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)
Bram Moolenaar41f08952021-02-22 22:13:49 +0100795 {
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 }
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200804 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
847get_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
Bram Moolenaar41f08952021-02-22 22:13:49 +0100853#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
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200859 // 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
Bram Moolenaar41f08952021-02-22 22:13:49 +0100863 // 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)))
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200869 prevcol_hl_flag = TRUE;
870 else
871 {
872 cur = wp->w_match_head;
873 while (cur != NULL)
874 {
Bram Moolenaar41f08952021-02-22 22:13:49 +0100875 if (!cur->hl.is_addpos && (prevcol == (long)cur->hl.startcol
876 || (prevcol > (long)cur->hl.startcol
877 && cur->hl.endcol == MAXCOL)))
Bram Moolenaar06cf97e2020-06-28 13:17:26 +0200878 {
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
893get_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
927matchadd_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
959f_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
973f_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
1041f_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;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001048 win_T *win;
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001049
1050 rettv->vval.v_number = -1;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001051
1052 if (in_vim9script()
1053 && (check_for_list_arg(argvars, 0) == FAIL
1054 || check_for_opt_number_arg(argvars, 1) == FAIL))
1055 return;
1056
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001057 if (argvars[0].v_type != VAR_LIST)
1058 {
1059 emsg(_(e_listreq));
1060 return;
1061 }
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001062 win = get_optional_window(argvars, 1);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001063 if (win == NULL)
1064 return;
1065
1066 if ((l = argvars[0].vval.v_list) != NULL)
1067 {
1068 // To some extent make sure that we are dealing with a list from
1069 // "getmatches()".
1070 li = l->lv_first;
1071 while (li != NULL)
1072 {
1073 if (li->li_tv.v_type != VAR_DICT
1074 || (d = li->li_tv.vval.v_dict) == NULL)
1075 {
1076 emsg(_(e_invarg));
1077 return;
1078 }
1079 if (!(dict_find(d, (char_u *)"group", -1) != NULL
1080 && (dict_find(d, (char_u *)"pattern", -1) != NULL
1081 || dict_find(d, (char_u *)"pos1", -1) != NULL)
1082 && dict_find(d, (char_u *)"priority", -1) != NULL
1083 && dict_find(d, (char_u *)"id", -1) != NULL))
1084 {
1085 emsg(_(e_invarg));
1086 return;
1087 }
1088 li = li->li_next;
1089 }
1090
1091 clear_matches(win);
1092 li = l->lv_first;
1093 while (li != NULL)
1094 {
1095 int i = 0;
1096 char buf[30]; // use 30 to avoid compiler warning
1097 dictitem_T *di;
1098 char_u *group;
1099 int priority;
1100 int id;
1101 char_u *conceal;
1102
1103 d = li->li_tv.vval.v_dict;
1104 if (dict_find(d, (char_u *)"pattern", -1) == NULL)
1105 {
1106 if (s == NULL)
1107 {
1108 s = list_alloc();
1109 if (s == NULL)
1110 return;
1111 }
1112
1113 // match from matchaddpos()
1114 for (i = 1; i < 9; i++)
1115 {
1116 sprintf((char *)buf, (char *)"pos%d", i);
1117 if ((di = dict_find(d, (char_u *)buf, -1)) != NULL)
1118 {
1119 if (di->di_tv.v_type != VAR_LIST)
1120 return;
1121
1122 list_append_tv(s, &di->di_tv);
1123 s->lv_refcount++;
1124 }
1125 else
1126 break;
1127 }
1128 }
1129
1130 group = dict_get_string(d, (char_u *)"group", TRUE);
1131 priority = (int)dict_get_number(d, (char_u *)"priority");
1132 id = (int)dict_get_number(d, (char_u *)"id");
1133 conceal = dict_find(d, (char_u *)"conceal", -1) != NULL
1134 ? dict_get_string(d, (char_u *)"conceal", TRUE)
1135 : NULL;
1136 if (i == 0)
1137 {
1138 match_add(win, group,
1139 dict_get_string(d, (char_u *)"pattern", FALSE),
1140 priority, id, NULL, conceal);
1141 }
1142 else
1143 {
1144 match_add(win, group, NULL, priority, id, s, conceal);
1145 list_unref(s);
1146 s = NULL;
1147 }
1148 vim_free(group);
1149 vim_free(conceal);
1150
1151 li = li->li_next;
1152 }
1153 rettv->vval.v_number = 0;
1154 }
1155#endif
1156}
1157
1158/*
1159 * "matchadd()" function
1160 */
1161 void
1162f_matchadd(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1163{
1164# ifdef FEAT_SEARCH_EXTRA
1165 char_u buf[NUMBUFLEN];
1166 char_u *grp = tv_get_string_buf_chk(&argvars[0], buf); // group
1167 char_u *pat = tv_get_string_buf_chk(&argvars[1], buf); // pattern
1168 int prio = 10; // default priority
1169 int id = -1;
1170 int error = FALSE;
1171 char_u *conceal_char = NULL;
1172 win_T *win = curwin;
1173
1174 rettv->vval.v_number = -1;
1175
1176 if (grp == NULL || pat == NULL)
1177 return;
1178 if (argvars[2].v_type != VAR_UNKNOWN)
1179 {
1180 prio = (int)tv_get_number_chk(&argvars[2], &error);
1181 if (argvars[3].v_type != VAR_UNKNOWN)
1182 {
1183 id = (int)tv_get_number_chk(&argvars[3], &error);
1184 if (argvars[4].v_type != VAR_UNKNOWN
1185 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
1186 return;
1187 }
1188 }
1189 if (error == TRUE)
1190 return;
1191 if (id >= 1 && id <= 3)
1192 {
1193 semsg(_("E798: ID is reserved for \":match\": %d"), id);
1194 return;
1195 }
1196
1197 rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL,
1198 conceal_char);
1199# endif
1200}
1201
1202/*
1203 * "matchaddpos()" function
1204 */
1205 void
1206f_matchaddpos(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1207{
1208# ifdef FEAT_SEARCH_EXTRA
1209 char_u buf[NUMBUFLEN];
1210 char_u *group;
1211 int prio = 10;
1212 int id = -1;
1213 int error = FALSE;
1214 list_T *l;
1215 char_u *conceal_char = NULL;
1216 win_T *win = curwin;
1217
1218 rettv->vval.v_number = -1;
1219
1220 group = tv_get_string_buf_chk(&argvars[0], buf);
1221 if (group == NULL)
1222 return;
1223
1224 if (argvars[1].v_type != VAR_LIST)
1225 {
1226 semsg(_(e_listarg), "matchaddpos()");
1227 return;
1228 }
1229 l = argvars[1].vval.v_list;
1230 if (l == NULL)
1231 return;
1232
1233 if (argvars[2].v_type != VAR_UNKNOWN)
1234 {
1235 prio = (int)tv_get_number_chk(&argvars[2], &error);
1236 if (argvars[3].v_type != VAR_UNKNOWN)
1237 {
1238 id = (int)tv_get_number_chk(&argvars[3], &error);
1239
1240 if (argvars[4].v_type != VAR_UNKNOWN
1241 && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL)
1242 return;
1243 }
1244 }
1245 if (error == TRUE)
1246 return;
1247
1248 // id == 3 is ok because matchaddpos() is supposed to substitute :3match
1249 if (id == 1 || id == 2)
1250 {
1251 semsg(_("E798: ID is reserved for \":match\": %d"), id);
1252 return;
1253 }
1254
1255 rettv->vval.v_number = match_add(win, group, NULL, prio, id, l,
1256 conceal_char);
1257# endif
1258}
1259
1260/*
1261 * "matcharg()" function
1262 */
1263 void
1264f_matcharg(typval_T *argvars UNUSED, typval_T *rettv)
1265{
1266 if (rettv_list_alloc(rettv) == OK)
1267 {
1268# ifdef FEAT_SEARCH_EXTRA
1269 int id = (int)tv_get_number(&argvars[0]);
1270 matchitem_T *m;
1271
1272 if (id >= 1 && id <= 3)
1273 {
1274 if ((m = (matchitem_T *)get_match(curwin, id)) != NULL)
1275 {
1276 list_append_string(rettv->vval.v_list,
1277 syn_id2name(m->hlg_id), -1);
1278 list_append_string(rettv->vval.v_list, m->pattern, -1);
1279 }
1280 else
1281 {
1282 list_append_string(rettv->vval.v_list, NULL, -1);
1283 list_append_string(rettv->vval.v_list, NULL, -1);
1284 }
1285 }
1286# endif
1287 }
1288}
1289
1290/*
1291 * "matchdelete()" function
1292 */
1293 void
1294f_matchdelete(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
1295{
1296# ifdef FEAT_SEARCH_EXTRA
1297 win_T *win = get_optional_window(argvars, 1);
1298
1299 if (win == NULL)
1300 rettv->vval.v_number = -1;
1301 else
1302 rettv->vval.v_number = match_delete(win,
1303 (int)tv_get_number(&argvars[0]), TRUE);
1304# endif
1305}
1306#endif
1307
1308#if defined(FEAT_SEARCH_EXTRA) || defined(PROTO)
1309/*
1310 * ":[N]match {group} {pattern}"
1311 * Sets nextcmd to the start of the next command, if any. Also called when
1312 * skipping commands to find the next command.
1313 */
1314 void
1315ex_match(exarg_T *eap)
1316{
1317 char_u *p;
1318 char_u *g = NULL;
1319 char_u *end;
1320 int c;
1321 int id;
1322
1323 if (eap->line2 <= 3)
1324 id = eap->line2;
1325 else
1326 {
Bram Moolenaar451c2e32020-08-15 16:33:28 +02001327 emsg(_(e_invalid_command));
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001328 return;
1329 }
1330
1331 // First clear any old pattern.
1332 if (!eap->skip)
1333 match_delete(curwin, id, FALSE);
1334
1335 if (ends_excmd2(eap->cmd, eap->arg))
1336 end = eap->arg;
1337 else if ((STRNICMP(eap->arg, "none", 4) == 0
1338 && (VIM_ISWHITE(eap->arg[4])
1339 || ends_excmd2(eap->arg, eap->arg + 4))))
1340 end = eap->arg + 4;
1341 else
1342 {
1343 p = skiptowhite(eap->arg);
1344 if (!eap->skip)
1345 g = vim_strnsave(eap->arg, p - eap->arg);
1346 p = skipwhite(p);
1347 if (*p == NUL)
1348 {
1349 // There must be two arguments.
1350 vim_free(g);
1351 semsg(_(e_invarg2), eap->arg);
1352 return;
1353 }
1354 end = skip_regexp(p + 1, *p, TRUE);
1355 if (!eap->skip)
1356 {
1357 if (*end != NUL && !ends_excmd2(end, skipwhite(end + 1)))
1358 {
1359 vim_free(g);
Bram Moolenaar8930caa2020-07-23 16:37:03 +02001360 eap->errmsg = ex_errmsg(e_trailing_arg, end);
Bram Moolenaar06cf97e2020-06-28 13:17:26 +02001361 return;
1362 }
1363 if (*end != *p)
1364 {
1365 vim_free(g);
1366 semsg(_(e_invarg2), p);
1367 return;
1368 }
1369
1370 c = *end;
1371 *end = NUL;
1372 match_add(curwin, g, p + 1, 10, id, NULL, NULL);
1373 vim_free(g);
1374 *end = c;
1375 }
1376 }
1377 eap->nextcmd = find_nextcmd(end);
1378}
1379#endif