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