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