blob: 3b6b437c6d30237b316fa17237378e55b5dcfb24 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
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
Bram Moolenaar071d4272004-06-13 20:20:40 +000015#ifdef FEAT_EVAL
Bram Moolenaarbaaa7e92016-01-29 22:47:03 +010016static void set_vv_searchforward(void);
17static int first_submatch(regmmatch_T *rp);
Bram Moolenaar071d4272004-06-13 20:20:40 +000018#endif
Bram Moolenaarbaaa7e92016-01-29 22:47:03 +010019static int check_linecomment(char_u *line);
Bram Moolenaar071d4272004-06-13 20:20:40 +000020#ifdef FEAT_FIND_ID
Bram Moolenaarbaaa7e92016-01-29 22:47:03 +010021static void show_pat_in_path(char_u *, int,
22 int, int, FILE *, linenr_T *, long);
Bram Moolenaar071d4272004-06-13 20:20:40 +000023#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020024
25typedef 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
36static 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);
37static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
38
Bram Moolenaarea6561a2020-06-01 21:32:45 +020039#define SEARCH_STAT_DEF_TIMEOUT 40L
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020040#define SEARCH_STAT_DEF_MAX_COUNT 99
41#define SEARCH_STAT_BUF_LEN 12
Bram Moolenaar071d4272004-06-13 20:20:40 +000042
Bram Moolenaar071d4272004-06-13 20:20:40 +000043/*
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
Bram Moolenaar071d4272004-06-13 20:20:40 +000066/*
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 */
Bram Moolenaarc3328162019-07-23 22:15:25 +020071static spat_T spats[2] =
Bram Moolenaar071d4272004-06-13 20:20:40 +000072{
Bram Moolenaar63d9e732019-12-05 21:10:38 +010073 {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat
74 {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat
Bram Moolenaar071d4272004-06-13 20:20:40 +000075};
76
Bram Moolenaar63d9e732019-12-05 21:10:38 +010077static int last_idx = 0; // index in spats[] for RE_LAST
Bram Moolenaar071d4272004-06-13 20:20:40 +000078
Bram Moolenaar63d9e732019-12-05 21:10:38 +010079static char_u lastc[2] = {NUL, NUL}; // last character searched for
80static int lastcdir = FORWARD; // last direction of character search
81static int last_t_cmd = TRUE; // last search t_cmd
Bram Moolenaardbd24b52015-08-11 14:26:19 +020082static char_u lastc_bytes[MB_MAXBYTES + 1];
Bram Moolenaar63d9e732019-12-05 21:10:38 +010083static int lastc_bytelen = 1; // >1 for multi-byte char
Bram Moolenaardbd24b52015-08-11 14:26:19 +020084
Bram Moolenaar63d9e732019-12-05 21:10:38 +010085// copy of spats[], for keeping the search patterns while executing autocmds
Bram Moolenaarc3328162019-07-23 22:15:25 +020086static spat_T saved_spats[2];
Bram Moolenaar071d4272004-06-13 20:20:40 +000087# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +010088static int saved_spats_last_idx = 0;
89static int saved_spats_no_hlsearch = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +000090# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000091
Bram Moolenaar63d9e732019-12-05 21:10:38 +010092static char_u *mr_pattern = NULL; // pattern used by search_regcomp()
Bram Moolenaar071d4272004-06-13 20:20:40 +000093#ifdef FEAT_RIGHTLEFT
Bram Moolenaar63d9e732019-12-05 21:10:38 +010094static int mr_pattern_alloced = FALSE; // mr_pattern was allocated
Bram Moolenaar071d4272004-06-13 20:20:40 +000095#endif
96
97#ifdef FEAT_FIND_ID
98/*
99 * Type used by find_pattern_in_path() to remember which included files have
100 * been searched already.
101 */
102typedef struct SearchedFile
103{
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100104 FILE *fp; // File pointer
105 char_u *name; // Full name of file
106 linenr_T lnum; // Line we were up to in file
107 int matched; // Found a match in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +0000108} SearchedFile;
109#endif
110
111/*
112 * translate search pattern for vim_regcomp()
113 *
114 * pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
115 * pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
116 * pat_save == RE_BOTH: save pat in both patterns (:global command)
117 * pat_use == RE_SEARCH: use previous search pattern if "pat" is NULL
Bram Moolenaarb8017e72007-05-10 18:59:07 +0000118 * pat_use == RE_SUBST: use previous substitute pattern if "pat" is NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000119 * pat_use == RE_LAST: use last used pattern if "pat" is NULL
120 * options & SEARCH_HIS: put search string in history
121 * options & SEARCH_KEEP: keep previous search pattern
122 *
123 * returns FAIL if failed, OK otherwise.
124 */
125 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100126search_regcomp(
127 char_u *pat,
128 int pat_save,
129 int pat_use,
130 int options,
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100131 regmmatch_T *regmatch) // return: pattern and ignore-case flag
Bram Moolenaar071d4272004-06-13 20:20:40 +0000132{
133 int magic;
134 int i;
135
136 rc_did_emsg = FALSE;
Bram Moolenaarf4e20992020-12-21 19:59:08 +0100137 magic = magic_isset();
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138
139 /*
140 * If no pattern given, use a previously defined pattern.
141 */
142 if (pat == NULL || *pat == NUL)
143 {
144 if (pat_use == RE_LAST)
145 i = last_idx;
146 else
147 i = pat_use;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100148 if (spats[i].pat == NULL) // pattern was never defined
Bram Moolenaar071d4272004-06-13 20:20:40 +0000149 {
150 if (pat_use == RE_SUBST)
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200151 emsg(_(e_no_previous_substitute_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000152 else
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200153 emsg(_(e_no_previous_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000154 rc_did_emsg = TRUE;
155 return FAIL;
156 }
157 pat = spats[i].pat;
158 magic = spats[i].magic;
159 no_smartcase = spats[i].no_scs;
160 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100161 else if (options & SEARCH_HIS) // put new pattern in history
Bram Moolenaar071d4272004-06-13 20:20:40 +0000162 add_to_history(HIST_SEARCH, pat, TRUE, NUL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000163
164#ifdef FEAT_RIGHTLEFT
165 if (mr_pattern_alloced)
166 {
167 vim_free(mr_pattern);
168 mr_pattern_alloced = FALSE;
169 }
170
171 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
172 {
173 char_u *rev_pattern;
174
175 rev_pattern = reverse_text(pat);
176 if (rev_pattern == NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100177 mr_pattern = pat; // out of memory, keep normal pattern.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000178 else
179 {
180 mr_pattern = rev_pattern;
181 mr_pattern_alloced = TRUE;
182 }
183 }
184 else
185#endif
186 mr_pattern = pat;
187
188 /*
189 * Save the currently used pattern in the appropriate place,
190 * unless the pattern should not be remembered.
191 */
Bram Moolenaare1004402020-10-24 20:49:43 +0200192 if (!(options & SEARCH_KEEP)
193 && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000194 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100195 // search or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000196 if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
197 save_re_pat(RE_SEARCH, pat, magic);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100198 // substitute or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000199 if (pat_save == RE_SUBST || pat_save == RE_BOTH)
200 save_re_pat(RE_SUBST, pat, magic);
201 }
202
203 regmatch->rmm_ic = ignorecase(pat);
Bram Moolenaar3b56eb32005-07-11 22:40:32 +0000204 regmatch->rmm_maxcol = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000205 regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
206 if (regmatch->regprog == NULL)
207 return FAIL;
208 return OK;
209}
210
211/*
212 * Get search pattern used by search_regcomp().
213 */
214 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100215get_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000216{
217 return mr_pattern;
218}
219
Bram Moolenaarabc97732007-08-08 20:49:37 +0000220#if defined(FEAT_RIGHTLEFT) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000221/*
222 * Reverse text into allocated memory.
223 * Returns the allocated string, NULL when out of memory.
224 */
Bram Moolenaarabc97732007-08-08 20:49:37 +0000225 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100226reverse_text(char_u *s)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000227{
228 unsigned len;
229 unsigned s_i, rev_i;
230 char_u *rev;
231
232 /*
233 * Reverse the pattern.
234 */
235 len = (unsigned)STRLEN(s);
236 rev = alloc(len + 1);
237 if (rev != NULL)
238 {
239 rev_i = len;
240 for (s_i = 0; s_i < len; ++s_i)
241 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000242 if (has_mbyte)
243 {
244 int mb_len;
245
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000246 mb_len = (*mb_ptr2len)(s + s_i);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000247 rev_i -= mb_len;
248 mch_memmove(rev + rev_i, s + s_i, mb_len);
249 s_i += mb_len - 1;
250 }
251 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000252 rev[--rev_i] = s[s_i];
253
254 }
255 rev[len] = NUL;
256 }
257 return rev;
258}
259#endif
260
Bram Moolenaarcc2b9d52014-12-13 03:17:11 +0100261 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100262save_re_pat(int idx, char_u *pat, int magic)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000263{
264 if (spats[idx].pat != pat)
265 {
266 vim_free(spats[idx].pat);
267 spats[idx].pat = vim_strsave(pat);
268 spats[idx].magic = magic;
269 spats[idx].no_scs = no_smartcase;
270 last_idx = idx;
271#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100272 // If 'hlsearch' set and search pat changed: need redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000273 if (p_hls)
Bram Moolenaar1c8f93f2006-03-12 22:10:07 +0000274 redraw_all_later(SOME_VALID);
Bram Moolenaar451fc7b2018-04-27 22:53:07 +0200275 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000276#endif
277 }
278}
279
Bram Moolenaar071d4272004-06-13 20:20:40 +0000280/*
281 * Save the search patterns, so they can be restored later.
282 * Used before/after executing autocommands and user functions.
283 */
284static int save_level = 0;
285
286 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100287save_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000288{
289 if (save_level++ == 0)
290 {
291 saved_spats[0] = spats[0];
292 if (spats[0].pat != NULL)
293 saved_spats[0].pat = vim_strsave(spats[0].pat);
294 saved_spats[1] = spats[1];
295 if (spats[1].pat != NULL)
296 saved_spats[1].pat = vim_strsave(spats[1].pat);
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100297#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100298 saved_spats_last_idx = last_idx;
299 saved_spats_no_hlsearch = no_hlsearch;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100300#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000301 }
302}
303
304 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100305restore_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000306{
307 if (--save_level == 0)
308 {
309 vim_free(spats[0].pat);
310 spats[0] = saved_spats[0];
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100311#if defined(FEAT_EVAL)
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000312 set_vv_searchforward();
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100313#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000314 vim_free(spats[1].pat);
315 spats[1] = saved_spats[1];
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100316#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100317 last_idx = saved_spats_last_idx;
318 set_no_hlsearch(saved_spats_no_hlsearch);
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100319#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000320 }
321}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000322
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000323#if defined(EXITFREE) || defined(PROTO)
324 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100325free_search_patterns(void)
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000326{
327 vim_free(spats[0].pat);
328 vim_free(spats[1].pat);
Bram Moolenaarf2427622009-04-22 16:45:21 +0000329
330# ifdef FEAT_RIGHTLEFT
331 if (mr_pattern_alloced)
332 {
Bram Moolenaarcc448b32010-07-14 16:52:17 +0200333 vim_free(mr_pattern);
334 mr_pattern_alloced = FALSE;
335 mr_pattern = NULL;
Bram Moolenaarf2427622009-04-22 16:45:21 +0000336 }
337# endif
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000338}
339#endif
340
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100341#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100342// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
343// searching
Bram Moolenaarc3328162019-07-23 22:15:25 +0200344static spat_T saved_last_search_spat;
Bram Moolenaared8bc782018-12-01 21:08:21 +0100345static int did_save_last_search_spat = 0;
346static int saved_last_idx = 0;
347static int saved_no_hlsearch = 0;
348
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100349/*
350 * Save and restore the search pattern for incremental highlight search
351 * feature.
352 *
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100353 * It's similar to but different from save_search_patterns() and
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100354 * restore_search_patterns(), because the search pattern must be restored when
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100355 * canceling incremental searching even if it's called inside user functions.
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100356 */
357 void
358save_last_search_pattern(void)
359{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200360 if (++did_save_last_search_spat != 1)
361 // nested call, nothing to do
362 return;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100363
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100364 saved_last_search_spat = spats[RE_SEARCH];
365 if (spats[RE_SEARCH].pat != NULL)
366 saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
367 saved_last_idx = last_idx;
368 saved_no_hlsearch = no_hlsearch;
369}
370
371 void
372restore_last_search_pattern(void)
373{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200374 if (--did_save_last_search_spat > 0)
375 // nested call, nothing to do
376 return;
377 if (did_save_last_search_spat != 0)
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100378 {
Bram Moolenaar442a8532020-06-04 20:56:09 +0200379 iemsg("restore_last_search_pattern() called more often than save_last_search_pattern()");
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100380 return;
381 }
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100382
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100383 vim_free(spats[RE_SEARCH].pat);
384 spats[RE_SEARCH] = saved_last_search_spat;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100385 saved_last_search_spat.pat = NULL;
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100386# if defined(FEAT_EVAL)
387 set_vv_searchforward();
388# endif
389 last_idx = saved_last_idx;
Bram Moolenaar451fc7b2018-04-27 22:53:07 +0200390 set_no_hlsearch(saved_no_hlsearch);
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100391}
Bram Moolenaard0480092017-11-16 22:20:39 +0100392
393 char_u *
394last_search_pattern(void)
395{
396 return spats[RE_SEARCH].pat;
397}
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100398#endif
399
Bram Moolenaar071d4272004-06-13 20:20:40 +0000400/*
401 * Return TRUE when case should be ignored for search pattern "pat".
402 * Uses the 'ignorecase' and 'smartcase' options.
403 */
404 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100405ignorecase(char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000406{
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200407 return ignorecase_opt(pat, p_ic, p_scs);
408}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000409
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200410/*
411 * As ignorecase() put pass the "ic" and "scs" flags.
412 */
413 int
414ignorecase_opt(char_u *pat, int ic_in, int scs)
415{
416 int ic = ic_in;
417
418 if (ic && !no_smartcase && scs
Bram Moolenaare2c453d2019-08-21 14:37:09 +0200419 && !(ctrl_x_mode_not_default() && curbuf->b_p_inf))
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200420 ic = !pat_has_uppercase(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000421 no_smartcase = FALSE;
422
423 return ic;
424}
425
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200426/*
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200427 * Return TRUE if pattern "pat" has an uppercase character.
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200428 */
429 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100430pat_has_uppercase(char_u *pat)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200431{
432 char_u *p = pat;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200433 magic_T magic_val = MAGIC_ON;
434
435 // get the magicness of the pattern
436 (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200437
438 while (*p != NUL)
439 {
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200440 int l;
441
442 if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
443 {
444 if (enc_utf8 && utf_isupper(utf_ptr2char(p)))
445 return TRUE;
446 p += l;
447 }
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200448 else if (*p == '\\' && magic_val <= MAGIC_ON)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200449 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100450 if (p[1] == '_' && p[2] != NUL) // skip "\_X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200451 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100452 else if (p[1] == '%' && p[2] != NUL) // skip "\%X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200453 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100454 else if (p[1] != NUL) // skip "\X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200455 p += 2;
456 else
457 p += 1;
458 }
Christian Brabandt78ba9332021-08-01 12:44:37 +0200459 else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL)
460 {
461 if (p[1] != NUL) // skip "_X" and %X
462 p += 2;
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200463 else
464 p++;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200465 }
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200466 else if (MB_ISUPPER(*p))
467 return TRUE;
468 else
469 ++p;
470 }
471 return FALSE;
472}
473
Bram Moolenaar113e1072019-01-20 15:30:40 +0100474#if defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000475 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100476last_csearch(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200477{
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200478 return lastc_bytes;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200479}
480
481 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100482last_csearch_forward(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200483{
484 return lastcdir == FORWARD;
485}
486
487 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100488last_csearch_until(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200489{
490 return last_t_cmd == TRUE;
491}
492
493 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100494set_last_csearch(int c, char_u *s UNUSED, int len UNUSED)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200495{
496 *lastc = c;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200497 lastc_bytelen = len;
498 if (len)
499 memcpy(lastc_bytes, s, len);
500 else
Bram Moolenaara80faa82020-04-12 19:37:17 +0200501 CLEAR_FIELD(lastc_bytes);
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200502}
Bram Moolenaar113e1072019-01-20 15:30:40 +0100503#endif
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200504
505 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100506set_csearch_direction(int cdir)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200507{
508 lastcdir = cdir;
509}
510
511 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100512set_csearch_until(int t_cmd)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200513{
514 last_t_cmd = t_cmd;
515}
516
517 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100518last_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000519{
520 return spats[last_idx].pat;
521}
522
523/*
524 * Reset search direction to forward. For "gd" and "gD" commands.
525 */
526 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100527reset_search_dir(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000528{
529 spats[0].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000530#if defined(FEAT_EVAL)
531 set_vv_searchforward();
532#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000533}
534
535#if defined(FEAT_EVAL) || defined(FEAT_VIMINFO)
536/*
537 * Set the last search pattern. For ":let @/ =" and viminfo.
538 * Also set the saved search pattern, so that this works in an autocommand.
539 */
540 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100541set_last_search_pat(
542 char_u *s,
543 int idx,
544 int magic,
545 int setlast)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000546{
547 vim_free(spats[idx].pat);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100548 // An empty string means that nothing should be matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000549 if (*s == NUL)
550 spats[idx].pat = NULL;
551 else
552 spats[idx].pat = vim_strsave(s);
553 spats[idx].magic = magic;
554 spats[idx].no_scs = FALSE;
555 spats[idx].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000556#if defined(FEAT_EVAL)
557 set_vv_searchforward();
558#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000559 spats[idx].off.line = FALSE;
560 spats[idx].off.end = FALSE;
561 spats[idx].off.off = 0;
562 if (setlast)
563 last_idx = idx;
564 if (save_level)
565 {
566 vim_free(saved_spats[idx].pat);
567 saved_spats[idx] = spats[0];
568 if (spats[idx].pat == NULL)
569 saved_spats[idx].pat = NULL;
570 else
571 saved_spats[idx].pat = vim_strsave(spats[idx].pat);
Bram Moolenaar975880b2019-03-03 14:42:11 +0100572# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100573 saved_spats_last_idx = last_idx;
Bram Moolenaar975880b2019-03-03 14:42:11 +0100574# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000575 }
576# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100577 // If 'hlsearch' set and search pat changed: need redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000578 if (p_hls && idx == last_idx && !no_hlsearch)
Bram Moolenaar1c8f93f2006-03-12 22:10:07 +0000579 redraw_all_later(SOME_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000580# endif
581}
582#endif
583
584#ifdef FEAT_SEARCH_EXTRA
585/*
586 * Get a regexp program for the last used search pattern.
587 * This is used for highlighting all matches in a window.
588 * Values returned in regmatch->regprog and regmatch->rmm_ic.
589 */
590 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100591last_pat_prog(regmmatch_T *regmatch)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000592{
593 if (spats[last_idx].pat == NULL)
594 {
595 regmatch->regprog = NULL;
596 return;
597 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100598 ++emsg_off; // So it doesn't beep if bad expr
Bram Moolenaar071d4272004-06-13 20:20:40 +0000599 (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch);
600 --emsg_off;
601}
602#endif
603
604/*
Bram Moolenaarf7ff6e82014-03-23 15:13:05 +0100605 * Lowest level search function.
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100606 * Search for 'count'th occurrence of pattern "pat" in direction "dir".
607 * Start at position "pos" and return the found position in "pos".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000608 *
609 * if (options & SEARCH_MSG) == 0 don't give any messages
610 * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
611 * if (options & SEARCH_MSG) == SEARCH_MSG give all messages
612 * if (options & SEARCH_HIS) put search pattern in history
613 * if (options & SEARCH_END) return position at end of match
614 * if (options & SEARCH_START) accept match at pos itself
615 * if (options & SEARCH_KEEP) keep previous search pattern
616 * if (options & SEARCH_FOLD) match only once in a closed fold
617 * if (options & SEARCH_PEEK) check for typed char, cancel search
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100618 * if (options & SEARCH_COL) start at pos->col instead of zero
Bram Moolenaar071d4272004-06-13 20:20:40 +0000619 *
620 * Return FAIL (zero) for failure, non-zero for success.
621 * When FEAT_EVAL is defined, returns the index of the first matching
622 * subpattern plus one; one if there was none.
623 */
624 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100625searchit(
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200626 win_T *win, // window to search in; can be NULL for a
627 // buffer without a window!
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100628 buf_T *buf,
629 pos_T *pos,
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100630 pos_T *end_pos, // set to end of the match, unless NULL
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100631 int dir,
632 char_u *pat,
633 long count,
634 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200635 int pat_use, // which pattern to use when "pat" is empty
636 searchit_arg_T *extra_arg) // optional extra arguments, can be NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000637{
638 int found;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100639 linenr_T lnum; // no init to shut up Apollo cc
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100640 colnr_T col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000641 regmmatch_T regmatch;
642 char_u *ptr;
643 colnr_T matchcol;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000644 lpos_T endpos;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000645 lpos_T matchpos;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000646 int loop;
647 pos_T start_pos;
648 int at_first_line;
649 int extra_col;
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200650 int start_char_len;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000651 int match_ok;
652 long nmatched;
653 int submatch = 0;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100654 int first_match = TRUE;
Bram Moolenaar53989552019-12-23 22:59:18 +0100655 int called_emsg_before = called_emsg;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000656#ifdef FEAT_SEARCH_EXTRA
657 int break_loop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000658#endif
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200659 linenr_T stop_lnum = 0; // stop after this line number when != 0
660#ifdef FEAT_RELTIME
661 proftime_T *tm = NULL; // timeout limit or NULL
662 int *timed_out = NULL; // set when timed out or NULL
663#endif
664
665 if (extra_arg != NULL)
666 {
667 stop_lnum = extra_arg->sa_stop_lnum;
668#ifdef FEAT_RELTIME
669 tm = extra_arg->sa_tm;
670 timed_out = &extra_arg->sa_timed_out;
671#endif
672 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000673
674 if (search_regcomp(pat, RE_SEARCH, pat_use,
675 (options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL)
676 {
677 if ((options & SEARCH_MSG) && !rc_did_emsg)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100678 semsg(_("E383: Invalid search string: %s"), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000679 return FAIL;
680 }
681
Bram Moolenaar280f1262006-01-30 00:14:18 +0000682 /*
683 * find the string
684 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100685 do // loop for count
Bram Moolenaar071d4272004-06-13 20:20:40 +0000686 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100687 // When not accepting a match at the start position set "extra_col" to
688 // a non-zero value. Don't do that when starting at MAXCOL, since
689 // MAXCOL + 1 is zero.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200690 if (pos->col == MAXCOL)
691 start_char_len = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100692 // Watch out for the "col" being MAXCOL - 2, used in a closed fold.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200693 else if (has_mbyte
694 && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count
695 && pos->col < MAXCOL - 2)
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100696 {
Bram Moolenaar82846a02018-02-09 18:09:54 +0100697 ptr = ml_get_buf(buf, pos->lnum, FALSE);
Bram Moolenaar8846ac52018-02-09 19:24:01 +0100698 if ((int)STRLEN(ptr) <= pos->col)
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200699 start_char_len = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100700 else
Bram Moolenaar82846a02018-02-09 18:09:54 +0100701 start_char_len = (*mb_ptr2len)(ptr + pos->col);
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100702 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100703 else
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200704 start_char_len = 1;
705 if (dir == FORWARD)
706 {
707 if (options & SEARCH_START)
708 extra_col = 0;
709 else
710 extra_col = start_char_len;
711 }
712 else
713 {
714 if (options & SEARCH_START)
715 extra_col = start_char_len;
716 else
717 extra_col = 0;
718 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100719
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100720 start_pos = *pos; // remember start pos for detecting no match
721 found = 0; // default: not found
722 at_first_line = TRUE; // default: start in first line
723 if (pos->lnum == 0) // correct lnum for when starting in line 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000724 {
725 pos->lnum = 1;
726 pos->col = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100727 at_first_line = FALSE; // not in first line now
Bram Moolenaar071d4272004-06-13 20:20:40 +0000728 }
729
730 /*
731 * Start searching in current line, unless searching backwards and
732 * we're in column 0.
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000733 * If we are searching backwards, in column 0, and not including the
734 * current position, gain some efficiency by skipping back a line.
735 * Otherwise begin the search in the current line.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000736 */
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000737 if (dir == BACKWARD && start_pos.col == 0
738 && (options & SEARCH_START) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000739 {
740 lnum = pos->lnum - 1;
741 at_first_line = FALSE;
742 }
743 else
744 lnum = pos->lnum;
745
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100746 for (loop = 0; loop <= 1; ++loop) // loop twice if 'wrapscan' set
Bram Moolenaar071d4272004-06-13 20:20:40 +0000747 {
748 for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
749 lnum += dir, at_first_line = FALSE)
750 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100751 // Stop after checking "stop_lnum", if it's set.
Bram Moolenaara23ccb82006-02-27 00:08:02 +0000752 if (stop_lnum != 0 && (dir == FORWARD
753 ? lnum > stop_lnum : lnum < stop_lnum))
754 break;
Bram Moolenaar76929292008-01-06 19:07:36 +0000755#ifdef FEAT_RELTIME
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100756 // Stop after passing the "tm" time limit.
Bram Moolenaar76929292008-01-06 19:07:36 +0000757 if (tm != NULL && profile_passed_limit(tm))
758 break;
759#endif
Bram Moolenaara23ccb82006-02-27 00:08:02 +0000760
Bram Moolenaar071d4272004-06-13 20:20:40 +0000761 /*
Bram Moolenaar677ee682005-01-27 14:41:15 +0000762 * Look for a match somewhere in line "lnum".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000763 */
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100764 col = at_first_line && (options & SEARCH_COL) ? pos->col
765 : (colnr_T)0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000766 nmatched = vim_regexec_multi(&regmatch, win, buf,
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100767 lnum, col,
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000768#ifdef FEAT_RELTIME
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200769 tm, timed_out
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000770#else
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200771 NULL, NULL
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000772#endif
773 );
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200774 // vim_regexec_multi() may clear "regprog"
775 if (regmatch.regprog == NULL)
776 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100777 // Abort searching on an error (e.g., out of stack).
Bram Moolenaar53989552019-12-23 22:59:18 +0100778 if (called_emsg > called_emsg_before
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200779#ifdef FEAT_RELTIME
780 || (timed_out != NULL && *timed_out)
781#endif
782 )
Bram Moolenaar071d4272004-06-13 20:20:40 +0000783 break;
784 if (nmatched > 0)
785 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100786 // match may actually be in another line when using \zs
Bram Moolenaar677ee682005-01-27 14:41:15 +0000787 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000788 endpos = regmatch.endpos[0];
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000789#ifdef FEAT_EVAL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000790 submatch = first_submatch(&regmatch);
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000791#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100792 // "lnum" may be past end of buffer for "\n\zs".
Bram Moolenaar32466aa2006-02-24 23:53:04 +0000793 if (lnum + matchpos.lnum > buf->b_ml.ml_line_count)
794 ptr = (char_u *)"";
795 else
796 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000797
798 /*
799 * Forward search in the first line: match should be after
800 * the start position. If not, continue at the end of the
801 * match (this is vi compatible) or on the next char.
802 */
803 if (dir == FORWARD && at_first_line)
804 {
805 match_ok = TRUE;
806 /*
Bram Moolenaar677ee682005-01-27 14:41:15 +0000807 * When the match starts in a next line it's certainly
808 * past the start position.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000809 * When match lands on a NUL the cursor will be put
810 * one back afterwards, compare with that position,
811 * otherwise "/$" will get stuck on end of line.
812 */
Bram Moolenaar677ee682005-01-27 14:41:15 +0000813 while (matchpos.lnum == 0
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100814 && ((options & SEARCH_END) && first_match
Bram Moolenaar677ee682005-01-27 14:41:15 +0000815 ? (nmatched == 1
816 && (int)endpos.col - 1
Bram Moolenaar071d4272004-06-13 20:20:40 +0000817 < (int)start_pos.col + extra_col)
Bram Moolenaar677ee682005-01-27 14:41:15 +0000818 : ((int)matchpos.col
819 - (ptr[matchpos.col] == NUL)
820 < (int)start_pos.col + extra_col)))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000821 {
822 /*
823 * If vi-compatible searching, continue at the end
824 * of the match, otherwise continue one position
825 * forward.
826 */
827 if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
828 {
829 if (nmatched > 1)
830 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100831 // end is in next line, thus no match in
832 // this line
Bram Moolenaar071d4272004-06-13 20:20:40 +0000833 match_ok = FALSE;
834 break;
835 }
836 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100837 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000838 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000839 && ptr[matchcol] != NUL)
840 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000841 if (has_mbyte)
842 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000843 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000844 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000845 ++matchcol;
846 }
847 }
848 else
849 {
Bram Moolenaar677ee682005-01-27 14:41:15 +0000850 matchcol = matchpos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000851 if (ptr[matchcol] != NUL)
852 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000853 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000854 matchcol += (*mb_ptr2len)(ptr
Bram Moolenaar071d4272004-06-13 20:20:40 +0000855 + matchcol);
856 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000857 ++matchcol;
858 }
859 }
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +0200860 if (matchcol == 0 && (options & SEARCH_START))
Bram Moolenaardb333a52013-03-19 15:27:48 +0100861 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000862 if (ptr[matchcol] == NUL
863 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +0000864 win, buf, lnum + matchpos.lnum,
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000865 matchcol,
866#ifdef FEAT_RELTIME
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200867 tm, timed_out
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000868#else
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200869 NULL, NULL
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000870#endif
871 )) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000872 {
873 match_ok = FALSE;
874 break;
875 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200876 // vim_regexec_multi() may clear "regprog"
877 if (regmatch.regprog == NULL)
878 break;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000879 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000880 endpos = regmatch.endpos[0];
881# ifdef FEAT_EVAL
882 submatch = first_submatch(&regmatch);
883# endif
884
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100885 // Need to get the line pointer again, a
886 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000887 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000888 }
889 if (!match_ok)
890 continue;
891 }
892 if (dir == BACKWARD)
893 {
894 /*
895 * Now, if there are multiple matches on this line,
896 * we have to get the last one. Or the last one before
897 * the cursor, if we're on that line.
898 * When putting the new cursor at the end, compare
899 * relative to the end of the match.
900 */
901 match_ok = FALSE;
902 for (;;)
903 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100904 // Remember a position that is before the start
905 // position, we use it if it's the last match in
906 // the line. Always accept a position after
907 // wrapping around.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000908 if (loop
909 || ((options & SEARCH_END)
910 ? (lnum + regmatch.endpos[0].lnum
911 < start_pos.lnum
912 || (lnum + regmatch.endpos[0].lnum
913 == start_pos.lnum
914 && (int)regmatch.endpos[0].col - 1
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200915 < (int)start_pos.col
916 + extra_col))
Bram Moolenaar677ee682005-01-27 14:41:15 +0000917 : (lnum + regmatch.startpos[0].lnum
918 < start_pos.lnum
919 || (lnum + regmatch.startpos[0].lnum
920 == start_pos.lnum
921 && (int)regmatch.startpos[0].col
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200922 < (int)start_pos.col
923 + extra_col))))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000924 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000925 match_ok = TRUE;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000926 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000927 endpos = regmatch.endpos[0];
928# ifdef FEAT_EVAL
929 submatch = first_submatch(&regmatch);
930# endif
931 }
932 else
933 break;
934
935 /*
936 * We found a valid match, now check if there is
937 * another one after it.
938 * If vi-compatible searching, continue at the end
939 * of the match, otherwise continue one position
940 * forward.
941 */
942 if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
943 {
944 if (nmatched > 1)
945 break;
946 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100947 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000948 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000949 && ptr[matchcol] != NUL)
950 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000951 if (has_mbyte)
952 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000953 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000954 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000955 ++matchcol;
956 }
957 }
958 else
959 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100960 // Stop when the match is in a next line.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000961 if (matchpos.lnum > 0)
962 break;
963 matchcol = matchpos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000964 if (ptr[matchcol] != NUL)
965 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000966 if (has_mbyte)
967 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000968 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000969 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000970 ++matchcol;
971 }
972 }
973 if (ptr[matchcol] == NUL
974 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +0000975 win, buf, lnum + matchpos.lnum,
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000976 matchcol,
977#ifdef FEAT_RELTIME
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200978 tm, timed_out
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000979#else
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +0200980 NULL, NULL
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000981#endif
982 )) == 0)
Bram Moolenaar9d322762018-02-09 16:04:25 +0100983 {
984#ifdef FEAT_RELTIME
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100985 // If the search timed out, we did find a match
986 // but it might be the wrong one, so that's not
987 // OK.
Bram Moolenaar9d322762018-02-09 16:04:25 +0100988 if (timed_out != NULL && *timed_out)
989 match_ok = FALSE;
990#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000991 break;
Bram Moolenaar9d322762018-02-09 16:04:25 +0100992 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200993 // vim_regexec_multi() may clear "regprog"
994 if (regmatch.regprog == NULL)
995 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000996
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100997 // Need to get the line pointer again, a
998 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000999 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001000 }
1001
1002 /*
1003 * If there is only a match after the cursor, skip
1004 * this match.
1005 */
1006 if (!match_ok)
1007 continue;
1008 }
1009
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001010 // With the SEARCH_END option move to the last character
1011 // of the match. Don't do it for an empty match, end
1012 // should be same as start then.
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +02001013 if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001014 && !(matchpos.lnum == endpos.lnum
1015 && matchpos.col == endpos.col))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001016 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001017 // For a match in the first column, set the position
1018 // on the NUL in the previous line.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001019 pos->lnum = lnum + endpos.lnum;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001020 pos->col = endpos.col;
1021 if (endpos.col == 0)
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001022 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001023 if (pos->lnum > 1) // just in case
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001024 {
1025 --pos->lnum;
1026 pos->col = (colnr_T)STRLEN(ml_get_buf(buf,
1027 pos->lnum, FALSE));
1028 }
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001029 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001030 else
1031 {
1032 --pos->col;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001033 if (has_mbyte
1034 && pos->lnum <= buf->b_ml.ml_line_count)
1035 {
1036 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1037 pos->col -= (*mb_head_off)(ptr, ptr + pos->col);
1038 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001039 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001040 if (end_pos != NULL)
1041 {
1042 end_pos->lnum = lnum + matchpos.lnum;
1043 end_pos->col = matchpos.col;
1044 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001045 }
1046 else
1047 {
Bram Moolenaar677ee682005-01-27 14:41:15 +00001048 pos->lnum = lnum + matchpos.lnum;
1049 pos->col = matchpos.col;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001050 if (end_pos != NULL)
1051 {
1052 end_pos->lnum = lnum + endpos.lnum;
1053 end_pos->col = endpos.col;
1054 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001055 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001056 pos->coladd = 0;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001057 if (end_pos != NULL)
1058 end_pos->coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001059 found = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +01001060 first_match = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001061
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001062 // Set variables used for 'incsearch' highlighting.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001063 search_match_lines = endpos.lnum - matchpos.lnum;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001064 search_match_endcol = endpos.col;
1065 break;
1066 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001067 line_breakcheck(); // stop if ctrl-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001068 if (got_int)
1069 break;
1070
1071#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001072 // Cancel searching if a character was typed. Used for
1073 // 'incsearch'. Don't check too often, that would slowdown
1074 // searching too much.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001075 if ((options & SEARCH_PEEK)
1076 && ((lnum - pos->lnum) & 0x3f) == 0
1077 && char_avail())
1078 {
1079 break_loop = TRUE;
1080 break;
1081 }
1082#endif
1083
1084 if (loop && lnum == start_pos.lnum)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001085 break; // if second loop, stop where started
Bram Moolenaar071d4272004-06-13 20:20:40 +00001086 }
1087 at_first_line = FALSE;
1088
Bram Moolenaar795aaa12020-10-02 20:36:01 +02001089 // vim_regexec_multi() may clear "regprog"
1090 if (regmatch.regprog == NULL)
1091 break;
1092
Bram Moolenaar071d4272004-06-13 20:20:40 +00001093 /*
Bram Moolenaara23ccb82006-02-27 00:08:02 +00001094 * Stop the search if wrapscan isn't set, "stop_lnum" is
1095 * specified, after an interrupt, after a match and after looping
1096 * twice.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001097 */
Bram Moolenaar53989552019-12-23 22:59:18 +01001098 if (!p_ws || stop_lnum != 0 || got_int
1099 || called_emsg > called_emsg_before
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001100#ifdef FEAT_RELTIME
1101 || (timed_out != NULL && *timed_out)
Bram Moolenaar78a15312009-05-15 19:33:18 +00001102#endif
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001103#ifdef FEAT_SEARCH_EXTRA
1104 || break_loop
1105#endif
1106 || found || loop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001107 break;
1108
1109 /*
1110 * If 'wrapscan' is set we continue at the other end of the file.
1111 * If 'shortmess' does not contain 's', we give a message.
1112 * This message is also remembered in keep_msg for when the screen
1113 * is redrawn. The keep_msg is cleared whenever another message is
1114 * written.
1115 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001116 if (dir == BACKWARD) // start second loop at the other end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001117 lnum = buf->b_ml.ml_line_count;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001118 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001119 lnum = 1;
Bram Moolenaar92d640f2005-09-05 22:11:52 +00001120 if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
1121 give_warning((char_u *)_(dir == BACKWARD
1122 ? top_bot_msg : bot_top_msg), TRUE);
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001123 if (extra_arg != NULL)
1124 extra_arg->sa_wrapped = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001125 }
Bram Moolenaar53989552019-12-23 22:59:18 +01001126 if (got_int || called_emsg > called_emsg_before
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001127#ifdef FEAT_RELTIME
1128 || (timed_out != NULL && *timed_out)
1129#endif
Bram Moolenaar78a15312009-05-15 19:33:18 +00001130#ifdef FEAT_SEARCH_EXTRA
1131 || break_loop
1132#endif
1133 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00001134 break;
1135 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001136 while (--count > 0 && found); // stop after count matches or no match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001137
Bram Moolenaar473de612013-06-08 18:19:48 +02001138 vim_regfree(regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001139
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001140 if (!found) // did not find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00001141 {
1142 if (got_int)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001143 emsg(_(e_interr));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001144 else if ((options & SEARCH_MSG) == SEARCH_MSG)
1145 {
1146 if (p_ws)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001147 semsg(_(e_patnotf2), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001148 else if (lnum == 0)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001149 semsg(_("E384: search hit TOP without match for: %s"),
Bram Moolenaar071d4272004-06-13 20:20:40 +00001150 mr_pattern);
1151 else
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001152 semsg(_("E385: search hit BOTTOM without match for: %s"),
Bram Moolenaar071d4272004-06-13 20:20:40 +00001153 mr_pattern);
1154 }
1155 return FAIL;
1156 }
1157
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001158 // A pattern like "\n\zs" may go past the last line.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001159 if (pos->lnum > buf->b_ml.ml_line_count)
1160 {
1161 pos->lnum = buf->b_ml.ml_line_count;
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00001162 pos->col = (int)STRLEN(ml_get_buf(buf, pos->lnum, FALSE));
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001163 if (pos->col > 0)
1164 --pos->col;
1165 }
1166
Bram Moolenaar071d4272004-06-13 20:20:40 +00001167 return submatch + 1;
1168}
1169
1170#ifdef FEAT_EVAL
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001171 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001172set_search_direction(int cdir)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001173{
1174 spats[0].off.dir = cdir;
1175}
1176
1177 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001178set_vv_searchforward(void)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001179{
1180 set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
1181}
1182
Bram Moolenaar071d4272004-06-13 20:20:40 +00001183/*
1184 * Return the number of the first subpat that matched.
Bram Moolenaarad4d8a12015-12-28 19:20:36 +01001185 * Return zero if none of them matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001186 */
1187 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001188first_submatch(regmmatch_T *rp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001189{
1190 int submatch;
1191
1192 for (submatch = 1; ; ++submatch)
1193 {
1194 if (rp->startpos[submatch].lnum >= 0)
1195 break;
1196 if (submatch == 9)
1197 {
1198 submatch = 0;
1199 break;
1200 }
1201 }
1202 return submatch;
1203}
1204#endif
1205
1206/*
1207 * Highest level string search function.
Bram Moolenaarb8017e72007-05-10 18:59:07 +00001208 * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001209 * If 'dirc' is 0: use previous dir.
1210 * If 'pat' is NULL or empty : use previous string.
1211 * If 'options & SEARCH_REV' : go in reverse of previous dir.
1212 * If 'options & SEARCH_ECHO': echo the search command and handle options
1213 * If 'options & SEARCH_MSG' : may give error message
1214 * If 'options & SEARCH_OPT' : interpret optional flags
1215 * If 'options & SEARCH_HIS' : put search pattern in history
1216 * If 'options & SEARCH_NOOF': don't add offset to position
1217 * If 'options & SEARCH_MARK': set previous context mark
1218 * If 'options & SEARCH_KEEP': keep previous search pattern
1219 * If 'options & SEARCH_START': accept match at curpos itself
1220 * If 'options & SEARCH_PEEK': check for typed char, cancel search
1221 *
1222 * Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
1223 * makes the movement linewise without moving the match position.
1224 *
Bram Moolenaarb6c27352015-03-05 19:57:49 +01001225 * Return 0 for failure, 1 for found, 2 for found and line offset added.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001226 */
1227 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001228do_search(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001229 oparg_T *oap, // can be NULL
1230 int dirc, // '/' or '?'
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001231 int search_delim, // the delimiter for the search, e.g. '%' in
1232 // s%regex%replacement%
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001233 char_u *pat,
1234 long count,
1235 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001236 searchit_arg_T *sia) // optional arguments or NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001237{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001238 pos_T pos; // position of the last match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001239 char_u *searchstr;
Bram Moolenaarc3328162019-07-23 22:15:25 +02001240 soffset_T old_off;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001241 int retval; // Return value
Bram Moolenaar071d4272004-06-13 20:20:40 +00001242 char_u *p;
1243 long c;
1244 char_u *dircp;
1245 char_u *strcopy = NULL;
1246 char_u *ps;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001247 char_u *msgbuf = NULL;
1248 size_t len;
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001249 int has_offset = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001250
1251 /*
1252 * A line offset is not remembered, this is vi compatible.
1253 */
1254 if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
1255 {
1256 spats[0].off.line = FALSE;
1257 spats[0].off.off = 0;
1258 }
1259
1260 /*
1261 * Save the values for when (options & SEARCH_KEEP) is used.
1262 * (there is no "if ()" around this because gcc wants them initialized)
1263 */
1264 old_off = spats[0].off;
1265
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001266 pos = curwin->w_cursor; // start searching at the cursor position
Bram Moolenaar071d4272004-06-13 20:20:40 +00001267
1268 /*
1269 * Find out the direction of the search.
1270 */
1271 if (dirc == 0)
1272 dirc = spats[0].off.dir;
1273 else
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001274 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001275 spats[0].off.dir = dirc;
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001276#if defined(FEAT_EVAL)
1277 set_vv_searchforward();
1278#endif
1279 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001280 if (options & SEARCH_REV)
1281 {
Bram Moolenaar4f974752019-02-17 17:44:42 +01001282#ifdef MSWIN
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001283 // There is a bug in the Visual C++ 2.2 compiler which means that
1284 // dirc always ends up being '/'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001285 dirc = (dirc == '/') ? '?' : '/';
1286#else
1287 if (dirc == '/')
1288 dirc = '?';
1289 else
1290 dirc = '/';
1291#endif
1292 }
1293
1294#ifdef FEAT_FOLDING
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001295 // If the cursor is in a closed fold, don't find another match in the same
1296 // fold.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001297 if (dirc == '/')
1298 {
1299 if (hasFolding(pos.lnum, NULL, &pos.lnum))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001300 pos.col = MAXCOL - 2; // avoid overflow when adding 1
Bram Moolenaar071d4272004-06-13 20:20:40 +00001301 }
1302 else
1303 {
1304 if (hasFolding(pos.lnum, &pos.lnum, NULL))
1305 pos.col = 0;
1306 }
1307#endif
1308
1309#ifdef FEAT_SEARCH_EXTRA
1310 /*
1311 * Turn 'hlsearch' highlighting back on.
1312 */
1313 if (no_hlsearch && !(options & SEARCH_KEEP))
1314 {
Bram Moolenaar1c8f93f2006-03-12 22:10:07 +00001315 redraw_all_later(SOME_VALID);
Bram Moolenaar451fc7b2018-04-27 22:53:07 +02001316 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001317 }
1318#endif
1319
1320 /*
1321 * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
1322 */
1323 for (;;)
1324 {
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001325 int show_top_bot_msg = FALSE;
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001326
Bram Moolenaar071d4272004-06-13 20:20:40 +00001327 searchstr = pat;
1328 dircp = NULL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001329 // use previous pattern
Bram Moolenaarc036e872020-02-21 21:30:52 +01001330 if (pat == NULL || *pat == NUL || *pat == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001331 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001332 if (spats[RE_SEARCH].pat == NULL) // no previous pattern
Bram Moolenaar071d4272004-06-13 20:20:40 +00001333 {
Bram Moolenaarea683da2016-09-09 21:41:34 +02001334 searchstr = spats[RE_SUBST].pat;
1335 if (searchstr == NULL)
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001336 {
Bram Moolenaare29a27f2021-07-20 21:07:36 +02001337 emsg(_(e_no_previous_regular_expression));
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001338 retval = 0;
1339 goto end_do_search;
1340 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001341 }
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001342 else
1343 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001344 // make search_regcomp() use spats[RE_SEARCH].pat
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001345 searchstr = (char_u *)"";
1346 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001347 }
1348
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001349 if (pat != NULL && *pat != NUL) // look for (new) offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001350 {
1351 /*
1352 * Find end of regular expression.
1353 * If there is a matching '/' or '?', toss it.
1354 */
1355 ps = strcopy;
Bram Moolenaarf4e20992020-12-21 19:59:08 +01001356 p = skip_regexp_ex(pat, search_delim, magic_isset(),
Bram Moolenaard93a7fc2021-01-04 12:42:13 +01001357 &strcopy, NULL, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001358 if (strcopy != ps)
1359 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001360 // made a copy of "pat" to change "\?" to "?"
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00001361 searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001362 pat = strcopy;
1363 searchstr = strcopy;
1364 }
Bram Moolenaarc036e872020-02-21 21:30:52 +01001365 if (*p == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001366 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001367 dircp = p; // remember where we put the NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001368 *p++ = NUL;
1369 }
1370 spats[0].off.line = FALSE;
1371 spats[0].off.end = FALSE;
1372 spats[0].off.off = 0;
1373 /*
1374 * Check for a line offset or a character offset.
1375 * For get_address (echo off) we don't check for a character
1376 * offset, because it is meaningless and the 's' could be a
1377 * substitute command.
1378 */
1379 if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p))
1380 spats[0].off.line = TRUE;
1381 else if ((options & SEARCH_OPT) &&
1382 (*p == 'e' || *p == 's' || *p == 'b'))
1383 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001384 if (*p == 'e') // end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001385 spats[0].off.end = SEARCH_END;
1386 ++p;
1387 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001388 if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001389 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001390 // 'nr' or '+nr' or '-nr'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391 if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
1392 spats[0].off.off = atol((char *)p);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001393 else if (*p == '-') // single '-'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001394 spats[0].off.off = -1;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001395 else // single '+'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001396 spats[0].off.off = 1;
1397 ++p;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001398 while (VIM_ISDIGIT(*p)) // skip number
Bram Moolenaar071d4272004-06-13 20:20:40 +00001399 ++p;
1400 }
1401
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001402 // compute length of search command for get_address()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001403 searchcmdlen += (int)(p - pat);
1404
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001405 pat = p; // put pat after search command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001406 }
1407
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001408 if ((options & SEARCH_ECHO) && messaging() &&
1409 !msg_silent &&
1410 (!cmd_silent || !shortmess(SHM_SEARCHCOUNT)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001411 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001412 char_u *trunc;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001413 char_u off_buf[40];
Bram Moolenaard33a7642019-05-24 17:56:14 +02001414 size_t off_len = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001415
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001416 // Compute msg_row early.
1417 msg_start();
1418
Bram Moolenaar984f0312019-05-24 13:11:47 +02001419 // Get the offset, so we know how long it is.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001420 if (!cmd_silent &&
1421 (spats[0].off.line || spats[0].off.end || spats[0].off.off))
Bram Moolenaar984f0312019-05-24 13:11:47 +02001422 {
1423 p = off_buf;
1424 *p++ = dirc;
1425 if (spats[0].off.end)
1426 *p++ = 'e';
1427 else if (!spats[0].off.line)
1428 *p++ = 's';
1429 if (spats[0].off.off > 0 || spats[0].off.line)
1430 *p++ = '+';
1431 *p = NUL;
1432 if (spats[0].off.off != 0 || spats[0].off.line)
1433 sprintf((char *)p, "%ld", spats[0].off.off);
1434 off_len = STRLEN(off_buf);
1435 }
1436
Bram Moolenaar071d4272004-06-13 20:20:40 +00001437 if (*searchstr == NUL)
Bram Moolenaar2fb8f682018-12-01 13:14:45 +01001438 p = spats[0].pat;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001439 else
1440 p = searchstr;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001441
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001442 if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001443 {
1444 // Reserve enough space for the search pattern + offset +
Bram Moolenaar984f0312019-05-24 13:11:47 +02001445 // search stat. Use all the space available, so that the
1446 // search state is right aligned. If there is not enough space
1447 // msg_strtrunc() will shorten in the middle.
Bram Moolenaar19e8ac72019-09-03 22:23:38 +02001448 if (msg_scrolled != 0 && !cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001449 // Use all the columns.
1450 len = (int)(Rows - msg_row) * Columns - 1;
1451 else
1452 // Use up to 'showcmd' column.
1453 len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001454 if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3)
1455 len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001456 }
1457 else
1458 // Reserve enough space for the search pattern + offset.
Bram Moolenaar984f0312019-05-24 13:11:47 +02001459 len = STRLEN(p) + off_len + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001460
Bram Moolenaar880e4d92020-04-11 21:31:28 +02001461 vim_free(msgbuf);
Bram Moolenaar51e14382019-05-25 20:21:28 +02001462 msgbuf = alloc(len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001463 if (msgbuf != NULL)
1464 {
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001465 vim_memset(msgbuf, ' ', len);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001466 msgbuf[len - 1] = NUL;
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001467 // do not fill the msgbuf buffer, if cmd_silent is set, leave it
1468 // empty for the search_stat feature.
1469 if (!cmd_silent)
Bram Moolenaarcafda4f2005-09-06 19:25:11 +00001470 {
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001471 msgbuf[0] = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001473 if (enc_utf8 && utf_iscomposing(utf_ptr2char(p)))
1474 {
1475 // Use a space to draw the composing char on.
1476 msgbuf[1] = ' ';
1477 mch_memmove(msgbuf + 2, p, STRLEN(p));
1478 }
1479 else
1480 mch_memmove(msgbuf + 1, p, STRLEN(p));
1481 if (off_len > 0)
1482 mch_memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001483
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001484 trunc = msg_strtrunc(msgbuf, TRUE);
1485 if (trunc != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001486 {
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001487 vim_free(msgbuf);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001488 msgbuf = trunc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001489 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001490
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001491#ifdef FEAT_RIGHTLEFT
1492 // The search pattern could be shown on the right in
1493 // rightleft mode, but the 'ruler' and 'showcmd' area use
1494 // it too, thus it would be blanked out again very soon.
1495 // Show it on the left, but do reverse the text.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001496 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
1497 {
1498 char_u *r;
1499 size_t pat_len;
1500
1501 r = reverse_text(msgbuf);
1502 if (r != NULL)
1503 {
1504 vim_free(msgbuf);
1505 msgbuf = r;
1506 // move reversed text to beginning of buffer
1507 while (*r != NUL && *r == ' ')
1508 r++;
1509 pat_len = msgbuf + STRLEN(msgbuf) - r;
1510 mch_memmove(msgbuf, r, pat_len);
1511 // overwrite old text
1512 if ((size_t)(r - msgbuf) >= pat_len)
1513 vim_memset(r, ' ', pat_len);
1514 else
1515 vim_memset(msgbuf + pat_len, ' ', r - msgbuf);
1516 }
1517 }
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001518#endif
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001519 msg_outtrans(msgbuf);
1520 msg_clr_eos();
1521 msg_check();
1522
1523 gotocmdline(FALSE);
1524 out_flush();
1525 msg_nowait = TRUE; // don't wait for this message
1526 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001527 }
1528 }
1529
1530 /*
1531 * If there is a character offset, subtract it from the current
1532 * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
Bram Moolenaared203462004-06-16 11:19:22 +00001533 * Skip this if pos.col is near MAXCOL (closed fold).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001534 * This is not done for a line offset, because then we would not be vi
1535 * compatible.
1536 */
Bram Moolenaared203462004-06-16 11:19:22 +00001537 if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001538 {
1539 if (spats[0].off.off > 0)
1540 {
1541 for (c = spats[0].off.off; c; --c)
1542 if (decl(&pos) == -1)
1543 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001544 if (c) // at start of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001545 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001546 pos.lnum = 0; // allow lnum == 0 here
Bram Moolenaar071d4272004-06-13 20:20:40 +00001547 pos.col = MAXCOL;
1548 }
1549 }
1550 else
1551 {
1552 for (c = spats[0].off.off; c; ++c)
1553 if (incl(&pos) == -1)
1554 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001555 if (c) // at end of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001556 {
1557 pos.lnum = curbuf->b_ml.ml_line_count + 1;
1558 pos.col = 0;
1559 }
1560 }
1561 }
1562
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001563 /*
1564 * The actual search.
1565 */
Bram Moolenaar14184a32019-02-16 15:10:30 +01001566 c = searchit(curwin, curbuf, &pos, NULL,
1567 dirc == '/' ? FORWARD : BACKWARD,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001568 searchstr, count, spats[0].off.end + (options &
1569 (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS
1570 + SEARCH_MSG + SEARCH_START
1571 + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001572 RE_LAST, sia);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001573
1574 if (dircp != NULL)
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001575 *dircp = search_delim; // restore second '/' or '?' for normal_cmd()
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001576
1577 if (!shortmess(SHM_SEARCH)
1578 && ((dirc == '/' && LT_POS(pos, curwin->w_cursor))
1579 || (dirc == '?' && LT_POS(curwin->w_cursor, pos))))
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001580 show_top_bot_msg = TRUE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001581
Bram Moolenaar071d4272004-06-13 20:20:40 +00001582 if (c == FAIL)
1583 {
1584 retval = 0;
1585 goto end_do_search;
1586 }
1587 if (spats[0].off.end && oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001588 oap->inclusive = TRUE; // 'e' includes last character
Bram Moolenaar071d4272004-06-13 20:20:40 +00001589
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001590 retval = 1; // pattern found
Bram Moolenaar071d4272004-06-13 20:20:40 +00001591
1592 /*
1593 * Add character and/or line offset
1594 */
Bram Moolenaar9160f302006-08-29 15:58:12 +00001595 if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001596 {
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001597 pos_T org_pos = pos;
1598
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001599 if (spats[0].off.line) // Add the offset to the line number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001600 {
1601 c = pos.lnum + spats[0].off.off;
1602 if (c < 1)
1603 pos.lnum = 1;
1604 else if (c > curbuf->b_ml.ml_line_count)
1605 pos.lnum = curbuf->b_ml.ml_line_count;
1606 else
1607 pos.lnum = c;
1608 pos.col = 0;
1609
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001610 retval = 2; // pattern found, line offset added
Bram Moolenaar071d4272004-06-13 20:20:40 +00001611 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001612 else if (pos.col < MAXCOL - 2) // just in case
Bram Moolenaar071d4272004-06-13 20:20:40 +00001613 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001614 // to the right, check for end of file
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001615 c = spats[0].off.off;
1616 if (c > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001617 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001618 while (c-- > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001619 if (incl(&pos) == -1)
1620 break;
1621 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001622 // to the left, check for start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00001623 else
1624 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001625 while (c++ < 0)
1626 if (decl(&pos) == -1)
1627 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001628 }
1629 }
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001630 if (!EQUAL_POS(pos, org_pos))
1631 has_offset = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001632 }
1633
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001634 // Show [1/15] if 'S' is not in 'shortmess'.
1635 if ((options & SEARCH_ECHO)
1636 && messaging()
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001637 && !msg_silent
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001638 && c != FAIL
1639 && !shortmess(SHM_SEARCHCOUNT)
1640 && msgbuf != NULL)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001641 cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
1642 show_top_bot_msg, msgbuf,
1643 (count != 1 || has_offset
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001644#ifdef FEAT_FOLDING
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001645 || (!(fdo_flags & FDO_SEARCH)
1646 && hasFolding(curwin->w_cursor.lnum,
1647 NULL, NULL))
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001648#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001649 ),
1650 SEARCH_STAT_DEF_MAX_COUNT,
1651 SEARCH_STAT_DEF_TIMEOUT);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001652
Bram Moolenaar071d4272004-06-13 20:20:40 +00001653 /*
1654 * The search command can be followed by a ';' to do another search.
1655 * For example: "/pat/;/foo/+3;?bar"
1656 * This is like doing another search command, except:
1657 * - The remembered direction '/' or '?' is from the first search.
1658 * - When an error happens the cursor isn't moved at all.
1659 * Don't do this when called by get_address() (it handles ';' itself).
1660 */
1661 if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';')
1662 break;
1663
1664 dirc = *++pat;
Bram Moolenaarc036e872020-02-21 21:30:52 +01001665 search_delim = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001666 if (dirc != '?' && dirc != '/')
1667 {
1668 retval = 0;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01001669 emsg(_("E386: Expected '?' or '/' after ';'"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001670 goto end_do_search;
1671 }
1672 ++pat;
1673 }
1674
1675 if (options & SEARCH_MARK)
1676 setpcmark();
1677 curwin->w_cursor = pos;
1678 curwin->w_set_curswant = TRUE;
1679
1680end_do_search:
Bram Moolenaare1004402020-10-24 20:49:43 +02001681 if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001682 spats[0].off = old_off;
1683 vim_free(strcopy);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001684 vim_free(msgbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001685
1686 return retval;
1687}
1688
Bram Moolenaar071d4272004-06-13 20:20:40 +00001689/*
1690 * search_for_exact_line(buf, pos, dir, pat)
1691 *
1692 * Search for a line starting with the given pattern (ignoring leading
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001693 * white-space), starting from pos and going in direction "dir". "pos" will
Bram Moolenaar071d4272004-06-13 20:20:40 +00001694 * contain the position of the match found. Blank lines match only if
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001695 * ADDING is set. If p_ic is set then the pattern must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001696 * Return OK for success, or FAIL if no line found.
1697 */
1698 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001699search_for_exact_line(
1700 buf_T *buf,
1701 pos_T *pos,
1702 int dir,
1703 char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001704{
1705 linenr_T start = 0;
1706 char_u *ptr;
1707 char_u *p;
1708
1709 if (buf->b_ml.ml_line_count == 0)
1710 return FAIL;
1711 for (;;)
1712 {
1713 pos->lnum += dir;
1714 if (pos->lnum < 1)
1715 {
1716 if (p_ws)
1717 {
1718 pos->lnum = buf->b_ml.ml_line_count;
1719 if (!shortmess(SHM_SEARCH))
1720 give_warning((char_u *)_(top_bot_msg), TRUE);
1721 }
1722 else
1723 {
1724 pos->lnum = 1;
1725 break;
1726 }
1727 }
1728 else if (pos->lnum > buf->b_ml.ml_line_count)
1729 {
1730 if (p_ws)
1731 {
1732 pos->lnum = 1;
1733 if (!shortmess(SHM_SEARCH))
1734 give_warning((char_u *)_(bot_top_msg), TRUE);
1735 }
1736 else
1737 {
1738 pos->lnum = 1;
1739 break;
1740 }
1741 }
1742 if (pos->lnum == start)
1743 break;
1744 if (start == 0)
1745 start = pos->lnum;
1746 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1747 p = skipwhite(ptr);
1748 pos->col = (colnr_T) (p - ptr);
1749
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001750 // when adding lines the matching line may be empty but it is not
1751 // ignored because we are interested in the next line -- Acevedo
Bram Moolenaar4be06f92005-07-29 22:36:03 +00001752 if ((compl_cont_status & CONT_ADDING)
1753 && !(compl_cont_status & CONT_SOL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001754 {
1755 if ((p_ic ? MB_STRICMP(p, pat) : STRCMP(p, pat)) == 0)
1756 return OK;
1757 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001758 else if (*p != NUL) // ignore empty lines
1759 { // expanding lines or words
Bram Moolenaar4be06f92005-07-29 22:36:03 +00001760 if ((p_ic ? MB_STRNICMP(p, pat, compl_length)
1761 : STRNCMP(p, pat, compl_length)) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001762 return OK;
1763 }
1764 }
1765 return FAIL;
1766}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001767
1768/*
1769 * Character Searches
1770 */
1771
1772/*
1773 * Search for a character in a line. If "t_cmd" is FALSE, move to the
1774 * position of the character, otherwise move to just before the char.
1775 * Do this "cap->count1" times.
1776 * Return FAIL or OK.
1777 */
1778 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001779searchc(cmdarg_T *cap, int t_cmd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001780{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001781 int c = cap->nchar; // char to search for
1782 int dir = cap->arg; // TRUE for searching forward
1783 long count = cap->count1; // repeat count
Bram Moolenaar071d4272004-06-13 20:20:40 +00001784 int col;
1785 char_u *p;
1786 int len;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001787 int stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001788
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001789 if (c != NUL) // normal search: remember args for repeat
Bram Moolenaar071d4272004-06-13 20:20:40 +00001790 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001791 if (!KeyStuffed) // don't remember when redoing
Bram Moolenaar071d4272004-06-13 20:20:40 +00001792 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001793 *lastc = c;
1794 set_csearch_direction(dir);
1795 set_csearch_until(t_cmd);
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001796 lastc_bytelen = (*mb_char2bytes)(c, lastc_bytes);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001797 if (cap->ncharC1 != 0)
1798 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001799 lastc_bytelen += (*mb_char2bytes)(cap->ncharC1,
1800 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001801 if (cap->ncharC2 != 0)
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001802 lastc_bytelen += (*mb_char2bytes)(cap->ncharC2,
1803 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001804 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001805 }
1806 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001807 else // repeat previous search
Bram Moolenaar071d4272004-06-13 20:20:40 +00001808 {
Bram Moolenaar264b74f2019-01-24 17:18:42 +01001809 if (*lastc == NUL && lastc_bytelen == 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001810 return FAIL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001811 if (dir) // repeat in opposite direction
Bram Moolenaar071d4272004-06-13 20:20:40 +00001812 dir = -lastcdir;
1813 else
1814 dir = lastcdir;
1815 t_cmd = last_t_cmd;
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001816 c = *lastc;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001817 // For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001818
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001819 // Force a move of at least one char, so ";" and "," will move the
1820 // cursor, even if the cursor is right in front of char we are looking
1821 // at.
Bram Moolenaar19fd09a2011-07-15 13:21:30 +02001822 if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd)
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001823 stop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001824 }
1825
Bram Moolenaar60a795a2005-09-16 21:55:43 +00001826 if (dir == BACKWARD)
1827 cap->oap->inclusive = FALSE;
1828 else
1829 cap->oap->inclusive = TRUE;
1830
Bram Moolenaar071d4272004-06-13 20:20:40 +00001831 p = ml_get_curline();
1832 col = curwin->w_cursor.col;
1833 len = (int)STRLEN(p);
1834
1835 while (count--)
1836 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001837 if (has_mbyte)
1838 {
1839 for (;;)
1840 {
1841 if (dir > 0)
1842 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00001843 col += (*mb_ptr2len)(p + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001844 if (col >= len)
1845 return FAIL;
1846 }
1847 else
1848 {
1849 if (col == 0)
1850 return FAIL;
1851 col -= (*mb_head_off)(p, p + col - 1) + 1;
1852 }
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001853 if (lastc_bytelen == 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001854 {
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001855 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001856 break;
1857 }
Bram Moolenaar66727e12017-03-01 22:17:05 +01001858 else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0
Bram Moolenaarb129a442016-12-01 17:25:20 +01001859 && stop)
Bram Moolenaar66727e12017-03-01 22:17:05 +01001860 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001861 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001862 }
1863 }
1864 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001865 {
1866 for (;;)
1867 {
1868 if ((col += dir) < 0 || col >= len)
1869 return FAIL;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001870 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001871 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001872 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001873 }
1874 }
1875 }
1876
1877 if (t_cmd)
1878 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001879 // backup to before the character (possibly double-byte)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001880 col -= dir;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001881 if (has_mbyte)
1882 {
1883 if (dir < 0)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001884 // Landed on the search char which is lastc_bytelen long
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001885 col += lastc_bytelen - 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001886 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001887 // To previous char, which may be multi-byte.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001888 col -= (*mb_head_off)(p, p + col);
1889 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001890 }
1891 curwin->w_cursor.col = col;
1892
1893 return OK;
1894}
1895
1896/*
1897 * "Other" Searches
1898 */
1899
1900/*
1901 * findmatch - find the matching paren or brace
1902 *
1903 * Improvement over vi: Braces inside quotes are ignored.
1904 */
1905 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001906findmatch(oparg_T *oap, int initc)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001907{
1908 return findmatchlimit(oap, initc, 0, 0);
1909}
1910
1911/*
1912 * Return TRUE if the character before "linep[col]" equals "ch".
1913 * Return FALSE if "col" is zero.
1914 * Update "*prevcol" to the column of the previous character, unless "prevcol"
1915 * is NULL.
1916 * Handles multibyte string correctly.
1917 */
1918 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001919check_prevcol(
1920 char_u *linep,
1921 int col,
1922 int ch,
1923 int *prevcol)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001924{
1925 --col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001926 if (col > 0 && has_mbyte)
1927 col -= (*mb_head_off)(linep, linep + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001928 if (prevcol)
1929 *prevcol = col;
1930 return (col >= 0 && linep[col] == ch) ? TRUE : FALSE;
1931}
1932
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001933/*
1934 * Raw string start is found at linep[startpos.col - 1].
1935 * Return TRUE if the matching end can be found between startpos and endpos.
1936 */
1937 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001938find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001939{
1940 char_u *p;
1941 char_u *delim_copy;
1942 size_t delim_len;
1943 linenr_T lnum;
1944 int found = FALSE;
1945
1946 for (p = linep + startpos->col + 1; *p && *p != '('; ++p)
1947 ;
1948 delim_len = (p - linep) - startpos->col - 1;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001949 delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len);
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001950 if (delim_copy == NULL)
1951 return FALSE;
1952 for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum)
1953 {
1954 char_u *line = ml_get(lnum);
1955
1956 for (p = line + (lnum == startpos->lnum
1957 ? startpos->col + 1 : 0); *p; ++p)
1958 {
1959 if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col)
1960 break;
Bram Moolenaar282f9c62020-08-04 21:46:18 +02001961 if (*p == ')' && STRNCMP(delim_copy, p + 1, delim_len) == 0
1962 && p[delim_len + 1] == '"')
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001963 {
1964 found = TRUE;
1965 break;
1966 }
1967 }
1968 if (found)
1969 break;
1970 }
1971 vim_free(delim_copy);
1972 return found;
1973}
1974
Bram Moolenaar071d4272004-06-13 20:20:40 +00001975/*
Bram Moolenaar556ae8e2019-11-21 22:27:22 +01001976 * Check matchpairs option for "*initc".
1977 * If there is a match set "*initc" to the matching character and "*findc" to
1978 * the opposite character. Set "*backwards" to the direction.
1979 * When "switchit" is TRUE swap the direction.
1980 */
1981 static void
1982find_mps_values(
1983 int *initc,
1984 int *findc,
1985 int *backwards,
1986 int switchit)
1987{
1988 char_u *ptr;
1989
1990 ptr = curbuf->b_p_mps;
1991 while (*ptr != NUL)
1992 {
1993 if (has_mbyte)
1994 {
1995 char_u *prev;
1996
1997 if (mb_ptr2char(ptr) == *initc)
1998 {
1999 if (switchit)
2000 {
2001 *findc = *initc;
2002 *initc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
2003 *backwards = TRUE;
2004 }
2005 else
2006 {
2007 *findc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
2008 *backwards = FALSE;
2009 }
2010 return;
2011 }
2012 prev = ptr;
2013 ptr += mb_ptr2len(ptr) + 1;
2014 if (mb_ptr2char(ptr) == *initc)
2015 {
2016 if (switchit)
2017 {
2018 *findc = *initc;
2019 *initc = mb_ptr2char(prev);
2020 *backwards = FALSE;
2021 }
2022 else
2023 {
2024 *findc = mb_ptr2char(prev);
2025 *backwards = TRUE;
2026 }
2027 return;
2028 }
2029 ptr += mb_ptr2len(ptr);
2030 }
2031 else
2032 {
2033 if (*ptr == *initc)
2034 {
2035 if (switchit)
2036 {
2037 *backwards = TRUE;
2038 *findc = *initc;
2039 *initc = ptr[2];
2040 }
2041 else
2042 {
2043 *backwards = FALSE;
2044 *findc = ptr[2];
2045 }
2046 return;
2047 }
2048 ptr += 2;
2049 if (*ptr == *initc)
2050 {
2051 if (switchit)
2052 {
2053 *backwards = FALSE;
2054 *findc = *initc;
2055 *initc = ptr[-2];
2056 }
2057 else
2058 {
2059 *backwards = TRUE;
2060 *findc = ptr[-2];
2061 }
2062 return;
2063 }
2064 ++ptr;
2065 }
2066 if (*ptr == ',')
2067 ++ptr;
2068 }
2069}
2070
2071/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002072 * findmatchlimit -- find the matching paren or brace, if it exists within
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002073 * maxtravel lines of the cursor. A maxtravel of 0 means search until falling
2074 * off the edge of the file.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002075 *
2076 * "initc" is the character to find a match for. NUL means to find the
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002077 * character at or after the cursor. Special values:
2078 * '*' look for C-style comment / *
2079 * '/' look for C-style comment / *, ignoring comment-end
2080 * '#' look for preprocessor directives
2081 * 'R' look for raw string start: R"delim(text)delim" (only backwards)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002082 *
2083 * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
2084 * FM_FORWARD search forwards (when initc is '/', '*' or '#')
2085 * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
2086 * FM_SKIPCOMM skip comments (not implemented yet!)
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002087 *
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002088 * "oap" is only used to set oap->motion_type for a linewise motion, it can be
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002089 * NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002090 */
2091
2092 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002093findmatchlimit(
2094 oparg_T *oap,
2095 int initc,
2096 int flags,
2097 int maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002098{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002099 static pos_T pos; // current search position
2100 int findc = 0; // matching brace
Bram Moolenaar071d4272004-06-13 20:20:40 +00002101 int c;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002102 int count = 0; // cumulative number of braces
2103 int backwards = FALSE; // init for gcc
2104 int raw_string = FALSE; // search for raw string
2105 int inquote = FALSE; // TRUE when inside quotes
2106 char_u *linep; // pointer to current line
Bram Moolenaar071d4272004-06-13 20:20:40 +00002107 char_u *ptr;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002108 int do_quotes; // check for quotes in current line
2109 int at_start; // do_quotes value at start position
2110 int hash_dir = 0; // Direction searched for # things
2111 int comment_dir = 0; // Direction searched for comments
2112 pos_T match_pos; // Where last slash-star was found
2113 int start_in_quotes; // start position is in quotes
2114 int traveled = 0; // how far we've searched so far
2115 int ignore_cend = FALSE; // ignore comment end
2116 int cpo_match; // vi compatible matching
2117 int cpo_bsl; // don't recognize backslashes
2118 int match_escaped = 0; // search for escaped match
2119 int dir; // Direction to search
2120 int comment_col = MAXCOL; // start of / / comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002121#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002122 int lispcomm = FALSE; // inside of Lisp-style comment
2123 int lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002124#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002125
2126 pos = curwin->w_cursor;
Bram Moolenaarc56c4592013-08-14 17:45:29 +02002127 pos.coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002128 linep = ml_get(pos.lnum);
2129
2130 cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
2131 cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
2132
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002133 // Direction to search when initc is '/', '*' or '#'
Bram Moolenaar071d4272004-06-13 20:20:40 +00002134 if (flags & FM_BACKWARD)
2135 dir = BACKWARD;
2136 else if (flags & FM_FORWARD)
2137 dir = FORWARD;
2138 else
2139 dir = 0;
2140
2141 /*
2142 * if initc given, look in the table for the matching character
2143 * '/' and '*' are special cases: look for start or end of comment.
2144 * When '/' is used, we ignore running backwards into an star-slash, for
2145 * "[*" command, we just want to find any comment.
2146 */
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002147 if (initc == '/' || initc == '*' || initc == 'R')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002148 {
2149 comment_dir = dir;
2150 if (initc == '/')
2151 ignore_cend = TRUE;
2152 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002153 raw_string = (initc == 'R');
Bram Moolenaar071d4272004-06-13 20:20:40 +00002154 initc = NUL;
2155 }
2156 else if (initc != '#' && initc != NUL)
2157 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002158 find_mps_values(&initc, &findc, &backwards, TRUE);
Connor Lane Smithb9115da2021-07-31 13:31:42 +02002159 if (dir)
2160 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002161 if (findc == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002162 return NULL;
2163 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002164 else
2165 {
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002166 /*
2167 * Either initc is '#', or no initc was given and we need to look
2168 * under the cursor.
2169 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002170 if (initc == '#')
2171 {
2172 hash_dir = dir;
2173 }
2174 else
2175 {
2176 /*
2177 * initc was not given, must look for something to match under
2178 * or near the cursor.
2179 * Only check for special things when 'cpo' doesn't have '%'.
2180 */
2181 if (!cpo_match)
2182 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002183 // Are we before or at #if, #else etc.?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002184 ptr = skipwhite(linep);
2185 if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
2186 {
2187 ptr = skipwhite(ptr + 1);
2188 if ( STRNCMP(ptr, "if", 2) == 0
2189 || STRNCMP(ptr, "endif", 5) == 0
2190 || STRNCMP(ptr, "el", 2) == 0)
2191 hash_dir = 1;
2192 }
2193
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002194 // Are we on a comment?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002195 else if (linep[pos.col] == '/')
2196 {
2197 if (linep[pos.col + 1] == '*')
2198 {
2199 comment_dir = FORWARD;
2200 backwards = FALSE;
2201 pos.col++;
2202 }
2203 else if (pos.col > 0 && linep[pos.col - 1] == '*')
2204 {
2205 comment_dir = BACKWARD;
2206 backwards = TRUE;
2207 pos.col--;
2208 }
2209 }
2210 else if (linep[pos.col] == '*')
2211 {
2212 if (linep[pos.col + 1] == '/')
2213 {
2214 comment_dir = BACKWARD;
2215 backwards = TRUE;
2216 }
2217 else if (pos.col > 0 && linep[pos.col - 1] == '/')
2218 {
2219 comment_dir = FORWARD;
2220 backwards = FALSE;
2221 }
2222 }
2223 }
2224
2225 /*
2226 * If we are not on a comment or the # at the start of a line, then
2227 * look for brace anywhere on this line after the cursor.
2228 */
2229 if (!hash_dir && !comment_dir)
2230 {
2231 /*
2232 * Find the brace under or after the cursor.
2233 * If beyond the end of the line, use the last character in
2234 * the line.
2235 */
2236 if (linep[pos.col] == NUL && pos.col)
2237 --pos.col;
2238 for (;;)
2239 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002240 initc = PTR2CHAR(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002241 if (initc == NUL)
2242 break;
2243
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002244 find_mps_values(&initc, &findc, &backwards, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002245 if (findc)
2246 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002247 pos.col += mb_ptr2len(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002248 }
2249 if (!findc)
2250 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002251 // no brace in the line, maybe use " #if" then
Bram Moolenaar071d4272004-06-13 20:20:40 +00002252 if (!cpo_match && *skipwhite(linep) == '#')
2253 hash_dir = 1;
2254 else
2255 return NULL;
2256 }
2257 else if (!cpo_bsl)
2258 {
2259 int col, bslcnt = 0;
2260
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002261 // Set "match_escaped" if there are an odd number of
2262 // backslashes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002263 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2264 bslcnt++;
2265 match_escaped = (bslcnt & 1);
2266 }
2267 }
2268 }
2269 if (hash_dir)
2270 {
2271 /*
2272 * Look for matching #if, #else, #elif, or #endif
2273 */
2274 if (oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002275 oap->motion_type = MLINE; // Linewise for this case only
Bram Moolenaar071d4272004-06-13 20:20:40 +00002276 if (initc != '#')
2277 {
2278 ptr = skipwhite(skipwhite(linep) + 1);
2279 if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0)
2280 hash_dir = 1;
2281 else if (STRNCMP(ptr, "endif", 5) == 0)
2282 hash_dir = -1;
2283 else
2284 return NULL;
2285 }
2286 pos.col = 0;
2287 while (!got_int)
2288 {
2289 if (hash_dir > 0)
2290 {
2291 if (pos.lnum == curbuf->b_ml.ml_line_count)
2292 break;
2293 }
2294 else if (pos.lnum == 1)
2295 break;
2296 pos.lnum += hash_dir;
2297 linep = ml_get(pos.lnum);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002298 line_breakcheck(); // check for CTRL-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00002299 ptr = skipwhite(linep);
2300 if (*ptr != '#')
2301 continue;
2302 pos.col = (colnr_T) (ptr - linep);
2303 ptr = skipwhite(ptr + 1);
2304 if (hash_dir > 0)
2305 {
2306 if (STRNCMP(ptr, "if", 2) == 0)
2307 count++;
2308 else if (STRNCMP(ptr, "el", 2) == 0)
2309 {
2310 if (count == 0)
2311 return &pos;
2312 }
2313 else if (STRNCMP(ptr, "endif", 5) == 0)
2314 {
2315 if (count == 0)
2316 return &pos;
2317 count--;
2318 }
2319 }
2320 else
2321 {
2322 if (STRNCMP(ptr, "if", 2) == 0)
2323 {
2324 if (count == 0)
2325 return &pos;
2326 count--;
2327 }
2328 else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0)
2329 {
2330 if (count == 0)
2331 return &pos;
2332 }
2333 else if (STRNCMP(ptr, "endif", 5) == 0)
2334 count++;
2335 }
2336 }
2337 return NULL;
2338 }
2339 }
2340
2341#ifdef FEAT_RIGHTLEFT
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002342 // This is just guessing: when 'rightleft' is set, search for a matching
2343 // paren/brace in the other direction.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002344 if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>", initc) != NULL)
2345 backwards = !backwards;
2346#endif
2347
2348 do_quotes = -1;
2349 start_in_quotes = MAYBE;
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01002350 CLEAR_POS(&match_pos);
Bram Moolenaarfd2ac762006-03-01 22:09:21 +00002351
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002352 // backward search: Check if this line contains a single-line comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002353 if ((backwards && comment_dir)
2354#ifdef FEAT_LISP
2355 || lisp
2356#endif
2357 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002358 comment_col = check_linecomment(linep);
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002359#ifdef FEAT_LISP
2360 if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002361 lispcomm = TRUE; // find match inside this comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002362#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002363 while (!got_int)
2364 {
2365 /*
2366 * Go to the next position, forward or backward. We could use
2367 * inc() and dec() here, but that is much slower
2368 */
2369 if (backwards)
2370 {
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002371#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002372 // char to match is inside of comment, don't search outside
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002373 if (lispcomm && pos.col < (colnr_T)comment_col)
2374 break;
2375#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002376 if (pos.col == 0) // at start of line, go to prev. one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002377 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002378 if (pos.lnum == 1) // start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00002379 break;
2380 --pos.lnum;
2381
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00002382 if (maxtravel > 0 && ++traveled > maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002383 break;
2384
2385 linep = ml_get(pos.lnum);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002386 pos.col = (colnr_T)STRLEN(linep); // pos.col on trailing NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002387 do_quotes = -1;
2388 line_breakcheck();
2389
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002390 // Check if this line contains a single-line comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002391 if (comment_dir
2392#ifdef FEAT_LISP
2393 || lisp
2394#endif
2395 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002396 comment_col = check_linecomment(linep);
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002397#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002398 // skip comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002399 if (lisp && comment_col != MAXCOL)
2400 pos.col = comment_col;
2401#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002402 }
2403 else
2404 {
2405 --pos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002406 if (has_mbyte)
2407 pos.col -= (*mb_head_off)(linep, linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002408 }
2409 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002410 else // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002411 {
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002412 if (linep[pos.col] == NUL
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002413 // at end of line, go to next one
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002414#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002415 // don't search for match in comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002416 || (lisp && comment_col != MAXCOL
2417 && pos.col == (colnr_T)comment_col)
2418#endif
2419 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002420 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002421 if (pos.lnum == curbuf->b_ml.ml_line_count // end of file
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002422#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002423 // line is exhausted and comment with it,
2424 // don't search for match in code
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002425 || lispcomm
2426#endif
2427 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00002428 break;
2429 ++pos.lnum;
2430
2431 if (maxtravel && traveled++ > maxtravel)
2432 break;
2433
2434 linep = ml_get(pos.lnum);
2435 pos.col = 0;
2436 do_quotes = -1;
2437 line_breakcheck();
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002438#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002439 if (lisp) // find comment pos in new line
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002440 comment_col = check_linecomment(linep);
2441#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002442 }
2443 else
2444 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00002445 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00002446 pos.col += (*mb_ptr2len)(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002447 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002448 ++pos.col;
2449 }
2450 }
2451
2452 /*
2453 * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
2454 */
2455 if (pos.col == 0 && (flags & FM_BLOCKSTOP) &&
2456 (linep[0] == '{' || linep[0] == '}'))
2457 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002458 if (linep[0] == findc && count == 0) // match!
Bram Moolenaar071d4272004-06-13 20:20:40 +00002459 return &pos;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002460 break; // out of scope
Bram Moolenaar071d4272004-06-13 20:20:40 +00002461 }
2462
2463 if (comment_dir)
2464 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002465 // Note: comments do not nest, and we ignore quotes in them
2466 // TODO: ignore comment brackets inside strings
Bram Moolenaar071d4272004-06-13 20:20:40 +00002467 if (comment_dir == FORWARD)
2468 {
2469 if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
2470 {
2471 pos.col++;
2472 return &pos;
2473 }
2474 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002475 else // Searching backwards
Bram Moolenaar071d4272004-06-13 20:20:40 +00002476 {
2477 /*
2478 * A comment may contain / * or / /, it may also start or end
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002479 * with / * /. Ignore a / * after / / and after *.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002480 */
2481 if (pos.col == 0)
2482 continue;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002483 else if (raw_string)
2484 {
2485 if (linep[pos.col - 1] == 'R'
2486 && linep[pos.col] == '"'
2487 && vim_strchr(linep + pos.col + 1, '(') != NULL)
2488 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002489 // Possible start of raw string. Now that we have the
2490 // delimiter we can check if it ends before where we
2491 // started searching, or before the previously found
2492 // raw string start.
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002493 if (!find_rawstring_end(linep, &pos,
2494 count > 0 ? &match_pos : &curwin->w_cursor))
2495 {
2496 count++;
2497 match_pos = pos;
2498 match_pos.col--;
2499 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002500 linep = ml_get(pos.lnum); // may have been released
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002501 }
2502 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002503 else if ( linep[pos.col - 1] == '/'
2504 && linep[pos.col] == '*'
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002505 && (pos.col == 1 || linep[pos.col - 2] != '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002506 && (int)pos.col < comment_col)
2507 {
2508 count++;
2509 match_pos = pos;
2510 match_pos.col--;
2511 }
2512 else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
2513 {
2514 if (count > 0)
2515 pos = match_pos;
2516 else if (pos.col > 1 && linep[pos.col - 2] == '/'
2517 && (int)pos.col <= comment_col)
2518 pos.col -= 2;
2519 else if (ignore_cend)
2520 continue;
2521 else
2522 return NULL;
2523 return &pos;
2524 }
2525 }
2526 continue;
2527 }
2528
2529 /*
2530 * If smart matching ('cpoptions' does not contain '%'), braces inside
2531 * of quotes are ignored, but only if there is an even number of
2532 * quotes in the line.
2533 */
2534 if (cpo_match)
2535 do_quotes = 0;
2536 else if (do_quotes == -1)
2537 {
2538 /*
2539 * Count the number of quotes in the line, skipping \" and '"'.
2540 * Watch out for "\\".
2541 */
2542 at_start = do_quotes;
2543 for (ptr = linep; *ptr; ++ptr)
2544 {
2545 if (ptr == linep + pos.col + backwards)
2546 at_start = (do_quotes & 1);
2547 if (*ptr == '"'
2548 && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
2549 ++do_quotes;
2550 if (*ptr == '\\' && ptr[1] != NUL)
2551 ++ptr;
2552 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002553 do_quotes &= 1; // result is 1 with even number of quotes
Bram Moolenaar071d4272004-06-13 20:20:40 +00002554
2555 /*
2556 * If we find an uneven count, check current line and previous
2557 * one for a '\' at the end.
2558 */
2559 if (!do_quotes)
2560 {
2561 inquote = FALSE;
2562 if (ptr[-1] == '\\')
2563 {
2564 do_quotes = 1;
2565 if (start_in_quotes == MAYBE)
2566 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002567 // Do we need to use at_start here?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002568 inquote = TRUE;
2569 start_in_quotes = TRUE;
2570 }
2571 else if (backwards)
2572 inquote = TRUE;
2573 }
2574 if (pos.lnum > 1)
2575 {
2576 ptr = ml_get(pos.lnum - 1);
2577 if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
2578 {
2579 do_quotes = 1;
2580 if (start_in_quotes == MAYBE)
2581 {
2582 inquote = at_start;
2583 if (inquote)
2584 start_in_quotes = TRUE;
2585 }
2586 else if (!backwards)
2587 inquote = TRUE;
2588 }
Bram Moolenaaraec11792007-07-10 11:09:36 +00002589
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002590 // ml_get() only keeps one line, need to get linep again
Bram Moolenaaraec11792007-07-10 11:09:36 +00002591 linep = ml_get(pos.lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002592 }
2593 }
2594 }
2595 if (start_in_quotes == MAYBE)
2596 start_in_quotes = FALSE;
2597
2598 /*
2599 * If 'smartmatch' is set:
2600 * Things inside quotes are ignored by setting 'inquote'. If we
2601 * find a quote without a preceding '\' invert 'inquote'. At the
2602 * end of a line not ending in '\' we reset 'inquote'.
2603 *
2604 * In lines with an uneven number of quotes (without preceding '\')
2605 * we do not know which part to ignore. Therefore we only set
2606 * inquote if the number of quotes in a line is even, unless this
2607 * line or the previous one ends in a '\'. Complicated, isn't it?
2608 */
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002609 c = PTR2CHAR(linep + pos.col);
2610 switch (c)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002611 {
2612 case NUL:
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002613 // at end of line without trailing backslash, reset inquote
Bram Moolenaar071d4272004-06-13 20:20:40 +00002614 if (pos.col == 0 || linep[pos.col - 1] != '\\')
2615 {
2616 inquote = FALSE;
2617 start_in_quotes = FALSE;
2618 }
2619 break;
2620
2621 case '"':
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002622 // a quote that is preceded with an odd number of backslashes is
2623 // ignored
Bram Moolenaar071d4272004-06-13 20:20:40 +00002624 if (do_quotes)
2625 {
2626 int col;
2627
2628 for (col = pos.col - 1; col >= 0; --col)
2629 if (linep[col] != '\\')
2630 break;
2631 if ((((int)pos.col - 1 - col) & 1) == 0)
2632 {
2633 inquote = !inquote;
2634 start_in_quotes = FALSE;
2635 }
2636 }
2637 break;
2638
2639 /*
2640 * If smart matching ('cpoptions' does not contain '%'):
2641 * Skip things in single quotes: 'x' or '\x'. Be careful for single
2642 * single quotes, eg jon's. Things like '\233' or '\x3f' are not
2643 * skipped, there is never a brace in them.
2644 * Ignore this when finding matches for `'.
2645 */
2646 case '\'':
2647 if (!cpo_match && initc != '\'' && findc != '\'')
2648 {
2649 if (backwards)
2650 {
2651 if (pos.col > 1)
2652 {
2653 if (linep[pos.col - 2] == '\'')
2654 {
2655 pos.col -= 2;
2656 break;
2657 }
2658 else if (linep[pos.col - 2] == '\\' &&
2659 pos.col > 2 && linep[pos.col - 3] == '\'')
2660 {
2661 pos.col -= 3;
2662 break;
2663 }
2664 }
2665 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002666 else if (linep[pos.col + 1]) // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002667 {
2668 if (linep[pos.col + 1] == '\\' &&
2669 linep[pos.col + 2] && linep[pos.col + 3] == '\'')
2670 {
2671 pos.col += 3;
2672 break;
2673 }
2674 else if (linep[pos.col + 2] == '\'')
2675 {
2676 pos.col += 2;
2677 break;
2678 }
2679 }
2680 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002681 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00002682
2683 default:
2684#ifdef FEAT_LISP
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002685 /*
2686 * For Lisp skip over backslashed (), {} and [].
2687 * (actually, we skip #\( et al)
2688 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002689 if (curbuf->b_p_lisp
2690 && vim_strchr((char_u *)"(){}[]", c) != NULL
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002691 && pos.col > 1
2692 && check_prevcol(linep, pos.col, '\\', NULL)
2693 && check_prevcol(linep, pos.col - 1, '#', NULL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002694 break;
2695#endif
2696
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002697 // Check for match outside of quotes, and inside of
2698 // quotes when the start is also inside of quotes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002699 if ((!inquote || start_in_quotes == TRUE)
2700 && (c == initc || c == findc))
2701 {
2702 int col, bslcnt = 0;
2703
2704 if (!cpo_bsl)
2705 {
2706 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2707 bslcnt++;
2708 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002709 // Only accept a match when 'M' is in 'cpo' or when escaping
2710 // is what we expect.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002711 if (cpo_bsl || (bslcnt & 1) == match_escaped)
2712 {
2713 if (c == initc)
2714 count++;
2715 else
2716 {
2717 if (count == 0)
2718 return &pos;
2719 count--;
2720 }
2721 }
2722 }
2723 }
2724 }
2725
2726 if (comment_dir == BACKWARD && count > 0)
2727 {
2728 pos = match_pos;
2729 return &pos;
2730 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002731 return (pos_T *)NULL; // never found it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002732}
2733
2734/*
2735 * Check if line[] contains a / / comment.
2736 * Return MAXCOL if not, otherwise return the column.
2737 * TODO: skip strings.
2738 */
2739 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002740check_linecomment(char_u *line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002741{
2742 char_u *p;
2743
2744 p = line;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002745#ifdef FEAT_LISP
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002746 // skip Lispish one-line comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002747 if (curbuf->b_p_lisp)
2748 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002749 if (vim_strchr(p, ';') != NULL) // there may be comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002750 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002751 int in_str = FALSE; // inside of string
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002752
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002753 p = line; // scan from start
Bram Moolenaar520470a2005-06-16 21:59:56 +00002754 while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002755 {
2756 if (*p == '"')
2757 {
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002758 if (in_str)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002759 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002760 if (*(p - 1) != '\\') // skip escaped quote
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002761 in_str = FALSE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002762 }
2763 else if (p == line || ((p - line) >= 2
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002764 // skip #\" form
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002765 && *(p - 1) != '\\' && *(p - 2) != '#'))
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002766 in_str = TRUE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002767 }
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002768 else if (!in_str && ((p - line) < 2
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002769 || (*(p - 1) != '\\' && *(p - 2) != '#')))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002770 break; // found!
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002771 ++p;
2772 }
2773 }
2774 else
2775 p = NULL;
2776 }
2777 else
2778#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002779 while ((p = vim_strchr(p, '/')) != NULL)
2780 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002781 // accept a double /, unless it's preceded with * and followed by *,
2782 // because * / / * is an end and start of a C comment
Bram Moolenaar78d4aba2008-01-01 14:43:35 +00002783 if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002784 break;
2785 ++p;
2786 }
2787
2788 if (p == NULL)
2789 return MAXCOL;
2790 return (int)(p - line);
2791}
2792
2793/*
2794 * Move cursor briefly to character matching the one under the cursor.
2795 * Used for Insert mode and "r" command.
2796 * Show the match only if it is visible on the screen.
2797 * If there isn't a match, then beep.
2798 */
2799 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002800showmatch(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002801 int c) // char to show match for
Bram Moolenaar071d4272004-06-13 20:20:40 +00002802{
2803 pos_T *lpos, save_cursor;
2804 pos_T mpos;
2805 colnr_T vcol;
2806 long save_so;
2807 long save_siso;
2808#ifdef CURSOR_SHAPE
2809 int save_state;
2810#endif
2811 colnr_T save_dollar_vcol;
2812 char_u *p;
Bram Moolenaar375e3392019-01-31 18:26:10 +01002813 long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
2814 long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002815
2816 /*
2817 * Only show match for chars in the 'matchpairs' option.
2818 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002819 // 'matchpairs' is "x:y,x:y"
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002820 for (p = curbuf->b_p_mps; *p != NUL; ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002821 {
2822#ifdef FEAT_RIGHTLEFT
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002823 if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002824 break;
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002825#endif
Bram Moolenaar1614a142019-10-06 22:00:13 +02002826 p += mb_ptr2len(p) + 1;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002827 if (PTR2CHAR(p) == c
Bram Moolenaar071d4272004-06-13 20:20:40 +00002828#ifdef FEAT_RIGHTLEFT
2829 && !(curwin->w_p_rl ^ p_ri)
2830#endif
2831 )
2832 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002833 p += mb_ptr2len(p);
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002834 if (*p == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002835 return;
2836 }
Bram Moolenaar5b8cabf2021-04-02 18:55:57 +02002837 if (*p == NUL)
2838 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002839
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002840 if ((lpos = findmatch(NULL, NUL)) == NULL) // no match, so beep
Bram Moolenaar165bc692015-07-21 17:53:25 +02002841 vim_beep(BO_MATCH);
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002842 else if (lpos->lnum >= curwin->w_topline && lpos->lnum < curwin->w_botline)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002843 {
2844 if (!curwin->w_p_wrap)
2845 getvcol(curwin, lpos, NULL, &vcol, NULL);
2846 if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol
Bram Moolenaar02631462017-09-22 15:20:32 +02002847 && vcol < curwin->w_leftcol + curwin->w_width))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002848 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002849 mpos = *lpos; // save the pos, update_screen() may change it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002850 save_cursor = curwin->w_cursor;
Bram Moolenaar375e3392019-01-31 18:26:10 +01002851 save_so = *so;
2852 save_siso = *siso;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002853 // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
2854 // stop displaying the "$".
Bram Moolenaar76b9b362012-02-04 23:35:00 +01002855 if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
2856 dollar_vcol = -1;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002857 ++curwin->w_virtcol; // do display ')' just before "$"
2858 update_screen(VALID); // show the new char first
Bram Moolenaar071d4272004-06-13 20:20:40 +00002859
2860 save_dollar_vcol = dollar_vcol;
2861#ifdef CURSOR_SHAPE
2862 save_state = State;
2863 State = SHOWMATCH;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002864 ui_cursor_shape(); // may show different cursor shape
Bram Moolenaar071d4272004-06-13 20:20:40 +00002865#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002866 curwin->w_cursor = mpos; // move to matching char
2867 *so = 0; // don't use 'scrolloff' here
2868 *siso = 0; // don't use 'sidescrolloff' here
Bram Moolenaar071d4272004-06-13 20:20:40 +00002869 showruler(FALSE);
2870 setcursor();
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002871 cursor_on(); // make sure that the cursor is shown
Bram Moolenaara338adc2018-01-31 20:51:47 +01002872 out_flush_cursor(TRUE, FALSE);
2873
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002874 // Restore dollar_vcol(), because setcursor() may call curs_rows()
2875 // which resets it if the matching position is in a previous line
2876 // and has a higher column number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002877 dollar_vcol = save_dollar_vcol;
2878
2879 /*
2880 * brief pause, unless 'm' is present in 'cpo' and a character is
2881 * available.
2882 */
2883 if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
Bram Moolenaareda1da02019-11-17 17:06:33 +01002884 ui_delay(p_mat * 100L + 8, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002885 else if (!char_avail())
Bram Moolenaareda1da02019-11-17 17:06:33 +01002886 ui_delay(p_mat * 100L + 9, FALSE);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002887 curwin->w_cursor = save_cursor; // restore cursor position
Bram Moolenaar375e3392019-01-31 18:26:10 +01002888 *so = save_so;
2889 *siso = save_siso;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002890#ifdef CURSOR_SHAPE
2891 State = save_state;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002892 ui_cursor_shape(); // may show different cursor shape
Bram Moolenaar071d4272004-06-13 20:20:40 +00002893#endif
2894 }
2895 }
2896}
2897
2898/*
Bram Moolenaar453c1922019-10-26 14:42:09 +02002899 * Check if the pattern is zero-width.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002900 * If move is TRUE, check from the beginning of the buffer, else from position
2901 * "cur".
2902 * "direction" is FORWARD or BACKWARD.
2903 * Returns TRUE, FALSE or -1 for failure.
2904 */
2905 static int
2906is_zero_width(char_u *pattern, int move, pos_T *cur, int direction)
2907{
2908 regmmatch_T regmatch;
2909 int nmatched = 0;
2910 int result = -1;
2911 pos_T pos;
Bram Moolenaar53989552019-12-23 22:59:18 +01002912 int called_emsg_before = called_emsg;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002913 int flag = 0;
2914
2915 if (pattern == NULL)
2916 pattern = spats[last_idx].pat;
2917
2918 if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH,
2919 SEARCH_KEEP, &regmatch) == FAIL)
2920 return -1;
2921
2922 // init startcol correctly
2923 regmatch.startpos[0].col = -1;
2924 // move to match
2925 if (move)
2926 {
2927 CLEAR_POS(&pos);
2928 }
2929 else
2930 {
2931 pos = *cur;
2932 // accept a match at the cursor position
2933 flag = SEARCH_START;
2934 }
2935
2936 if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
2937 SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL)
2938 {
2939 // Zero-width pattern should match somewhere, then we can check if
2940 // start and end are in the same position.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002941 do
2942 {
2943 regmatch.startpos[0].col++;
2944 nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
2945 pos.lnum, regmatch.startpos[0].col, NULL, NULL);
2946 if (nmatched != 0)
2947 break;
Bram Moolenaar795aaa12020-10-02 20:36:01 +02002948 } while (regmatch.regprog != NULL
2949 && direction == FORWARD ? regmatch.startpos[0].col < pos.col
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002950 : regmatch.startpos[0].col > pos.col);
2951
Bram Moolenaar53989552019-12-23 22:59:18 +01002952 if (called_emsg == called_emsg_before)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002953 {
2954 result = (nmatched != 0
2955 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
2956 && regmatch.startpos[0].col == regmatch.endpos[0].col);
2957 }
2958 }
2959
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002960 vim_regfree(regmatch.regprog);
2961 return result;
2962}
2963
Bram Moolenaardde0efe2012-08-23 15:53:05 +02002964
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002965/*
2966 * Find next search match under cursor, cursor at end.
2967 * Used while an operator is pending, and in Visual mode.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002968 */
2969 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002970current_search(
2971 long count,
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002972 int forward) // TRUE for forward, FALSE for backward
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002973{
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002974 pos_T start_pos; // start position of the pattern match
2975 pos_T end_pos; // end position of the pattern match
2976 pos_T orig_pos; // position of the cursor at beginning
2977 pos_T pos; // position after the pattern
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002978 int i;
2979 int dir;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002980 int result; // result of various function calls
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002981 char_u old_p_ws = p_ws;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002982 int flags = 0;
Bram Moolenaarde9149e2013-07-17 19:22:13 +02002983 pos_T save_VIsual = VIsual;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002984 int zero_width;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002985 int skip_first_backward;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002986
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002987 // Correct cursor when 'selection' is exclusive
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01002988 if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002989 dec_cursor();
2990
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002991 // When searching forward and the cursor is at the start of the Visual
2992 // area, skip the first search backward, otherwise it doesn't move.
2993 skip_first_backward = forward && VIsual_active
2994 && LT_POS(curwin->w_cursor, VIsual);
2995
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002996 orig_pos = pos = curwin->w_cursor;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002997 if (VIsual_active)
2998 {
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002999 if (forward)
3000 incl(&pos);
3001 else
3002 decl(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003003 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003004
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003005 // Is the pattern is zero-width?, this time, don't care about the direction
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003006 zero_width = is_zero_width(spats[last_idx].pat, TRUE, &curwin->w_cursor,
Bram Moolenaar22ab5472017-09-26 12:28:45 +02003007 FORWARD);
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003008 if (zero_width == -1)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003009 return FAIL; // pattern not found
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003010
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003011 /*
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003012 * The trick is to first search backwards and then search forward again,
3013 * so that a match at the current cursor position will be correctly
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003014 * captured. When "forward" is false do it the other way around.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003015 */
3016 for (i = 0; i < 2; i++)
3017 {
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003018 if (forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003019 {
3020 if (i == 0 && skip_first_backward)
3021 continue;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003022 dir = i;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003023 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003024 else
3025 dir = !i;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003026
3027 flags = 0;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003028 if (!dir && !zero_width)
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003029 flags = SEARCH_END;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003030 end_pos = pos;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003031
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01003032 // wrapping should not occur in the first round
3033 if (i == 0)
3034 p_ws = FALSE;
3035
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003036 result = searchit(curwin, curbuf, &pos, &end_pos,
3037 (dir ? FORWARD : BACKWARD),
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003038 spats[last_idx].pat, (long) (i ? count : 1),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02003039 SEARCH_KEEP | flags, RE_SEARCH, NULL);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003040
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01003041 p_ws = old_p_ws;
3042
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003043 // First search may fail, but then start searching from the
3044 // beginning of the file (cursor might be on the search match)
3045 // except when Visual mode is active, so that extending the visual
3046 // selection works.
3047 if (i == 1 && !result) // not found, abort
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003048 {
3049 curwin->w_cursor = orig_pos;
3050 if (VIsual_active)
3051 VIsual = save_VIsual;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003052 return FAIL;
3053 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003054 else if (i == 0 && !result)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003055 {
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003056 if (forward)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003057 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003058 // try again from start of buffer
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003059 CLEAR_POS(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003060 }
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003061 else
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003062 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003063 // try again from end of buffer
3064 // searching backwards, so set pos to last line and col
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003065 pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
Bram Moolenaar09168a72012-08-02 21:24:42 +02003066 pos.col = (colnr_T)STRLEN(
3067 ml_get(curwin->w_buffer->b_ml.ml_line_count));
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003068 }
3069 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003070 }
3071
3072 start_pos = pos;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003073
3074 if (!VIsual_active)
3075 VIsual = start_pos;
3076
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003077 // put the cursor after the match
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003078 curwin->w_cursor = end_pos;
Bram Moolenaar453c1922019-10-26 14:42:09 +02003079 if (LT_POS(VIsual, end_pos) && forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003080 {
3081 if (skip_first_backward)
3082 // put the cursor on the start of the match
3083 curwin->w_cursor = pos;
3084 else
3085 // put the cursor on last character of match
3086 dec_cursor();
3087 }
Bram Moolenaar28f224b2020-10-10 16:45:25 +02003088 else if (VIsual_active && LT_POS(curwin->w_cursor, VIsual) && forward)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003089 curwin->w_cursor = pos; // put the cursor on the start of the match
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003090 VIsual_active = TRUE;
3091 VIsual_mode = 'v';
3092
Bram Moolenaarb7633612019-02-10 21:48:25 +01003093 if (*p_sel == 'e')
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003094 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003095 // Correction for exclusive selection depends on the direction.
Bram Moolenaarb7633612019-02-10 21:48:25 +01003096 if (forward && LTOREQ_POS(VIsual, curwin->w_cursor))
3097 inc_cursor();
3098 else if (!forward && LTOREQ_POS(curwin->w_cursor, VIsual))
3099 inc(&VIsual);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003100 }
3101
3102#ifdef FEAT_FOLDING
3103 if (fdo_flags & FDO_SEARCH && KeyTyped)
3104 foldOpenCursor();
3105#endif
3106
3107 may_start_select('c');
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003108 setmouse();
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003109#ifdef FEAT_CLIPBOARD
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003110 // Make sure the clipboard gets updated. Needed because start and
3111 // end are still the same, and the selection needs to be owned
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003112 clip_star.vmode = NUL;
3113#endif
3114 redraw_curbuf_later(INVERTED);
3115 showmode();
3116
3117 return OK;
3118}
Bram Moolenaardde0efe2012-08-23 15:53:05 +02003119
Bram Moolenaar071d4272004-06-13 20:20:40 +00003120#if defined(FEAT_LISP) || defined(FEAT_CINDENT) || defined(FEAT_TEXTOBJ) \
3121 || defined(PROTO)
3122/*
3123 * return TRUE if line 'lnum' is empty or has white chars only.
3124 */
3125 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003126linewhite(linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003127{
3128 char_u *p;
3129
3130 p = skipwhite(ml_get(lnum));
3131 return (*p == NUL);
3132}
3133#endif
3134
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003135/*
3136 * Add the search count "[3/19]" to "msgbuf".
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003137 * See update_search_stat() for other arguments.
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003138 */
3139 static void
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003140cmdline_search_stat(
3141 int dirc,
3142 pos_T *pos,
3143 pos_T *cursor_pos,
3144 int show_top_bot_msg,
3145 char_u *msgbuf,
3146 int recompute,
3147 int maxcount,
3148 long timeout)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003149{
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003150 searchstat_T stat;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003151
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003152 update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
3153 timeout);
3154 if (stat.cur > 0)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003155 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003156 char t[SEARCH_STAT_BUF_LEN];
Bram Moolenaare2ad8262019-05-24 13:22:22 +02003157 size_t len;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003158
3159#ifdef FEAT_RIGHTLEFT
3160 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
3161 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003162 if (stat.incomplete == 1)
Bram Moolenaarb6cb26f2019-05-07 21:34:37 +02003163 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003164 else if (stat.cnt > maxcount && stat.cur > maxcount)
3165 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3166 maxcount, maxcount);
3167 else if (stat.cnt > maxcount)
3168 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
3169 maxcount, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003170 else
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003171 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3172 stat.cnt, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003173 }
3174 else
3175#endif
3176 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003177 if (stat.incomplete == 1)
Bram Moolenaarb6cb26f2019-05-07 21:34:37 +02003178 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003179 else if (stat.cnt > maxcount && stat.cur > maxcount)
3180 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3181 maxcount, maxcount);
3182 else if (stat.cnt > maxcount)
3183 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
3184 stat.cur, maxcount);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003185 else
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003186 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3187 stat.cur, stat.cnt);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003188 }
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003189
3190 len = STRLEN(t);
Bram Moolenaardc6855a2019-05-18 19:26:29 +02003191 if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003192 {
Bram Moolenaar16b58ae2019-09-06 20:40:21 +02003193 mch_memmove(t + 2, t, len);
3194 t[0] = 'W';
3195 t[1] = ' ';
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003196 len += 2;
3197 }
3198
3199 mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003200 if (dirc == '?' && stat.cur == maxcount + 1)
3201 stat.cur = -1;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003202
Bram Moolenaar984f0312019-05-24 13:11:47 +02003203 // keep the message even after redraw, but don't put in history
3204 msg_hist_off = TRUE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003205 give_warning(msgbuf, FALSE);
Bram Moolenaar984f0312019-05-24 13:11:47 +02003206 msg_hist_off = FALSE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003207 }
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003208}
3209
3210/*
3211 * Add the search count information to "stat".
3212 * "stat" must not be NULL.
3213 * When "recompute" is TRUE always recompute the numbers.
3214 * dirc == 0: don't find the next/previous match (only set the result to "stat")
3215 * dirc == '/': find the next match
3216 * dirc == '?': find the previous match
3217 */
3218 static void
3219update_search_stat(
3220 int dirc,
3221 pos_T *pos,
3222 pos_T *cursor_pos,
3223 searchstat_T *stat,
3224 int recompute,
3225 int maxcount,
Bram Moolenaarf9ca08e2020-06-01 18:56:03 +02003226 long timeout UNUSED)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003227{
3228 int save_ws = p_ws;
3229 int wraparound = FALSE;
3230 pos_T p = (*pos);
Bram Moolenaar14681622020-06-03 22:57:39 +02003231 static pos_T lastpos = {0, 0, 0};
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003232 static int cur = 0;
3233 static int cnt = 0;
3234 static int exact_match = FALSE;
3235 static int incomplete = 0;
3236 static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
3237 static int chgtick = 0;
3238 static char_u *lastpat = NULL;
3239 static buf_T *lbuf = NULL;
3240#ifdef FEAT_RELTIME
3241 proftime_T start;
3242#endif
3243
3244 vim_memset(stat, 0, sizeof(searchstat_T));
3245
3246 if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
3247 {
3248 stat->cur = cur;
3249 stat->cnt = cnt;
3250 stat->exact_match = exact_match;
3251 stat->incomplete = incomplete;
3252 stat->last_maxcount = last_maxcount;
3253 return;
3254 }
3255 last_maxcount = maxcount;
3256
3257 wraparound = ((dirc == '?' && LT_POS(lastpos, p))
3258 || (dirc == '/' && LT_POS(p, lastpos)));
3259
3260 // If anything relevant changed the count has to be recomputed.
3261 // MB_STRNICMP ignores case, but we should not ignore case.
3262 // Unfortunately, there is no MB_STRNICMP function.
3263 // XXX: above comment should be "no MB_STRCMP function" ?
3264 if (!(chgtick == CHANGEDTICK(curbuf)
3265 && MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
3266 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
3267 && EQUAL_POS(lastpos, *cursor_pos)
3268 && lbuf == curbuf) || wraparound || cur < 0
3269 || (maxcount > 0 && cur > maxcount) || recompute)
3270 {
3271 cur = 0;
3272 cnt = 0;
3273 exact_match = FALSE;
3274 incomplete = 0;
3275 CLEAR_POS(&lastpos);
3276 lbuf = curbuf;
3277 }
3278
3279 if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
3280 && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0))
3281 cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
3282 else
3283 {
3284 int done_search = FALSE;
3285 pos_T endpos = {0, 0, 0};
3286
3287 p_ws = FALSE;
3288#ifdef FEAT_RELTIME
3289 if (timeout > 0)
3290 profile_setlimit(timeout, &start);
3291#endif
3292 while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
3293 FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
3294 {
3295 done_search = TRUE;
3296#ifdef FEAT_RELTIME
3297 // Stop after passing the time limit.
3298 if (timeout > 0 && profile_passed_limit(&start))
3299 {
3300 incomplete = 1;
3301 break;
3302 }
3303#endif
3304 cnt++;
3305 if (LTOREQ_POS(lastpos, p))
3306 {
3307 cur = cnt;
Bram Moolenaar57f75a52020-06-02 22:06:21 +02003308 if (LT_POS(p, endpos))
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003309 exact_match = TRUE;
3310 }
3311 fast_breakcheck();
3312 if (maxcount > 0 && cnt > maxcount)
3313 {
3314 incomplete = 2; // max count exceeded
3315 break;
3316 }
3317 }
3318 if (got_int)
3319 cur = -1; // abort
3320 if (done_search)
3321 {
3322 vim_free(lastpat);
3323 lastpat = vim_strsave(spats[last_idx].pat);
3324 chgtick = CHANGEDTICK(curbuf);
3325 lbuf = curbuf;
3326 lastpos = p;
3327 }
3328 }
3329 stat->cur = cur;
3330 stat->cnt = cnt;
3331 stat->exact_match = exact_match;
3332 stat->incomplete = incomplete;
3333 stat->last_maxcount = last_maxcount;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003334 p_ws = save_ws;
3335}
3336
Bram Moolenaar071d4272004-06-13 20:20:40 +00003337#if defined(FEAT_FIND_ID) || defined(PROTO)
3338/*
3339 * Find identifiers or defines in included files.
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01003340 * If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003341 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00003342 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003343find_pattern_in_path(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003344 char_u *ptr, // pointer to search pattern
3345 int dir UNUSED, // direction of expansion
3346 int len, // length of search pattern
3347 int whole, // match whole words only
3348 int skip_comments, // don't match inside comments
3349 int type, // Type of search; are we looking for a type?
3350 // a macro?
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003351 long count,
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003352 int action, // What to do when we find it
3353 linenr_T start_lnum, // first line to start searching
3354 linenr_T end_lnum) // last line for searching
Bram Moolenaar071d4272004-06-13 20:20:40 +00003355{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003356 SearchedFile *files; // Stack of included files
3357 SearchedFile *bigger; // When we need more space
Bram Moolenaar071d4272004-06-13 20:20:40 +00003358 int max_path_depth = 50;
3359 long match_count = 1;
3360
3361 char_u *pat;
3362 char_u *new_fname;
3363 char_u *curr_fname = curbuf->b_fname;
3364 char_u *prev_fname = NULL;
3365 linenr_T lnum;
3366 int depth;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003367 int depth_displayed; // For type==CHECK_PATH
Bram Moolenaar071d4272004-06-13 20:20:40 +00003368 int old_files;
3369 int already_searched;
3370 char_u *file_line;
3371 char_u *line;
3372 char_u *p;
3373 char_u save_char;
3374 int define_matched;
3375 regmatch_T regmatch;
3376 regmatch_T incl_regmatch;
3377 regmatch_T def_regmatch;
3378 int matched = FALSE;
3379 int did_show = FALSE;
3380 int found = FALSE;
3381 int i;
3382 char_u *already = NULL;
3383 char_u *startp = NULL;
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003384 char_u *inc_opt = NULL;
Bram Moolenaar4033c552017-09-16 20:54:51 +02003385#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003386 win_T *curwin_save = NULL;
3387#endif
3388
3389 regmatch.regprog = NULL;
3390 incl_regmatch.regprog = NULL;
3391 def_regmatch.regprog = NULL;
3392
3393 file_line = alloc(LSIZE);
3394 if (file_line == NULL)
3395 return;
3396
Bram Moolenaar071d4272004-06-13 20:20:40 +00003397 if (type != CHECK_PATH && type != FIND_DEFINE
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003398 // when CONT_SOL is set compare "ptr" with the beginning of the line
3399 // is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003400 && !(compl_cont_status & CONT_SOL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003401 {
3402 pat = alloc(len + 5);
3403 if (pat == NULL)
3404 goto fpip_end;
3405 sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003406 // ignore case according to p_ic, p_scs and pat
Bram Moolenaar071d4272004-06-13 20:20:40 +00003407 regmatch.rm_ic = ignorecase(pat);
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003408 regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003409 vim_free(pat);
3410 if (regmatch.regprog == NULL)
3411 goto fpip_end;
3412 }
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003413 inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
3414 if (*inc_opt != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003415 {
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003416 incl_regmatch.regprog = vim_regcomp(inc_opt,
3417 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003418 if (incl_regmatch.regprog == NULL)
3419 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003420 incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003421 }
3422 if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL))
3423 {
3424 def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003425 ? p_def : curbuf->b_p_def,
3426 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003427 if (def_regmatch.regprog == NULL)
3428 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003429 def_regmatch.rm_ic = FALSE; // don't ignore case in define pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003430 }
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003431 files = lalloc_clear(max_path_depth * sizeof(SearchedFile), TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003432 if (files == NULL)
3433 goto fpip_end;
3434 old_files = max_path_depth;
3435 depth = depth_displayed = -1;
3436
3437 lnum = start_lnum;
3438 if (end_lnum > curbuf->b_ml.ml_line_count)
3439 end_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003440 if (lnum > end_lnum) // do at least one line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003441 lnum = end_lnum;
3442 line = ml_get(lnum);
3443
3444 for (;;)
3445 {
3446 if (incl_regmatch.regprog != NULL
3447 && vim_regexec(&incl_regmatch, line, (colnr_T)0))
3448 {
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003449 char_u *p_fname = (curr_fname == curbuf->b_fname)
3450 ? curbuf->b_ffname : curr_fname;
3451
3452 if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003453 // Use text from '\zs' to '\ze' (or end) of 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003454 new_fname = find_file_name_in_path(incl_regmatch.startp[0],
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003455 (int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]),
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003456 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname);
3457 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003458 // Use text after match with 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003459 new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
Bram Moolenaard1f56e62006-02-22 21:25:37 +00003460 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003461 already_searched = FALSE;
3462 if (new_fname != NULL)
3463 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003464 // Check whether we have already searched in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003465 for (i = 0;; i++)
3466 {
3467 if (i == depth + 1)
3468 i = old_files;
3469 if (i == max_path_depth)
3470 break;
Bram Moolenaar99499b12019-05-23 21:35:48 +02003471 if (fullpathcmp(new_fname, files[i].name, TRUE, TRUE)
3472 & FPC_SAME)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003473 {
3474 if (type != CHECK_PATH &&
3475 action == ACTION_SHOW_ALL && files[i].matched)
3476 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003477 msg_putchar('\n'); // cursor below last one
3478 if (!got_int) // don't display if 'q'
3479 // typed at "--more--"
3480 // message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003481 {
3482 msg_home_replace_hl(new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003483 msg_puts(_(" (includes previously listed match)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003484 prev_fname = NULL;
3485 }
3486 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01003487 VIM_CLEAR(new_fname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003488 already_searched = TRUE;
3489 break;
3490 }
3491 }
3492 }
3493
3494 if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
3495 || (new_fname == NULL && !already_searched)))
3496 {
3497 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003498 msg_putchar('\n'); // cursor below last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00003499 else
3500 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003501 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar32526b32019-01-19 17:43:09 +01003502 msg_puts_title(_("--- Included files "));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003503 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003504 msg_puts_title(_("not found "));
3505 msg_puts_title(_("in path ---\n"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003506 }
3507 did_show = TRUE;
3508 while (depth_displayed < depth && !got_int)
3509 {
3510 ++depth_displayed;
3511 for (i = 0; i < depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003512 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003513 msg_home_replace(files[depth_displayed].name);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003514 msg_puts(" -->\n");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003515 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003516 if (!got_int) // don't display if 'q' typed
3517 // for "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003518 {
3519 for (i = 0; i <= depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003520 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003521 if (new_fname != NULL)
3522 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003523 // using "new_fname" is more reliable, e.g., when
3524 // 'includeexpr' is set.
Bram Moolenaar8820b482017-03-16 17:23:31 +01003525 msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003526 }
3527 else
3528 {
3529 /*
3530 * Isolate the file name.
3531 * Include the surrounding "" or <> if present.
3532 */
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003533 if (inc_opt != NULL
3534 && strstr((char *)inc_opt, "\\zs") != NULL)
3535 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003536 // pattern contains \zs, use the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003537 p = incl_regmatch.startp[0];
3538 i = (int)(incl_regmatch.endp[0]
3539 - incl_regmatch.startp[0]);
3540 }
3541 else
3542 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003543 // find the file name after the end of the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003544 for (p = incl_regmatch.endp[0];
3545 *p && !vim_isfilec(*p); p++)
3546 ;
3547 for (i = 0; vim_isfilec(p[i]); i++)
3548 ;
3549 }
3550
Bram Moolenaar071d4272004-06-13 20:20:40 +00003551 if (i == 0)
3552 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003553 // Nothing found, use the rest of the line.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003554 p = incl_regmatch.endp[0];
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00003555 i = (int)STRLEN(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003556 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003557 // Avoid checking before the start of the line, can
3558 // happen if \zs appears in the regexp.
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003559 else if (p > line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003560 {
3561 if (p[-1] == '"' || p[-1] == '<')
3562 {
3563 --p;
3564 ++i;
3565 }
3566 if (p[i] == '"' || p[i] == '>')
3567 ++i;
3568 }
3569 save_char = p[i];
3570 p[i] = NUL;
Bram Moolenaar8820b482017-03-16 17:23:31 +01003571 msg_outtrans_attr(p, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003572 p[i] = save_char;
3573 }
3574
3575 if (new_fname == NULL && action == ACTION_SHOW_ALL)
3576 {
3577 if (already_searched)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003578 msg_puts(_(" (Already listed)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003579 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01003580 msg_puts(_(" NOT FOUND"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003581 }
3582 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003583 out_flush(); // output each line directly
Bram Moolenaar071d4272004-06-13 20:20:40 +00003584 }
3585
3586 if (new_fname != NULL)
3587 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003588 // Push the new file onto the file stack
Bram Moolenaar071d4272004-06-13 20:20:40 +00003589 if (depth + 1 == old_files)
3590 {
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003591 bigger = ALLOC_MULT(SearchedFile, max_path_depth * 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003592 if (bigger != NULL)
3593 {
3594 for (i = 0; i <= depth; i++)
3595 bigger[i] = files[i];
3596 for (i = depth + 1; i < old_files + max_path_depth; i++)
3597 {
3598 bigger[i].fp = NULL;
3599 bigger[i].name = NULL;
3600 bigger[i].lnum = 0;
3601 bigger[i].matched = FALSE;
3602 }
3603 for (i = old_files; i < max_path_depth; i++)
3604 bigger[i + max_path_depth] = files[i];
3605 old_files += max_path_depth;
3606 max_path_depth *= 2;
3607 vim_free(files);
3608 files = bigger;
3609 }
3610 }
3611 if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
3612 == NULL)
3613 vim_free(new_fname);
3614 else
3615 {
3616 if (++depth == old_files)
3617 {
3618 /*
3619 * lalloc() for 'bigger' must have failed above. We
3620 * will forget one of our already visited files now.
3621 */
3622 vim_free(files[old_files].name);
3623 ++old_files;
3624 }
3625 files[depth].name = curr_fname = new_fname;
3626 files[depth].lnum = 0;
3627 files[depth].matched = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003628 if (action == ACTION_EXPAND)
3629 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003630 msg_hist_off = TRUE; // reset in msg_trunc_attr()
Bram Moolenaar555b2802005-05-19 21:08:39 +00003631 vim_snprintf((char*)IObuff, IOSIZE,
3632 _("Scanning included file: %s"),
3633 (char *)new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003634 msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003635 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003636 else if (p_verbose >= 5)
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003637 {
3638 verbose_enter();
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003639 smsg(_("Searching included file %s"),
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003640 (char *)new_fname);
3641 verbose_leave();
3642 }
3643
Bram Moolenaar071d4272004-06-13 20:20:40 +00003644 }
3645 }
3646 }
3647 else
3648 {
3649 /*
3650 * Check if the line is a define (type == FIND_DEFINE)
3651 */
3652 p = line;
3653search_line:
3654 define_matched = FALSE;
3655 if (def_regmatch.regprog != NULL
3656 && vim_regexec(&def_regmatch, line, (colnr_T)0))
3657 {
3658 /*
3659 * Pattern must be first identifier after 'define', so skip
3660 * to that position before checking for match of pattern. Also
3661 * don't let it match beyond the end of this identifier.
3662 */
3663 p = def_regmatch.endp[0];
3664 while (*p && !vim_iswordc(*p))
3665 p++;
3666 define_matched = TRUE;
3667 }
3668
3669 /*
3670 * Look for a match. Don't do this if we are looking for a
3671 * define and this line didn't match define_prog above.
3672 */
3673 if (def_regmatch.regprog == NULL || define_matched)
3674 {
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003675 if (define_matched || (compl_cont_status & CONT_SOL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003676 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003677 // compare the first "len" chars from "ptr"
Bram Moolenaar071d4272004-06-13 20:20:40 +00003678 startp = skipwhite(p);
3679 if (p_ic)
3680 matched = !MB_STRNICMP(startp, ptr, len);
3681 else
3682 matched = !STRNCMP(startp, ptr, len);
3683 if (matched && define_matched && whole
3684 && vim_iswordc(startp[len]))
3685 matched = FALSE;
3686 }
3687 else if (regmatch.regprog != NULL
3688 && vim_regexec(&regmatch, line, (colnr_T)(p - line)))
3689 {
3690 matched = TRUE;
3691 startp = regmatch.startp[0];
3692 /*
3693 * Check if the line is not a comment line (unless we are
3694 * looking for a define). A line starting with "# define"
3695 * is not considered to be a comment line.
3696 */
3697 if (!define_matched && skip_comments)
3698 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003699 if ((*line != '#' ||
3700 STRNCMP(skipwhite(line + 1), "define", 6) != 0)
Bram Moolenaar81340392012-06-06 16:12:59 +02003701 && get_leader_len(line, NULL, FALSE, TRUE))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003702 matched = FALSE;
3703
3704 /*
3705 * Also check for a "/ *" or "/ /" before the match.
3706 * Skips lines like "int backwards; / * normal index
3707 * * /" when looking for "normal".
3708 * Note: Doesn't skip "/ *" in comments.
3709 */
3710 p = skipwhite(line);
3711 if (matched
3712 || (p[0] == '/' && p[1] == '*') || p[0] == '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00003713 for (p = line; *p && p < startp; ++p)
3714 {
3715 if (matched
3716 && p[0] == '/'
3717 && (p[1] == '*' || p[1] == '/'))
3718 {
3719 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003720 // After "//" all text is comment
Bram Moolenaar071d4272004-06-13 20:20:40 +00003721 if (p[1] == '/')
3722 break;
3723 ++p;
3724 }
3725 else if (!matched && p[0] == '*' && p[1] == '/')
3726 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003727 // Can find match after "* /".
Bram Moolenaar071d4272004-06-13 20:20:40 +00003728 matched = TRUE;
3729 ++p;
3730 }
3731 }
3732 }
3733 }
3734 }
3735 }
3736 if (matched)
3737 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003738 if (action == ACTION_EXPAND)
3739 {
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003740 int cont_s_ipos = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003741 int add_r;
3742 char_u *aux;
3743
3744 if (depth == -1 && lnum == curwin->w_cursor.lnum)
3745 break;
3746 found = TRUE;
3747 aux = p = startp;
Bram Moolenaar4be06f92005-07-29 22:36:03 +00003748 if (compl_cont_status & CONT_ADDING)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003749 {
Bram Moolenaar4be06f92005-07-29 22:36:03 +00003750 p += compl_length;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003751 if (vim_iswordp(p))
3752 goto exit_matched;
3753 p = find_word_start(p);
3754 }
3755 p = find_word_end(p);
3756 i = (int)(p - aux);
3757
Bram Moolenaar4be06f92005-07-29 22:36:03 +00003758 if ((compl_cont_status & CONT_ADDING) && i == compl_length)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003759 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003760 // IOSIZE > compl_length, so the STRNCPY works
Bram Moolenaar071d4272004-06-13 20:20:40 +00003761 STRNCPY(IObuff, aux, i);
Bram Moolenaar89d40322006-08-29 15:30:07 +00003762
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003763 // Get the next line: when "depth" < 0 from the current
3764 // buffer, otherwise from the included file. Jump to
3765 // exit_matched when past the last line.
Bram Moolenaar89d40322006-08-29 15:30:07 +00003766 if (depth < 0)
3767 {
3768 if (lnum >= end_lnum)
3769 goto exit_matched;
3770 line = ml_get(++lnum);
3771 }
3772 else if (vim_fgets(line = file_line,
3773 LSIZE, files[depth].fp))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003774 goto exit_matched;
3775
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003776 // we read a line, set "already" to check this "line" later
3777 // if depth >= 0 we'll increase files[depth].lnum far
Bram Moolenaar8e7d6222020-12-18 19:49:56 +01003778 // below -- Acevedo
Bram Moolenaar071d4272004-06-13 20:20:40 +00003779 already = aux = p = skipwhite(line);
3780 p = find_word_start(p);
3781 p = find_word_end(p);
3782 if (p > aux)
3783 {
3784 if (*aux != ')' && IObuff[i-1] != TAB)
3785 {
3786 if (IObuff[i-1] != ' ')
3787 IObuff[i++] = ' ';
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003788 // IObuf =~ "\(\k\|\i\).* ", thus i >= 2
Bram Moolenaar071d4272004-06-13 20:20:40 +00003789 if (p_js
3790 && (IObuff[i-2] == '.'
3791 || (vim_strchr(p_cpo, CPO_JOINSP) == NULL
3792 && (IObuff[i-2] == '?'
3793 || IObuff[i-2] == '!'))))
3794 IObuff[i++] = ' ';
3795 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003796 // copy as much as possible of the new word
Bram Moolenaar071d4272004-06-13 20:20:40 +00003797 if (p - aux >= IOSIZE - i)
3798 p = aux + IOSIZE - i - 1;
3799 STRNCPY(IObuff + i, aux, p - aux);
3800 i += (int)(p - aux);
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003801 cont_s_ipos = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003802 }
3803 IObuff[i] = NUL;
3804 aux = IObuff;
3805
Bram Moolenaar4be06f92005-07-29 22:36:03 +00003806 if (i == compl_length)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003807 goto exit_matched;
3808 }
3809
Bram Moolenaare8c3a142006-08-29 14:30:35 +00003810 add_r = ins_compl_add_infercase(aux, i, p_ic,
Bram Moolenaar071d4272004-06-13 20:20:40 +00003811 curr_fname == curbuf->b_fname ? NULL : curr_fname,
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003812 dir, cont_s_ipos);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003813 if (add_r == OK)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003814 // if dir was BACKWARD then honor it just once
Bram Moolenaar071d4272004-06-13 20:20:40 +00003815 dir = FORWARD;
Bram Moolenaar572cb562005-08-05 21:35:02 +00003816 else if (add_r == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003817 break;
3818 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003819 else if (action == ACTION_SHOW_ALL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003820 {
3821 found = TRUE;
3822 if (!did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003823 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003824 if (curr_fname != prev_fname)
3825 {
3826 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003827 msg_putchar('\n'); // cursor below last one
3828 if (!got_int) // don't display if 'q' typed
3829 // at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003830 msg_home_replace_hl(curr_fname);
3831 prev_fname = curr_fname;
3832 }
3833 did_show = TRUE;
3834 if (!got_int)
3835 show_pat_in_path(line, type, TRUE, action,
3836 (depth == -1) ? NULL : files[depth].fp,
3837 (depth == -1) ? &lnum : &files[depth].lnum,
3838 match_count++);
3839
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003840 // Set matched flag for this file and all the ones that
3841 // include it
Bram Moolenaar071d4272004-06-13 20:20:40 +00003842 for (i = 0; i <= depth; ++i)
3843 files[i].matched = TRUE;
3844 }
3845 else if (--count <= 0)
3846 {
3847 found = TRUE;
3848 if (depth == -1 && lnum == curwin->w_cursor.lnum
Bram Moolenaar4033c552017-09-16 20:54:51 +02003849#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003850 && g_do_tagpreview == 0
3851#endif
3852 )
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003853 emsg(_("E387: Match is on current line"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003854 else if (action == ACTION_SHOW)
3855 {
3856 show_pat_in_path(line, type, did_show, action,
3857 (depth == -1) ? NULL : files[depth].fp,
3858 (depth == -1) ? &lnum : &files[depth].lnum, 1L);
3859 did_show = TRUE;
3860 }
3861 else
3862 {
3863#ifdef FEAT_GUI
3864 need_mouse_correct = TRUE;
3865#endif
Bram Moolenaar4033c552017-09-16 20:54:51 +02003866#if defined(FEAT_QUICKFIX)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003867 // ":psearch" uses the preview window
Bram Moolenaar071d4272004-06-13 20:20:40 +00003868 if (g_do_tagpreview != 0)
3869 {
3870 curwin_save = curwin;
Bram Moolenaar576a4a62019-08-18 15:25:17 +02003871 prepare_tagpreview(TRUE, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003872 }
3873#endif
3874 if (action == ACTION_SPLIT)
3875 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003876 if (win_split(0, 0) == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003877 break;
Bram Moolenaar3368ea22010-09-21 16:56:35 +02003878 RESET_BINDING(curwin);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003879 }
3880 if (depth == -1)
3881 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003882 // match in current file
Bram Moolenaar4033c552017-09-16 20:54:51 +02003883#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003884 if (g_do_tagpreview != 0)
3885 {
Bram Moolenaar92bb83e2021-02-03 23:04:46 +01003886 if (!win_valid(curwin_save))
3887 break;
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003888 if (!GETFILE_SUCCESS(getfile(
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003889 curwin_save->w_buffer->b_fnum, NULL,
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003890 NULL, TRUE, lnum, FALSE)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003891 break; // failed to jump to file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003892 }
3893 else
3894#endif
3895 setpcmark();
3896 curwin->w_cursor.lnum = lnum;
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003897 check_cursor();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003898 }
3899 else
3900 {
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003901 if (!GETFILE_SUCCESS(getfile(
3902 0, files[depth].name, NULL, TRUE,
3903 files[depth].lnum, FALSE)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003904 break; // failed to jump to file
3905 // autocommands may have changed the lnum, we don't
3906 // want that here
Bram Moolenaar071d4272004-06-13 20:20:40 +00003907 curwin->w_cursor.lnum = files[depth].lnum;
3908 }
3909 }
3910 if (action != ACTION_SHOW)
3911 {
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003912 curwin->w_cursor.col = (colnr_T)(startp - line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003913 curwin->w_set_curswant = TRUE;
3914 }
3915
Bram Moolenaar4033c552017-09-16 20:54:51 +02003916#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003917 if (g_do_tagpreview != 0
Bram Moolenaar997fb4b2006-02-17 21:53:23 +00003918 && curwin != curwin_save && win_valid(curwin_save))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003919 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003920 // Return cursor to where we were
Bram Moolenaar071d4272004-06-13 20:20:40 +00003921 validate_cursor();
3922 redraw_later(VALID);
3923 win_enter(curwin_save, TRUE);
3924 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01003925# ifdef FEAT_PROP_POPUP
Bram Moolenaar1b6d9c42019-08-05 21:52:04 +02003926 else if (WIN_IS_POPUP(curwin))
3927 // can't keep focus in popup window
3928 win_enter(firstwin, TRUE);
3929# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003930#endif
3931 break;
3932 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003933exit_matched:
Bram Moolenaar071d4272004-06-13 20:20:40 +00003934 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003935 // look for other matches in the rest of the line if we
3936 // are not at the end of it already
Bram Moolenaar071d4272004-06-13 20:20:40 +00003937 if (def_regmatch.regprog == NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00003938 && action == ACTION_EXPAND
Bram Moolenaar4be06f92005-07-29 22:36:03 +00003939 && !(compl_cont_status & CONT_SOL)
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003940 && *startp != NUL
Bram Moolenaar1614a142019-10-06 22:00:13 +02003941 && *(p = startp + mb_ptr2len(startp)) != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003942 goto search_line;
3943 }
3944 line_breakcheck();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003945 if (action == ACTION_EXPAND)
Bram Moolenaar472e8592016-10-15 17:06:47 +02003946 ins_compl_check_keys(30, FALSE);
Bram Moolenaar7591bb32019-03-30 13:53:47 +01003947 if (got_int || ins_compl_interrupted())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003948 break;
3949
3950 /*
3951 * Read the next line. When reading an included file and encountering
3952 * end-of-file, close the file and continue in the file that included
3953 * it.
3954 */
3955 while (depth >= 0 && !already
3956 && vim_fgets(line = file_line, LSIZE, files[depth].fp))
3957 {
3958 fclose(files[depth].fp);
3959 --old_files;
3960 files[old_files].name = files[depth].name;
3961 files[old_files].matched = files[depth].matched;
3962 --depth;
3963 curr_fname = (depth == -1) ? curbuf->b_fname
3964 : files[depth].name;
3965 if (depth < depth_displayed)
3966 depth_displayed = depth;
3967 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003968 if (depth >= 0) // we could read the line
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003969 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003970 files[depth].lnum++;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003971 // Remove any CR and LF from the line.
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003972 i = (int)STRLEN(line);
3973 if (i > 0 && line[i - 1] == '\n')
3974 line[--i] = NUL;
3975 if (i > 0 && line[i - 1] == '\r')
3976 line[--i] = NUL;
3977 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003978 else if (!already)
3979 {
3980 if (++lnum > end_lnum)
3981 break;
3982 line = ml_get(lnum);
3983 }
3984 already = NULL;
3985 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003986 // End of big for (;;) loop.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003987
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003988 // Close any files that are still open.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003989 for (i = 0; i <= depth; i++)
3990 {
3991 fclose(files[i].fp);
3992 vim_free(files[i].name);
3993 }
3994 for (i = old_files; i < max_path_depth; i++)
3995 vim_free(files[i].name);
3996 vim_free(files);
3997
3998 if (type == CHECK_PATH)
3999 {
4000 if (!did_show)
4001 {
4002 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01004003 msg(_("All included files were found"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004004 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01004005 msg(_("No included files"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004006 }
4007 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02004008 else if (!found && action != ACTION_EXPAND)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004009 {
Bram Moolenaar7591bb32019-03-30 13:53:47 +01004010 if (got_int || ins_compl_interrupted())
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004011 emsg(_(e_interr));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004012 else if (type == FIND_DEFINE)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004013 emsg(_("E388: Couldn't find definition"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004014 else
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004015 emsg(_("E389: Couldn't find pattern"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004016 }
4017 if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
4018 msg_end();
4019
4020fpip_end:
4021 vim_free(file_line);
Bram Moolenaar473de612013-06-08 18:19:48 +02004022 vim_regfree(regmatch.regprog);
4023 vim_regfree(incl_regmatch.regprog);
4024 vim_regfree(def_regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004025}
4026
4027 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01004028show_pat_in_path(
4029 char_u *line,
4030 int type,
4031 int did_show,
4032 int action,
4033 FILE *fp,
4034 linenr_T *lnum,
4035 long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004036{
4037 char_u *p;
4038
4039 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004040 msg_putchar('\n'); // cursor below last one
Bram Moolenaar91170f82006-05-05 21:15:17 +00004041 else if (!msg_silent)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004042 gotocmdline(TRUE); // cursor at status line
4043 if (got_int) // 'q' typed at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00004044 return;
4045 for (;;)
4046 {
4047 p = line + STRLEN(line) - 1;
4048 if (fp != NULL)
4049 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004050 // We used fgets(), so get rid of newline at end
Bram Moolenaar071d4272004-06-13 20:20:40 +00004051 if (p >= line && *p == '\n')
4052 --p;
4053 if (p >= line && *p == '\r')
4054 --p;
4055 *(p + 1) = NUL;
4056 }
4057 if (action == ACTION_SHOW_ALL)
4058 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004059 sprintf((char *)IObuff, "%3ld: ", count); // show match nr
Bram Moolenaar32526b32019-01-19 17:43:09 +01004060 msg_puts((char *)IObuff);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004061 sprintf((char *)IObuff, "%4ld", *lnum); // show line nr
4062 // Highlight line numbers
Bram Moolenaar32526b32019-01-19 17:43:09 +01004063 msg_puts_attr((char *)IObuff, HL_ATTR(HLF_N));
4064 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00004065 }
Bram Moolenaar26a60b42005-02-22 08:49:11 +00004066 msg_prt_line(line, FALSE);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004067 out_flush(); // show one line at a time
Bram Moolenaar071d4272004-06-13 20:20:40 +00004068
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004069 // Definition continues until line that doesn't end with '\'
Bram Moolenaar071d4272004-06-13 20:20:40 +00004070 if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
4071 break;
4072
4073 if (fp != NULL)
4074 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004075 if (vim_fgets(line, LSIZE, fp)) // end of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00004076 break;
4077 ++*lnum;
4078 }
4079 else
4080 {
4081 if (++*lnum > curbuf->b_ml.ml_line_count)
4082 break;
4083 line = ml_get(*lnum);
4084 }
4085 msg_putchar('\n');
4086 }
4087}
4088#endif
4089
4090#ifdef FEAT_VIMINFO
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004091/*
4092 * Return the last used search pattern at "idx".
4093 */
Bram Moolenaarc3328162019-07-23 22:15:25 +02004094 spat_T *
4095get_spat(int idx)
4096{
4097 return &spats[idx];
4098}
4099
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004100/*
4101 * Return the last used search pattern index.
4102 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004103 int
Bram Moolenaarc3328162019-07-23 22:15:25 +02004104get_spat_last_idx(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004105{
Bram Moolenaarc3328162019-07-23 22:15:25 +02004106 return last_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004107}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004108#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004109
4110#ifdef FEAT_EVAL
4111/*
4112 * "searchcount()" function
4113 */
4114 void
4115f_searchcount(typval_T *argvars, typval_T *rettv)
4116{
4117 pos_T pos = curwin->w_cursor;
4118 char_u *pattern = NULL;
4119 int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
4120 long timeout = SEARCH_STAT_DEF_TIMEOUT;
Bram Moolenaar4140c4f2020-09-05 23:16:00 +02004121 int recompute = TRUE;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004122 searchstat_T stat;
4123
4124 if (rettv_dict_alloc(rettv) == FAIL)
4125 return;
4126
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02004127 if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
4128 return;
4129
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004130 if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
4131 recompute = TRUE;
4132
4133 if (argvars[0].v_type != VAR_UNKNOWN)
4134 {
Bram Moolenaar14681622020-06-03 22:57:39 +02004135 dict_T *dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004136 dictitem_T *di;
4137 listitem_T *li;
4138 int error = FALSE;
4139
Bram Moolenaar14681622020-06-03 22:57:39 +02004140 if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL)
4141 {
4142 emsg(_(e_dictreq));
4143 return;
4144 }
4145 dict = argvars[0].vval.v_dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004146 di = dict_find(dict, (char_u *)"timeout", -1);
4147 if (di != NULL)
4148 {
4149 timeout = (long)tv_get_number_chk(&di->di_tv, &error);
4150 if (error)
4151 return;
4152 }
4153 di = dict_find(dict, (char_u *)"maxcount", -1);
4154 if (di != NULL)
4155 {
4156 maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
4157 if (error)
4158 return;
4159 }
Bram Moolenaar597aaac2020-09-05 21:21:16 +02004160 recompute = dict_get_bool(dict, (char_u *)"recompute", recompute);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004161 di = dict_find(dict, (char_u *)"pattern", -1);
4162 if (di != NULL)
4163 {
4164 pattern = tv_get_string_chk(&di->di_tv);
4165 if (pattern == NULL)
4166 return;
4167 }
4168 di = dict_find(dict, (char_u *)"pos", -1);
4169 if (di != NULL)
4170 {
4171 if (di->di_tv.v_type != VAR_LIST)
4172 {
4173 semsg(_(e_invarg2), "pos");
4174 return;
4175 }
4176 if (list_len(di->di_tv.vval.v_list) != 3)
4177 {
4178 semsg(_(e_invarg2), "List format should be [lnum, col, off]");
4179 return;
4180 }
4181 li = list_find(di->di_tv.vval.v_list, 0L);
4182 if (li != NULL)
4183 {
4184 pos.lnum = tv_get_number_chk(&li->li_tv, &error);
4185 if (error)
4186 return;
4187 }
4188 li = list_find(di->di_tv.vval.v_list, 1L);
4189 if (li != NULL)
4190 {
4191 pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
4192 if (error)
4193 return;
4194 }
4195 li = list_find(di->di_tv.vval.v_list, 2L);
4196 if (li != NULL)
4197 {
4198 pos.coladd = tv_get_number_chk(&li->li_tv, &error);
4199 if (error)
4200 return;
4201 }
4202 }
4203 }
4204
4205 save_last_search_pattern();
4206 if (pattern != NULL)
4207 {
4208 if (*pattern == NUL)
4209 goto the_end;
Bram Moolenaar109aece2020-06-01 19:08:54 +02004210 vim_free(spats[last_idx].pat);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004211 spats[last_idx].pat = vim_strsave(pattern);
4212 }
4213 if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
4214 goto the_end; // the previous pattern was never defined
4215
4216 update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
4217
4218 dict_add_number(rettv->vval.v_dict, "current", stat.cur);
4219 dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
4220 dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
4221 dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
4222 dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
4223
4224the_end:
4225 restore_last_search_pattern();
4226}
Bram Moolenaar635414d2020-09-11 22:25:15 +02004227
4228/*
4229 * Fuzzy string matching
4230 *
4231 * Ported from the lib_fts library authored by Forrest Smith.
4232 * https://github.com/forrestthewoods/lib_fts/tree/master/code
4233 *
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004234 * The following blog describes the fuzzy matching algorithm:
Bram Moolenaar635414d2020-09-11 22:25:15 +02004235 * https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
4236 *
4237 * Each matching string is assigned a score. The following factors are checked:
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004238 * - Matched letter
4239 * - Unmatched letter
4240 * - Consecutively matched letters
4241 * - Proximity to start
4242 * - Letter following a separator (space, underscore)
4243 * - Uppercase letter following lowercase (aka CamelCase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004244 *
4245 * Matched letters are good. Unmatched letters are bad. Matching near the start
4246 * is good. Matching the first letter in the middle of a phrase is good.
4247 * Matching the uppercase letters in camel case entries is good.
4248 *
4249 * The score assigned for each factor is explained below.
4250 * File paths are different from file names. File extensions may be ignorable.
4251 * Single words care about consecutive matches but not separators or camel
4252 * case.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004253 * Score starts at 100
Bram Moolenaar635414d2020-09-11 22:25:15 +02004254 * Matched letter: +0 points
4255 * Unmatched letter: -1 point
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004256 * Consecutive match bonus: +15 points
4257 * First letter bonus: +15 points
4258 * Separator bonus: +30 points
4259 * Camel case bonus: +30 points
4260 * Unmatched leading letter: -5 points (max: -15)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004261 *
4262 * There is some nuance to this. Scores don’t have an intrinsic meaning. The
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004263 * score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
Bram Moolenaar635414d2020-09-11 22:25:15 +02004264 * lower minimum score due to unmatched letter penalty. Longer search patterns
4265 * have a higher maximum score due to match bonuses.
4266 *
4267 * Separator and camel case bonus is worth a LOT. Consecutive matches are worth
4268 * quite a bit.
4269 *
4270 * There is a penalty if you DON’T match the first three letters. Which
4271 * effectively rewards matching near the start. However there’s no difference
4272 * in matching between the middle and end.
4273 *
4274 * There is not an explicit bonus for an exact match. Unmatched letters receive
4275 * a penalty. So shorter strings and closer matches are worth more.
4276 */
4277typedef struct
4278{
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004279 int idx; // used for stable sort
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004280 listitem_T *item;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004281 int score;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004282 list_T *lmatchpos;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004283} fuzzyItem_T;
4284
Bram Moolenaare9f9f162020-10-20 19:01:30 +02004285// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
4286// matching a whole word is preferred.
4287#define SEQUENTIAL_BONUS 40
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004288// bonus if match occurs after a path separator
4289#define PATH_SEPARATOR_BONUS 30
4290// bonus if match occurs after a word separator
4291#define WORD_SEPARATOR_BONUS 25
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004292// bonus if match is uppercase and prev is lower
4293#define CAMEL_BONUS 30
4294// bonus if the first letter is matched
4295#define FIRST_LETTER_BONUS 15
4296// penalty applied for every letter in str before the first match
4297#define LEADING_LETTER_PENALTY -5
4298// maximum penalty for leading letters
4299#define MAX_LEADING_LETTER_PENALTY -15
4300// penalty for every letter that doesn't match
4301#define UNMATCHED_LETTER_PENALTY -1
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004302// penalty for gap in matching positions (-2 * k)
4303#define GAP_PENALTY -2
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004304// Score for a string that doesn't fuzzy match the pattern
4305#define SCORE_NONE -9999
4306
4307#define FUZZY_MATCH_RECURSION_LIMIT 10
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004308
4309/*
4310 * Compute a score for a fuzzy matched string. The matching character locations
4311 * are in 'matches'.
4312 */
4313 static int
4314fuzzy_match_compute_score(
4315 char_u *str,
4316 int strSz,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004317 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004318 int numMatches)
4319{
4320 int score;
4321 int penalty;
4322 int unmatched;
4323 int i;
4324 char_u *p = str;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004325 int_u sidx = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004326
4327 // Initialize score
4328 score = 100;
4329
4330 // Apply leading letter penalty
4331 penalty = LEADING_LETTER_PENALTY * matches[0];
4332 if (penalty < MAX_LEADING_LETTER_PENALTY)
4333 penalty = MAX_LEADING_LETTER_PENALTY;
4334 score += penalty;
4335
4336 // Apply unmatched penalty
4337 unmatched = strSz - numMatches;
4338 score += UNMATCHED_LETTER_PENALTY * unmatched;
4339
4340 // Apply ordering bonuses
4341 for (i = 0; i < numMatches; ++i)
4342 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004343 int_u currIdx = matches[i];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004344
4345 if (i > 0)
4346 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004347 int_u prevIdx = matches[i - 1];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004348
4349 // Sequential
4350 if (currIdx == (prevIdx + 1))
4351 score += SEQUENTIAL_BONUS;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004352 else
4353 score += GAP_PENALTY * (currIdx - prevIdx);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004354 }
4355
4356 // Check for bonuses based on neighbor character value
4357 if (currIdx > 0)
4358 {
4359 // Camel case
Bram Moolenaarc53e9c52020-09-22 22:08:32 +02004360 int neighbor = ' ';
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004361 int curr;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004362
4363 if (has_mbyte)
4364 {
4365 while (sidx < currIdx)
4366 {
4367 neighbor = (*mb_ptr2char)(p);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004368 MB_PTR_ADV(p);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004369 sidx++;
4370 }
4371 curr = (*mb_ptr2char)(p);
4372 }
4373 else
4374 {
4375 neighbor = str[currIdx - 1];
4376 curr = str[currIdx];
4377 }
4378
4379 if (vim_islower(neighbor) && vim_isupper(curr))
4380 score += CAMEL_BONUS;
4381
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004382 // Bonus if the match follows a separator character
4383 if (neighbor == '/' || neighbor == '\\')
4384 score += PATH_SEPARATOR_BONUS;
4385 else if (neighbor == ' ' || neighbor == '_')
4386 score += WORD_SEPARATOR_BONUS;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004387 }
4388 else
4389 {
4390 // First letter
4391 score += FIRST_LETTER_BONUS;
4392 }
4393 }
4394 return score;
4395}
4396
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004397/*
4398 * Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
4399 * Return the number of matching characters.
4400 */
Bram Moolenaar635414d2020-09-11 22:25:15 +02004401 static int
4402fuzzy_match_recursive(
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004403 char_u *fuzpat,
4404 char_u *str,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004405 int_u strIdx,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004406 int *outScore,
4407 char_u *strBegin,
4408 int strLen,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004409 int_u *srcMatches,
4410 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004411 int maxMatches,
4412 int nextMatch,
4413 int *recursionCount)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004414{
4415 // Recursion params
4416 int recursiveMatch = FALSE;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004417 int_u bestRecursiveMatches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004418 int bestRecursiveScore = 0;
4419 int first_match;
4420 int matched;
4421
4422 // Count recursions
4423 ++*recursionCount;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004424 if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004425 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004426
4427 // Detect end of strings
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004428 if (*fuzpat == NUL || *str == NUL)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004429 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004430
4431 // Loop through fuzpat and str looking for a match
4432 first_match = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004433 while (*fuzpat != NUL && *str != NUL)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004434 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004435 int c1;
4436 int c2;
4437
4438 c1 = PTR2CHAR(fuzpat);
4439 c2 = PTR2CHAR(str);
4440
Bram Moolenaar635414d2020-09-11 22:25:15 +02004441 // Found match
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004442 if (vim_tolower(c1) == vim_tolower(c2))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004443 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004444 int_u recursiveMatches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004445 int recursiveScore = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004446 char_u *next_char;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004447
4448 // Supplied matches buffer was too short
4449 if (nextMatch >= maxMatches)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004450 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004451
4452 // "Copy-on-Write" srcMatches into matches
4453 if (first_match && srcMatches)
4454 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004455 memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004456 first_match = FALSE;
4457 }
4458
4459 // Recursive call that "skips" this match
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004460 if (has_mbyte)
4461 next_char = str + (*mb_ptr2len)(str);
4462 else
4463 next_char = str + 1;
4464 if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
4465 &recursiveScore, strBegin, strLen, matches,
4466 recursiveMatches,
K.Takataeeec2542021-06-02 13:28:16 +02004467 ARRAY_LENGTH(recursiveMatches),
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004468 nextMatch, recursionCount))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004469 {
4470 // Pick best recursive score
4471 if (!recursiveMatch || recursiveScore > bestRecursiveScore)
4472 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004473 memcpy(bestRecursiveMatches, recursiveMatches,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004474 MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004475 bestRecursiveScore = recursiveScore;
4476 }
4477 recursiveMatch = TRUE;
4478 }
4479
4480 // Advance
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004481 matches[nextMatch++] = strIdx;
4482 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004483 MB_PTR_ADV(fuzpat);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004484 else
4485 ++fuzpat;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004486 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004487 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004488 MB_PTR_ADV(str);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004489 else
4490 ++str;
4491 strIdx++;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004492 }
4493
4494 // Determine if full fuzpat was matched
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004495 matched = *fuzpat == NUL ? TRUE : FALSE;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004496
4497 // Calculate score
4498 if (matched)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004499 *outScore = fuzzy_match_compute_score(strBegin, strLen, matches,
4500 nextMatch);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004501
4502 // Return best result
4503 if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
4504 {
4505 // Recursive score is better than "this"
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004506 memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004507 *outScore = bestRecursiveScore;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004508 return nextMatch;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004509 }
4510 else if (matched)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004511 return nextMatch; // "this" score is better than recursive
Bram Moolenaar635414d2020-09-11 22:25:15 +02004512
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004513 return 0; // no match
Bram Moolenaar635414d2020-09-11 22:25:15 +02004514}
4515
4516/*
4517 * fuzzy_match()
4518 *
4519 * Performs exhaustive search via recursion to find all possible matches and
4520 * match with highest score.
4521 * Scores values have no intrinsic meaning. Possible score range is not
4522 * normalized and varies with pattern.
4523 * Recursion is limited internally (default=10) to prevent degenerate cases
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004524 * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004525 * Uses char_u for match indices. Therefore patterns are limited to
4526 * MAX_FUZZY_MATCHES characters.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004527 *
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004528 * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004529 * 'outScore' and the matching character positions in 'matches'.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004530 */
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004531 int
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004532fuzzy_match(
4533 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004534 char_u *pat_arg,
4535 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004536 int *outScore,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004537 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004538 int maxMatches)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004539{
Bram Moolenaar635414d2020-09-11 22:25:15 +02004540 int recursionCount = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004541 int len = MB_CHARLEN(str);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004542 char_u *save_pat;
4543 char_u *pat;
4544 char_u *p;
4545 int complete = FALSE;
4546 int score = 0;
4547 int numMatches = 0;
4548 int matchCount;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004549
4550 *outScore = 0;
4551
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004552 save_pat = vim_strsave(pat_arg);
4553 if (save_pat == NULL)
4554 return FALSE;
4555 pat = save_pat;
4556 p = pat;
4557
4558 // Try matching each word in 'pat_arg' in 'str'
4559 while (TRUE)
4560 {
4561 if (matchseq)
4562 complete = TRUE;
4563 else
4564 {
4565 // Extract one word from the pattern (separated by space)
4566 p = skipwhite(p);
4567 if (*p == NUL)
4568 break;
4569 pat = p;
4570 while (*p != NUL && !VIM_ISWHITE(PTR2CHAR(p)))
4571 {
4572 if (has_mbyte)
4573 MB_PTR_ADV(p);
4574 else
4575 ++p;
4576 }
4577 if (*p == NUL) // processed all the words
4578 complete = TRUE;
4579 *p = NUL;
4580 }
4581
4582 score = 0;
4583 recursionCount = 0;
4584 matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
4585 matches + numMatches, maxMatches - numMatches,
4586 0, &recursionCount);
4587 if (matchCount == 0)
4588 {
4589 numMatches = 0;
4590 break;
4591 }
4592
4593 // Accumulate the match score and the number of matches
4594 *outScore += score;
4595 numMatches += matchCount;
4596
4597 if (complete)
4598 break;
4599
4600 // try matching the next word
4601 ++p;
4602 }
4603
4604 vim_free(save_pat);
4605 return numMatches != 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004606}
4607
4608/*
4609 * Sort the fuzzy matches in the descending order of the match score.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004610 * For items with same score, retain the order using the index (stable sort)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004611 */
4612 static int
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004613fuzzy_match_item_compare(const void *s1, const void *s2)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004614{
4615 int v1 = ((fuzzyItem_T *)s1)->score;
4616 int v2 = ((fuzzyItem_T *)s2)->score;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004617 int idx1 = ((fuzzyItem_T *)s1)->idx;
4618 int idx2 = ((fuzzyItem_T *)s2)->idx;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004619
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004620 return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004621}
4622
4623/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004624 * Fuzzy search the string 'str' in a list of 'items' and return the matching
4625 * strings in 'fmatchlist'.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004626 * If 'matchseq' is TRUE, then for multi-word search strings, match all the
4627 * words in sequence.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004628 * If 'items' is a list of strings, then search for 'str' in the list.
4629 * If 'items' is a list of dicts, then either use 'key' to lookup the string
4630 * for each item or use 'item_cb' Funcref function to get the string.
4631 * If 'retmatchpos' is TRUE, then return a list of positions where 'str'
4632 * matches for each item.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004633 */
4634 static void
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004635fuzzy_match_in_list(
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004636 list_T *items,
4637 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004638 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004639 char_u *key,
4640 callback_T *item_cb,
4641 int retmatchpos,
4642 list_T *fmatchlist)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004643{
4644 long len;
4645 fuzzyItem_T *ptrs;
4646 listitem_T *li;
4647 long i = 0;
4648 int found_match = FALSE;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004649 int_u matches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004650
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004651 len = list_len(items);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004652 if (len == 0)
4653 return;
4654
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004655 ptrs = ALLOC_CLEAR_MULT(fuzzyItem_T, len);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004656 if (ptrs == NULL)
4657 return;
4658
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004659 // For all the string items in items, get the fuzzy matching score
4660 FOR_ALL_LIST_ITEMS(items, li)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004661 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004662 int score;
4663 char_u *itemstr;
4664 typval_T rettv;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004665
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004666 ptrs[i].idx = i;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004667 ptrs[i].item = li;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004668 ptrs[i].score = SCORE_NONE;
4669 itemstr = NULL;
4670 rettv.v_type = VAR_UNKNOWN;
4671 if (li->li_tv.v_type == VAR_STRING) // list of strings
4672 itemstr = li->li_tv.vval.v_string;
4673 else if (li->li_tv.v_type == VAR_DICT &&
4674 (key != NULL || item_cb->cb_name != NULL))
4675 {
4676 // For a dict, either use the specified key to lookup the string or
4677 // use the specified callback function to get the string.
4678 if (key != NULL)
4679 itemstr = dict_get_string(li->li_tv.vval.v_dict, key, FALSE);
4680 else
Bram Moolenaar635414d2020-09-11 22:25:15 +02004681 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004682 typval_T argv[2];
4683
4684 // Invoke the supplied callback (if any) to get the dict item
4685 li->li_tv.vval.v_dict->dv_refcount++;
4686 argv[0].v_type = VAR_DICT;
4687 argv[0].vval.v_dict = li->li_tv.vval.v_dict;
4688 argv[1].v_type = VAR_UNKNOWN;
4689 if (call_callback(item_cb, -1, &rettv, 1, argv) != FAIL)
4690 {
4691 if (rettv.v_type == VAR_STRING)
4692 itemstr = rettv.vval.v_string;
4693 }
4694 dict_unref(li->li_tv.vval.v_dict);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004695 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004696 }
4697
4698 if (itemstr != NULL
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004699 && fuzzy_match(itemstr, str, matchseq, &score, matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004700 sizeof(matches) / sizeof(matches[0])))
4701 {
4702 // Copy the list of matching positions in itemstr to a list, if
4703 // 'retmatchpos' is set.
4704 if (retmatchpos)
4705 {
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004706 int j = 0;
4707 char_u *p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004708
4709 ptrs[i].lmatchpos = list_alloc();
4710 if (ptrs[i].lmatchpos == NULL)
4711 goto done;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004712
4713 p = str;
4714 while (*p != NUL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004715 {
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004716 if (!VIM_ISWHITE(PTR2CHAR(p)))
4717 {
4718 if (list_append_number(ptrs[i].lmatchpos,
4719 matches[j]) == FAIL)
4720 goto done;
4721 j++;
4722 }
4723 if (has_mbyte)
4724 MB_PTR_ADV(p);
4725 else
4726 ++p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004727 }
4728 }
4729 ptrs[i].score = score;
4730 found_match = TRUE;
4731 }
Bram Moolenaar635414d2020-09-11 22:25:15 +02004732 ++i;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004733 clear_tv(&rettv);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004734 }
4735
4736 if (found_match)
4737 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004738 list_T *l;
4739
Bram Moolenaar635414d2020-09-11 22:25:15 +02004740 // Sort the list by the descending order of the match score
4741 qsort((void *)ptrs, (size_t)len, sizeof(fuzzyItem_T),
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004742 fuzzy_match_item_compare);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004743
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004744 // For matchfuzzy(), return a list of matched strings.
4745 // ['str1', 'str2', 'str3']
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004746 // For matchfuzzypos(), return a list with three items.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004747 // The first item is a list of matched strings. The second item
4748 // is a list of lists where each list item is a list of matched
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004749 // character positions. The third item is a list of matching scores.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004750 // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
4751 if (retmatchpos)
4752 {
4753 li = list_find(fmatchlist, 0);
4754 if (li == NULL || li->li_tv.vval.v_list == NULL)
4755 goto done;
4756 l = li->li_tv.vval.v_list;
4757 }
4758 else
4759 l = fmatchlist;
4760
4761 // Copy the matching strings with a valid score to the return list
Bram Moolenaar635414d2020-09-11 22:25:15 +02004762 for (i = 0; i < len; i++)
4763 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004764 if (ptrs[i].score == SCORE_NONE)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004765 break;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004766 list_append_tv(l, &ptrs[i].item->li_tv);
4767 }
4768
4769 // next copy the list of matching positions
4770 if (retmatchpos)
4771 {
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004772 li = list_find(fmatchlist, -2);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004773 if (li == NULL || li->li_tv.vval.v_list == NULL)
4774 goto done;
4775 l = li->li_tv.vval.v_list;
4776
4777 for (i = 0; i < len; i++)
4778 {
4779 if (ptrs[i].score == SCORE_NONE)
4780 break;
4781 if (ptrs[i].lmatchpos != NULL &&
4782 list_append_list(l, ptrs[i].lmatchpos) == FAIL)
4783 goto done;
4784 }
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004785
4786 // copy the matching scores
4787 li = list_find(fmatchlist, -1);
4788 if (li == NULL || li->li_tv.vval.v_list == NULL)
4789 goto done;
4790 l = li->li_tv.vval.v_list;
4791 for (i = 0; i < len; i++)
4792 {
4793 if (ptrs[i].score == SCORE_NONE)
4794 break;
4795 if (list_append_number(l, ptrs[i].score) == FAIL)
4796 goto done;
4797 }
Bram Moolenaar635414d2020-09-11 22:25:15 +02004798 }
4799 }
4800
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004801done:
Bram Moolenaar635414d2020-09-11 22:25:15 +02004802 vim_free(ptrs);
4803}
4804
4805/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004806 * Do fuzzy matching. Returns the list of matched strings in 'rettv'.
4807 * If 'retmatchpos' is TRUE, also returns the matching character positions.
4808 */
4809 static void
4810do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
4811{
4812 callback_T cb;
4813 char_u *key = NULL;
4814 int ret;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004815 int matchseq = FALSE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004816
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02004817 if (in_vim9script()
4818 && (check_for_list_arg(argvars, 0) == FAIL
4819 || check_for_string_arg(argvars, 1) == FAIL
4820 || check_for_opt_dict_arg(argvars, 2) == FAIL))
4821 return;
4822
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004823 CLEAR_POINTER(&cb);
4824
4825 // validate and get the arguments
4826 if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
4827 {
4828 semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
4829 return;
4830 }
4831 if (argvars[1].v_type != VAR_STRING
4832 || argvars[1].vval.v_string == NULL)
4833 {
4834 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
4835 return;
4836 }
4837
4838 if (argvars[2].v_type != VAR_UNKNOWN)
4839 {
4840 dict_T *d;
4841 dictitem_T *di;
4842
4843 if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL)
4844 {
4845 emsg(_(e_dictreq));
4846 return;
4847 }
4848
4849 // To search a dict, either a callback function or a key can be
4850 // specified.
4851 d = argvars[2].vval.v_dict;
4852 if ((di = dict_find(d, (char_u *)"key", -1)) != NULL)
4853 {
4854 if (di->di_tv.v_type != VAR_STRING
4855 || di->di_tv.vval.v_string == NULL
4856 || *di->di_tv.vval.v_string == NUL)
4857 {
4858 semsg(_(e_invarg2), tv_get_string(&di->di_tv));
4859 return;
4860 }
4861 key = tv_get_string(&di->di_tv);
4862 }
4863 else if ((di = dict_find(d, (char_u *)"text_cb", -1)) != NULL)
4864 {
4865 cb = get_callback(&di->di_tv);
4866 if (cb.cb_name == NULL)
4867 {
4868 semsg(_(e_invargval), "text_cb");
4869 return;
4870 }
4871 }
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004872 if (dict_find(d, (char_u *)"matchseq", -1) != NULL)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004873 matchseq = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004874 }
4875
4876 // get the fuzzy matches
4877 ret = rettv_list_alloc(rettv);
4878 if (ret != OK)
4879 goto done;
4880 if (retmatchpos)
4881 {
4882 list_T *l;
4883
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004884 // For matchfuzzypos(), a list with three items are returned. First
4885 // item is a list of matching strings, the second item is a list of
4886 // lists with matching positions within each string and the third item
4887 // is the list of scores of the matches.
4888 l = list_alloc();
4889 if (l == NULL)
4890 goto done;
4891 if (list_append_list(rettv->vval.v_list, l) == FAIL)
4892 goto done;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004893 l = list_alloc();
4894 if (l == NULL)
4895 goto done;
4896 if (list_append_list(rettv->vval.v_list, l) == FAIL)
4897 goto done;
4898 l = list_alloc();
4899 if (l == NULL)
4900 goto done;
4901 if (list_append_list(rettv->vval.v_list, l) == FAIL)
4902 goto done;
4903 }
4904
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004905 fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
4906 matchseq, key, &cb, retmatchpos, rettv->vval.v_list);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004907
4908done:
4909 free_callback(&cb);
4910}
4911
4912/*
Bram Moolenaar635414d2020-09-11 22:25:15 +02004913 * "matchfuzzy()" function
4914 */
4915 void
4916f_matchfuzzy(typval_T *argvars, typval_T *rettv)
4917{
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004918 do_fuzzymatch(argvars, rettv, FALSE);
4919}
4920
4921/*
4922 * "matchfuzzypos()" function
4923 */
4924 void
4925f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
4926{
4927 do_fuzzymatch(argvars, rettv, TRUE);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004928}
4929
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004930#endif