xref: /vim-8.2.3635/src/search.c (revision a2cff1db)
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  * search.c: code for normal mode searching commands
11  */
12 
13 #include "vim.h"
14 
15 #ifdef FEAT_EVAL
16 static void set_vv_searchforward(void);
17 static int first_submatch(regmmatch_T *rp);
18 #endif
19 static int check_linecomment(char_u *line);
20 #ifdef FEAT_FIND_ID
21 static void show_pat_in_path(char_u *, int,
22 					 int, int, FILE *, linenr_T *, long);
23 #endif
24 
25 typedef struct searchstat
26 {
27     int	    cur;	    // current position of found words
28     int	    cnt;	    // total count of found words
29     int	    exact_match;    // TRUE if matched exactly on specified position
30     int	    incomplete;	    // 0: search was fully completed
31 			    // 1: recomputing was timed out
32 			    // 2: max count exceeded
33     int	    last_maxcount;  // the max count of the last search
34 } searchstat_T;
35 
36 static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, int recompute, int maxcount, long timeout);
37 static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
38 
39 #define SEARCH_STAT_DEF_TIMEOUT 40L
40 #define SEARCH_STAT_DEF_MAX_COUNT 99
41 #define SEARCH_STAT_BUF_LEN 12
42 
43 /*
44  * This file contains various searching-related routines. These fall into
45  * three groups:
46  * 1. string searches (for /, ?, n, and N)
47  * 2. character searches within a single line (for f, F, t, T, etc)
48  * 3. "other" kinds of searches like the '%' command, and 'word' searches.
49  */
50 
51 /*
52  * String searches
53  *
54  * The string search functions are divided into two levels:
55  * lowest:  searchit(); uses an pos_T for starting position and found match.
56  * Highest: do_search(); uses curwin->w_cursor; calls searchit().
57  *
58  * The last search pattern is remembered for repeating the same search.
59  * This pattern is shared between the :g, :s, ? and / commands.
60  * This is in search_regcomp().
61  *
62  * The actual string matching is done using a heavily modified version of
63  * Henry Spencer's regular expression library.  See regexp.c.
64  */
65 
66 /*
67  * Two search patterns are remembered: One for the :substitute command and
68  * one for other searches.  last_idx points to the one that was used the last
69  * time.
70  */
71 static spat_T spats[2] =
72 {
73     {NULL, TRUE, FALSE, {'/', 0, 0, 0L}},	// last used search pat
74     {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}	// last used substitute pat
75 };
76 
77 static int last_idx = 0;	// index in spats[] for RE_LAST
78 
79 static char_u lastc[2] = {NUL, NUL};	// last character searched for
80 static int lastcdir = FORWARD;		// last direction of character search
81 static int last_t_cmd = TRUE;		// last search t_cmd
82 static char_u	lastc_bytes[MB_MAXBYTES + 1];
83 static int	lastc_bytelen = 1;	// >1 for multi-byte char
84 
85 // copy of spats[], for keeping the search patterns while executing autocmds
86 static spat_T	    saved_spats[2];
87 static char_u	    *saved_mr_pattern = NULL;
88 # ifdef FEAT_SEARCH_EXTRA
89 static int	    saved_spats_last_idx = 0;
90 static int	    saved_spats_no_hlsearch = 0;
91 # endif
92 
93 // allocated copy of pattern used by search_regcomp()
94 static char_u	    *mr_pattern = NULL;
95 
96 #ifdef FEAT_FIND_ID
97 /*
98  * Type used by find_pattern_in_path() to remember which included files have
99  * been searched already.
100  */
101 typedef struct SearchedFile
102 {
103     FILE	*fp;		// File pointer
104     char_u	*name;		// Full name of file
105     linenr_T	lnum;		// Line we were up to in file
106     int		matched;	// Found a match in this file
107 } SearchedFile;
108 #endif
109 
110 /*
111  * translate search pattern for vim_regcomp()
112  *
113  * pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
114  * pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
115  * pat_save == RE_BOTH: save pat in both patterns (:global command)
116  * pat_use  == RE_SEARCH: use previous search pattern if "pat" is NULL
117  * pat_use  == RE_SUBST: use previous substitute pattern if "pat" is NULL
118  * pat_use  == RE_LAST: use last used pattern if "pat" is NULL
119  * options & SEARCH_HIS: put search string in history
120  * options & SEARCH_KEEP: keep previous search pattern
121  *
122  * returns FAIL if failed, OK otherwise.
123  */
124     int
search_regcomp(char_u * pat,int pat_save,int pat_use,int options,regmmatch_T * regmatch)125 search_regcomp(
126     char_u	*pat,
127     int		pat_save,
128     int		pat_use,
129     int		options,
130     regmmatch_T	*regmatch)	// return: pattern and ignore-case flag
131 {
132     int		magic;
133     int		i;
134 
135     rc_did_emsg = FALSE;
136     magic = magic_isset();
137 
138     /*
139      * If no pattern given, use a previously defined pattern.
140      */
141     if (pat == NULL || *pat == NUL)
142     {
143 	if (pat_use == RE_LAST)
144 	    i = last_idx;
145 	else
146 	    i = pat_use;
147 	if (spats[i].pat == NULL)	// pattern was never defined
148 	{
149 	    if (pat_use == RE_SUBST)
150 		emsg(_(e_no_previous_substitute_regular_expression));
151 	    else
152 		emsg(_(e_no_previous_regular_expression));
153 	    rc_did_emsg = TRUE;
154 	    return FAIL;
155 	}
156 	pat = spats[i].pat;
157 	magic = spats[i].magic;
158 	no_smartcase = spats[i].no_scs;
159     }
160     else if (options & SEARCH_HIS)	// put new pattern in history
161 	add_to_history(HIST_SEARCH, pat, TRUE, NUL);
162 
163     vim_free(mr_pattern);
164 #ifdef FEAT_RIGHTLEFT
165     if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
166 	mr_pattern = reverse_text(pat);
167     else
168 #endif
169 	mr_pattern = vim_strsave(pat);
170 
171     /*
172      * Save the currently used pattern in the appropriate place,
173      * unless the pattern should not be remembered.
174      */
175     if (!(options & SEARCH_KEEP)
176 			       && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
177     {
178 	// search or global command
179 	if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
180 	    save_re_pat(RE_SEARCH, pat, magic);
181 	// substitute or global command
182 	if (pat_save == RE_SUBST || pat_save == RE_BOTH)
183 	    save_re_pat(RE_SUBST, pat, magic);
184     }
185 
186     regmatch->rmm_ic = ignorecase(pat);
187     regmatch->rmm_maxcol = 0;
188     regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
189     if (regmatch->regprog == NULL)
190 	return FAIL;
191     return OK;
192 }
193 
194 /*
195  * Get search pattern used by search_regcomp().
196  */
197     char_u *
get_search_pat(void)198 get_search_pat(void)
199 {
200     return mr_pattern;
201 }
202 
203 #if defined(FEAT_RIGHTLEFT) || defined(PROTO)
204 /*
205  * Reverse text into allocated memory.
206  * Returns the allocated string, NULL when out of memory.
207  */
208     char_u *
reverse_text(char_u * s)209 reverse_text(char_u *s)
210 {
211     unsigned	len;
212     unsigned	s_i, rev_i;
213     char_u	*rev;
214 
215     /*
216      * Reverse the pattern.
217      */
218     len = (unsigned)STRLEN(s);
219     rev = alloc(len + 1);
220     if (rev != NULL)
221     {
222 	rev_i = len;
223 	for (s_i = 0; s_i < len; ++s_i)
224 	{
225 	    if (has_mbyte)
226 	    {
227 		int	mb_len;
228 
229 		mb_len = (*mb_ptr2len)(s + s_i);
230 		rev_i -= mb_len;
231 		mch_memmove(rev + rev_i, s + s_i, mb_len);
232 		s_i += mb_len - 1;
233 	    }
234 	    else
235 		rev[--rev_i] = s[s_i];
236 
237 	}
238 	rev[len] = NUL;
239     }
240     return rev;
241 }
242 #endif
243 
244     void
save_re_pat(int idx,char_u * pat,int magic)245 save_re_pat(int idx, char_u *pat, int magic)
246 {
247     if (spats[idx].pat != pat)
248     {
249 	vim_free(spats[idx].pat);
250 	spats[idx].pat = vim_strsave(pat);
251 	spats[idx].magic = magic;
252 	spats[idx].no_scs = no_smartcase;
253 	last_idx = idx;
254 #ifdef FEAT_SEARCH_EXTRA
255 	// If 'hlsearch' set and search pat changed: need redraw.
256 	if (p_hls)
257 	    redraw_all_later(SOME_VALID);
258 	set_no_hlsearch(FALSE);
259 #endif
260     }
261 }
262 
263 /*
264  * Save the search patterns, so they can be restored later.
265  * Used before/after executing autocommands and user functions.
266  */
267 static int save_level = 0;
268 
269     void
save_search_patterns(void)270 save_search_patterns(void)
271 {
272     if (save_level++ == 0)
273     {
274 	saved_spats[0] = spats[0];
275 	if (spats[0].pat != NULL)
276 	    saved_spats[0].pat = vim_strsave(spats[0].pat);
277 	saved_spats[1] = spats[1];
278 	if (spats[1].pat != NULL)
279 	    saved_spats[1].pat = vim_strsave(spats[1].pat);
280 	if (mr_pattern == NULL)
281 	    saved_mr_pattern = NULL;
282 	else
283 	    saved_mr_pattern = vim_strsave(mr_pattern);
284 #ifdef FEAT_SEARCH_EXTRA
285 	saved_spats_last_idx = last_idx;
286 	saved_spats_no_hlsearch = no_hlsearch;
287 #endif
288     }
289 }
290 
291     void
restore_search_patterns(void)292 restore_search_patterns(void)
293 {
294     if (--save_level == 0)
295     {
296 	vim_free(spats[0].pat);
297 	spats[0] = saved_spats[0];
298 #if defined(FEAT_EVAL)
299 	set_vv_searchforward();
300 #endif
301 	vim_free(spats[1].pat);
302 	spats[1] = saved_spats[1];
303 	vim_free(mr_pattern);
304 	mr_pattern = saved_mr_pattern;
305 #ifdef FEAT_SEARCH_EXTRA
306 	last_idx = saved_spats_last_idx;
307 	set_no_hlsearch(saved_spats_no_hlsearch);
308 #endif
309     }
310 }
311 
312 #if defined(EXITFREE) || defined(PROTO)
313     void
free_search_patterns(void)314 free_search_patterns(void)
315 {
316     vim_free(spats[0].pat);
317     vim_free(spats[1].pat);
318     VIM_CLEAR(mr_pattern);
319 }
320 #endif
321 
322 #ifdef FEAT_SEARCH_EXTRA
323 // copy of spats[RE_SEARCH], for keeping the search patterns while incremental
324 // searching
325 static spat_T	    saved_last_search_spat;
326 static int	    did_save_last_search_spat = 0;
327 static int	    saved_last_idx = 0;
328 static int	    saved_no_hlsearch = 0;
329 
330 /*
331  * Save and restore the search pattern for incremental highlight search
332  * feature.
333  *
334  * It's similar to but different from save_search_patterns() and
335  * restore_search_patterns(), because the search pattern must be restored when
336  * canceling incremental searching even if it's called inside user functions.
337  */
338     void
save_last_search_pattern(void)339 save_last_search_pattern(void)
340 {
341     if (++did_save_last_search_spat != 1)
342 	// nested call, nothing to do
343 	return;
344 
345     saved_last_search_spat = spats[RE_SEARCH];
346     if (spats[RE_SEARCH].pat != NULL)
347 	saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
348     saved_last_idx = last_idx;
349     saved_no_hlsearch = no_hlsearch;
350 }
351 
352     void
restore_last_search_pattern(void)353 restore_last_search_pattern(void)
354 {
355     if (--did_save_last_search_spat > 0)
356 	// nested call, nothing to do
357 	return;
358     if (did_save_last_search_spat != 0)
359     {
360 	iemsg("restore_last_search_pattern() called more often than save_last_search_pattern()");
361 	return;
362     }
363 
364     vim_free(spats[RE_SEARCH].pat);
365     spats[RE_SEARCH] = saved_last_search_spat;
366     saved_last_search_spat.pat = NULL;
367 # if defined(FEAT_EVAL)
368     set_vv_searchforward();
369 # endif
370     last_idx = saved_last_idx;
371     set_no_hlsearch(saved_no_hlsearch);
372 }
373 
374     char_u *
last_search_pattern(void)375 last_search_pattern(void)
376 {
377     return spats[RE_SEARCH].pat;
378 }
379 #endif
380 
381 /*
382  * Return TRUE when case should be ignored for search pattern "pat".
383  * Uses the 'ignorecase' and 'smartcase' options.
384  */
385     int
ignorecase(char_u * pat)386 ignorecase(char_u *pat)
387 {
388     return ignorecase_opt(pat, p_ic, p_scs);
389 }
390 
391 /*
392  * As ignorecase() put pass the "ic" and "scs" flags.
393  */
394     int
ignorecase_opt(char_u * pat,int ic_in,int scs)395 ignorecase_opt(char_u *pat, int ic_in, int scs)
396 {
397     int		ic = ic_in;
398 
399     if (ic && !no_smartcase && scs
400 			    && !(ctrl_x_mode_not_default() && curbuf->b_p_inf))
401 	ic = !pat_has_uppercase(pat);
402     no_smartcase = FALSE;
403 
404     return ic;
405 }
406 
407 /*
408  * Return TRUE if pattern "pat" has an uppercase character.
409  */
410     int
pat_has_uppercase(char_u * pat)411 pat_has_uppercase(char_u *pat)
412 {
413     char_u *p = pat;
414     magic_T magic_val = MAGIC_ON;
415 
416     // get the magicness of the pattern
417     (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
418 
419     while (*p != NUL)
420     {
421 	int		l;
422 
423 	if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
424 	{
425 	    if (enc_utf8 && utf_isupper(utf_ptr2char(p)))
426 		return TRUE;
427 	    p += l;
428 	}
429 	else if (*p == '\\' && magic_val <= MAGIC_ON)
430 	{
431 	    if (p[1] == '_' && p[2] != NUL)  // skip "\_X"
432 		p += 3;
433 	    else if (p[1] == '%' && p[2] != NUL)  // skip "\%X"
434 		p += 3;
435 	    else if (p[1] != NUL)  // skip "\X"
436 		p += 2;
437 	    else
438 		p += 1;
439 	}
440 	else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL)
441 	{
442 	    if (p[1] != NUL)  // skip "_X" and %X
443 		p += 2;
444 	    else
445 		p++;
446 	}
447 	else if (MB_ISUPPER(*p))
448 	    return TRUE;
449 	else
450 	    ++p;
451     }
452     return FALSE;
453 }
454 
455 #if defined(FEAT_EVAL) || defined(PROTO)
456     char_u *
last_csearch(void)457 last_csearch(void)
458 {
459     return lastc_bytes;
460 }
461 
462     int
last_csearch_forward(void)463 last_csearch_forward(void)
464 {
465     return lastcdir == FORWARD;
466 }
467 
468     int
last_csearch_until(void)469 last_csearch_until(void)
470 {
471     return last_t_cmd == TRUE;
472 }
473 
474     void
set_last_csearch(int c,char_u * s UNUSED,int len UNUSED)475 set_last_csearch(int c, char_u *s UNUSED, int len UNUSED)
476 {
477     *lastc = c;
478     lastc_bytelen = len;
479     if (len)
480 	memcpy(lastc_bytes, s, len);
481     else
482 	CLEAR_FIELD(lastc_bytes);
483 }
484 #endif
485 
486     void
set_csearch_direction(int cdir)487 set_csearch_direction(int cdir)
488 {
489     lastcdir = cdir;
490 }
491 
492     void
set_csearch_until(int t_cmd)493 set_csearch_until(int t_cmd)
494 {
495     last_t_cmd = t_cmd;
496 }
497 
498     char_u *
last_search_pat(void)499 last_search_pat(void)
500 {
501     return spats[last_idx].pat;
502 }
503 
504 /*
505  * Reset search direction to forward.  For "gd" and "gD" commands.
506  */
507     void
reset_search_dir(void)508 reset_search_dir(void)
509 {
510     spats[0].off.dir = '/';
511 #if defined(FEAT_EVAL)
512     set_vv_searchforward();
513 #endif
514 }
515 
516 #if defined(FEAT_EVAL) || defined(FEAT_VIMINFO)
517 /*
518  * Set the last search pattern.  For ":let @/ =" and viminfo.
519  * Also set the saved search pattern, so that this works in an autocommand.
520  */
521     void
set_last_search_pat(char_u * s,int idx,int magic,int setlast)522 set_last_search_pat(
523     char_u	*s,
524     int		idx,
525     int		magic,
526     int		setlast)
527 {
528     vim_free(spats[idx].pat);
529     // An empty string means that nothing should be matched.
530     if (*s == NUL)
531 	spats[idx].pat = NULL;
532     else
533 	spats[idx].pat = vim_strsave(s);
534     spats[idx].magic = magic;
535     spats[idx].no_scs = FALSE;
536     spats[idx].off.dir = '/';
537 #if defined(FEAT_EVAL)
538     set_vv_searchforward();
539 #endif
540     spats[idx].off.line = FALSE;
541     spats[idx].off.end = FALSE;
542     spats[idx].off.off = 0;
543     if (setlast)
544 	last_idx = idx;
545     if (save_level)
546     {
547 	vim_free(saved_spats[idx].pat);
548 	saved_spats[idx] = spats[0];
549 	if (spats[idx].pat == NULL)
550 	    saved_spats[idx].pat = NULL;
551 	else
552 	    saved_spats[idx].pat = vim_strsave(spats[idx].pat);
553 # ifdef FEAT_SEARCH_EXTRA
554 	saved_spats_last_idx = last_idx;
555 # endif
556     }
557 # ifdef FEAT_SEARCH_EXTRA
558     // If 'hlsearch' set and search pat changed: need redraw.
559     if (p_hls && idx == last_idx && !no_hlsearch)
560 	redraw_all_later(SOME_VALID);
561 # endif
562 }
563 #endif
564 
565 #ifdef FEAT_SEARCH_EXTRA
566 /*
567  * Get a regexp program for the last used search pattern.
568  * This is used for highlighting all matches in a window.
569  * Values returned in regmatch->regprog and regmatch->rmm_ic.
570  */
571     void
last_pat_prog(regmmatch_T * regmatch)572 last_pat_prog(regmmatch_T *regmatch)
573 {
574     if (spats[last_idx].pat == NULL)
575     {
576 	regmatch->regprog = NULL;
577 	return;
578     }
579     ++emsg_off;		// So it doesn't beep if bad expr
580     (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch);
581     --emsg_off;
582 }
583 #endif
584 
585 /*
586  * Lowest level search function.
587  * Search for 'count'th occurrence of pattern "pat" in direction "dir".
588  * Start at position "pos" and return the found position in "pos".
589  *
590  * if (options & SEARCH_MSG) == 0 don't give any messages
591  * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
592  * if (options & SEARCH_MSG) == SEARCH_MSG give all messages
593  * if (options & SEARCH_HIS) put search pattern in history
594  * if (options & SEARCH_END) return position at end of match
595  * if (options & SEARCH_START) accept match at pos itself
596  * if (options & SEARCH_KEEP) keep previous search pattern
597  * if (options & SEARCH_FOLD) match only once in a closed fold
598  * if (options & SEARCH_PEEK) check for typed char, cancel search
599  * if (options & SEARCH_COL) start at pos->col instead of zero
600  *
601  * Return FAIL (zero) for failure, non-zero for success.
602  * When FEAT_EVAL is defined, returns the index of the first matching
603  * subpattern plus one; one if there was none.
604  */
605     int
searchit(win_T * win,buf_T * buf,pos_T * pos,pos_T * end_pos,int dir,char_u * pat,long count,int options,int pat_use,searchit_arg_T * extra_arg)606 searchit(
607     win_T	*win,		// window to search in; can be NULL for a
608 				// buffer without a window!
609     buf_T	*buf,
610     pos_T	*pos,
611     pos_T	*end_pos,	// set to end of the match, unless NULL
612     int		dir,
613     char_u	*pat,
614     long	count,
615     int		options,
616     int		pat_use,	// which pattern to use when "pat" is empty
617     searchit_arg_T *extra_arg)	// optional extra arguments, can be NULL
618 {
619     int		found;
620     linenr_T	lnum;		// no init to shut up Apollo cc
621     colnr_T	col;
622     regmmatch_T	regmatch;
623     char_u	*ptr;
624     colnr_T	matchcol;
625     lpos_T	endpos;
626     lpos_T	matchpos;
627     int		loop;
628     pos_T	start_pos;
629     int		at_first_line;
630     int		extra_col;
631     int		start_char_len;
632     int		match_ok;
633     long	nmatched;
634     int		submatch = 0;
635     int		first_match = TRUE;
636     int		called_emsg_before = called_emsg;
637 #ifdef FEAT_SEARCH_EXTRA
638     int		break_loop = FALSE;
639 #endif
640     linenr_T	stop_lnum = 0;	// stop after this line number when != 0
641 #ifdef FEAT_RELTIME
642     proftime_T	*tm = NULL;	// timeout limit or NULL
643     int		*timed_out = NULL;  // set when timed out or NULL
644 #endif
645 
646     if (extra_arg != NULL)
647     {
648 	stop_lnum = extra_arg->sa_stop_lnum;
649 #ifdef FEAT_RELTIME
650 	tm = extra_arg->sa_tm;
651 	timed_out = &extra_arg->sa_timed_out;
652 #endif
653     }
654 
655     if (search_regcomp(pat, RE_SEARCH, pat_use,
656 		   (options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL)
657     {
658 	if ((options & SEARCH_MSG) && !rc_did_emsg)
659 	    semsg(_("E383: Invalid search string: %s"), mr_pattern);
660 	return FAIL;
661     }
662 
663     /*
664      * find the string
665      */
666     do	// loop for count
667     {
668 	// When not accepting a match at the start position set "extra_col" to
669 	// a non-zero value.  Don't do that when starting at MAXCOL, since
670 	// MAXCOL + 1 is zero.
671 	if (pos->col == MAXCOL)
672 	    start_char_len = 0;
673 	// Watch out for the "col" being MAXCOL - 2, used in a closed fold.
674 	else if (has_mbyte
675 		    && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count
676 						    && pos->col < MAXCOL - 2)
677 	{
678 	    ptr = ml_get_buf(buf, pos->lnum, FALSE);
679 	    if ((int)STRLEN(ptr) <= pos->col)
680 		start_char_len = 1;
681 	    else
682 		start_char_len = (*mb_ptr2len)(ptr + pos->col);
683 	}
684 	else
685 	    start_char_len = 1;
686 	if (dir == FORWARD)
687 	{
688 	    if (options & SEARCH_START)
689 		extra_col = 0;
690 	    else
691 		extra_col = start_char_len;
692 	}
693 	else
694 	{
695 	    if (options & SEARCH_START)
696 		extra_col = start_char_len;
697 	    else
698 		extra_col = 0;
699 	}
700 
701 	start_pos = *pos;	// remember start pos for detecting no match
702 	found = 0;		// default: not found
703 	at_first_line = TRUE;	// default: start in first line
704 	if (pos->lnum == 0)	// correct lnum for when starting in line 0
705 	{
706 	    pos->lnum = 1;
707 	    pos->col = 0;
708 	    at_first_line = FALSE;  // not in first line now
709 	}
710 
711 	/*
712 	 * Start searching in current line, unless searching backwards and
713 	 * we're in column 0.
714 	 * If we are searching backwards, in column 0, and not including the
715 	 * current position, gain some efficiency by skipping back a line.
716 	 * Otherwise begin the search in the current line.
717 	 */
718 	if (dir == BACKWARD && start_pos.col == 0
719 					     && (options & SEARCH_START) == 0)
720 	{
721 	    lnum = pos->lnum - 1;
722 	    at_first_line = FALSE;
723 	}
724 	else
725 	    lnum = pos->lnum;
726 
727 	for (loop = 0; loop <= 1; ++loop)   // loop twice if 'wrapscan' set
728 	{
729 	    for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
730 					   lnum += dir, at_first_line = FALSE)
731 	    {
732 		// Stop after checking "stop_lnum", if it's set.
733 		if (stop_lnum != 0 && (dir == FORWARD
734 				       ? lnum > stop_lnum : lnum < stop_lnum))
735 		    break;
736 #ifdef FEAT_RELTIME
737 		// Stop after passing the "tm" time limit.
738 		if (tm != NULL && profile_passed_limit(tm))
739 		    break;
740 #endif
741 
742 		/*
743 		 * Look for a match somewhere in line "lnum".
744 		 */
745 		col = at_first_line && (options & SEARCH_COL) ? pos->col
746 								 : (colnr_T)0;
747 		nmatched = vim_regexec_multi(&regmatch, win, buf,
748 					     lnum, col,
749 #ifdef FEAT_RELTIME
750 					     tm, timed_out
751 #else
752 					     NULL, NULL
753 #endif
754 						      );
755 		// vim_regexec_multi() may clear "regprog"
756 		if (regmatch.regprog == NULL)
757 		    break;
758 		// Abort searching on an error (e.g., out of stack).
759 		if (called_emsg > called_emsg_before
760 #ifdef FEAT_RELTIME
761 			|| (timed_out != NULL && *timed_out)
762 #endif
763 			)
764 		    break;
765 		if (nmatched > 0)
766 		{
767 		    // match may actually be in another line when using \zs
768 		    matchpos = regmatch.startpos[0];
769 		    endpos = regmatch.endpos[0];
770 #ifdef FEAT_EVAL
771 		    submatch = first_submatch(&regmatch);
772 #endif
773 		    // "lnum" may be past end of buffer for "\n\zs".
774 		    if (lnum + matchpos.lnum > buf->b_ml.ml_line_count)
775 			ptr = (char_u *)"";
776 		    else
777 			ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
778 
779 		    /*
780 		     * Forward search in the first line: match should be after
781 		     * the start position. If not, continue at the end of the
782 		     * match (this is vi compatible) or on the next char.
783 		     */
784 		    if (dir == FORWARD && at_first_line)
785 		    {
786 			match_ok = TRUE;
787 			/*
788 			 * When the match starts in a next line it's certainly
789 			 * past the start position.
790 			 * When match lands on a NUL the cursor will be put
791 			 * one back afterwards, compare with that position,
792 			 * otherwise "/$" will get stuck on end of line.
793 			 */
794 			while (matchpos.lnum == 0
795 				&& ((options & SEARCH_END) && first_match
796 				    ?  (nmatched == 1
797 					&& (int)endpos.col - 1
798 					     < (int)start_pos.col + extra_col)
799 				    : ((int)matchpos.col
800 						  - (ptr[matchpos.col] == NUL)
801 					    < (int)start_pos.col + extra_col)))
802 			{
803 			    /*
804 			     * If vi-compatible searching, continue at the end
805 			     * of the match, otherwise continue one position
806 			     * forward.
807 			     */
808 			    if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
809 			    {
810 				if (nmatched > 1)
811 				{
812 				    // end is in next line, thus no match in
813 				    // this line
814 				    match_ok = FALSE;
815 				    break;
816 				}
817 				matchcol = endpos.col;
818 				// for empty match: advance one char
819 				if (matchcol == matchpos.col
820 						      && ptr[matchcol] != NUL)
821 				{
822 				    if (has_mbyte)
823 					matchcol +=
824 					  (*mb_ptr2len)(ptr + matchcol);
825 				    else
826 					++matchcol;
827 				}
828 			    }
829 			    else
830 			    {
831 				matchcol = matchpos.col;
832 				if (ptr[matchcol] != NUL)
833 				{
834 				    if (has_mbyte)
835 					matchcol += (*mb_ptr2len)(ptr
836 								  + matchcol);
837 				    else
838 					++matchcol;
839 				}
840 			    }
841 			    if (matchcol == 0 && (options & SEARCH_START))
842 				break;
843 			    if (ptr[matchcol] == NUL
844 				    || (nmatched = vim_regexec_multi(&regmatch,
845 					      win, buf, lnum + matchpos.lnum,
846 					      matchcol,
847 #ifdef FEAT_RELTIME
848 					      tm, timed_out
849 #else
850 					      NULL, NULL
851 #endif
852 					      )) == 0)
853 			    {
854 				match_ok = FALSE;
855 				break;
856 			    }
857 			    // vim_regexec_multi() may clear "regprog"
858 			    if (regmatch.regprog == NULL)
859 				break;
860 			    matchpos = regmatch.startpos[0];
861 			    endpos = regmatch.endpos[0];
862 # ifdef FEAT_EVAL
863 			    submatch = first_submatch(&regmatch);
864 # endif
865 
866 			    // Need to get the line pointer again, a
867 			    // multi-line search may have made it invalid.
868 			    ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
869 			}
870 			if (!match_ok)
871 			    continue;
872 		    }
873 		    if (dir == BACKWARD)
874 		    {
875 			/*
876 			 * Now, if there are multiple matches on this line,
877 			 * we have to get the last one. Or the last one before
878 			 * the cursor, if we're on that line.
879 			 * When putting the new cursor at the end, compare
880 			 * relative to the end of the match.
881 			 */
882 			match_ok = FALSE;
883 			for (;;)
884 			{
885 			    // Remember a position that is before the start
886 			    // position, we use it if it's the last match in
887 			    // the line.  Always accept a position after
888 			    // wrapping around.
889 			    if (loop
890 				|| ((options & SEARCH_END)
891 				    ? (lnum + regmatch.endpos[0].lnum
892 							      < start_pos.lnum
893 					|| (lnum + regmatch.endpos[0].lnum
894 							     == start_pos.lnum
895 					     && (int)regmatch.endpos[0].col - 1
896 							< (int)start_pos.col
897 								+ extra_col))
898 				    : (lnum + regmatch.startpos[0].lnum
899 							      < start_pos.lnum
900 					|| (lnum + regmatch.startpos[0].lnum
901 							     == start_pos.lnum
902 					     && (int)regmatch.startpos[0].col
903 						      < (int)start_pos.col
904 							      + extra_col))))
905 			    {
906 				match_ok = TRUE;
907 				matchpos = regmatch.startpos[0];
908 				endpos = regmatch.endpos[0];
909 # ifdef FEAT_EVAL
910 				submatch = first_submatch(&regmatch);
911 # endif
912 			    }
913 			    else
914 				break;
915 
916 			    /*
917 			     * We found a valid match, now check if there is
918 			     * another one after it.
919 			     * If vi-compatible searching, continue at the end
920 			     * of the match, otherwise continue one position
921 			     * forward.
922 			     */
923 			    if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
924 			    {
925 				if (nmatched > 1)
926 				    break;
927 				matchcol = endpos.col;
928 				// for empty match: advance one char
929 				if (matchcol == matchpos.col
930 						      && ptr[matchcol] != NUL)
931 				{
932 				    if (has_mbyte)
933 					matchcol +=
934 					  (*mb_ptr2len)(ptr + matchcol);
935 				    else
936 					++matchcol;
937 				}
938 			    }
939 			    else
940 			    {
941 				// Stop when the match is in a next line.
942 				if (matchpos.lnum > 0)
943 				    break;
944 				matchcol = matchpos.col;
945 				if (ptr[matchcol] != NUL)
946 				{
947 				    if (has_mbyte)
948 					matchcol +=
949 					  (*mb_ptr2len)(ptr + matchcol);
950 				    else
951 					++matchcol;
952 				}
953 			    }
954 			    if (ptr[matchcol] == NUL
955 				    || (nmatched = vim_regexec_multi(&regmatch,
956 					      win, buf, lnum + matchpos.lnum,
957 					      matchcol,
958 #ifdef FEAT_RELTIME
959 					      tm, timed_out
960 #else
961 					      NULL, NULL
962 #endif
963 					    )) == 0)
964 			    {
965 #ifdef FEAT_RELTIME
966 				// If the search timed out, we did find a match
967 				// but it might be the wrong one, so that's not
968 				// OK.
969 				if (timed_out != NULL && *timed_out)
970 				    match_ok = FALSE;
971 #endif
972 				break;
973 			    }
974 			    // vim_regexec_multi() may clear "regprog"
975 			    if (regmatch.regprog == NULL)
976 				break;
977 
978 			    // Need to get the line pointer again, a
979 			    // multi-line search may have made it invalid.
980 			    ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
981 			}
982 
983 			/*
984 			 * If there is only a match after the cursor, skip
985 			 * this match.
986 			 */
987 			if (!match_ok)
988 			    continue;
989 		    }
990 
991 		    // With the SEARCH_END option move to the last character
992 		    // of the match.  Don't do it for an empty match, end
993 		    // should be same as start then.
994 		    if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
995 			    && !(matchpos.lnum == endpos.lnum
996 				&& matchpos.col == endpos.col))
997 		    {
998 			// For a match in the first column, set the position
999 			// on the NUL in the previous line.
1000 			pos->lnum = lnum + endpos.lnum;
1001 			pos->col = endpos.col;
1002 			if (endpos.col == 0)
1003 			{
1004 			    if (pos->lnum > 1)  // just in case
1005 			    {
1006 				--pos->lnum;
1007 				pos->col = (colnr_T)STRLEN(ml_get_buf(buf,
1008 							   pos->lnum, FALSE));
1009 			    }
1010 			}
1011 			else
1012 			{
1013 			    --pos->col;
1014 			    if (has_mbyte
1015 				    && pos->lnum <= buf->b_ml.ml_line_count)
1016 			    {
1017 				ptr = ml_get_buf(buf, pos->lnum, FALSE);
1018 				pos->col -= (*mb_head_off)(ptr, ptr + pos->col);
1019 			    }
1020 			}
1021 			if (end_pos != NULL)
1022 			{
1023 			    end_pos->lnum = lnum + matchpos.lnum;
1024 			    end_pos->col = matchpos.col;
1025 			}
1026 		    }
1027 		    else
1028 		    {
1029 			pos->lnum = lnum + matchpos.lnum;
1030 			pos->col = matchpos.col;
1031 			if (end_pos != NULL)
1032 			{
1033 			    end_pos->lnum = lnum + endpos.lnum;
1034 			    end_pos->col = endpos.col;
1035 			}
1036 		    }
1037 		    pos->coladd = 0;
1038 		    if (end_pos != NULL)
1039 			end_pos->coladd = 0;
1040 		    found = 1;
1041 		    first_match = FALSE;
1042 
1043 		    // Set variables used for 'incsearch' highlighting.
1044 		    search_match_lines = endpos.lnum - matchpos.lnum;
1045 		    search_match_endcol = endpos.col;
1046 		    break;
1047 		}
1048 		line_breakcheck();	// stop if ctrl-C typed
1049 		if (got_int)
1050 		    break;
1051 
1052 #ifdef FEAT_SEARCH_EXTRA
1053 		// Cancel searching if a character was typed.  Used for
1054 		// 'incsearch'.  Don't check too often, that would slowdown
1055 		// searching too much.
1056 		if ((options & SEARCH_PEEK)
1057 			&& ((lnum - pos->lnum) & 0x3f) == 0
1058 			&& char_avail())
1059 		{
1060 		    break_loop = TRUE;
1061 		    break;
1062 		}
1063 #endif
1064 
1065 		if (loop && lnum == start_pos.lnum)
1066 		    break;	    // if second loop, stop where started
1067 	    }
1068 	    at_first_line = FALSE;
1069 
1070 	    // vim_regexec_multi() may clear "regprog"
1071 	    if (regmatch.regprog == NULL)
1072 		break;
1073 
1074 	    /*
1075 	     * Stop the search if wrapscan isn't set, "stop_lnum" is
1076 	     * specified, after an interrupt, after a match and after looping
1077 	     * twice.
1078 	     */
1079 	    if (!p_ws || stop_lnum != 0 || got_int
1080 					    || called_emsg > called_emsg_before
1081 #ifdef FEAT_RELTIME
1082 				|| (timed_out != NULL && *timed_out)
1083 #endif
1084 #ifdef FEAT_SEARCH_EXTRA
1085 				|| break_loop
1086 #endif
1087 				|| found || loop)
1088 		break;
1089 
1090 	    /*
1091 	     * If 'wrapscan' is set we continue at the other end of the file.
1092 	     * If 'shortmess' does not contain 's', we give a message.
1093 	     * This message is also remembered in keep_msg for when the screen
1094 	     * is redrawn. The keep_msg is cleared whenever another message is
1095 	     * written.
1096 	     */
1097 	    if (dir == BACKWARD)    // start second loop at the other end
1098 		lnum = buf->b_ml.ml_line_count;
1099 	    else
1100 		lnum = 1;
1101 	    if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
1102 		give_warning((char_u *)_(dir == BACKWARD
1103 					  ? top_bot_msg : bot_top_msg), TRUE);
1104 	    if (extra_arg != NULL)
1105 		extra_arg->sa_wrapped = TRUE;
1106 	}
1107 	if (got_int || called_emsg > called_emsg_before
1108 #ifdef FEAT_RELTIME
1109 		|| (timed_out != NULL && *timed_out)
1110 #endif
1111 #ifdef FEAT_SEARCH_EXTRA
1112 		|| break_loop
1113 #endif
1114 		)
1115 	    break;
1116     }
1117     while (--count > 0 && found);   // stop after count matches or no match
1118 
1119     vim_regfree(regmatch.regprog);
1120 
1121     if (!found)		    // did not find it
1122     {
1123 	if (got_int)
1124 	    emsg(_(e_interr));
1125 	else if ((options & SEARCH_MSG) == SEARCH_MSG)
1126 	{
1127 	    if (p_ws)
1128 		semsg(_(e_patnotf2), mr_pattern);
1129 	    else if (lnum == 0)
1130 		semsg(_("E384: search hit TOP without match for: %s"),
1131 								  mr_pattern);
1132 	    else
1133 		semsg(_("E385: search hit BOTTOM without match for: %s"),
1134 								  mr_pattern);
1135 	}
1136 	return FAIL;
1137     }
1138 
1139     // A pattern like "\n\zs" may go past the last line.
1140     if (pos->lnum > buf->b_ml.ml_line_count)
1141     {
1142 	pos->lnum = buf->b_ml.ml_line_count;
1143 	pos->col = (int)STRLEN(ml_get_buf(buf, pos->lnum, FALSE));
1144 	if (pos->col > 0)
1145 	    --pos->col;
1146     }
1147 
1148     return submatch + 1;
1149 }
1150 
1151 #ifdef FEAT_EVAL
1152     void
set_search_direction(int cdir)1153 set_search_direction(int cdir)
1154 {
1155     spats[0].off.dir = cdir;
1156 }
1157 
1158     static void
set_vv_searchforward(void)1159 set_vv_searchforward(void)
1160 {
1161     set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
1162 }
1163 
1164 /*
1165  * Return the number of the first subpat that matched.
1166  * Return zero if none of them matched.
1167  */
1168     static int
first_submatch(regmmatch_T * rp)1169 first_submatch(regmmatch_T *rp)
1170 {
1171     int		submatch;
1172 
1173     for (submatch = 1; ; ++submatch)
1174     {
1175 	if (rp->startpos[submatch].lnum >= 0)
1176 	    break;
1177 	if (submatch == 9)
1178 	{
1179 	    submatch = 0;
1180 	    break;
1181 	}
1182     }
1183     return submatch;
1184 }
1185 #endif
1186 
1187 /*
1188  * Highest level string search function.
1189  * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
1190  *		  If 'dirc' is 0: use previous dir.
1191  *    If 'pat' is NULL or empty : use previous string.
1192  *    If 'options & SEARCH_REV' : go in reverse of previous dir.
1193  *    If 'options & SEARCH_ECHO': echo the search command and handle options
1194  *    If 'options & SEARCH_MSG' : may give error message
1195  *    If 'options & SEARCH_OPT' : interpret optional flags
1196  *    If 'options & SEARCH_HIS' : put search pattern in history
1197  *    If 'options & SEARCH_NOOF': don't add offset to position
1198  *    If 'options & SEARCH_MARK': set previous context mark
1199  *    If 'options & SEARCH_KEEP': keep previous search pattern
1200  *    If 'options & SEARCH_START': accept match at curpos itself
1201  *    If 'options & SEARCH_PEEK': check for typed char, cancel search
1202  *
1203  * Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
1204  * makes the movement linewise without moving the match position.
1205  *
1206  * Return 0 for failure, 1 for found, 2 for found and line offset added.
1207  */
1208     int
do_search(oparg_T * oap,int dirc,int search_delim,char_u * pat,long count,int options,searchit_arg_T * sia)1209 do_search(
1210     oparg_T	    *oap,	// can be NULL
1211     int		    dirc,	// '/' or '?'
1212     int		    search_delim, // the delimiter for the search, e.g. '%' in
1213 				  // s%regex%replacement%
1214     char_u	    *pat,
1215     long	    count,
1216     int		    options,
1217     searchit_arg_T  *sia)	// optional arguments or NULL
1218 {
1219     pos_T	    pos;	// position of the last match
1220     char_u	    *searchstr;
1221     soffset_T	    old_off;
1222     int		    retval;	// Return value
1223     char_u	    *p;
1224     long	    c;
1225     char_u	    *dircp;
1226     char_u	    *strcopy = NULL;
1227     char_u	    *ps;
1228     char_u	    *msgbuf = NULL;
1229     size_t	    len;
1230     int		    has_offset = FALSE;
1231 
1232     /*
1233      * A line offset is not remembered, this is vi compatible.
1234      */
1235     if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
1236     {
1237 	spats[0].off.line = FALSE;
1238 	spats[0].off.off = 0;
1239     }
1240 
1241     /*
1242      * Save the values for when (options & SEARCH_KEEP) is used.
1243      * (there is no "if ()" around this because gcc wants them initialized)
1244      */
1245     old_off = spats[0].off;
1246 
1247     pos = curwin->w_cursor;	// start searching at the cursor position
1248 
1249     /*
1250      * Find out the direction of the search.
1251      */
1252     if (dirc == 0)
1253 	dirc = spats[0].off.dir;
1254     else
1255     {
1256 	spats[0].off.dir = dirc;
1257 #if defined(FEAT_EVAL)
1258 	set_vv_searchforward();
1259 #endif
1260     }
1261     if (options & SEARCH_REV)
1262     {
1263 #ifdef MSWIN
1264 	// There is a bug in the Visual C++ 2.2 compiler which means that
1265 	// dirc always ends up being '/'
1266 	dirc = (dirc == '/')  ?  '?'  :  '/';
1267 #else
1268 	if (dirc == '/')
1269 	    dirc = '?';
1270 	else
1271 	    dirc = '/';
1272 #endif
1273     }
1274 
1275 #ifdef FEAT_FOLDING
1276     // If the cursor is in a closed fold, don't find another match in the same
1277     // fold.
1278     if (dirc == '/')
1279     {
1280 	if (hasFolding(pos.lnum, NULL, &pos.lnum))
1281 	    pos.col = MAXCOL - 2;	// avoid overflow when adding 1
1282     }
1283     else
1284     {
1285 	if (hasFolding(pos.lnum, &pos.lnum, NULL))
1286 	    pos.col = 0;
1287     }
1288 #endif
1289 
1290 #ifdef FEAT_SEARCH_EXTRA
1291     /*
1292      * Turn 'hlsearch' highlighting back on.
1293      */
1294     if (no_hlsearch && !(options & SEARCH_KEEP))
1295     {
1296 	redraw_all_later(SOME_VALID);
1297 	set_no_hlsearch(FALSE);
1298     }
1299 #endif
1300 
1301     /*
1302      * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
1303      */
1304     for (;;)
1305     {
1306 	int		show_top_bot_msg = FALSE;
1307 
1308 	searchstr = pat;
1309 	dircp = NULL;
1310 					    // use previous pattern
1311 	if (pat == NULL || *pat == NUL || *pat == search_delim)
1312 	{
1313 	    if (spats[RE_SEARCH].pat == NULL)	    // no previous pattern
1314 	    {
1315 		searchstr = spats[RE_SUBST].pat;
1316 		if (searchstr == NULL)
1317 		{
1318 		    emsg(_(e_no_previous_regular_expression));
1319 		    retval = 0;
1320 		    goto end_do_search;
1321 		}
1322 	    }
1323 	    else
1324 	    {
1325 		// make search_regcomp() use spats[RE_SEARCH].pat
1326 		searchstr = (char_u *)"";
1327 	    }
1328 	}
1329 
1330 	if (pat != NULL && *pat != NUL)	// look for (new) offset
1331 	{
1332 	    /*
1333 	     * Find end of regular expression.
1334 	     * If there is a matching '/' or '?', toss it.
1335 	     */
1336 	    ps = strcopy;
1337 	    p = skip_regexp_ex(pat, search_delim, magic_isset(),
1338 							&strcopy, NULL, NULL);
1339 	    if (strcopy != ps)
1340 	    {
1341 		// made a copy of "pat" to change "\?" to "?"
1342 		searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy));
1343 		pat = strcopy;
1344 		searchstr = strcopy;
1345 	    }
1346 	    if (*p == search_delim)
1347 	    {
1348 		dircp = p;	// remember where we put the NUL
1349 		*p++ = NUL;
1350 	    }
1351 	    spats[0].off.line = FALSE;
1352 	    spats[0].off.end = FALSE;
1353 	    spats[0].off.off = 0;
1354 	    /*
1355 	     * Check for a line offset or a character offset.
1356 	     * For get_address (echo off) we don't check for a character
1357 	     * offset, because it is meaningless and the 's' could be a
1358 	     * substitute command.
1359 	     */
1360 	    if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p))
1361 		spats[0].off.line = TRUE;
1362 	    else if ((options & SEARCH_OPT) &&
1363 					(*p == 'e' || *p == 's' || *p == 'b'))
1364 	    {
1365 		if (*p == 'e')		// end
1366 		    spats[0].off.end = SEARCH_END;
1367 		++p;
1368 	    }
1369 	    if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-')  // got an offset
1370 	    {
1371 					    // 'nr' or '+nr' or '-nr'
1372 		if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
1373 		    spats[0].off.off = atol((char *)p);
1374 		else if (*p == '-')	    // single '-'
1375 		    spats[0].off.off = -1;
1376 		else			    // single '+'
1377 		    spats[0].off.off = 1;
1378 		++p;
1379 		while (VIM_ISDIGIT(*p))	    // skip number
1380 		    ++p;
1381 	    }
1382 
1383 	    // compute length of search command for get_address()
1384 	    searchcmdlen += (int)(p - pat);
1385 
1386 	    pat = p;			    // put pat after search command
1387 	}
1388 
1389 	if ((options & SEARCH_ECHO) && messaging() &&
1390 		!msg_silent &&
1391 		(!cmd_silent || !shortmess(SHM_SEARCHCOUNT)))
1392 	{
1393 	    char_u	*trunc;
1394 	    char_u	off_buf[40];
1395 	    size_t	off_len = 0;
1396 
1397 	    // Compute msg_row early.
1398 	    msg_start();
1399 
1400 	    // Get the offset, so we know how long it is.
1401 	    if (!cmd_silent &&
1402 		    (spats[0].off.line || spats[0].off.end || spats[0].off.off))
1403 	    {
1404 		p = off_buf;
1405 		*p++ = dirc;
1406 		if (spats[0].off.end)
1407 		    *p++ = 'e';
1408 		else if (!spats[0].off.line)
1409 		    *p++ = 's';
1410 		if (spats[0].off.off > 0 || spats[0].off.line)
1411 		    *p++ = '+';
1412 		*p = NUL;
1413 		if (spats[0].off.off != 0 || spats[0].off.line)
1414 		    sprintf((char *)p, "%ld", spats[0].off.off);
1415 		off_len = STRLEN(off_buf);
1416 	    }
1417 
1418 	    if (*searchstr == NUL)
1419 		p = spats[0].pat;
1420 	    else
1421 		p = searchstr;
1422 
1423 	    if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent)
1424 	    {
1425 		// Reserve enough space for the search pattern + offset +
1426 		// search stat.  Use all the space available, so that the
1427 		// search state is right aligned.  If there is not enough space
1428 		// msg_strtrunc() will shorten in the middle.
1429 		if (msg_scrolled != 0 && !cmd_silent)
1430 		    // Use all the columns.
1431 		    len = (int)(Rows - msg_row) * Columns - 1;
1432 		else
1433 		    // Use up to 'showcmd' column.
1434 		    len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
1435 		if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3)
1436 		    len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3;
1437 	    }
1438 	    else
1439 		// Reserve enough space for the search pattern + offset.
1440 		len = STRLEN(p) + off_len + 3;
1441 
1442 	    vim_free(msgbuf);
1443 	    msgbuf = alloc(len);
1444 	    if (msgbuf != NULL)
1445 	    {
1446 		vim_memset(msgbuf, ' ', len);
1447 		msgbuf[len - 1] = NUL;
1448 		// do not fill the msgbuf buffer, if cmd_silent is set, leave it
1449 		// empty for the search_stat feature.
1450 		if (!cmd_silent)
1451 		{
1452 		    msgbuf[0] = dirc;
1453 
1454 		    if (enc_utf8 && utf_iscomposing(utf_ptr2char(p)))
1455 		    {
1456 			// Use a space to draw the composing char on.
1457 			msgbuf[1] = ' ';
1458 			mch_memmove(msgbuf + 2, p, STRLEN(p));
1459 		    }
1460 		    else
1461 			mch_memmove(msgbuf + 1, p, STRLEN(p));
1462 		    if (off_len > 0)
1463 			mch_memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len);
1464 
1465 		    trunc = msg_strtrunc(msgbuf, TRUE);
1466 		    if (trunc != NULL)
1467 		    {
1468 			vim_free(msgbuf);
1469 			msgbuf = trunc;
1470 		    }
1471 
1472 #ifdef FEAT_RIGHTLEFT
1473 		    // The search pattern could be shown on the right in
1474 		    // rightleft mode, but the 'ruler' and 'showcmd' area use
1475 		    // it too, thus it would be blanked out again very soon.
1476 		    // Show it on the left, but do reverse the text.
1477 		    if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
1478 		    {
1479 			char_u *r;
1480 			size_t pat_len;
1481 
1482 			r = reverse_text(msgbuf);
1483 			if (r != NULL)
1484 			{
1485 			    vim_free(msgbuf);
1486 			    msgbuf = r;
1487 			    // move reversed text to beginning of buffer
1488 			    while (*r != NUL && *r == ' ')
1489 				r++;
1490 			    pat_len = msgbuf + STRLEN(msgbuf) - r;
1491 			    mch_memmove(msgbuf, r, pat_len);
1492 			    // overwrite old text
1493 			    if ((size_t)(r - msgbuf) >= pat_len)
1494 				vim_memset(r, ' ', pat_len);
1495 			    else
1496 				vim_memset(msgbuf + pat_len, ' ', r - msgbuf);
1497 			}
1498 		    }
1499 #endif
1500 		    msg_outtrans(msgbuf);
1501 		    msg_clr_eos();
1502 		    msg_check();
1503 
1504 		    gotocmdline(FALSE);
1505 		    out_flush();
1506 		    msg_nowait = TRUE;	    // don't wait for this message
1507 		}
1508 	    }
1509 	}
1510 
1511 	/*
1512 	 * If there is a character offset, subtract it from the current
1513 	 * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
1514 	 * Skip this if pos.col is near MAXCOL (closed fold).
1515 	 * This is not done for a line offset, because then we would not be vi
1516 	 * compatible.
1517 	 */
1518 	if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2)
1519 	{
1520 	    if (spats[0].off.off > 0)
1521 	    {
1522 		for (c = spats[0].off.off; c; --c)
1523 		    if (decl(&pos) == -1)
1524 			break;
1525 		if (c)			// at start of buffer
1526 		{
1527 		    pos.lnum = 0;	// allow lnum == 0 here
1528 		    pos.col = MAXCOL;
1529 		}
1530 	    }
1531 	    else
1532 	    {
1533 		for (c = spats[0].off.off; c; ++c)
1534 		    if (incl(&pos) == -1)
1535 			break;
1536 		if (c)			// at end of buffer
1537 		{
1538 		    pos.lnum = curbuf->b_ml.ml_line_count + 1;
1539 		    pos.col = 0;
1540 		}
1541 	    }
1542 	}
1543 
1544 	/*
1545 	 * The actual search.
1546 	 */
1547 	c = searchit(curwin, curbuf, &pos, NULL,
1548 					      dirc == '/' ? FORWARD : BACKWARD,
1549 		searchstr, count, spats[0].off.end + (options &
1550 		       (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS
1551 			+ SEARCH_MSG + SEARCH_START
1552 			+ ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))),
1553 		RE_LAST, sia);
1554 
1555 	if (dircp != NULL)
1556 	    *dircp = search_delim; // restore second '/' or '?' for normal_cmd()
1557 
1558 	if (!shortmess(SHM_SEARCH)
1559 		&& ((dirc == '/' && LT_POS(pos, curwin->w_cursor))
1560 			    || (dirc == '?' && LT_POS(curwin->w_cursor, pos))))
1561 	    show_top_bot_msg = TRUE;
1562 
1563 	if (c == FAIL)
1564 	{
1565 	    retval = 0;
1566 	    goto end_do_search;
1567 	}
1568 	if (spats[0].off.end && oap != NULL)
1569 	    oap->inclusive = TRUE;  // 'e' includes last character
1570 
1571 	retval = 1;		    // pattern found
1572 
1573 	/*
1574 	 * Add character and/or line offset
1575 	 */
1576 	if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';'))
1577 	{
1578 	    pos_T org_pos = pos;
1579 
1580 	    if (spats[0].off.line)	// Add the offset to the line number.
1581 	    {
1582 		c = pos.lnum + spats[0].off.off;
1583 		if (c < 1)
1584 		    pos.lnum = 1;
1585 		else if (c > curbuf->b_ml.ml_line_count)
1586 		    pos.lnum = curbuf->b_ml.ml_line_count;
1587 		else
1588 		    pos.lnum = c;
1589 		pos.col = 0;
1590 
1591 		retval = 2;	    // pattern found, line offset added
1592 	    }
1593 	    else if (pos.col < MAXCOL - 2)	// just in case
1594 	    {
1595 		// to the right, check for end of file
1596 		c = spats[0].off.off;
1597 		if (c > 0)
1598 		{
1599 		    while (c-- > 0)
1600 			if (incl(&pos) == -1)
1601 			    break;
1602 		}
1603 		// to the left, check for start of file
1604 		else
1605 		{
1606 		    while (c++ < 0)
1607 			if (decl(&pos) == -1)
1608 			    break;
1609 		}
1610 	    }
1611 	    if (!EQUAL_POS(pos, org_pos))
1612 		has_offset = TRUE;
1613 	}
1614 
1615 	// Show [1/15] if 'S' is not in 'shortmess'.
1616 	if ((options & SEARCH_ECHO)
1617 		&& messaging()
1618 		&& !msg_silent
1619 		&& c != FAIL
1620 		&& !shortmess(SHM_SEARCHCOUNT)
1621 		&& msgbuf != NULL)
1622 	     cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
1623 				show_top_bot_msg, msgbuf,
1624 				(count != 1 || has_offset
1625 #ifdef FEAT_FOLDING
1626 				 || (!(fdo_flags & FDO_SEARCH)
1627 				     && hasFolding(curwin->w_cursor.lnum,
1628 								   NULL, NULL))
1629 #endif
1630 				),
1631 				SEARCH_STAT_DEF_MAX_COUNT,
1632 				SEARCH_STAT_DEF_TIMEOUT);
1633 
1634 	/*
1635 	 * The search command can be followed by a ';' to do another search.
1636 	 * For example: "/pat/;/foo/+3;?bar"
1637 	 * This is like doing another search command, except:
1638 	 * - The remembered direction '/' or '?' is from the first search.
1639 	 * - When an error happens the cursor isn't moved at all.
1640 	 * Don't do this when called by get_address() (it handles ';' itself).
1641 	 */
1642 	if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';')
1643 	    break;
1644 
1645 	dirc = *++pat;
1646 	search_delim = dirc;
1647 	if (dirc != '?' && dirc != '/')
1648 	{
1649 	    retval = 0;
1650 	    emsg(_("E386: Expected '?' or '/'  after ';'"));
1651 	    goto end_do_search;
1652 	}
1653 	++pat;
1654     }
1655 
1656     if (options & SEARCH_MARK)
1657 	setpcmark();
1658     curwin->w_cursor = pos;
1659     curwin->w_set_curswant = TRUE;
1660 
1661 end_do_search:
1662     if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS))
1663 	spats[0].off = old_off;
1664     vim_free(strcopy);
1665     vim_free(msgbuf);
1666 
1667     return retval;
1668 }
1669 
1670 /*
1671  * search_for_exact_line(buf, pos, dir, pat)
1672  *
1673  * Search for a line starting with the given pattern (ignoring leading
1674  * white-space), starting from pos and going in direction "dir". "pos" will
1675  * contain the position of the match found.    Blank lines match only if
1676  * ADDING is set.  If p_ic is set then the pattern must be in lowercase.
1677  * Return OK for success, or FAIL if no line found.
1678  */
1679     int
search_for_exact_line(buf_T * buf,pos_T * pos,int dir,char_u * pat)1680 search_for_exact_line(
1681     buf_T	*buf,
1682     pos_T	*pos,
1683     int		dir,
1684     char_u	*pat)
1685 {
1686     linenr_T	start = 0;
1687     char_u	*ptr;
1688     char_u	*p;
1689 
1690     if (buf->b_ml.ml_line_count == 0)
1691 	return FAIL;
1692     for (;;)
1693     {
1694 	pos->lnum += dir;
1695 	if (pos->lnum < 1)
1696 	{
1697 	    if (p_ws)
1698 	    {
1699 		pos->lnum = buf->b_ml.ml_line_count;
1700 		if (!shortmess(SHM_SEARCH))
1701 		    give_warning((char_u *)_(top_bot_msg), TRUE);
1702 	    }
1703 	    else
1704 	    {
1705 		pos->lnum = 1;
1706 		break;
1707 	    }
1708 	}
1709 	else if (pos->lnum > buf->b_ml.ml_line_count)
1710 	{
1711 	    if (p_ws)
1712 	    {
1713 		pos->lnum = 1;
1714 		if (!shortmess(SHM_SEARCH))
1715 		    give_warning((char_u *)_(bot_top_msg), TRUE);
1716 	    }
1717 	    else
1718 	    {
1719 		pos->lnum = 1;
1720 		break;
1721 	    }
1722 	}
1723 	if (pos->lnum == start)
1724 	    break;
1725 	if (start == 0)
1726 	    start = pos->lnum;
1727 	ptr = ml_get_buf(buf, pos->lnum, FALSE);
1728 	p = skipwhite(ptr);
1729 	pos->col = (colnr_T) (p - ptr);
1730 
1731 	// when adding lines the matching line may be empty but it is not
1732 	// ignored because we are interested in the next line -- Acevedo
1733 	if ((compl_cont_status & CONT_ADDING)
1734 					   && !(compl_cont_status & CONT_SOL))
1735 	{
1736 	    if ((p_ic ? MB_STRICMP(p, pat) : STRCMP(p, pat)) == 0)
1737 		return OK;
1738 	}
1739 	else if (*p != NUL)	// ignore empty lines
1740 	{	// expanding lines or words
1741 	    if ((p_ic ? MB_STRNICMP(p, pat, compl_length)
1742 				   : STRNCMP(p, pat, compl_length)) == 0)
1743 		return OK;
1744 	}
1745     }
1746     return FAIL;
1747 }
1748 
1749 /*
1750  * Character Searches
1751  */
1752 
1753 /*
1754  * Search for a character in a line.  If "t_cmd" is FALSE, move to the
1755  * position of the character, otherwise move to just before the char.
1756  * Do this "cap->count1" times.
1757  * Return FAIL or OK.
1758  */
1759     int
searchc(cmdarg_T * cap,int t_cmd)1760 searchc(cmdarg_T *cap, int t_cmd)
1761 {
1762     int			c = cap->nchar;	// char to search for
1763     int			dir = cap->arg;	// TRUE for searching forward
1764     long		count = cap->count1;	// repeat count
1765     int			col;
1766     char_u		*p;
1767     int			len;
1768     int			stop = TRUE;
1769 
1770     if (c != NUL)	// normal search: remember args for repeat
1771     {
1772 	if (!KeyStuffed)    // don't remember when redoing
1773 	{
1774 	    *lastc = c;
1775 	    set_csearch_direction(dir);
1776 	    set_csearch_until(t_cmd);
1777 	    lastc_bytelen = (*mb_char2bytes)(c, lastc_bytes);
1778 	    if (cap->ncharC1 != 0)
1779 	    {
1780 		lastc_bytelen += (*mb_char2bytes)(cap->ncharC1,
1781 			lastc_bytes + lastc_bytelen);
1782 		if (cap->ncharC2 != 0)
1783 		    lastc_bytelen += (*mb_char2bytes)(cap->ncharC2,
1784 			    lastc_bytes + lastc_bytelen);
1785 	    }
1786 	}
1787     }
1788     else		// repeat previous search
1789     {
1790 	if (*lastc == NUL && lastc_bytelen == 1)
1791 	    return FAIL;
1792 	if (dir)	// repeat in opposite direction
1793 	    dir = -lastcdir;
1794 	else
1795 	    dir = lastcdir;
1796 	t_cmd = last_t_cmd;
1797 	c = *lastc;
1798 	// For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
1799 
1800 	// Force a move of at least one char, so ";" and "," will move the
1801 	// cursor, even if the cursor is right in front of char we are looking
1802 	// at.
1803 	if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd)
1804 	    stop = FALSE;
1805     }
1806 
1807     if (dir == BACKWARD)
1808 	cap->oap->inclusive = FALSE;
1809     else
1810 	cap->oap->inclusive = TRUE;
1811 
1812     p = ml_get_curline();
1813     col = curwin->w_cursor.col;
1814     len = (int)STRLEN(p);
1815 
1816     while (count--)
1817     {
1818 	if (has_mbyte)
1819 	{
1820 	    for (;;)
1821 	    {
1822 		if (dir > 0)
1823 		{
1824 		    col += (*mb_ptr2len)(p + col);
1825 		    if (col >= len)
1826 			return FAIL;
1827 		}
1828 		else
1829 		{
1830 		    if (col == 0)
1831 			return FAIL;
1832 		    col -= (*mb_head_off)(p, p + col - 1) + 1;
1833 		}
1834 		if (lastc_bytelen == 1)
1835 		{
1836 		    if (p[col] == c && stop)
1837 			break;
1838 		}
1839 		else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0
1840 								       && stop)
1841 		    break;
1842 		stop = TRUE;
1843 	    }
1844 	}
1845 	else
1846 	{
1847 	    for (;;)
1848 	    {
1849 		if ((col += dir) < 0 || col >= len)
1850 		    return FAIL;
1851 		if (p[col] == c && stop)
1852 		    break;
1853 		stop = TRUE;
1854 	    }
1855 	}
1856     }
1857 
1858     if (t_cmd)
1859     {
1860 	// backup to before the character (possibly double-byte)
1861 	col -= dir;
1862 	if (has_mbyte)
1863 	{
1864 	    if (dir < 0)
1865 		// Landed on the search char which is lastc_bytelen long
1866 		col += lastc_bytelen - 1;
1867 	    else
1868 		// To previous char, which may be multi-byte.
1869 		col -= (*mb_head_off)(p, p + col);
1870 	}
1871     }
1872     curwin->w_cursor.col = col;
1873 
1874     return OK;
1875 }
1876 
1877 /*
1878  * "Other" Searches
1879  */
1880 
1881 /*
1882  * findmatch - find the matching paren or brace
1883  *
1884  * Improvement over vi: Braces inside quotes are ignored.
1885  */
1886     pos_T *
findmatch(oparg_T * oap,int initc)1887 findmatch(oparg_T *oap, int initc)
1888 {
1889     return findmatchlimit(oap, initc, 0, 0);
1890 }
1891 
1892 /*
1893  * Return TRUE if the character before "linep[col]" equals "ch".
1894  * Return FALSE if "col" is zero.
1895  * Update "*prevcol" to the column of the previous character, unless "prevcol"
1896  * is NULL.
1897  * Handles multibyte string correctly.
1898  */
1899     static int
check_prevcol(char_u * linep,int col,int ch,int * prevcol)1900 check_prevcol(
1901     char_u	*linep,
1902     int		col,
1903     int		ch,
1904     int		*prevcol)
1905 {
1906     --col;
1907     if (col > 0 && has_mbyte)
1908 	col -= (*mb_head_off)(linep, linep + col);
1909     if (prevcol)
1910 	*prevcol = col;
1911     return (col >= 0 && linep[col] == ch) ? TRUE : FALSE;
1912 }
1913 
1914 /*
1915  * Raw string start is found at linep[startpos.col - 1].
1916  * Return TRUE if the matching end can be found between startpos and endpos.
1917  */
1918     static int
find_rawstring_end(char_u * linep,pos_T * startpos,pos_T * endpos)1919 find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
1920 {
1921     char_u	*p;
1922     char_u	*delim_copy;
1923     size_t	delim_len;
1924     linenr_T	lnum;
1925     int		found = FALSE;
1926 
1927     for (p = linep + startpos->col + 1; *p && *p != '('; ++p)
1928 	;
1929     delim_len = (p - linep) - startpos->col - 1;
1930     delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len);
1931     if (delim_copy == NULL)
1932 	return FALSE;
1933     for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum)
1934     {
1935 	char_u *line = ml_get(lnum);
1936 
1937 	for (p = line + (lnum == startpos->lnum
1938 					    ? startpos->col + 1 : 0); *p; ++p)
1939 	{
1940 	    if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col)
1941 		break;
1942 	    if (*p == ')' && STRNCMP(delim_copy, p + 1, delim_len) == 0
1943 			  && p[delim_len + 1] == '"')
1944 	    {
1945 		found = TRUE;
1946 		break;
1947 	    }
1948 	}
1949 	if (found)
1950 	    break;
1951     }
1952     vim_free(delim_copy);
1953     return found;
1954 }
1955 
1956 /*
1957  * Check matchpairs option for "*initc".
1958  * If there is a match set "*initc" to the matching character and "*findc" to
1959  * the opposite character.  Set "*backwards" to the direction.
1960  * When "switchit" is TRUE swap the direction.
1961  */
1962     static void
find_mps_values(int * initc,int * findc,int * backwards,int switchit)1963 find_mps_values(
1964     int	    *initc,
1965     int	    *findc,
1966     int	    *backwards,
1967     int	    switchit)
1968 {
1969     char_u	*ptr;
1970 
1971     ptr = curbuf->b_p_mps;
1972     while (*ptr != NUL)
1973     {
1974 	if (has_mbyte)
1975 	{
1976 	    char_u *prev;
1977 
1978 	    if (mb_ptr2char(ptr) == *initc)
1979 	    {
1980 		if (switchit)
1981 		{
1982 		    *findc = *initc;
1983 		    *initc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
1984 		    *backwards = TRUE;
1985 		}
1986 		else
1987 		{
1988 		    *findc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
1989 		    *backwards = FALSE;
1990 		}
1991 		return;
1992 	    }
1993 	    prev = ptr;
1994 	    ptr += mb_ptr2len(ptr) + 1;
1995 	    if (mb_ptr2char(ptr) == *initc)
1996 	    {
1997 		if (switchit)
1998 		{
1999 		    *findc = *initc;
2000 		    *initc = mb_ptr2char(prev);
2001 		    *backwards = FALSE;
2002 		}
2003 		else
2004 		{
2005 		    *findc = mb_ptr2char(prev);
2006 		    *backwards = TRUE;
2007 		}
2008 		return;
2009 	    }
2010 	    ptr += mb_ptr2len(ptr);
2011 	}
2012 	else
2013 	{
2014 	    if (*ptr == *initc)
2015 	    {
2016 		if (switchit)
2017 		{
2018 		    *backwards = TRUE;
2019 		    *findc = *initc;
2020 		    *initc = ptr[2];
2021 		}
2022 		else
2023 		{
2024 		    *backwards = FALSE;
2025 		    *findc = ptr[2];
2026 		}
2027 		return;
2028 	    }
2029 	    ptr += 2;
2030 	    if (*ptr == *initc)
2031 	    {
2032 		if (switchit)
2033 		{
2034 		    *backwards = FALSE;
2035 		    *findc = *initc;
2036 		    *initc = ptr[-2];
2037 		}
2038 		else
2039 		{
2040 		    *backwards = TRUE;
2041 		    *findc =  ptr[-2];
2042 		}
2043 		return;
2044 	    }
2045 	    ++ptr;
2046 	}
2047 	if (*ptr == ',')
2048 	    ++ptr;
2049     }
2050 }
2051 
2052 /*
2053  * findmatchlimit -- find the matching paren or brace, if it exists within
2054  * maxtravel lines of the cursor.  A maxtravel of 0 means search until falling
2055  * off the edge of the file.
2056  *
2057  * "initc" is the character to find a match for.  NUL means to find the
2058  * character at or after the cursor. Special values:
2059  * '*'  look for C-style comment / *
2060  * '/'  look for C-style comment / *, ignoring comment-end
2061  * '#'  look for preprocessor directives
2062  * 'R'  look for raw string start: R"delim(text)delim" (only backwards)
2063  *
2064  * flags: FM_BACKWARD	search backwards (when initc is '/', '*' or '#')
2065  *	  FM_FORWARD	search forwards (when initc is '/', '*' or '#')
2066  *	  FM_BLOCKSTOP	stop at start/end of block ({ or } in column 0)
2067  *	  FM_SKIPCOMM	skip comments (not implemented yet!)
2068  *
2069  * "oap" is only used to set oap->motion_type for a linewise motion, it can be
2070  * NULL
2071  */
2072 
2073     pos_T *
findmatchlimit(oparg_T * oap,int initc,int flags,int maxtravel)2074 findmatchlimit(
2075     oparg_T	*oap,
2076     int		initc,
2077     int		flags,
2078     int		maxtravel)
2079 {
2080     static pos_T pos;			// current search position
2081     int		findc = 0;		// matching brace
2082     int		c;
2083     int		count = 0;		// cumulative number of braces
2084     int		backwards = FALSE;	// init for gcc
2085     int		raw_string = FALSE;	// search for raw string
2086     int		inquote = FALSE;	// TRUE when inside quotes
2087     char_u	*linep;			// pointer to current line
2088     char_u	*ptr;
2089     int		do_quotes;		// check for quotes in current line
2090     int		at_start;		// do_quotes value at start position
2091     int		hash_dir = 0;		// Direction searched for # things
2092     int		comment_dir = 0;	// Direction searched for comments
2093     pos_T	match_pos;		// Where last slash-star was found
2094     int		start_in_quotes;	// start position is in quotes
2095     int		traveled = 0;		// how far we've searched so far
2096     int		ignore_cend = FALSE;    // ignore comment end
2097     int		cpo_match;		// vi compatible matching
2098     int		cpo_bsl;		// don't recognize backslashes
2099     int		match_escaped = 0;	// search for escaped match
2100     int		dir;			// Direction to search
2101     int		comment_col = MAXCOL;   // start of / / comment
2102 #ifdef FEAT_LISP
2103     int		lispcomm = FALSE;	// inside of Lisp-style comment
2104     int		lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;)
2105 #endif
2106 
2107     pos = curwin->w_cursor;
2108     pos.coladd = 0;
2109     linep = ml_get(pos.lnum);
2110 
2111     cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
2112     cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
2113 
2114     // Direction to search when initc is '/', '*' or '#'
2115     if (flags & FM_BACKWARD)
2116 	dir = BACKWARD;
2117     else if (flags & FM_FORWARD)
2118 	dir = FORWARD;
2119     else
2120 	dir = 0;
2121 
2122     /*
2123      * if initc given, look in the table for the matching character
2124      * '/' and '*' are special cases: look for start or end of comment.
2125      * When '/' is used, we ignore running backwards into an star-slash, for
2126      * "[*" command, we just want to find any comment.
2127      */
2128     if (initc == '/' || initc == '*' || initc == 'R')
2129     {
2130 	comment_dir = dir;
2131 	if (initc == '/')
2132 	    ignore_cend = TRUE;
2133 	backwards = (dir == FORWARD) ? FALSE : TRUE;
2134 	raw_string = (initc == 'R');
2135 	initc = NUL;
2136     }
2137     else if (initc != '#' && initc != NUL)
2138     {
2139 	find_mps_values(&initc, &findc, &backwards, TRUE);
2140 	if (dir)
2141 	    backwards = (dir == FORWARD) ? FALSE : TRUE;
2142 	if (findc == NUL)
2143 	    return NULL;
2144     }
2145     else
2146     {
2147 	/*
2148 	 * Either initc is '#', or no initc was given and we need to look
2149 	 * under the cursor.
2150 	 */
2151 	if (initc == '#')
2152 	{
2153 	    hash_dir = dir;
2154 	}
2155 	else
2156 	{
2157 	    /*
2158 	     * initc was not given, must look for something to match under
2159 	     * or near the cursor.
2160 	     * Only check for special things when 'cpo' doesn't have '%'.
2161 	     */
2162 	    if (!cpo_match)
2163 	    {
2164 		// Are we before or at #if, #else etc.?
2165 		ptr = skipwhite(linep);
2166 		if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
2167 		{
2168 		    ptr = skipwhite(ptr + 1);
2169 		    if (   STRNCMP(ptr, "if", 2) == 0
2170 			|| STRNCMP(ptr, "endif", 5) == 0
2171 			|| STRNCMP(ptr, "el", 2) == 0)
2172 			hash_dir = 1;
2173 		}
2174 
2175 		// Are we on a comment?
2176 		else if (linep[pos.col] == '/')
2177 		{
2178 		    if (linep[pos.col + 1] == '*')
2179 		    {
2180 			comment_dir = FORWARD;
2181 			backwards = FALSE;
2182 			pos.col++;
2183 		    }
2184 		    else if (pos.col > 0 && linep[pos.col - 1] == '*')
2185 		    {
2186 			comment_dir = BACKWARD;
2187 			backwards = TRUE;
2188 			pos.col--;
2189 		    }
2190 		}
2191 		else if (linep[pos.col] == '*')
2192 		{
2193 		    if (linep[pos.col + 1] == '/')
2194 		    {
2195 			comment_dir = BACKWARD;
2196 			backwards = TRUE;
2197 		    }
2198 		    else if (pos.col > 0 && linep[pos.col - 1] == '/')
2199 		    {
2200 			comment_dir = FORWARD;
2201 			backwards = FALSE;
2202 		    }
2203 		}
2204 	    }
2205 
2206 	    /*
2207 	     * If we are not on a comment or the # at the start of a line, then
2208 	     * look for brace anywhere on this line after the cursor.
2209 	     */
2210 	    if (!hash_dir && !comment_dir)
2211 	    {
2212 		/*
2213 		 * Find the brace under or after the cursor.
2214 		 * If beyond the end of the line, use the last character in
2215 		 * the line.
2216 		 */
2217 		if (linep[pos.col] == NUL && pos.col)
2218 		    --pos.col;
2219 		for (;;)
2220 		{
2221 		    initc = PTR2CHAR(linep + pos.col);
2222 		    if (initc == NUL)
2223 			break;
2224 
2225 		    find_mps_values(&initc, &findc, &backwards, FALSE);
2226 		    if (findc)
2227 			break;
2228 		    pos.col += mb_ptr2len(linep + pos.col);
2229 		}
2230 		if (!findc)
2231 		{
2232 		    // no brace in the line, maybe use "  #if" then
2233 		    if (!cpo_match && *skipwhite(linep) == '#')
2234 			hash_dir = 1;
2235 		    else
2236 			return NULL;
2237 		}
2238 		else if (!cpo_bsl)
2239 		{
2240 		    int col, bslcnt = 0;
2241 
2242 		    // Set "match_escaped" if there are an odd number of
2243 		    // backslashes.
2244 		    for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2245 			bslcnt++;
2246 		    match_escaped = (bslcnt & 1);
2247 		}
2248 	    }
2249 	}
2250 	if (hash_dir)
2251 	{
2252 	    /*
2253 	     * Look for matching #if, #else, #elif, or #endif
2254 	     */
2255 	    if (oap != NULL)
2256 		oap->motion_type = MLINE;   // Linewise for this case only
2257 	    if (initc != '#')
2258 	    {
2259 		ptr = skipwhite(skipwhite(linep) + 1);
2260 		if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0)
2261 		    hash_dir = 1;
2262 		else if (STRNCMP(ptr, "endif", 5) == 0)
2263 		    hash_dir = -1;
2264 		else
2265 		    return NULL;
2266 	    }
2267 	    pos.col = 0;
2268 	    while (!got_int)
2269 	    {
2270 		if (hash_dir > 0)
2271 		{
2272 		    if (pos.lnum == curbuf->b_ml.ml_line_count)
2273 			break;
2274 		}
2275 		else if (pos.lnum == 1)
2276 		    break;
2277 		pos.lnum += hash_dir;
2278 		linep = ml_get(pos.lnum);
2279 		line_breakcheck();	// check for CTRL-C typed
2280 		ptr = skipwhite(linep);
2281 		if (*ptr != '#')
2282 		    continue;
2283 		pos.col = (colnr_T) (ptr - linep);
2284 		ptr = skipwhite(ptr + 1);
2285 		if (hash_dir > 0)
2286 		{
2287 		    if (STRNCMP(ptr, "if", 2) == 0)
2288 			count++;
2289 		    else if (STRNCMP(ptr, "el", 2) == 0)
2290 		    {
2291 			if (count == 0)
2292 			    return &pos;
2293 		    }
2294 		    else if (STRNCMP(ptr, "endif", 5) == 0)
2295 		    {
2296 			if (count == 0)
2297 			    return &pos;
2298 			count--;
2299 		    }
2300 		}
2301 		else
2302 		{
2303 		    if (STRNCMP(ptr, "if", 2) == 0)
2304 		    {
2305 			if (count == 0)
2306 			    return &pos;
2307 			count--;
2308 		    }
2309 		    else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0)
2310 		    {
2311 			if (count == 0)
2312 			    return &pos;
2313 		    }
2314 		    else if (STRNCMP(ptr, "endif", 5) == 0)
2315 			count++;
2316 		}
2317 	    }
2318 	    return NULL;
2319 	}
2320     }
2321 
2322 #ifdef FEAT_RIGHTLEFT
2323     // This is just guessing: when 'rightleft' is set, search for a matching
2324     // paren/brace in the other direction.
2325     if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>", initc) != NULL)
2326 	backwards = !backwards;
2327 #endif
2328 
2329     do_quotes = -1;
2330     start_in_quotes = MAYBE;
2331     CLEAR_POS(&match_pos);
2332 
2333     // backward search: Check if this line contains a single-line comment
2334     if ((backwards && comment_dir)
2335 #ifdef FEAT_LISP
2336 	    || lisp
2337 #endif
2338 	    )
2339 	comment_col = check_linecomment(linep);
2340 #ifdef FEAT_LISP
2341     if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col)
2342 	lispcomm = TRUE;    // find match inside this comment
2343 #endif
2344     while (!got_int)
2345     {
2346 	/*
2347 	 * Go to the next position, forward or backward. We could use
2348 	 * inc() and dec() here, but that is much slower
2349 	 */
2350 	if (backwards)
2351 	{
2352 #ifdef FEAT_LISP
2353 	    // char to match is inside of comment, don't search outside
2354 	    if (lispcomm && pos.col < (colnr_T)comment_col)
2355 		break;
2356 #endif
2357 	    if (pos.col == 0)		// at start of line, go to prev. one
2358 	    {
2359 		if (pos.lnum == 1)	// start of file
2360 		    break;
2361 		--pos.lnum;
2362 
2363 		if (maxtravel > 0 && ++traveled > maxtravel)
2364 		    break;
2365 
2366 		linep = ml_get(pos.lnum);
2367 		pos.col = (colnr_T)STRLEN(linep); // pos.col on trailing NUL
2368 		do_quotes = -1;
2369 		line_breakcheck();
2370 
2371 		// Check if this line contains a single-line comment
2372 		if (comment_dir
2373 #ifdef FEAT_LISP
2374 			|| lisp
2375 #endif
2376 			)
2377 		    comment_col = check_linecomment(linep);
2378 #ifdef FEAT_LISP
2379 		// skip comment
2380 		if (lisp && comment_col != MAXCOL)
2381 		    pos.col = comment_col;
2382 #endif
2383 	    }
2384 	    else
2385 	    {
2386 		--pos.col;
2387 		if (has_mbyte)
2388 		    pos.col -= (*mb_head_off)(linep, linep + pos.col);
2389 	    }
2390 	}
2391 	else				// forward search
2392 	{
2393 	    if (linep[pos.col] == NUL
2394 		    // at end of line, go to next one
2395 #ifdef FEAT_LISP
2396 		    // don't search for match in comment
2397 		    || (lisp && comment_col != MAXCOL
2398 					   && pos.col == (colnr_T)comment_col)
2399 #endif
2400 		    )
2401 	    {
2402 		if (pos.lnum == curbuf->b_ml.ml_line_count  // end of file
2403 #ifdef FEAT_LISP
2404 			// line is exhausted and comment with it,
2405 			// don't search for match in code
2406 			 || lispcomm
2407 #endif
2408 			 )
2409 		    break;
2410 		++pos.lnum;
2411 
2412 		if (maxtravel && traveled++ > maxtravel)
2413 		    break;
2414 
2415 		linep = ml_get(pos.lnum);
2416 		pos.col = 0;
2417 		do_quotes = -1;
2418 		line_breakcheck();
2419 #ifdef FEAT_LISP
2420 		if (lisp)   // find comment pos in new line
2421 		    comment_col = check_linecomment(linep);
2422 #endif
2423 	    }
2424 	    else
2425 	    {
2426 		if (has_mbyte)
2427 		    pos.col += (*mb_ptr2len)(linep + pos.col);
2428 		else
2429 		    ++pos.col;
2430 	    }
2431 	}
2432 
2433 	/*
2434 	 * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
2435 	 */
2436 	if (pos.col == 0 && (flags & FM_BLOCKSTOP) &&
2437 					 (linep[0] == '{' || linep[0] == '}'))
2438 	{
2439 	    if (linep[0] == findc && count == 0)	// match!
2440 		return &pos;
2441 	    break;					// out of scope
2442 	}
2443 
2444 	if (comment_dir)
2445 	{
2446 	    // Note: comments do not nest, and we ignore quotes in them
2447 	    // TODO: ignore comment brackets inside strings
2448 	    if (comment_dir == FORWARD)
2449 	    {
2450 		if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
2451 		{
2452 		    pos.col++;
2453 		    return &pos;
2454 		}
2455 	    }
2456 	    else    // Searching backwards
2457 	    {
2458 		/*
2459 		 * A comment may contain / * or / /, it may also start or end
2460 		 * with / * /.	Ignore a / * after / / and after *.
2461 		 */
2462 		if (pos.col == 0)
2463 		    continue;
2464 		else if (raw_string)
2465 		{
2466 		    if (linep[pos.col - 1] == 'R'
2467 			&& linep[pos.col] == '"'
2468 			&& vim_strchr(linep + pos.col + 1, '(') != NULL)
2469 		    {
2470 			// Possible start of raw string. Now that we have the
2471 			// delimiter we can check if it ends before where we
2472 			// started searching, or before the previously found
2473 			// raw string start.
2474 			if (!find_rawstring_end(linep, &pos,
2475 				  count > 0 ? &match_pos : &curwin->w_cursor))
2476 			{
2477 			    count++;
2478 			    match_pos = pos;
2479 			    match_pos.col--;
2480 			}
2481 			linep = ml_get(pos.lnum); // may have been released
2482 		    }
2483 		}
2484 		else if (  linep[pos.col - 1] == '/'
2485 			&& linep[pos.col] == '*'
2486 			&& (pos.col == 1 || linep[pos.col - 2] != '*')
2487 			&& (int)pos.col < comment_col)
2488 		{
2489 		    count++;
2490 		    match_pos = pos;
2491 		    match_pos.col--;
2492 		}
2493 		else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
2494 		{
2495 		    if (count > 0)
2496 			pos = match_pos;
2497 		    else if (pos.col > 1 && linep[pos.col - 2] == '/'
2498 					       && (int)pos.col <= comment_col)
2499 			pos.col -= 2;
2500 		    else if (ignore_cend)
2501 			continue;
2502 		    else
2503 			return NULL;
2504 		    return &pos;
2505 		}
2506 	    }
2507 	    continue;
2508 	}
2509 
2510 	/*
2511 	 * If smart matching ('cpoptions' does not contain '%'), braces inside
2512 	 * of quotes are ignored, but only if there is an even number of
2513 	 * quotes in the line.
2514 	 */
2515 	if (cpo_match)
2516 	    do_quotes = 0;
2517 	else if (do_quotes == -1)
2518 	{
2519 	    /*
2520 	     * Count the number of quotes in the line, skipping \" and '"'.
2521 	     * Watch out for "\\".
2522 	     */
2523 	    at_start = do_quotes;
2524 	    for (ptr = linep; *ptr; ++ptr)
2525 	    {
2526 		if (ptr == linep + pos.col + backwards)
2527 		    at_start = (do_quotes & 1);
2528 		if (*ptr == '"'
2529 			&& (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
2530 		    ++do_quotes;
2531 		if (*ptr == '\\' && ptr[1] != NUL)
2532 		    ++ptr;
2533 	    }
2534 	    do_quotes &= 1;	    // result is 1 with even number of quotes
2535 
2536 	    /*
2537 	     * If we find an uneven count, check current line and previous
2538 	     * one for a '\' at the end.
2539 	     */
2540 	    if (!do_quotes)
2541 	    {
2542 		inquote = FALSE;
2543 		if (ptr[-1] == '\\')
2544 		{
2545 		    do_quotes = 1;
2546 		    if (start_in_quotes == MAYBE)
2547 		    {
2548 			// Do we need to use at_start here?
2549 			inquote = TRUE;
2550 			start_in_quotes = TRUE;
2551 		    }
2552 		    else if (backwards)
2553 			inquote = TRUE;
2554 		}
2555 		if (pos.lnum > 1)
2556 		{
2557 		    ptr = ml_get(pos.lnum - 1);
2558 		    if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
2559 		    {
2560 			do_quotes = 1;
2561 			if (start_in_quotes == MAYBE)
2562 			{
2563 			    inquote = at_start;
2564 			    if (inquote)
2565 				start_in_quotes = TRUE;
2566 			}
2567 			else if (!backwards)
2568 			    inquote = TRUE;
2569 		    }
2570 
2571 		    // ml_get() only keeps one line, need to get linep again
2572 		    linep = ml_get(pos.lnum);
2573 		}
2574 	    }
2575 	}
2576 	if (start_in_quotes == MAYBE)
2577 	    start_in_quotes = FALSE;
2578 
2579 	/*
2580 	 * If 'smartmatch' is set:
2581 	 *   Things inside quotes are ignored by setting 'inquote'.  If we
2582 	 *   find a quote without a preceding '\' invert 'inquote'.  At the
2583 	 *   end of a line not ending in '\' we reset 'inquote'.
2584 	 *
2585 	 *   In lines with an uneven number of quotes (without preceding '\')
2586 	 *   we do not know which part to ignore. Therefore we only set
2587 	 *   inquote if the number of quotes in a line is even, unless this
2588 	 *   line or the previous one ends in a '\'.  Complicated, isn't it?
2589 	 */
2590 	c = PTR2CHAR(linep + pos.col);
2591 	switch (c)
2592 	{
2593 	case NUL:
2594 	    // at end of line without trailing backslash, reset inquote
2595 	    if (pos.col == 0 || linep[pos.col - 1] != '\\')
2596 	    {
2597 		inquote = FALSE;
2598 		start_in_quotes = FALSE;
2599 	    }
2600 	    break;
2601 
2602 	case '"':
2603 	    // a quote that is preceded with an odd number of backslashes is
2604 	    // ignored
2605 	    if (do_quotes)
2606 	    {
2607 		int col;
2608 
2609 		for (col = pos.col - 1; col >= 0; --col)
2610 		    if (linep[col] != '\\')
2611 			break;
2612 		if ((((int)pos.col - 1 - col) & 1) == 0)
2613 		{
2614 		    inquote = !inquote;
2615 		    start_in_quotes = FALSE;
2616 		}
2617 	    }
2618 	    break;
2619 
2620 	/*
2621 	 * If smart matching ('cpoptions' does not contain '%'):
2622 	 *   Skip things in single quotes: 'x' or '\x'.  Be careful for single
2623 	 *   single quotes, eg jon's.  Things like '\233' or '\x3f' are not
2624 	 *   skipped, there is never a brace in them.
2625 	 *   Ignore this when finding matches for `'.
2626 	 */
2627 	case '\'':
2628 	    if (!cpo_match && initc != '\'' && findc != '\'')
2629 	    {
2630 		if (backwards)
2631 		{
2632 		    if (pos.col > 1)
2633 		    {
2634 			if (linep[pos.col - 2] == '\'')
2635 			{
2636 			    pos.col -= 2;
2637 			    break;
2638 			}
2639 			else if (linep[pos.col - 2] == '\\' &&
2640 				    pos.col > 2 && linep[pos.col - 3] == '\'')
2641 			{
2642 			    pos.col -= 3;
2643 			    break;
2644 			}
2645 		    }
2646 		}
2647 		else if (linep[pos.col + 1])	// forward search
2648 		{
2649 		    if (linep[pos.col + 1] == '\\' &&
2650 			    linep[pos.col + 2] && linep[pos.col + 3] == '\'')
2651 		    {
2652 			pos.col += 3;
2653 			break;
2654 		    }
2655 		    else if (linep[pos.col + 2] == '\'')
2656 		    {
2657 			pos.col += 2;
2658 			break;
2659 		    }
2660 		}
2661 	    }
2662 	    // FALLTHROUGH
2663 
2664 	default:
2665 #ifdef FEAT_LISP
2666 	    /*
2667 	     * For Lisp skip over backslashed (), {} and [].
2668 	     * (actually, we skip #\( et al)
2669 	     */
2670 	    if (curbuf->b_p_lisp
2671 		    && vim_strchr((char_u *)"(){}[]", c) != NULL
2672 		    && pos.col > 1
2673 		    && check_prevcol(linep, pos.col, '\\', NULL)
2674 		    && check_prevcol(linep, pos.col - 1, '#', NULL))
2675 		break;
2676 #endif
2677 
2678 	    // Check for match outside of quotes, and inside of
2679 	    // quotes when the start is also inside of quotes.
2680 	    if ((!inquote || start_in_quotes == TRUE)
2681 		    && (c == initc || c == findc))
2682 	    {
2683 		int	col, bslcnt = 0;
2684 
2685 		if (!cpo_bsl)
2686 		{
2687 		    for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2688 			bslcnt++;
2689 		}
2690 		// Only accept a match when 'M' is in 'cpo' or when escaping
2691 		// is what we expect.
2692 		if (cpo_bsl || (bslcnt & 1) == match_escaped)
2693 		{
2694 		    if (c == initc)
2695 			count++;
2696 		    else
2697 		    {
2698 			if (count == 0)
2699 			    return &pos;
2700 			count--;
2701 		    }
2702 		}
2703 	    }
2704 	}
2705     }
2706 
2707     if (comment_dir == BACKWARD && count > 0)
2708     {
2709 	pos = match_pos;
2710 	return &pos;
2711     }
2712     return (pos_T *)NULL;	// never found it
2713 }
2714 
2715 /*
2716  * Check if line[] contains a / / comment.
2717  * Return MAXCOL if not, otherwise return the column.
2718  * TODO: skip strings.
2719  */
2720     static int
check_linecomment(char_u * line)2721 check_linecomment(char_u *line)
2722 {
2723     char_u  *p;
2724 
2725     p = line;
2726 #ifdef FEAT_LISP
2727     // skip Lispish one-line comments
2728     if (curbuf->b_p_lisp)
2729     {
2730 	if (vim_strchr(p, ';') != NULL) // there may be comments
2731 	{
2732 	    int in_str = FALSE;	// inside of string
2733 
2734 	    p = line;		// scan from start
2735 	    while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
2736 	    {
2737 		if (*p == '"')
2738 		{
2739 		    if (in_str)
2740 		    {
2741 			if (*(p - 1) != '\\') // skip escaped quote
2742 			    in_str = FALSE;
2743 		    }
2744 		    else if (p == line || ((p - line) >= 2
2745 				      // skip #\" form
2746 				      && *(p - 1) != '\\' && *(p - 2) != '#'))
2747 			in_str = TRUE;
2748 		}
2749 		else if (!in_str && ((p - line) < 2
2750 				    || (*(p - 1) != '\\' && *(p - 2) != '#')))
2751 		    break;	// found!
2752 		++p;
2753 	    }
2754 	}
2755 	else
2756 	    p = NULL;
2757     }
2758     else
2759 #endif
2760     while ((p = vim_strchr(p, '/')) != NULL)
2761     {
2762 	// accept a double /, unless it's preceded with * and followed by *,
2763 	// because * / / * is an end and start of a C comment
2764 	if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*'))
2765 	    break;
2766 	++p;
2767     }
2768 
2769     if (p == NULL)
2770 	return MAXCOL;
2771     return (int)(p - line);
2772 }
2773 
2774 /*
2775  * Move cursor briefly to character matching the one under the cursor.
2776  * Used for Insert mode and "r" command.
2777  * Show the match only if it is visible on the screen.
2778  * If there isn't a match, then beep.
2779  */
2780     void
showmatch(int c)2781 showmatch(
2782     int		c)	    // char to show match for
2783 {
2784     pos_T	*lpos, save_cursor;
2785     pos_T	mpos;
2786     colnr_T	vcol;
2787     long	save_so;
2788     long	save_siso;
2789 #ifdef CURSOR_SHAPE
2790     int		save_state;
2791 #endif
2792     colnr_T	save_dollar_vcol;
2793     char_u	*p;
2794     long        *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
2795     long        *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
2796 
2797     /*
2798      * Only show match for chars in the 'matchpairs' option.
2799      */
2800     // 'matchpairs' is "x:y,x:y"
2801     for (p = curbuf->b_p_mps; *p != NUL; ++p)
2802     {
2803 #ifdef FEAT_RIGHTLEFT
2804 	if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
2805 	    break;
2806 #endif
2807 	p += mb_ptr2len(p) + 1;
2808 	if (PTR2CHAR(p) == c
2809 #ifdef FEAT_RIGHTLEFT
2810 		&& !(curwin->w_p_rl ^ p_ri)
2811 #endif
2812 	   )
2813 	    break;
2814 	p += mb_ptr2len(p);
2815 	if (*p == NUL)
2816 	    return;
2817     }
2818     if (*p == NUL)
2819 	return;
2820 
2821     if ((lpos = findmatch(NULL, NUL)) == NULL)	    // no match, so beep
2822 	vim_beep(BO_MATCH);
2823     else if (lpos->lnum >= curwin->w_topline && lpos->lnum < curwin->w_botline)
2824     {
2825 	if (!curwin->w_p_wrap)
2826 	    getvcol(curwin, lpos, NULL, &vcol, NULL);
2827 	if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol
2828 			       && vcol < curwin->w_leftcol + curwin->w_width))
2829 	{
2830 	    mpos = *lpos;    // save the pos, update_screen() may change it
2831 	    save_cursor = curwin->w_cursor;
2832 	    save_so = *so;
2833 	    save_siso = *siso;
2834 	    // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
2835 	    // stop displaying the "$".
2836 	    if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
2837 		dollar_vcol = -1;
2838 	    ++curwin->w_virtcol;	// do display ')' just before "$"
2839 	    update_screen(VALID);	// show the new char first
2840 
2841 	    save_dollar_vcol = dollar_vcol;
2842 #ifdef CURSOR_SHAPE
2843 	    save_state = State;
2844 	    State = SHOWMATCH;
2845 	    ui_cursor_shape();		// may show different cursor shape
2846 #endif
2847 	    curwin->w_cursor = mpos;	// move to matching char
2848 	    *so = 0;			// don't use 'scrolloff' here
2849 	    *siso = 0;			// don't use 'sidescrolloff' here
2850 	    showruler(FALSE);
2851 	    setcursor();
2852 	    cursor_on();		// make sure that the cursor is shown
2853 	    out_flush_cursor(TRUE, FALSE);
2854 
2855 	    // Restore dollar_vcol(), because setcursor() may call curs_rows()
2856 	    // which resets it if the matching position is in a previous line
2857 	    // and has a higher column number.
2858 	    dollar_vcol = save_dollar_vcol;
2859 
2860 	    /*
2861 	     * brief pause, unless 'm' is present in 'cpo' and a character is
2862 	     * available.
2863 	     */
2864 	    if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
2865 		ui_delay(p_mat * 100L + 8, TRUE);
2866 	    else if (!char_avail())
2867 		ui_delay(p_mat * 100L + 9, FALSE);
2868 	    curwin->w_cursor = save_cursor;	// restore cursor position
2869 	    *so = save_so;
2870 	    *siso = save_siso;
2871 #ifdef CURSOR_SHAPE
2872 	    State = save_state;
2873 	    ui_cursor_shape();		// may show different cursor shape
2874 #endif
2875 	}
2876     }
2877 }
2878 
2879 /*
2880  * Check if the pattern is zero-width.
2881  * If move is TRUE, check from the beginning of the buffer, else from position
2882  * "cur".
2883  * "direction" is FORWARD or BACKWARD.
2884  * Returns TRUE, FALSE or -1 for failure.
2885  */
2886     static int
is_zero_width(char_u * pattern,int move,pos_T * cur,int direction)2887 is_zero_width(char_u *pattern, int move, pos_T *cur, int direction)
2888 {
2889     regmmatch_T	regmatch;
2890     int		nmatched = 0;
2891     int		result = -1;
2892     pos_T	pos;
2893     int		called_emsg_before = called_emsg;
2894     int		flag = 0;
2895 
2896     if (pattern == NULL)
2897 	pattern = spats[last_idx].pat;
2898 
2899     if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH,
2900 					      SEARCH_KEEP, &regmatch) == FAIL)
2901 	return -1;
2902 
2903     // init startcol correctly
2904     regmatch.startpos[0].col = -1;
2905     // move to match
2906     if (move)
2907     {
2908 	CLEAR_POS(&pos);
2909     }
2910     else
2911     {
2912 	pos = *cur;
2913 	// accept a match at the cursor position
2914 	flag = SEARCH_START;
2915     }
2916 
2917     if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
2918 				  SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL)
2919     {
2920 	// Zero-width pattern should match somewhere, then we can check if
2921 	// start and end are in the same position.
2922 	do
2923 	{
2924 	    regmatch.startpos[0].col++;
2925 	    nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
2926 			       pos.lnum, regmatch.startpos[0].col, NULL, NULL);
2927 	    if (nmatched != 0)
2928 		break;
2929 	} while (regmatch.regprog != NULL
2930 		&& direction == FORWARD ? regmatch.startpos[0].col < pos.col
2931 				      : regmatch.startpos[0].col > pos.col);
2932 
2933 	if (called_emsg == called_emsg_before)
2934 	{
2935 	    result = (nmatched != 0
2936 		&& regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
2937 		&& regmatch.startpos[0].col == regmatch.endpos[0].col);
2938 	}
2939     }
2940 
2941     vim_regfree(regmatch.regprog);
2942     return result;
2943 }
2944 
2945 
2946 /*
2947  * Find next search match under cursor, cursor at end.
2948  * Used while an operator is pending, and in Visual mode.
2949  */
2950     int
current_search(long count,int forward)2951 current_search(
2952     long	count,
2953     int		forward)	// TRUE for forward, FALSE for backward
2954 {
2955     pos_T	start_pos;	// start position of the pattern match
2956     pos_T	end_pos;	// end position of the pattern match
2957     pos_T	orig_pos;	// position of the cursor at beginning
2958     pos_T	pos;		// position after the pattern
2959     int		i;
2960     int		dir;
2961     int		result;		// result of various function calls
2962     char_u	old_p_ws = p_ws;
2963     int		flags = 0;
2964     pos_T	save_VIsual = VIsual;
2965     int		zero_width;
2966     int		skip_first_backward;
2967 
2968     // Correct cursor when 'selection' is exclusive
2969     if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
2970 	dec_cursor();
2971 
2972     // When searching forward and the cursor is at the start of the Visual
2973     // area, skip the first search backward, otherwise it doesn't move.
2974     skip_first_backward = forward && VIsual_active
2975 					   && LT_POS(curwin->w_cursor, VIsual);
2976 
2977     orig_pos = pos = curwin->w_cursor;
2978     if (VIsual_active)
2979     {
2980 	if (forward)
2981 	    incl(&pos);
2982 	else
2983 	    decl(&pos);
2984     }
2985 
2986     // Is the pattern is zero-width?, this time, don't care about the direction
2987     zero_width = is_zero_width(spats[last_idx].pat, TRUE, &curwin->w_cursor,
2988 								      FORWARD);
2989     if (zero_width == -1)
2990 	return FAIL;  // pattern not found
2991 
2992     /*
2993      * The trick is to first search backwards and then search forward again,
2994      * so that a match at the current cursor position will be correctly
2995      * captured.  When "forward" is false do it the other way around.
2996      */
2997     for (i = 0; i < 2; i++)
2998     {
2999 	if (forward)
3000 	{
3001 	    if (i == 0 && skip_first_backward)
3002 		continue;
3003 	    dir = i;
3004 	}
3005 	else
3006 	    dir = !i;
3007 
3008 	flags = 0;
3009 	if (!dir && !zero_width)
3010 	    flags = SEARCH_END;
3011 	end_pos = pos;
3012 
3013 	// wrapping should not occur in the first round
3014 	if (i == 0)
3015 	    p_ws = FALSE;
3016 
3017 	result = searchit(curwin, curbuf, &pos, &end_pos,
3018 		(dir ? FORWARD : BACKWARD),
3019 		spats[last_idx].pat, (long) (i ? count : 1),
3020 		SEARCH_KEEP | flags, RE_SEARCH, NULL);
3021 
3022 	p_ws = old_p_ws;
3023 
3024 	// First search may fail, but then start searching from the
3025 	// beginning of the file (cursor might be on the search match)
3026 	// except when Visual mode is active, so that extending the visual
3027 	// selection works.
3028 	if (i == 1 && !result) // not found, abort
3029 	{
3030 	    curwin->w_cursor = orig_pos;
3031 	    if (VIsual_active)
3032 		VIsual = save_VIsual;
3033 	    return FAIL;
3034 	}
3035 	else if (i == 0 && !result)
3036 	{
3037 	    if (forward)
3038 	    {
3039 		// try again from start of buffer
3040 		CLEAR_POS(&pos);
3041 	    }
3042 	    else
3043 	    {
3044 		// try again from end of buffer
3045 		// searching backwards, so set pos to last line and col
3046 		pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
3047 		pos.col  = (colnr_T)STRLEN(
3048 				ml_get(curwin->w_buffer->b_ml.ml_line_count));
3049 	    }
3050 	}
3051     }
3052 
3053     start_pos = pos;
3054 
3055     if (!VIsual_active)
3056 	VIsual = start_pos;
3057 
3058     // put the cursor after the match
3059     curwin->w_cursor = end_pos;
3060     if (LT_POS(VIsual, end_pos) && forward)
3061     {
3062 	if (skip_first_backward)
3063 	    // put the cursor on the start of the match
3064 	    curwin->w_cursor = pos;
3065 	else
3066 	    // put the cursor on last character of match
3067 	    dec_cursor();
3068     }
3069     else if (VIsual_active && LT_POS(curwin->w_cursor, VIsual) && forward)
3070 	curwin->w_cursor = pos;   // put the cursor on the start of the match
3071     VIsual_active = TRUE;
3072     VIsual_mode = 'v';
3073 
3074     if (*p_sel == 'e')
3075     {
3076 	// Correction for exclusive selection depends on the direction.
3077 	if (forward && LTOREQ_POS(VIsual, curwin->w_cursor))
3078 	    inc_cursor();
3079 	else if (!forward && LTOREQ_POS(curwin->w_cursor, VIsual))
3080 	    inc(&VIsual);
3081     }
3082 
3083 #ifdef FEAT_FOLDING
3084     if (fdo_flags & FDO_SEARCH && KeyTyped)
3085 	foldOpenCursor();
3086 #endif
3087 
3088     may_start_select('c');
3089     setmouse();
3090 #ifdef FEAT_CLIPBOARD
3091     // Make sure the clipboard gets updated.  Needed because start and
3092     // end are still the same, and the selection needs to be owned
3093     clip_star.vmode = NUL;
3094 #endif
3095     redraw_curbuf_later(INVERTED);
3096     showmode();
3097 
3098     return OK;
3099 }
3100 
3101 #if defined(FEAT_LISP) || defined(FEAT_CINDENT) || defined(FEAT_TEXTOBJ) \
3102 	|| defined(PROTO)
3103 /*
3104  * return TRUE if line 'lnum' is empty or has white chars only.
3105  */
3106     int
linewhite(linenr_T lnum)3107 linewhite(linenr_T lnum)
3108 {
3109     char_u  *p;
3110 
3111     p = skipwhite(ml_get(lnum));
3112     return (*p == NUL);
3113 }
3114 #endif
3115 
3116 /*
3117  * Add the search count "[3/19]" to "msgbuf".
3118  * See update_search_stat() for other arguments.
3119  */
3120     static void
cmdline_search_stat(int dirc,pos_T * pos,pos_T * cursor_pos,int show_top_bot_msg,char_u * msgbuf,int recompute,int maxcount,long timeout)3121 cmdline_search_stat(
3122     int		dirc,
3123     pos_T	*pos,
3124     pos_T	*cursor_pos,
3125     int		show_top_bot_msg,
3126     char_u	*msgbuf,
3127     int		recompute,
3128     int		maxcount,
3129     long	timeout)
3130 {
3131     searchstat_T stat;
3132 
3133     update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
3134 								      timeout);
3135     if (stat.cur > 0)
3136     {
3137 	char	t[SEARCH_STAT_BUF_LEN];
3138 	size_t	len;
3139 
3140 #ifdef FEAT_RIGHTLEFT
3141 	if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
3142 	{
3143 	    if (stat.incomplete == 1)
3144 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
3145 	    else if (stat.cnt > maxcount && stat.cur > maxcount)
3146 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3147 							   maxcount, maxcount);
3148 	    else if (stat.cnt > maxcount)
3149 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
3150 							   maxcount, stat.cur);
3151 	    else
3152 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3153 							   stat.cnt, stat.cur);
3154 	}
3155 	else
3156 #endif
3157 	{
3158 	    if (stat.incomplete == 1)
3159 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
3160 	    else if (stat.cnt > maxcount && stat.cur > maxcount)
3161 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3162 							   maxcount, maxcount);
3163 	    else if (stat.cnt > maxcount)
3164 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
3165 							   stat.cur, maxcount);
3166 	    else
3167 		vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3168 							   stat.cur, stat.cnt);
3169 	}
3170 
3171 	len = STRLEN(t);
3172 	if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
3173 	{
3174 	    mch_memmove(t + 2, t, len);
3175 	    t[0] = 'W';
3176 	    t[1] = ' ';
3177 	    len += 2;
3178 	}
3179 
3180 	mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
3181 	if (dirc == '?' && stat.cur == maxcount + 1)
3182 	    stat.cur = -1;
3183 
3184 	// keep the message even after redraw, but don't put in history
3185 	msg_hist_off = TRUE;
3186 	give_warning(msgbuf, FALSE);
3187 	msg_hist_off = FALSE;
3188     }
3189 }
3190 
3191 /*
3192  * Add the search count information to "stat".
3193  * "stat" must not be NULL.
3194  * When "recompute" is TRUE always recompute the numbers.
3195  * dirc == 0: don't find the next/previous match (only set the result to "stat")
3196  * dirc == '/': find the next match
3197  * dirc == '?': find the previous match
3198  */
3199     static void
update_search_stat(int dirc,pos_T * pos,pos_T * cursor_pos,searchstat_T * stat,int recompute,int maxcount,long timeout UNUSED)3200 update_search_stat(
3201     int			dirc,
3202     pos_T		*pos,
3203     pos_T		*cursor_pos,
3204     searchstat_T	*stat,
3205     int			recompute,
3206     int			maxcount,
3207     long		timeout UNUSED)
3208 {
3209     int		    save_ws = p_ws;
3210     int		    wraparound = FALSE;
3211     pos_T	    p = (*pos);
3212     static pos_T    lastpos = {0, 0, 0};
3213     static int	    cur = 0;
3214     static int	    cnt = 0;
3215     static int	    exact_match = FALSE;
3216     static int	    incomplete = 0;
3217     static int	    last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
3218     static int	    chgtick = 0;
3219     static char_u   *lastpat = NULL;
3220     static buf_T    *lbuf = NULL;
3221 #ifdef FEAT_RELTIME
3222     proftime_T  start;
3223 #endif
3224 
3225     vim_memset(stat, 0, sizeof(searchstat_T));
3226 
3227     if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
3228     {
3229 	stat->cur = cur;
3230 	stat->cnt = cnt;
3231 	stat->exact_match = exact_match;
3232 	stat->incomplete = incomplete;
3233 	stat->last_maxcount = last_maxcount;
3234 	return;
3235     }
3236     last_maxcount = maxcount;
3237 
3238     wraparound = ((dirc == '?' && LT_POS(lastpos, p))
3239 	       || (dirc == '/' && LT_POS(p, lastpos)));
3240 
3241     // If anything relevant changed the count has to be recomputed.
3242     // MB_STRNICMP ignores case, but we should not ignore case.
3243     // Unfortunately, there is no MB_STRNICMP function.
3244     // XXX: above comment should be "no MB_STRCMP function" ?
3245     if (!(chgtick == CHANGEDTICK(curbuf)
3246 	&& MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
3247 	&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
3248 	&& EQUAL_POS(lastpos, *cursor_pos)
3249 	&& lbuf == curbuf) || wraparound || cur < 0
3250 	    || (maxcount > 0 && cur > maxcount) || recompute)
3251     {
3252 	cur = 0;
3253 	cnt = 0;
3254 	exact_match = FALSE;
3255 	incomplete = 0;
3256 	CLEAR_POS(&lastpos);
3257 	lbuf = curbuf;
3258     }
3259 
3260     if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
3261 		&& (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0))
3262 	cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
3263     else
3264     {
3265 	int	done_search = FALSE;
3266 	pos_T	endpos = {0, 0, 0};
3267 
3268 	p_ws = FALSE;
3269 #ifdef FEAT_RELTIME
3270 	if (timeout > 0)
3271 	    profile_setlimit(timeout, &start);
3272 #endif
3273 	while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
3274 			 FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
3275 	{
3276 	    done_search = TRUE;
3277 #ifdef FEAT_RELTIME
3278 	    // Stop after passing the time limit.
3279 	    if (timeout > 0 && profile_passed_limit(&start))
3280 	    {
3281 		incomplete = 1;
3282 		break;
3283 	    }
3284 #endif
3285 	    cnt++;
3286 	    if (LTOREQ_POS(lastpos, p))
3287 	    {
3288 		cur = cnt;
3289 		if (LT_POS(p, endpos))
3290 		    exact_match = TRUE;
3291 	    }
3292 	    fast_breakcheck();
3293 	    if (maxcount > 0 && cnt > maxcount)
3294 	    {
3295 		incomplete = 2;    // max count exceeded
3296 		break;
3297 	    }
3298 	}
3299 	if (got_int)
3300 	    cur = -1; // abort
3301 	if (done_search)
3302 	{
3303 	    vim_free(lastpat);
3304 	    lastpat = vim_strsave(spats[last_idx].pat);
3305 	    chgtick = CHANGEDTICK(curbuf);
3306 	    lbuf = curbuf;
3307 	    lastpos = p;
3308 	}
3309     }
3310     stat->cur = cur;
3311     stat->cnt = cnt;
3312     stat->exact_match = exact_match;
3313     stat->incomplete = incomplete;
3314     stat->last_maxcount = last_maxcount;
3315     p_ws = save_ws;
3316 }
3317 
3318 #if defined(FEAT_FIND_ID) || defined(PROTO)
3319 /*
3320  * Find identifiers or defines in included files.
3321  * If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
3322  */
3323     void
find_pattern_in_path(char_u * ptr,int dir UNUSED,int len,int whole,int skip_comments,int type,long count,int action,linenr_T start_lnum,linenr_T end_lnum)3324 find_pattern_in_path(
3325     char_u	*ptr,		// pointer to search pattern
3326     int		dir UNUSED,	// direction of expansion
3327     int		len,		// length of search pattern
3328     int		whole,		// match whole words only
3329     int		skip_comments,	// don't match inside comments
3330     int		type,		// Type of search; are we looking for a type?
3331 				// a macro?
3332     long	count,
3333     int		action,		// What to do when we find it
3334     linenr_T	start_lnum,	// first line to start searching
3335     linenr_T	end_lnum)	// last line for searching
3336 {
3337     SearchedFile *files;		// Stack of included files
3338     SearchedFile *bigger;		// When we need more space
3339     int		max_path_depth = 50;
3340     long	match_count = 1;
3341 
3342     char_u	*pat;
3343     char_u	*new_fname;
3344     char_u	*curr_fname = curbuf->b_fname;
3345     char_u	*prev_fname = NULL;
3346     linenr_T	lnum;
3347     int		depth;
3348     int		depth_displayed;	// For type==CHECK_PATH
3349     int		old_files;
3350     int		already_searched;
3351     char_u	*file_line;
3352     char_u	*line;
3353     char_u	*p;
3354     char_u	save_char;
3355     int		define_matched;
3356     regmatch_T	regmatch;
3357     regmatch_T	incl_regmatch;
3358     regmatch_T	def_regmatch;
3359     int		matched = FALSE;
3360     int		did_show = FALSE;
3361     int		found = FALSE;
3362     int		i;
3363     char_u	*already = NULL;
3364     char_u	*startp = NULL;
3365     char_u	*inc_opt = NULL;
3366 #if defined(FEAT_QUICKFIX)
3367     win_T	*curwin_save = NULL;
3368 #endif
3369 
3370     regmatch.regprog = NULL;
3371     incl_regmatch.regprog = NULL;
3372     def_regmatch.regprog = NULL;
3373 
3374     file_line = alloc(LSIZE);
3375     if (file_line == NULL)
3376 	return;
3377 
3378     if (type != CHECK_PATH && type != FIND_DEFINE
3379 	// when CONT_SOL is set compare "ptr" with the beginning of the line
3380 	// is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
3381 	    && !(compl_cont_status & CONT_SOL))
3382     {
3383 	pat = alloc(len + 5);
3384 	if (pat == NULL)
3385 	    goto fpip_end;
3386 	sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
3387 	// ignore case according to p_ic, p_scs and pat
3388 	regmatch.rm_ic = ignorecase(pat);
3389 	regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
3390 	vim_free(pat);
3391 	if (regmatch.regprog == NULL)
3392 	    goto fpip_end;
3393     }
3394     inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
3395     if (*inc_opt != NUL)
3396     {
3397 	incl_regmatch.regprog = vim_regcomp(inc_opt,
3398 						 magic_isset() ? RE_MAGIC : 0);
3399 	if (incl_regmatch.regprog == NULL)
3400 	    goto fpip_end;
3401 	incl_regmatch.rm_ic = FALSE;	// don't ignore case in incl. pat.
3402     }
3403     if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL))
3404     {
3405 	def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL
3406 			   ? p_def : curbuf->b_p_def,
3407 						 magic_isset() ? RE_MAGIC : 0);
3408 	if (def_regmatch.regprog == NULL)
3409 	    goto fpip_end;
3410 	def_regmatch.rm_ic = FALSE;	// don't ignore case in define pat.
3411     }
3412     files = lalloc_clear(max_path_depth * sizeof(SearchedFile), TRUE);
3413     if (files == NULL)
3414 	goto fpip_end;
3415     old_files = max_path_depth;
3416     depth = depth_displayed = -1;
3417 
3418     lnum = start_lnum;
3419     if (end_lnum > curbuf->b_ml.ml_line_count)
3420 	end_lnum = curbuf->b_ml.ml_line_count;
3421     if (lnum > end_lnum)		// do at least one line
3422 	lnum = end_lnum;
3423     line = ml_get(lnum);
3424 
3425     for (;;)
3426     {
3427 	if (incl_regmatch.regprog != NULL
3428 		&& vim_regexec(&incl_regmatch, line, (colnr_T)0))
3429 	{
3430 	    char_u *p_fname = (curr_fname == curbuf->b_fname)
3431 					      ? curbuf->b_ffname : curr_fname;
3432 
3433 	    if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL)
3434 		// Use text from '\zs' to '\ze' (or end) of 'include'.
3435 		new_fname = find_file_name_in_path(incl_regmatch.startp[0],
3436 		       (int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]),
3437 				 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname);
3438 	    else
3439 		// Use text after match with 'include'.
3440 		new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
3441 			     FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL);
3442 	    already_searched = FALSE;
3443 	    if (new_fname != NULL)
3444 	    {
3445 		// Check whether we have already searched in this file
3446 		for (i = 0;; i++)
3447 		{
3448 		    if (i == depth + 1)
3449 			i = old_files;
3450 		    if (i == max_path_depth)
3451 			break;
3452 		    if (fullpathcmp(new_fname, files[i].name, TRUE, TRUE)
3453 								    & FPC_SAME)
3454 		    {
3455 			if (type != CHECK_PATH &&
3456 				action == ACTION_SHOW_ALL && files[i].matched)
3457 			{
3458 			    msg_putchar('\n');	    // cursor below last one
3459 			    if (!got_int)	    // don't display if 'q'
3460 						    // typed at "--more--"
3461 						    // message
3462 			    {
3463 				msg_home_replace_hl(new_fname);
3464 				msg_puts(_(" (includes previously listed match)"));
3465 				prev_fname = NULL;
3466 			    }
3467 			}
3468 			VIM_CLEAR(new_fname);
3469 			already_searched = TRUE;
3470 			break;
3471 		    }
3472 		}
3473 	    }
3474 
3475 	    if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
3476 				 || (new_fname == NULL && !already_searched)))
3477 	    {
3478 		if (did_show)
3479 		    msg_putchar('\n');	    // cursor below last one
3480 		else
3481 		{
3482 		    gotocmdline(TRUE);	    // cursor at status line
3483 		    msg_puts_title(_("--- Included files "));
3484 		    if (action != ACTION_SHOW_ALL)
3485 			msg_puts_title(_("not found "));
3486 		    msg_puts_title(_("in path ---\n"));
3487 		}
3488 		did_show = TRUE;
3489 		while (depth_displayed < depth && !got_int)
3490 		{
3491 		    ++depth_displayed;
3492 		    for (i = 0; i < depth_displayed; i++)
3493 			msg_puts("  ");
3494 		    msg_home_replace(files[depth_displayed].name);
3495 		    msg_puts(" -->\n");
3496 		}
3497 		if (!got_int)		    // don't display if 'q' typed
3498 					    // for "--more--" message
3499 		{
3500 		    for (i = 0; i <= depth_displayed; i++)
3501 			msg_puts("  ");
3502 		    if (new_fname != NULL)
3503 		    {
3504 			// using "new_fname" is more reliable, e.g., when
3505 			// 'includeexpr' is set.
3506 			msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
3507 		    }
3508 		    else
3509 		    {
3510 			/*
3511 			 * Isolate the file name.
3512 			 * Include the surrounding "" or <> if present.
3513 			 */
3514 			if (inc_opt != NULL
3515 				   && strstr((char *)inc_opt, "\\zs") != NULL)
3516 			{
3517 			    // pattern contains \zs, use the match
3518 			    p = incl_regmatch.startp[0];
3519 			    i = (int)(incl_regmatch.endp[0]
3520 						   - incl_regmatch.startp[0]);
3521 			}
3522 			else
3523 			{
3524 			    // find the file name after the end of the match
3525 			    for (p = incl_regmatch.endp[0];
3526 						  *p && !vim_isfilec(*p); p++)
3527 				;
3528 			    for (i = 0; vim_isfilec(p[i]); i++)
3529 				;
3530 			}
3531 
3532 			if (i == 0)
3533 			{
3534 			    // Nothing found, use the rest of the line.
3535 			    p = incl_regmatch.endp[0];
3536 			    i = (int)STRLEN(p);
3537 			}
3538 			// Avoid checking before the start of the line, can
3539 			// happen if \zs appears in the regexp.
3540 			else if (p > line)
3541 			{
3542 			    if (p[-1] == '"' || p[-1] == '<')
3543 			    {
3544 				--p;
3545 				++i;
3546 			    }
3547 			    if (p[i] == '"' || p[i] == '>')
3548 				++i;
3549 			}
3550 			save_char = p[i];
3551 			p[i] = NUL;
3552 			msg_outtrans_attr(p, HL_ATTR(HLF_D));
3553 			p[i] = save_char;
3554 		    }
3555 
3556 		    if (new_fname == NULL && action == ACTION_SHOW_ALL)
3557 		    {
3558 			if (already_searched)
3559 			    msg_puts(_("  (Already listed)"));
3560 			else
3561 			    msg_puts(_("  NOT FOUND"));
3562 		    }
3563 		}
3564 		out_flush();	    // output each line directly
3565 	    }
3566 
3567 	    if (new_fname != NULL)
3568 	    {
3569 		// Push the new file onto the file stack
3570 		if (depth + 1 == old_files)
3571 		{
3572 		    bigger = ALLOC_MULT(SearchedFile, max_path_depth * 2);
3573 		    if (bigger != NULL)
3574 		    {
3575 			for (i = 0; i <= depth; i++)
3576 			    bigger[i] = files[i];
3577 			for (i = depth + 1; i < old_files + max_path_depth; i++)
3578 			{
3579 			    bigger[i].fp = NULL;
3580 			    bigger[i].name = NULL;
3581 			    bigger[i].lnum = 0;
3582 			    bigger[i].matched = FALSE;
3583 			}
3584 			for (i = old_files; i < max_path_depth; i++)
3585 			    bigger[i + max_path_depth] = files[i];
3586 			old_files += max_path_depth;
3587 			max_path_depth *= 2;
3588 			vim_free(files);
3589 			files = bigger;
3590 		    }
3591 		}
3592 		if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
3593 								    == NULL)
3594 		    vim_free(new_fname);
3595 		else
3596 		{
3597 		    if (++depth == old_files)
3598 		    {
3599 			/*
3600 			 * lalloc() for 'bigger' must have failed above.  We
3601 			 * will forget one of our already visited files now.
3602 			 */
3603 			vim_free(files[old_files].name);
3604 			++old_files;
3605 		    }
3606 		    files[depth].name = curr_fname = new_fname;
3607 		    files[depth].lnum = 0;
3608 		    files[depth].matched = FALSE;
3609 		    if (action == ACTION_EXPAND)
3610 		    {
3611 			msg_hist_off = TRUE;	// reset in msg_trunc_attr()
3612 			vim_snprintf((char*)IObuff, IOSIZE,
3613 				_("Scanning included file: %s"),
3614 				(char *)new_fname);
3615 			msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R));
3616 		    }
3617 		    else if (p_verbose >= 5)
3618 		    {
3619 			verbose_enter();
3620 			smsg(_("Searching included file %s"),
3621 							   (char *)new_fname);
3622 			verbose_leave();
3623 		    }
3624 
3625 		}
3626 	    }
3627 	}
3628 	else
3629 	{
3630 	    /*
3631 	     * Check if the line is a define (type == FIND_DEFINE)
3632 	     */
3633 	    p = line;
3634 search_line:
3635 	    define_matched = FALSE;
3636 	    if (def_regmatch.regprog != NULL
3637 			      && vim_regexec(&def_regmatch, line, (colnr_T)0))
3638 	    {
3639 		/*
3640 		 * Pattern must be first identifier after 'define', so skip
3641 		 * to that position before checking for match of pattern.  Also
3642 		 * don't let it match beyond the end of this identifier.
3643 		 */
3644 		p = def_regmatch.endp[0];
3645 		while (*p && !vim_iswordc(*p))
3646 		    p++;
3647 		define_matched = TRUE;
3648 	    }
3649 
3650 	    /*
3651 	     * Look for a match.  Don't do this if we are looking for a
3652 	     * define and this line didn't match define_prog above.
3653 	     */
3654 	    if (def_regmatch.regprog == NULL || define_matched)
3655 	    {
3656 		if (define_matched || (compl_cont_status & CONT_SOL))
3657 		{
3658 		    // compare the first "len" chars from "ptr"
3659 		    startp = skipwhite(p);
3660 		    if (p_ic)
3661 			matched = !MB_STRNICMP(startp, ptr, len);
3662 		    else
3663 			matched = !STRNCMP(startp, ptr, len);
3664 		    if (matched && define_matched && whole
3665 						  && vim_iswordc(startp[len]))
3666 			matched = FALSE;
3667 		}
3668 		else if (regmatch.regprog != NULL
3669 			 && vim_regexec(&regmatch, line, (colnr_T)(p - line)))
3670 		{
3671 		    matched = TRUE;
3672 		    startp = regmatch.startp[0];
3673 		    /*
3674 		     * Check if the line is not a comment line (unless we are
3675 		     * looking for a define).  A line starting with "# define"
3676 		     * is not considered to be a comment line.
3677 		     */
3678 		    if (!define_matched && skip_comments)
3679 		    {
3680 			if ((*line != '#' ||
3681 				STRNCMP(skipwhite(line + 1), "define", 6) != 0)
3682 				&& get_leader_len(line, NULL, FALSE, TRUE))
3683 			    matched = FALSE;
3684 
3685 			/*
3686 			 * Also check for a "/ *" or "/ /" before the match.
3687 			 * Skips lines like "int backwards;  / * normal index
3688 			 * * /" when looking for "normal".
3689 			 * Note: Doesn't skip "/ *" in comments.
3690 			 */
3691 			p = skipwhite(line);
3692 			if (matched
3693 				|| (p[0] == '/' && p[1] == '*') || p[0] == '*')
3694 			    for (p = line; *p && p < startp; ++p)
3695 			    {
3696 				if (matched
3697 					&& p[0] == '/'
3698 					&& (p[1] == '*' || p[1] == '/'))
3699 				{
3700 				    matched = FALSE;
3701 				    // After "//" all text is comment
3702 				    if (p[1] == '/')
3703 					break;
3704 				    ++p;
3705 				}
3706 				else if (!matched && p[0] == '*' && p[1] == '/')
3707 				{
3708 				    // Can find match after "* /".
3709 				    matched = TRUE;
3710 				    ++p;
3711 				}
3712 			    }
3713 		    }
3714 		}
3715 	    }
3716 	}
3717 	if (matched)
3718 	{
3719 	    if (action == ACTION_EXPAND)
3720 	    {
3721 		int	cont_s_ipos = FALSE;
3722 		int	add_r;
3723 		char_u	*aux;
3724 
3725 		if (depth == -1 && lnum == curwin->w_cursor.lnum)
3726 		    break;
3727 		found = TRUE;
3728 		aux = p = startp;
3729 		if (compl_cont_status & CONT_ADDING)
3730 		{
3731 		    p += compl_length;
3732 		    if (vim_iswordp(p))
3733 			goto exit_matched;
3734 		    p = find_word_start(p);
3735 		}
3736 		p = find_word_end(p);
3737 		i = (int)(p - aux);
3738 
3739 		if ((compl_cont_status & CONT_ADDING) && i == compl_length)
3740 		{
3741 		    // IOSIZE > compl_length, so the STRNCPY works
3742 		    STRNCPY(IObuff, aux, i);
3743 
3744 		    // Get the next line: when "depth" < 0  from the current
3745 		    // buffer, otherwise from the included file.  Jump to
3746 		    // exit_matched when past the last line.
3747 		    if (depth < 0)
3748 		    {
3749 			if (lnum >= end_lnum)
3750 			    goto exit_matched;
3751 			line = ml_get(++lnum);
3752 		    }
3753 		    else if (vim_fgets(line = file_line,
3754 						      LSIZE, files[depth].fp))
3755 			goto exit_matched;
3756 
3757 		    // we read a line, set "already" to check this "line" later
3758 		    // if depth >= 0 we'll increase files[depth].lnum far
3759 		    // below  -- Acevedo
3760 		    already = aux = p = skipwhite(line);
3761 		    p = find_word_start(p);
3762 		    p = find_word_end(p);
3763 		    if (p > aux)
3764 		    {
3765 			if (*aux != ')' && IObuff[i-1] != TAB)
3766 			{
3767 			    if (IObuff[i-1] != ' ')
3768 				IObuff[i++] = ' ';
3769 			    // IObuf =~ "\(\k\|\i\).* ", thus i >= 2
3770 			    if (p_js
3771 				&& (IObuff[i-2] == '.'
3772 				    || (vim_strchr(p_cpo, CPO_JOINSP) == NULL
3773 					&& (IObuff[i-2] == '?'
3774 					    || IObuff[i-2] == '!'))))
3775 				IObuff[i++] = ' ';
3776 			}
3777 			// copy as much as possible of the new word
3778 			if (p - aux >= IOSIZE - i)
3779 			    p = aux + IOSIZE - i - 1;
3780 			STRNCPY(IObuff + i, aux, p - aux);
3781 			i += (int)(p - aux);
3782 			cont_s_ipos = TRUE;
3783 		    }
3784 		    IObuff[i] = NUL;
3785 		    aux = IObuff;
3786 
3787 		    if (i == compl_length)
3788 			goto exit_matched;
3789 		}
3790 
3791 		add_r = ins_compl_add_infercase(aux, i, p_ic,
3792 			curr_fname == curbuf->b_fname ? NULL : curr_fname,
3793 			dir, cont_s_ipos);
3794 		if (add_r == OK)
3795 		    // if dir was BACKWARD then honor it just once
3796 		    dir = FORWARD;
3797 		else if (add_r == FAIL)
3798 		    break;
3799 	    }
3800 	    else if (action == ACTION_SHOW_ALL)
3801 	    {
3802 		found = TRUE;
3803 		if (!did_show)
3804 		    gotocmdline(TRUE);		// cursor at status line
3805 		if (curr_fname != prev_fname)
3806 		{
3807 		    if (did_show)
3808 			msg_putchar('\n');	// cursor below last one
3809 		    if (!got_int)		// don't display if 'q' typed
3810 						// at "--more--" message
3811 			msg_home_replace_hl(curr_fname);
3812 		    prev_fname = curr_fname;
3813 		}
3814 		did_show = TRUE;
3815 		if (!got_int)
3816 		    show_pat_in_path(line, type, TRUE, action,
3817 			    (depth == -1) ? NULL : files[depth].fp,
3818 			    (depth == -1) ? &lnum : &files[depth].lnum,
3819 			    match_count++);
3820 
3821 		// Set matched flag for this file and all the ones that
3822 		// include it
3823 		for (i = 0; i <= depth; ++i)
3824 		    files[i].matched = TRUE;
3825 	    }
3826 	    else if (--count <= 0)
3827 	    {
3828 		found = TRUE;
3829 		if (depth == -1 && lnum == curwin->w_cursor.lnum
3830 #if defined(FEAT_QUICKFIX)
3831 						      && g_do_tagpreview == 0
3832 #endif
3833 						      )
3834 		    emsg(_("E387: Match is on current line"));
3835 		else if (action == ACTION_SHOW)
3836 		{
3837 		    show_pat_in_path(line, type, did_show, action,
3838 			(depth == -1) ? NULL : files[depth].fp,
3839 			(depth == -1) ? &lnum : &files[depth].lnum, 1L);
3840 		    did_show = TRUE;
3841 		}
3842 		else
3843 		{
3844 #ifdef FEAT_GUI
3845 		    need_mouse_correct = TRUE;
3846 #endif
3847 #if defined(FEAT_QUICKFIX)
3848 		    // ":psearch" uses the preview window
3849 		    if (g_do_tagpreview != 0)
3850 		    {
3851 			curwin_save = curwin;
3852 			prepare_tagpreview(TRUE, TRUE, FALSE);
3853 		    }
3854 #endif
3855 		    if (action == ACTION_SPLIT)
3856 		    {
3857 			if (win_split(0, 0) == FAIL)
3858 			    break;
3859 			RESET_BINDING(curwin);
3860 		    }
3861 		    if (depth == -1)
3862 		    {
3863 			// match in current file
3864 #if defined(FEAT_QUICKFIX)
3865 			if (g_do_tagpreview != 0)
3866 			{
3867 			    if (!win_valid(curwin_save))
3868 				break;
3869 			    if (!GETFILE_SUCCESS(getfile(
3870 					   curwin_save->w_buffer->b_fnum, NULL,
3871 						     NULL, TRUE, lnum, FALSE)))
3872 				break;	// failed to jump to file
3873 			}
3874 			else
3875 #endif
3876 			    setpcmark();
3877 			curwin->w_cursor.lnum = lnum;
3878 			check_cursor();
3879 		    }
3880 		    else
3881 		    {
3882 			if (!GETFILE_SUCCESS(getfile(
3883 					0, files[depth].name, NULL, TRUE,
3884 						    files[depth].lnum, FALSE)))
3885 			    break;	// failed to jump to file
3886 			// autocommands may have changed the lnum, we don't
3887 			// want that here
3888 			curwin->w_cursor.lnum = files[depth].lnum;
3889 		    }
3890 		}
3891 		if (action != ACTION_SHOW)
3892 		{
3893 		    curwin->w_cursor.col = (colnr_T)(startp - line);
3894 		    curwin->w_set_curswant = TRUE;
3895 		}
3896 
3897 #if defined(FEAT_QUICKFIX)
3898 		if (g_do_tagpreview != 0
3899 			   && curwin != curwin_save && win_valid(curwin_save))
3900 		{
3901 		    // Return cursor to where we were
3902 		    validate_cursor();
3903 		    redraw_later(VALID);
3904 		    win_enter(curwin_save, TRUE);
3905 		}
3906 # ifdef FEAT_PROP_POPUP
3907 		else if (WIN_IS_POPUP(curwin))
3908 		    // can't keep focus in popup window
3909 		    win_enter(firstwin, TRUE);
3910 # endif
3911 #endif
3912 		break;
3913 	    }
3914 exit_matched:
3915 	    matched = FALSE;
3916 	    // look for other matches in the rest of the line if we
3917 	    // are not at the end of it already
3918 	    if (def_regmatch.regprog == NULL
3919 		    && action == ACTION_EXPAND
3920 		    && !(compl_cont_status & CONT_SOL)
3921 		    && *startp != NUL
3922 		    && *(p = startp + mb_ptr2len(startp)) != NUL)
3923 		goto search_line;
3924 	}
3925 	line_breakcheck();
3926 	if (action == ACTION_EXPAND)
3927 	    ins_compl_check_keys(30, FALSE);
3928 	if (got_int || ins_compl_interrupted())
3929 	    break;
3930 
3931 	/*
3932 	 * Read the next line.  When reading an included file and encountering
3933 	 * end-of-file, close the file and continue in the file that included
3934 	 * it.
3935 	 */
3936 	while (depth >= 0 && !already
3937 		&& vim_fgets(line = file_line, LSIZE, files[depth].fp))
3938 	{
3939 	    fclose(files[depth].fp);
3940 	    --old_files;
3941 	    files[old_files].name = files[depth].name;
3942 	    files[old_files].matched = files[depth].matched;
3943 	    --depth;
3944 	    curr_fname = (depth == -1) ? curbuf->b_fname
3945 				       : files[depth].name;
3946 	    if (depth < depth_displayed)
3947 		depth_displayed = depth;
3948 	}
3949 	if (depth >= 0)		// we could read the line
3950 	{
3951 	    files[depth].lnum++;
3952 	    // Remove any CR and LF from the line.
3953 	    i = (int)STRLEN(line);
3954 	    if (i > 0 && line[i - 1] == '\n')
3955 		line[--i] = NUL;
3956 	    if (i > 0 && line[i - 1] == '\r')
3957 		line[--i] = NUL;
3958 	}
3959 	else if (!already)
3960 	{
3961 	    if (++lnum > end_lnum)
3962 		break;
3963 	    line = ml_get(lnum);
3964 	}
3965 	already = NULL;
3966     }
3967     // End of big for (;;) loop.
3968 
3969     // Close any files that are still open.
3970     for (i = 0; i <= depth; i++)
3971     {
3972 	fclose(files[i].fp);
3973 	vim_free(files[i].name);
3974     }
3975     for (i = old_files; i < max_path_depth; i++)
3976 	vim_free(files[i].name);
3977     vim_free(files);
3978 
3979     if (type == CHECK_PATH)
3980     {
3981 	if (!did_show)
3982 	{
3983 	    if (action != ACTION_SHOW_ALL)
3984 		msg(_("All included files were found"));
3985 	    else
3986 		msg(_("No included files"));
3987 	}
3988     }
3989     else if (!found && action != ACTION_EXPAND)
3990     {
3991 	if (got_int || ins_compl_interrupted())
3992 	    emsg(_(e_interr));
3993 	else if (type == FIND_DEFINE)
3994 	    emsg(_("E388: Couldn't find definition"));
3995 	else
3996 	    emsg(_("E389: Couldn't find pattern"));
3997     }
3998     if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
3999 	msg_end();
4000 
4001 fpip_end:
4002     vim_free(file_line);
4003     vim_regfree(regmatch.regprog);
4004     vim_regfree(incl_regmatch.regprog);
4005     vim_regfree(def_regmatch.regprog);
4006 }
4007 
4008     static void
show_pat_in_path(char_u * line,int type,int did_show,int action,FILE * fp,linenr_T * lnum,long count)4009 show_pat_in_path(
4010     char_u  *line,
4011     int	    type,
4012     int	    did_show,
4013     int	    action,
4014     FILE    *fp,
4015     linenr_T *lnum,
4016     long    count)
4017 {
4018     char_u  *p;
4019 
4020     if (did_show)
4021 	msg_putchar('\n');	// cursor below last one
4022     else if (!msg_silent)
4023 	gotocmdline(TRUE);	// cursor at status line
4024     if (got_int)		// 'q' typed at "--more--" message
4025 	return;
4026     for (;;)
4027     {
4028 	p = line + STRLEN(line) - 1;
4029 	if (fp != NULL)
4030 	{
4031 	    // We used fgets(), so get rid of newline at end
4032 	    if (p >= line && *p == '\n')
4033 		--p;
4034 	    if (p >= line && *p == '\r')
4035 		--p;
4036 	    *(p + 1) = NUL;
4037 	}
4038 	if (action == ACTION_SHOW_ALL)
4039 	{
4040 	    sprintf((char *)IObuff, "%3ld: ", count);	// show match nr
4041 	    msg_puts((char *)IObuff);
4042 	    sprintf((char *)IObuff, "%4ld", *lnum);	// show line nr
4043 						// Highlight line numbers
4044 	    msg_puts_attr((char *)IObuff, HL_ATTR(HLF_N));
4045 	    msg_puts(" ");
4046 	}
4047 	msg_prt_line(line, FALSE);
4048 	out_flush();			// show one line at a time
4049 
4050 	// Definition continues until line that doesn't end with '\'
4051 	if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
4052 	    break;
4053 
4054 	if (fp != NULL)
4055 	{
4056 	    if (vim_fgets(line, LSIZE, fp)) // end of file
4057 		break;
4058 	    ++*lnum;
4059 	}
4060 	else
4061 	{
4062 	    if (++*lnum > curbuf->b_ml.ml_line_count)
4063 		break;
4064 	    line = ml_get(*lnum);
4065 	}
4066 	msg_putchar('\n');
4067     }
4068 }
4069 #endif
4070 
4071 #ifdef FEAT_VIMINFO
4072 /*
4073  * Return the last used search pattern at "idx".
4074  */
4075     spat_T *
get_spat(int idx)4076 get_spat(int idx)
4077 {
4078     return &spats[idx];
4079 }
4080 
4081 /*
4082  * Return the last used search pattern index.
4083  */
4084     int
get_spat_last_idx(void)4085 get_spat_last_idx(void)
4086 {
4087     return last_idx;
4088 }
4089 #endif
4090 
4091 #ifdef FEAT_EVAL
4092 /*
4093  * "searchcount()" function
4094  */
4095     void
f_searchcount(typval_T * argvars,typval_T * rettv)4096 f_searchcount(typval_T *argvars, typval_T *rettv)
4097 {
4098     pos_T		pos = curwin->w_cursor;
4099     char_u		*pattern = NULL;
4100     int			maxcount = SEARCH_STAT_DEF_MAX_COUNT;
4101     long		timeout = SEARCH_STAT_DEF_TIMEOUT;
4102     int			recompute = TRUE;
4103     searchstat_T	stat;
4104 
4105     if (rettv_dict_alloc(rettv) == FAIL)
4106 	return;
4107 
4108     if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
4109 	return;
4110 
4111     if (shortmess(SHM_SEARCHCOUNT))	// 'shortmess' contains 'S' flag
4112 	recompute = TRUE;
4113 
4114     if (argvars[0].v_type != VAR_UNKNOWN)
4115     {
4116 	dict_T		*dict;
4117 	dictitem_T	*di;
4118 	listitem_T	*li;
4119 	int		error = FALSE;
4120 
4121 	if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
4122 	{
4123 	    emsg(_(e_dictreq));
4124 	    return;
4125 	}
4126 	dict = argvars[0].vval.v_dict;
4127 	di = dict_find(dict, (char_u *)"timeout", -1);
4128 	if (di != NULL)
4129 	{
4130 	    timeout = (long)tv_get_number_chk(&di->di_tv, &error);
4131 	    if (error)
4132 		return;
4133 	}
4134 	di = dict_find(dict, (char_u *)"maxcount", -1);
4135 	if (di != NULL)
4136 	{
4137 	    maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
4138 	    if (error)
4139 		return;
4140 	}
4141 	recompute = dict_get_bool(dict, (char_u *)"recompute", recompute);
4142 	di = dict_find(dict, (char_u *)"pattern", -1);
4143 	if (di != NULL)
4144 	{
4145 	    pattern = tv_get_string_chk(&di->di_tv);
4146 	    if (pattern == NULL)
4147 		return;
4148 	}
4149 	di = dict_find(dict, (char_u *)"pos", -1);
4150 	if (di != NULL)
4151 	{
4152 	    if (di->di_tv.v_type != VAR_LIST)
4153 	    {
4154 		semsg(_(e_invarg2), "pos");
4155 		return;
4156 	    }
4157 	    if (list_len(di->di_tv.vval.v_list) != 3)
4158 	    {
4159 		semsg(_(e_invarg2), "List format should be [lnum, col, off]");
4160 		return;
4161 	    }
4162 	    li = list_find(di->di_tv.vval.v_list, 0L);
4163 	    if (li != NULL)
4164 	    {
4165 		pos.lnum = tv_get_number_chk(&li->li_tv, &error);
4166 		if (error)
4167 		    return;
4168 	    }
4169 	    li = list_find(di->di_tv.vval.v_list, 1L);
4170 	    if (li != NULL)
4171 	    {
4172 	        pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
4173 		if (error)
4174 		    return;
4175 	    }
4176 	    li = list_find(di->di_tv.vval.v_list, 2L);
4177 	    if (li != NULL)
4178 	    {
4179 	        pos.coladd = tv_get_number_chk(&li->li_tv, &error);
4180 		if (error)
4181 		    return;
4182 	    }
4183 	}
4184     }
4185 
4186     save_last_search_pattern();
4187     if (pattern != NULL)
4188     {
4189 	if (*pattern == NUL)
4190 	    goto the_end;
4191 	vim_free(spats[last_idx].pat);
4192 	spats[last_idx].pat = vim_strsave(pattern);
4193     }
4194     if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
4195 	goto the_end;	// the previous pattern was never defined
4196 
4197     update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
4198 
4199     dict_add_number(rettv->vval.v_dict, "current", stat.cur);
4200     dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
4201     dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
4202     dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
4203     dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
4204 
4205 the_end:
4206     restore_last_search_pattern();
4207 }
4208 
4209 /*
4210  * Fuzzy string matching
4211  *
4212  * Ported from the lib_fts library authored by Forrest Smith.
4213  * https://github.com/forrestthewoods/lib_fts/tree/master/code
4214  *
4215  * The following blog describes the fuzzy matching algorithm:
4216  * https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
4217  *
4218  * Each matching string is assigned a score. The following factors are checked:
4219  *   - Matched letter
4220  *   - Unmatched letter
4221  *   - Consecutively matched letters
4222  *   - Proximity to start
4223  *   - Letter following a separator (space, underscore)
4224  *   - Uppercase letter following lowercase (aka CamelCase)
4225  *
4226  * Matched letters are good. Unmatched letters are bad. Matching near the start
4227  * is good. Matching the first letter in the middle of a phrase is good.
4228  * Matching the uppercase letters in camel case entries is good.
4229  *
4230  * The score assigned for each factor is explained below.
4231  * File paths are different from file names. File extensions may be ignorable.
4232  * Single words care about consecutive matches but not separators or camel
4233  * case.
4234  *   Score starts at 100
4235  *   Matched letter: +0 points
4236  *   Unmatched letter: -1 point
4237  *   Consecutive match bonus: +15 points
4238  *   First letter bonus: +15 points
4239  *   Separator bonus: +30 points
4240  *   Camel case bonus: +30 points
4241  *   Unmatched leading letter: -5 points (max: -15)
4242  *
4243  * There is some nuance to this. Scores don’t have an intrinsic meaning. The
4244  * score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
4245  * lower minimum score due to unmatched letter penalty. Longer search patterns
4246  * have a higher maximum score due to match bonuses.
4247  *
4248  * Separator and camel case bonus is worth a LOT. Consecutive matches are worth
4249  * quite a bit.
4250  *
4251  * There is a penalty if you DON’T match the first three letters. Which
4252  * effectively rewards matching near the start. However there’s no difference
4253  * in matching between the middle and end.
4254  *
4255  * There is not an explicit bonus for an exact match. Unmatched letters receive
4256  * a penalty. So shorter strings and closer matches are worth more.
4257  */
4258 typedef struct
4259 {
4260     int		idx;		// used for stable sort
4261     listitem_T	*item;
4262     int		score;
4263     list_T	*lmatchpos;
4264 } fuzzyItem_T;
4265 
4266 // bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
4267 // matching a whole word is preferred.
4268 #define SEQUENTIAL_BONUS 40
4269 // bonus if match occurs after a path separator
4270 #define PATH_SEPARATOR_BONUS 30
4271 // bonus if match occurs after a word separator
4272 #define WORD_SEPARATOR_BONUS 25
4273 // bonus if match is uppercase and prev is lower
4274 #define CAMEL_BONUS 30
4275 // bonus if the first letter is matched
4276 #define FIRST_LETTER_BONUS 15
4277 // penalty applied for every letter in str before the first match
4278 #define LEADING_LETTER_PENALTY -5
4279 // maximum penalty for leading letters
4280 #define MAX_LEADING_LETTER_PENALTY -15
4281 // penalty for every letter that doesn't match
4282 #define UNMATCHED_LETTER_PENALTY -1
4283 // penalty for gap in matching positions (-2 * k)
4284 #define GAP_PENALTY	-2
4285 // Score for a string that doesn't fuzzy match the pattern
4286 #define SCORE_NONE	-9999
4287 
4288 #define FUZZY_MATCH_RECURSION_LIMIT	10
4289 
4290 /*
4291  * Compute a score for a fuzzy matched string. The matching character locations
4292  * are in 'matches'.
4293  */
4294     static int
fuzzy_match_compute_score(char_u * str,int strSz,int_u * matches,int numMatches)4295 fuzzy_match_compute_score(
4296 	char_u		*str,
4297 	int		strSz,
4298 	int_u		*matches,
4299 	int		numMatches)
4300 {
4301     int		score;
4302     int		penalty;
4303     int		unmatched;
4304     int		i;
4305     char_u	*p = str;
4306     int_u	sidx = 0;
4307 
4308     // Initialize score
4309     score = 100;
4310 
4311     // Apply leading letter penalty
4312     penalty = LEADING_LETTER_PENALTY * matches[0];
4313     if (penalty < MAX_LEADING_LETTER_PENALTY)
4314 	penalty = MAX_LEADING_LETTER_PENALTY;
4315     score += penalty;
4316 
4317     // Apply unmatched penalty
4318     unmatched = strSz - numMatches;
4319     score += UNMATCHED_LETTER_PENALTY * unmatched;
4320 
4321     // Apply ordering bonuses
4322     for (i = 0; i < numMatches; ++i)
4323     {
4324 	int_u	currIdx = matches[i];
4325 
4326 	if (i > 0)
4327 	{
4328 	    int_u	prevIdx = matches[i - 1];
4329 
4330 	    // Sequential
4331 	    if (currIdx == (prevIdx + 1))
4332 		score += SEQUENTIAL_BONUS;
4333 	    else
4334 		score += GAP_PENALTY * (currIdx - prevIdx);
4335 	}
4336 
4337 	// Check for bonuses based on neighbor character value
4338 	if (currIdx > 0)
4339 	{
4340 	    // Camel case
4341 	    int	neighbor = ' ';
4342 	    int	curr;
4343 
4344 	    if (has_mbyte)
4345 	    {
4346 		while (sidx < currIdx)
4347 		{
4348 		    neighbor = (*mb_ptr2char)(p);
4349 		    MB_PTR_ADV(p);
4350 		    sidx++;
4351 		}
4352 		curr = (*mb_ptr2char)(p);
4353 	    }
4354 	    else
4355 	    {
4356 		neighbor = str[currIdx - 1];
4357 		curr = str[currIdx];
4358 	    }
4359 
4360 	    if (vim_islower(neighbor) && vim_isupper(curr))
4361 		score += CAMEL_BONUS;
4362 
4363 	    // Bonus if the match follows a separator character
4364 	    if (neighbor == '/' || neighbor == '\\')
4365 		score += PATH_SEPARATOR_BONUS;
4366 	    else if (neighbor == ' ' || neighbor == '_')
4367 		score += WORD_SEPARATOR_BONUS;
4368 	}
4369 	else
4370 	{
4371 	    // First letter
4372 	    score += FIRST_LETTER_BONUS;
4373 	}
4374     }
4375     return score;
4376 }
4377 
4378 /*
4379  * Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
4380  * Return the number of matching characters.
4381  */
4382     static int
fuzzy_match_recursive(char_u * fuzpat,char_u * str,int_u strIdx,int * outScore,char_u * strBegin,int strLen,int_u * srcMatches,int_u * matches,int maxMatches,int nextMatch,int * recursionCount)4383 fuzzy_match_recursive(
4384 	char_u		*fuzpat,
4385 	char_u		*str,
4386 	int_u		strIdx,
4387 	int		*outScore,
4388 	char_u		*strBegin,
4389 	int		strLen,
4390 	int_u		*srcMatches,
4391 	int_u		*matches,
4392 	int		maxMatches,
4393 	int		nextMatch,
4394 	int		*recursionCount)
4395 {
4396     // Recursion params
4397     int		recursiveMatch = FALSE;
4398     int_u	bestRecursiveMatches[MAX_FUZZY_MATCHES];
4399     int		bestRecursiveScore = 0;
4400     int		first_match;
4401     int		matched;
4402 
4403     // Count recursions
4404     ++*recursionCount;
4405     if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT)
4406 	return 0;
4407 
4408     // Detect end of strings
4409     if (*fuzpat == NUL || *str == NUL)
4410 	return 0;
4411 
4412     // Loop through fuzpat and str looking for a match
4413     first_match = TRUE;
4414     while (*fuzpat != NUL && *str != NUL)
4415     {
4416 	int	c1;
4417 	int	c2;
4418 
4419 	c1 = PTR2CHAR(fuzpat);
4420 	c2 = PTR2CHAR(str);
4421 
4422 	// Found match
4423 	if (vim_tolower(c1) == vim_tolower(c2))
4424 	{
4425 	    int_u	recursiveMatches[MAX_FUZZY_MATCHES];
4426 	    int		recursiveScore = 0;
4427 	    char_u	*next_char;
4428 
4429 	    // Supplied matches buffer was too short
4430 	    if (nextMatch >= maxMatches)
4431 		return 0;
4432 
4433 	    // "Copy-on-Write" srcMatches into matches
4434 	    if (first_match && srcMatches)
4435 	    {
4436 		memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
4437 		first_match = FALSE;
4438 	    }
4439 
4440 	    // Recursive call that "skips" this match
4441 	    if (has_mbyte)
4442 		next_char = str + (*mb_ptr2len)(str);
4443 	    else
4444 		next_char = str + 1;
4445 	    if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
4446 			&recursiveScore, strBegin, strLen, matches,
4447 			recursiveMatches,
4448 			ARRAY_LENGTH(recursiveMatches),
4449 			nextMatch, recursionCount))
4450 	    {
4451 		// Pick best recursive score
4452 		if (!recursiveMatch || recursiveScore > bestRecursiveScore)
4453 		{
4454 		    memcpy(bestRecursiveMatches, recursiveMatches,
4455 			    MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
4456 		    bestRecursiveScore = recursiveScore;
4457 		}
4458 		recursiveMatch = TRUE;
4459 	    }
4460 
4461 	    // Advance
4462 	    matches[nextMatch++] = strIdx;
4463 	    if (has_mbyte)
4464 		MB_PTR_ADV(fuzpat);
4465 	    else
4466 		++fuzpat;
4467 	}
4468 	if (has_mbyte)
4469 	    MB_PTR_ADV(str);
4470 	else
4471 	    ++str;
4472 	strIdx++;
4473     }
4474 
4475     // Determine if full fuzpat was matched
4476     matched = *fuzpat == NUL ? TRUE : FALSE;
4477 
4478     // Calculate score
4479     if (matched)
4480 	*outScore = fuzzy_match_compute_score(strBegin, strLen, matches,
4481 		nextMatch);
4482 
4483     // Return best result
4484     if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
4485     {
4486 	// Recursive score is better than "this"
4487 	memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
4488 	*outScore = bestRecursiveScore;
4489 	return nextMatch;
4490     }
4491     else if (matched)
4492 	return nextMatch;	// "this" score is better than recursive
4493 
4494     return 0;		// no match
4495 }
4496 
4497 /*
4498  * fuzzy_match()
4499  *
4500  * Performs exhaustive search via recursion to find all possible matches and
4501  * match with highest score.
4502  * Scores values have no intrinsic meaning.  Possible score range is not
4503  * normalized and varies with pattern.
4504  * Recursion is limited internally (default=10) to prevent degenerate cases
4505  * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
4506  * Uses char_u for match indices. Therefore patterns are limited to
4507  * MAX_FUZZY_MATCHES characters.
4508  *
4509  * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in
4510  * 'outScore' and the matching character positions in 'matches'.
4511  */
4512     int
fuzzy_match(char_u * str,char_u * pat_arg,int matchseq,int * outScore,int_u * matches,int maxMatches)4513 fuzzy_match(
4514 	char_u		*str,
4515 	char_u		*pat_arg,
4516 	int		matchseq,
4517 	int		*outScore,
4518 	int_u		*matches,
4519 	int		maxMatches)
4520 {
4521     int		recursionCount = 0;
4522     int		len = MB_CHARLEN(str);
4523     char_u	*save_pat;
4524     char_u	*pat;
4525     char_u	*p;
4526     int		complete = FALSE;
4527     int		score = 0;
4528     int		numMatches = 0;
4529     int		matchCount;
4530 
4531     *outScore = 0;
4532 
4533     save_pat = vim_strsave(pat_arg);
4534     if (save_pat == NULL)
4535 	return FALSE;
4536     pat = save_pat;
4537     p = pat;
4538 
4539     // Try matching each word in 'pat_arg' in 'str'
4540     while (TRUE)
4541     {
4542 	if (matchseq)
4543 	    complete = TRUE;
4544 	else
4545 	{
4546 	    // Extract one word from the pattern (separated by space)
4547 	    p = skipwhite(p);
4548 	    if (*p == NUL)
4549 		break;
4550 	    pat = p;
4551 	    while (*p != NUL && !VIM_ISWHITE(PTR2CHAR(p)))
4552 	    {
4553 		if (has_mbyte)
4554 		    MB_PTR_ADV(p);
4555 		else
4556 		    ++p;
4557 	    }
4558 	    if (*p == NUL)		// processed all the words
4559 		complete = TRUE;
4560 	    *p = NUL;
4561 	}
4562 
4563 	score = 0;
4564 	recursionCount = 0;
4565 	matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
4566 				matches + numMatches, maxMatches - numMatches,
4567 				0, &recursionCount);
4568 	if (matchCount == 0)
4569 	{
4570 	    numMatches = 0;
4571 	    break;
4572 	}
4573 
4574 	// Accumulate the match score and the number of matches
4575 	*outScore += score;
4576 	numMatches += matchCount;
4577 
4578 	if (complete)
4579 	    break;
4580 
4581 	// try matching the next word
4582 	++p;
4583     }
4584 
4585     vim_free(save_pat);
4586     return numMatches != 0;
4587 }
4588 
4589 /*
4590  * Sort the fuzzy matches in the descending order of the match score.
4591  * For items with same score, retain the order using the index (stable sort)
4592  */
4593     static int
fuzzy_match_item_compare(const void * s1,const void * s2)4594 fuzzy_match_item_compare(const void *s1, const void *s2)
4595 {
4596     int		v1 = ((fuzzyItem_T *)s1)->score;
4597     int		v2 = ((fuzzyItem_T *)s2)->score;
4598     int		idx1 = ((fuzzyItem_T *)s1)->idx;
4599     int		idx2 = ((fuzzyItem_T *)s2)->idx;
4600 
4601     return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
4602 }
4603 
4604 /*
4605  * Fuzzy search the string 'str' in a list of 'items' and return the matching
4606  * strings in 'fmatchlist'.
4607  * If 'matchseq' is TRUE, then for multi-word search strings, match all the
4608  * words in sequence.
4609  * If 'items' is a list of strings, then search for 'str' in the list.
4610  * If 'items' is a list of dicts, then either use 'key' to lookup the string
4611  * for each item or use 'item_cb' Funcref function to get the string.
4612  * If 'retmatchpos' is TRUE, then return a list of positions where 'str'
4613  * matches for each item.
4614  */
4615     static void
fuzzy_match_in_list(list_T * items,char_u * str,int matchseq,char_u * key,callback_T * item_cb,int retmatchpos,list_T * fmatchlist)4616 fuzzy_match_in_list(
4617 	list_T		*items,
4618 	char_u		*str,
4619 	int		matchseq,
4620 	char_u		*key,
4621 	callback_T	*item_cb,
4622 	int		retmatchpos,
4623 	list_T		*fmatchlist)
4624 {
4625     long	len;
4626     fuzzyItem_T	*ptrs;
4627     listitem_T	*li;
4628     long	i = 0;
4629     int		found_match = FALSE;
4630     int_u	matches[MAX_FUZZY_MATCHES];
4631 
4632     len = list_len(items);
4633     if (len == 0)
4634 	return;
4635 
4636     ptrs = ALLOC_CLEAR_MULT(fuzzyItem_T, len);
4637     if (ptrs == NULL)
4638 	return;
4639 
4640     // For all the string items in items, get the fuzzy matching score
4641     FOR_ALL_LIST_ITEMS(items, li)
4642     {
4643 	int		score;
4644 	char_u		*itemstr;
4645 	typval_T	rettv;
4646 
4647 	ptrs[i].idx = i;
4648 	ptrs[i].item = li;
4649 	ptrs[i].score = SCORE_NONE;
4650 	itemstr = NULL;
4651 	rettv.v_type = VAR_UNKNOWN;
4652 	if (li->li_tv.v_type == VAR_STRING)	// list of strings
4653 	    itemstr = li->li_tv.vval.v_string;
4654 	else if (li->li_tv.v_type == VAR_DICT &&
4655 				(key != NULL || item_cb->cb_name != NULL))
4656 	{
4657 	    // For a dict, either use the specified key to lookup the string or
4658 	    // use the specified callback function to get the string.
4659 	    if (key != NULL)
4660 		itemstr = dict_get_string(li->li_tv.vval.v_dict, key, FALSE);
4661 	    else
4662 	    {
4663 		typval_T	argv[2];
4664 
4665 		// Invoke the supplied callback (if any) to get the dict item
4666 		li->li_tv.vval.v_dict->dv_refcount++;
4667 		argv[0].v_type = VAR_DICT;
4668 		argv[0].vval.v_dict = li->li_tv.vval.v_dict;
4669 		argv[1].v_type = VAR_UNKNOWN;
4670 		if (call_callback(item_cb, -1, &rettv, 1, argv) != FAIL)
4671 		{
4672 		    if (rettv.v_type == VAR_STRING)
4673 			itemstr = rettv.vval.v_string;
4674 		}
4675 		dict_unref(li->li_tv.vval.v_dict);
4676 	    }
4677 	}
4678 
4679 	if (itemstr != NULL
4680 		&& fuzzy_match(itemstr, str, matchseq, &score, matches,
4681 		    sizeof(matches) / sizeof(matches[0])))
4682 	{
4683 	    // Copy the list of matching positions in itemstr to a list, if
4684 	    // 'retmatchpos' is set.
4685 	    if (retmatchpos)
4686 	    {
4687 		int	j = 0;
4688 		char_u	*p;
4689 
4690 		ptrs[i].lmatchpos = list_alloc();
4691 		if (ptrs[i].lmatchpos == NULL)
4692 		    goto done;
4693 
4694 		p = str;
4695 		while (*p != NUL)
4696 		{
4697 		    if (!VIM_ISWHITE(PTR2CHAR(p)))
4698 		    {
4699 			if (list_append_number(ptrs[i].lmatchpos,
4700 				    matches[j]) == FAIL)
4701 			    goto done;
4702 			j++;
4703 		    }
4704 		    if (has_mbyte)
4705 			MB_PTR_ADV(p);
4706 		    else
4707 			++p;
4708 		}
4709 	    }
4710 	    ptrs[i].score = score;
4711 	    found_match = TRUE;
4712 	}
4713 	++i;
4714 	clear_tv(&rettv);
4715     }
4716 
4717     if (found_match)
4718     {
4719 	list_T		*l;
4720 
4721 	// Sort the list by the descending order of the match score
4722 	qsort((void *)ptrs, (size_t)len, sizeof(fuzzyItem_T),
4723 		fuzzy_match_item_compare);
4724 
4725 	// For matchfuzzy(), return a list of matched strings.
4726 	//	    ['str1', 'str2', 'str3']
4727 	// For matchfuzzypos(), return a list with three items.
4728 	// The first item is a list of matched strings. The second item
4729 	// is a list of lists where each list item is a list of matched
4730 	// character positions. The third item is a list of matching scores.
4731 	//	[['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
4732 	if (retmatchpos)
4733 	{
4734 	    li = list_find(fmatchlist, 0);
4735 	    if (li == NULL || li->li_tv.vval.v_list == NULL)
4736 		goto done;
4737 	    l = li->li_tv.vval.v_list;
4738 	}
4739 	else
4740 	    l = fmatchlist;
4741 
4742 	// Copy the matching strings with a valid score to the return list
4743 	for (i = 0; i < len; i++)
4744 	{
4745 	    if (ptrs[i].score == SCORE_NONE)
4746 		break;
4747 	    list_append_tv(l, &ptrs[i].item->li_tv);
4748 	}
4749 
4750 	// next copy the list of matching positions
4751 	if (retmatchpos)
4752 	{
4753 	    li = list_find(fmatchlist, -2);
4754 	    if (li == NULL || li->li_tv.vval.v_list == NULL)
4755 		goto done;
4756 	    l = li->li_tv.vval.v_list;
4757 
4758 	    for (i = 0; i < len; i++)
4759 	    {
4760 		if (ptrs[i].score == SCORE_NONE)
4761 		    break;
4762 		if (ptrs[i].lmatchpos != NULL &&
4763 			list_append_list(l, ptrs[i].lmatchpos) == FAIL)
4764 		    goto done;
4765 	    }
4766 
4767 	    // copy the matching scores
4768 	    li = list_find(fmatchlist, -1);
4769 	    if (li == NULL || li->li_tv.vval.v_list == NULL)
4770 		goto done;
4771 	    l = li->li_tv.vval.v_list;
4772 	    for (i = 0; i < len; i++)
4773 	    {
4774 		if (ptrs[i].score == SCORE_NONE)
4775 		    break;
4776 		if (list_append_number(l, ptrs[i].score) == FAIL)
4777 		    goto done;
4778 	    }
4779 	}
4780     }
4781 
4782 done:
4783     vim_free(ptrs);
4784 }
4785 
4786 /*
4787  * Do fuzzy matching. Returns the list of matched strings in 'rettv'.
4788  * If 'retmatchpos' is TRUE, also returns the matching character positions.
4789  */
4790     static void
do_fuzzymatch(typval_T * argvars,typval_T * rettv,int retmatchpos)4791 do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
4792 {
4793     callback_T	cb;
4794     char_u	*key = NULL;
4795     int		ret;
4796     int		matchseq = FALSE;
4797 
4798     if (in_vim9script()
4799 	    && (check_for_list_arg(argvars, 0) == FAIL
4800 		|| check_for_string_arg(argvars, 1) == FAIL
4801 		|| check_for_opt_dict_arg(argvars, 2) == FAIL))
4802 	return;
4803 
4804     CLEAR_POINTER(&cb);
4805 
4806     // validate and get the arguments
4807     if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
4808     {
4809 	semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
4810 	return;
4811     }
4812     if (argvars[1].v_type != VAR_STRING
4813 	    || argvars[1].vval.v_string == NULL)
4814     {
4815 	semsg(_(e_invarg2), tv_get_string(&argvars[1]));
4816 	return;
4817     }
4818 
4819     if (argvars[2].v_type != VAR_UNKNOWN)
4820     {
4821 	dict_T		*d;
4822 	dictitem_T	*di;
4823 
4824 	if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL)
4825 	{
4826 	    emsg(_(e_dictreq));
4827 	    return;
4828 	}
4829 
4830 	// To search a dict, either a callback function or a key can be
4831 	// specified.
4832 	d = argvars[2].vval.v_dict;
4833 	if ((di = dict_find(d, (char_u *)"key", -1)) != NULL)
4834 	{
4835 	    if (di->di_tv.v_type != VAR_STRING
4836 		    || di->di_tv.vval.v_string == NULL
4837 		    || *di->di_tv.vval.v_string == NUL)
4838 	    {
4839 		semsg(_(e_invarg2), tv_get_string(&di->di_tv));
4840 		return;
4841 	    }
4842 	    key = tv_get_string(&di->di_tv);
4843 	}
4844 	else if ((di = dict_find(d, (char_u *)"text_cb", -1)) != NULL)
4845 	{
4846 	    cb = get_callback(&di->di_tv);
4847 	    if (cb.cb_name == NULL)
4848 	    {
4849 		semsg(_(e_invargval), "text_cb");
4850 		return;
4851 	    }
4852 	}
4853 	if (dict_find(d, (char_u *)"matchseq", -1) != NULL)
4854 	    matchseq = TRUE;
4855     }
4856 
4857     // get the fuzzy matches
4858     ret = rettv_list_alloc(rettv);
4859     if (ret != OK)
4860 	goto done;
4861     if (retmatchpos)
4862     {
4863 	list_T	*l;
4864 
4865 	// For matchfuzzypos(), a list with three items are returned. First
4866 	// item is a list of matching strings, the second item is a list of
4867 	// lists with matching positions within each string and the third item
4868 	// is the list of scores of the matches.
4869 	l = list_alloc();
4870 	if (l == NULL)
4871 	    goto done;
4872 	if (list_append_list(rettv->vval.v_list, l) == FAIL)
4873 	    goto done;
4874 	l = list_alloc();
4875 	if (l == NULL)
4876 	    goto done;
4877 	if (list_append_list(rettv->vval.v_list, l) == FAIL)
4878 	    goto done;
4879 	l = list_alloc();
4880 	if (l == NULL)
4881 	    goto done;
4882 	if (list_append_list(rettv->vval.v_list, l) == FAIL)
4883 	    goto done;
4884     }
4885 
4886     fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
4887 	    matchseq, key, &cb, retmatchpos, rettv->vval.v_list);
4888 
4889 done:
4890     free_callback(&cb);
4891 }
4892 
4893 /*
4894  * "matchfuzzy()" function
4895  */
4896     void
f_matchfuzzy(typval_T * argvars,typval_T * rettv)4897 f_matchfuzzy(typval_T *argvars, typval_T *rettv)
4898 {
4899     do_fuzzymatch(argvars, rettv, FALSE);
4900 }
4901 
4902 /*
4903  * "matchfuzzypos()" function
4904  */
4905     void
f_matchfuzzypos(typval_T * argvars,typval_T * rettv)4906 f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
4907 {
4908     do_fuzzymatch(argvars, rettv, TRUE);
4909 }
4910 
4911 #endif
4912