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