blob: a4ec4448a0cc2e9826e1f0e0397a8e4fd1c4114a [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 Moolenaar071d4272004-06-13 20:20:40 +000019#ifdef FEAT_FIND_ID
Bram Moolenaarbaaa7e92016-01-29 22:47:03 +010020static void show_pat_in_path(char_u *, int,
21 int, int, FILE *, linenr_T *, long);
Bram Moolenaar071d4272004-06-13 20:20:40 +000022#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020023
24typedef struct searchstat
25{
26 int cur; // current position of found words
27 int cnt; // total count of found words
28 int exact_match; // TRUE if matched exactly on specified position
29 int incomplete; // 0: search was fully completed
30 // 1: recomputing was timed out
31 // 2: max count exceeded
32 int last_maxcount; // the max count of the last search
33} searchstat_T;
34
35static 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);
36static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
37
Bram Moolenaarea6561a2020-06-01 21:32:45 +020038#define SEARCH_STAT_DEF_TIMEOUT 40L
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020039#define SEARCH_STAT_DEF_MAX_COUNT 99
40#define SEARCH_STAT_BUF_LEN 12
Bram Moolenaar071d4272004-06-13 20:20:40 +000041
Bram Moolenaar071d4272004-06-13 20:20:40 +000042/*
43 * This file contains various searching-related routines. These fall into
44 * three groups:
45 * 1. string searches (for /, ?, n, and N)
46 * 2. character searches within a single line (for f, F, t, T, etc)
47 * 3. "other" kinds of searches like the '%' command, and 'word' searches.
48 */
49
50/*
51 * String searches
52 *
53 * The string search functions are divided into two levels:
54 * lowest: searchit(); uses an pos_T for starting position and found match.
55 * Highest: do_search(); uses curwin->w_cursor; calls searchit().
56 *
57 * The last search pattern is remembered for repeating the same search.
58 * This pattern is shared between the :g, :s, ? and / commands.
59 * This is in search_regcomp().
60 *
61 * The actual string matching is done using a heavily modified version of
62 * Henry Spencer's regular expression library. See regexp.c.
63 */
64
Bram Moolenaar071d4272004-06-13 20:20:40 +000065/*
66 * Two search patterns are remembered: One for the :substitute command and
67 * one for other searches. last_idx points to the one that was used the last
68 * time.
69 */
Bram Moolenaarc3328162019-07-23 22:15:25 +020070static spat_T spats[2] =
Bram Moolenaar071d4272004-06-13 20:20:40 +000071{
Bram Moolenaar63d9e732019-12-05 21:10:38 +010072 {NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat
73 {NULL, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat
Bram Moolenaar071d4272004-06-13 20:20:40 +000074};
75
Bram Moolenaar63d9e732019-12-05 21:10:38 +010076static int last_idx = 0; // index in spats[] for RE_LAST
Bram Moolenaar071d4272004-06-13 20:20:40 +000077
Bram Moolenaar63d9e732019-12-05 21:10:38 +010078static char_u lastc[2] = {NUL, NUL}; // last character searched for
79static int lastcdir = FORWARD; // last direction of character search
80static int last_t_cmd = TRUE; // last search t_cmd
Bram Moolenaardbd24b52015-08-11 14:26:19 +020081static char_u lastc_bytes[MB_MAXBYTES + 1];
Bram Moolenaar63d9e732019-12-05 21:10:38 +010082static int lastc_bytelen = 1; // >1 for multi-byte char
Bram Moolenaardbd24b52015-08-11 14:26:19 +020083
Bram Moolenaar63d9e732019-12-05 21:10:38 +010084// copy of spats[], for keeping the search patterns while executing autocmds
Bram Moolenaarc3328162019-07-23 22:15:25 +020085static spat_T saved_spats[2];
Bram Moolenaara2cff1d2021-10-15 12:51:29 +010086static char_u *saved_mr_pattern = NULL;
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 Moolenaara2cff1d2021-10-15 12:51:29 +010092// allocated copy of pattern used by search_regcomp()
93static char_u *mr_pattern = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +000094
95#ifdef FEAT_FIND_ID
96/*
97 * Type used by find_pattern_in_path() to remember which included files have
98 * been searched already.
99 */
100typedef struct SearchedFile
101{
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100102 FILE *fp; // File pointer
103 char_u *name; // Full name of file
104 linenr_T lnum; // Line we were up to in file
105 int matched; // Found a match in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +0000106} SearchedFile;
107#endif
108
109/*
110 * translate search pattern for vim_regcomp()
111 *
112 * pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
113 * pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
114 * pat_save == RE_BOTH: save pat in both patterns (:global command)
115 * pat_use == RE_SEARCH: use previous search pattern if "pat" is NULL
Bram Moolenaarb8017e72007-05-10 18:59:07 +0000116 * pat_use == RE_SUBST: use previous substitute pattern if "pat" is NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000117 * pat_use == RE_LAST: use last used pattern if "pat" is NULL
118 * options & SEARCH_HIS: put search string in history
119 * options & SEARCH_KEEP: keep previous search pattern
120 *
121 * returns FAIL if failed, OK otherwise.
122 */
123 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100124search_regcomp(
125 char_u *pat,
126 int pat_save,
127 int pat_use,
128 int options,
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100129 regmmatch_T *regmatch) // return: pattern and ignore-case flag
Bram Moolenaar071d4272004-06-13 20:20:40 +0000130{
131 int magic;
132 int i;
133
134 rc_did_emsg = FALSE;
Bram Moolenaarf4e20992020-12-21 19:59:08 +0100135 magic = magic_isset();
Bram Moolenaar071d4272004-06-13 20:20:40 +0000136
137 /*
138 * If no pattern given, use a previously defined pattern.
139 */
140 if (pat == NULL || *pat == NUL)
141 {
142 if (pat_use == RE_LAST)
143 i = last_idx;
144 else
145 i = pat_use;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100146 if (spats[i].pat == NULL) // pattern was never defined
Bram Moolenaar071d4272004-06-13 20:20:40 +0000147 {
148 if (pat_use == RE_SUBST)
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200149 emsg(_(e_no_previous_substitute_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000150 else
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200151 emsg(_(e_no_previous_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000152 rc_did_emsg = TRUE;
153 return FAIL;
154 }
155 pat = spats[i].pat;
156 magic = spats[i].magic;
157 no_smartcase = spats[i].no_scs;
158 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100159 else if (options & SEARCH_HIS) // put new pattern in history
Bram Moolenaar071d4272004-06-13 20:20:40 +0000160 add_to_history(HIST_SEARCH, pat, TRUE, NUL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000161
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100162 vim_free(mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000163#ifdef FEAT_RIGHTLEFT
Bram Moolenaar071d4272004-06-13 20:20:40 +0000164 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100165 mr_pattern = reverse_text(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000166 else
167#endif
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100168 mr_pattern = vim_strsave(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000169
170 /*
171 * Save the currently used pattern in the appropriate place,
172 * unless the pattern should not be remembered.
173 */
Bram Moolenaare1004402020-10-24 20:49:43 +0200174 if (!(options & SEARCH_KEEP)
175 && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000176 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100177 // search or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000178 if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
179 save_re_pat(RE_SEARCH, pat, magic);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100180 // substitute or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000181 if (pat_save == RE_SUBST || pat_save == RE_BOTH)
182 save_re_pat(RE_SUBST, pat, magic);
183 }
184
185 regmatch->rmm_ic = ignorecase(pat);
Bram Moolenaar3b56eb32005-07-11 22:40:32 +0000186 regmatch->rmm_maxcol = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000187 regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
188 if (regmatch->regprog == NULL)
189 return FAIL;
190 return OK;
191}
192
193/*
194 * Get search pattern used by search_regcomp().
195 */
196 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100197get_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000198{
199 return mr_pattern;
200}
201
Bram Moolenaarabc97732007-08-08 20:49:37 +0000202#if defined(FEAT_RIGHTLEFT) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000203/*
204 * Reverse text into allocated memory.
205 * Returns the allocated string, NULL when out of memory.
206 */
Bram Moolenaarabc97732007-08-08 20:49:37 +0000207 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100208reverse_text(char_u *s)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000209{
210 unsigned len;
211 unsigned s_i, rev_i;
212 char_u *rev;
213
214 /*
215 * Reverse the pattern.
216 */
217 len = (unsigned)STRLEN(s);
218 rev = alloc(len + 1);
219 if (rev != NULL)
220 {
221 rev_i = len;
222 for (s_i = 0; s_i < len; ++s_i)
223 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000224 if (has_mbyte)
225 {
226 int mb_len;
227
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000228 mb_len = (*mb_ptr2len)(s + s_i);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000229 rev_i -= mb_len;
230 mch_memmove(rev + rev_i, s + s_i, mb_len);
231 s_i += mb_len - 1;
232 }
233 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000234 rev[--rev_i] = s[s_i];
235
236 }
237 rev[len] = NUL;
238 }
239 return rev;
240}
241#endif
242
Bram Moolenaarcc2b9d52014-12-13 03:17:11 +0100243 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100244save_re_pat(int idx, char_u *pat, int magic)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000245{
246 if (spats[idx].pat != pat)
247 {
248 vim_free(spats[idx].pat);
249 spats[idx].pat = vim_strsave(pat);
250 spats[idx].magic = magic;
251 spats[idx].no_scs = no_smartcase;
252 last_idx = idx;
253#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100254 // If 'hlsearch' set and search pat changed: need redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000255 if (p_hls)
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100256 redraw_all_later(UPD_SOME_VALID);
Bram Moolenaar451fc7b2018-04-27 22:53:07 +0200257 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000258#endif
259 }
260}
261
Bram Moolenaar071d4272004-06-13 20:20:40 +0000262/*
263 * Save the search patterns, so they can be restored later.
264 * Used before/after executing autocommands and user functions.
265 */
266static int save_level = 0;
267
268 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100269save_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000270{
271 if (save_level++ == 0)
272 {
273 saved_spats[0] = spats[0];
274 if (spats[0].pat != NULL)
275 saved_spats[0].pat = vim_strsave(spats[0].pat);
276 saved_spats[1] = spats[1];
277 if (spats[1].pat != NULL)
278 saved_spats[1].pat = vim_strsave(spats[1].pat);
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100279 if (mr_pattern == NULL)
280 saved_mr_pattern = NULL;
281 else
282 saved_mr_pattern = vim_strsave(mr_pattern);
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100283#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100284 saved_spats_last_idx = last_idx;
285 saved_spats_no_hlsearch = no_hlsearch;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100286#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000287 }
288}
289
290 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100291restore_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000292{
293 if (--save_level == 0)
294 {
295 vim_free(spats[0].pat);
296 spats[0] = saved_spats[0];
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100297#if defined(FEAT_EVAL)
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000298 set_vv_searchforward();
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100299#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000300 vim_free(spats[1].pat);
301 spats[1] = saved_spats[1];
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100302 vim_free(mr_pattern);
303 mr_pattern = saved_mr_pattern;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100304#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100305 last_idx = saved_spats_last_idx;
306 set_no_hlsearch(saved_spats_no_hlsearch);
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100307#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000308 }
309}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000310
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000311#if defined(EXITFREE) || defined(PROTO)
312 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100313free_search_patterns(void)
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000314{
315 vim_free(spats[0].pat);
316 vim_free(spats[1].pat);
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100317 VIM_CLEAR(mr_pattern);
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000318}
319#endif
320
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100321#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100322// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
323// searching
Bram Moolenaarc3328162019-07-23 22:15:25 +0200324static spat_T saved_last_search_spat;
Bram Moolenaared8bc782018-12-01 21:08:21 +0100325static int did_save_last_search_spat = 0;
326static int saved_last_idx = 0;
327static int saved_no_hlsearch = 0;
Christian Brabandt6dd74242022-02-14 12:44:32 +0000328static int saved_search_match_endcol;
329static int saved_search_match_lines;
Bram Moolenaared8bc782018-12-01 21:08:21 +0100330
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100331/*
332 * Save and restore the search pattern for incremental highlight search
333 * feature.
334 *
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100335 * It's similar to but different from save_search_patterns() and
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100336 * restore_search_patterns(), because the search pattern must be restored when
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100337 * canceling incremental searching even if it's called inside user functions.
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100338 */
339 void
340save_last_search_pattern(void)
341{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200342 if (++did_save_last_search_spat != 1)
343 // nested call, nothing to do
344 return;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100345
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100346 saved_last_search_spat = spats[RE_SEARCH];
347 if (spats[RE_SEARCH].pat != NULL)
348 saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
349 saved_last_idx = last_idx;
350 saved_no_hlsearch = no_hlsearch;
351}
352
353 void
354restore_last_search_pattern(void)
355{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200356 if (--did_save_last_search_spat > 0)
357 // nested call, nothing to do
358 return;
359 if (did_save_last_search_spat != 0)
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100360 {
Bram Moolenaar442a8532020-06-04 20:56:09 +0200361 iemsg("restore_last_search_pattern() called more often than save_last_search_pattern()");
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100362 return;
363 }
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100364
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100365 vim_free(spats[RE_SEARCH].pat);
366 spats[RE_SEARCH] = saved_last_search_spat;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100367 saved_last_search_spat.pat = NULL;
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100368# if defined(FEAT_EVAL)
369 set_vv_searchforward();
370# endif
371 last_idx = saved_last_idx;
Bram Moolenaar451fc7b2018-04-27 22:53:07 +0200372 set_no_hlsearch(saved_no_hlsearch);
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100373}
Bram Moolenaard0480092017-11-16 22:20:39 +0100374
Christian Brabandt6dd74242022-02-14 12:44:32 +0000375/*
376 * Save and restore the incsearch highlighting variables.
377 * This is required so that calling searchcount() at does not invalidate the
378 * incsearch highlighting.
379 */
380 static void
381save_incsearch_state(void)
382{
383 saved_search_match_endcol = search_match_endcol;
384 saved_search_match_lines = search_match_lines;
385}
386
387 static void
388restore_incsearch_state(void)
389{
390 search_match_endcol = saved_search_match_endcol;
391 search_match_lines = saved_search_match_lines;
392}
393
Bram Moolenaard0480092017-11-16 22:20:39 +0100394 char_u *
395last_search_pattern(void)
396{
397 return spats[RE_SEARCH].pat;
398}
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100399#endif
400
Bram Moolenaar071d4272004-06-13 20:20:40 +0000401/*
402 * Return TRUE when case should be ignored for search pattern "pat".
403 * Uses the 'ignorecase' and 'smartcase' options.
404 */
405 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100406ignorecase(char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000407{
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200408 return ignorecase_opt(pat, p_ic, p_scs);
409}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000410
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200411/*
412 * As ignorecase() put pass the "ic" and "scs" flags.
413 */
414 int
415ignorecase_opt(char_u *pat, int ic_in, int scs)
416{
417 int ic = ic_in;
418
419 if (ic && !no_smartcase && scs
Bram Moolenaare2c453d2019-08-21 14:37:09 +0200420 && !(ctrl_x_mode_not_default() && curbuf->b_p_inf))
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200421 ic = !pat_has_uppercase(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000422 no_smartcase = FALSE;
423
424 return ic;
425}
426
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200427/*
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200428 * Return TRUE if pattern "pat" has an uppercase character.
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200429 */
430 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100431pat_has_uppercase(char_u *pat)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200432{
433 char_u *p = pat;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200434 magic_T magic_val = MAGIC_ON;
435
436 // get the magicness of the pattern
437 (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200438
439 while (*p != NUL)
440 {
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200441 int l;
442
443 if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
444 {
445 if (enc_utf8 && utf_isupper(utf_ptr2char(p)))
446 return TRUE;
447 p += l;
448 }
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200449 else if (*p == '\\' && magic_val <= MAGIC_ON)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200450 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100451 if (p[1] == '_' && p[2] != NUL) // skip "\_X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200452 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100453 else if (p[1] == '%' && p[2] != NUL) // skip "\%X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200454 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100455 else if (p[1] != NUL) // skip "\X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200456 p += 2;
457 else
458 p += 1;
459 }
Christian Brabandt78ba9332021-08-01 12:44:37 +0200460 else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL)
461 {
462 if (p[1] != NUL) // skip "_X" and %X
463 p += 2;
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200464 else
465 p++;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200466 }
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200467 else if (MB_ISUPPER(*p))
468 return TRUE;
469 else
470 ++p;
471 }
472 return FALSE;
473}
474
Bram Moolenaar113e1072019-01-20 15:30:40 +0100475#if defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000476 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100477last_csearch(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200478{
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200479 return lastc_bytes;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200480}
481
482 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100483last_csearch_forward(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200484{
485 return lastcdir == FORWARD;
486}
487
488 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100489last_csearch_until(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200490{
491 return last_t_cmd == TRUE;
492}
493
494 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100495set_last_csearch(int c, char_u *s UNUSED, int len UNUSED)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200496{
497 *lastc = c;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200498 lastc_bytelen = len;
499 if (len)
500 memcpy(lastc_bytes, s, len);
501 else
Bram Moolenaara80faa82020-04-12 19:37:17 +0200502 CLEAR_FIELD(lastc_bytes);
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200503}
Bram Moolenaar113e1072019-01-20 15:30:40 +0100504#endif
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200505
506 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100507set_csearch_direction(int cdir)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200508{
509 lastcdir = cdir;
510}
511
512 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100513set_csearch_until(int t_cmd)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200514{
515 last_t_cmd = t_cmd;
516}
517
518 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100519last_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000520{
521 return spats[last_idx].pat;
522}
523
524/*
525 * Reset search direction to forward. For "gd" and "gD" commands.
526 */
527 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100528reset_search_dir(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000529{
530 spats[0].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000531#if defined(FEAT_EVAL)
532 set_vv_searchforward();
533#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000534}
535
536#if defined(FEAT_EVAL) || defined(FEAT_VIMINFO)
537/*
538 * Set the last search pattern. For ":let @/ =" and viminfo.
539 * Also set the saved search pattern, so that this works in an autocommand.
540 */
541 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100542set_last_search_pat(
543 char_u *s,
544 int idx,
545 int magic,
546 int setlast)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000547{
548 vim_free(spats[idx].pat);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100549 // An empty string means that nothing should be matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000550 if (*s == NUL)
551 spats[idx].pat = NULL;
552 else
553 spats[idx].pat = vim_strsave(s);
554 spats[idx].magic = magic;
555 spats[idx].no_scs = FALSE;
556 spats[idx].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000557#if defined(FEAT_EVAL)
558 set_vv_searchforward();
559#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000560 spats[idx].off.line = FALSE;
561 spats[idx].off.end = FALSE;
562 spats[idx].off.off = 0;
563 if (setlast)
564 last_idx = idx;
565 if (save_level)
566 {
567 vim_free(saved_spats[idx].pat);
568 saved_spats[idx] = spats[0];
569 if (spats[idx].pat == NULL)
570 saved_spats[idx].pat = NULL;
571 else
572 saved_spats[idx].pat = vim_strsave(spats[idx].pat);
Bram Moolenaar975880b2019-03-03 14:42:11 +0100573# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100574 saved_spats_last_idx = last_idx;
Bram Moolenaar975880b2019-03-03 14:42:11 +0100575# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000576 }
577# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100578 // If 'hlsearch' set and search pat changed: need redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000579 if (p_hls && idx == last_idx && !no_hlsearch)
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100580 redraw_all_later(UPD_SOME_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000581# endif
582}
583#endif
584
585#ifdef FEAT_SEARCH_EXTRA
586/*
587 * Get a regexp program for the last used search pattern.
588 * This is used for highlighting all matches in a window.
589 * Values returned in regmatch->regprog and regmatch->rmm_ic.
590 */
591 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100592last_pat_prog(regmmatch_T *regmatch)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000593{
594 if (spats[last_idx].pat == NULL)
595 {
596 regmatch->regprog = NULL;
597 return;
598 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100599 ++emsg_off; // So it doesn't beep if bad expr
Bram Moolenaar071d4272004-06-13 20:20:40 +0000600 (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch);
601 --emsg_off;
602}
603#endif
604
605/*
Bram Moolenaarf7ff6e82014-03-23 15:13:05 +0100606 * Lowest level search function.
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100607 * Search for 'count'th occurrence of pattern "pat" in direction "dir".
608 * Start at position "pos" and return the found position in "pos".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000609 *
610 * if (options & SEARCH_MSG) == 0 don't give any messages
611 * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
612 * if (options & SEARCH_MSG) == SEARCH_MSG give all messages
613 * if (options & SEARCH_HIS) put search pattern in history
614 * if (options & SEARCH_END) return position at end of match
615 * if (options & SEARCH_START) accept match at pos itself
616 * if (options & SEARCH_KEEP) keep previous search pattern
617 * if (options & SEARCH_FOLD) match only once in a closed fold
618 * if (options & SEARCH_PEEK) check for typed char, cancel search
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100619 * if (options & SEARCH_COL) start at pos->col instead of zero
Bram Moolenaar071d4272004-06-13 20:20:40 +0000620 *
621 * Return FAIL (zero) for failure, non-zero for success.
622 * When FEAT_EVAL is defined, returns the index of the first matching
623 * subpattern plus one; one if there was none.
624 */
625 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100626searchit(
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200627 win_T *win, // window to search in; can be NULL for a
628 // buffer without a window!
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100629 buf_T *buf,
630 pos_T *pos,
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100631 pos_T *end_pos, // set to end of the match, unless NULL
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100632 int dir,
633 char_u *pat,
634 long count,
635 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200636 int pat_use, // which pattern to use when "pat" is empty
637 searchit_arg_T *extra_arg) // optional extra arguments, can be NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000638{
639 int found;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100640 linenr_T lnum; // no init to shut up Apollo cc
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100641 colnr_T col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000642 regmmatch_T regmatch;
643 char_u *ptr;
644 colnr_T matchcol;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000645 lpos_T endpos;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000646 lpos_T matchpos;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000647 int loop;
648 pos_T start_pos;
649 int at_first_line;
650 int extra_col;
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200651 int start_char_len;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000652 int match_ok;
653 long nmatched;
654 int submatch = 0;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100655 int first_match = TRUE;
Bram Moolenaar53989552019-12-23 22:59:18 +0100656 int called_emsg_before = called_emsg;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000657#ifdef FEAT_SEARCH_EXTRA
658 int break_loop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000659#endif
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200660 linenr_T stop_lnum = 0; // stop after this line number when != 0
Paul Ollis65745772022-06-05 16:55:54 +0100661 int unused_timeout_flag = FALSE;
662 int *timed_out = &unused_timeout_flag; // set when timed out.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000663
664 if (search_regcomp(pat, RE_SEARCH, pat_use,
665 (options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL)
666 {
667 if ((options & SEARCH_MSG) && !rc_did_emsg)
Bram Moolenaarac78dd42022-01-02 19:25:26 +0000668 semsg(_(e_invalid_search_string_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000669 return FAIL;
670 }
671
Paul Ollis65745772022-06-05 16:55:54 +0100672 if (extra_arg != NULL)
673 {
674 stop_lnum = extra_arg->sa_stop_lnum;
675#ifdef FEAT_RELTIME
676 if (extra_arg->sa_tm > 0)
Paul Ollis65745772022-06-05 16:55:54 +0100677 init_regexp_timeout(extra_arg->sa_tm);
Bram Moolenaar5ea38d12022-06-16 21:20:48 +0100678 // Also set the pointer when sa_tm is zero, the caller may have set the
679 // timeout.
680 timed_out = &extra_arg->sa_timed_out;
Paul Ollis65745772022-06-05 16:55:54 +0100681#endif
682 }
683
Bram Moolenaar280f1262006-01-30 00:14:18 +0000684 /*
685 * find the string
686 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100687 do // loop for count
Bram Moolenaar071d4272004-06-13 20:20:40 +0000688 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100689 // When not accepting a match at the start position set "extra_col" to
690 // a non-zero value. Don't do that when starting at MAXCOL, since
691 // MAXCOL + 1 is zero.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200692 if (pos->col == MAXCOL)
693 start_char_len = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100694 // Watch out for the "col" being MAXCOL - 2, used in a closed fold.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200695 else if (has_mbyte
696 && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count
697 && pos->col < MAXCOL - 2)
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100698 {
Bram Moolenaar82846a02018-02-09 18:09:54 +0100699 ptr = ml_get_buf(buf, pos->lnum, FALSE);
Bram Moolenaar8846ac52018-02-09 19:24:01 +0100700 if ((int)STRLEN(ptr) <= pos->col)
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200701 start_char_len = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100702 else
Bram Moolenaar82846a02018-02-09 18:09:54 +0100703 start_char_len = (*mb_ptr2len)(ptr + pos->col);
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100704 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100705 else
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200706 start_char_len = 1;
707 if (dir == FORWARD)
708 {
709 if (options & SEARCH_START)
710 extra_col = 0;
711 else
712 extra_col = start_char_len;
713 }
714 else
715 {
716 if (options & SEARCH_START)
717 extra_col = start_char_len;
718 else
719 extra_col = 0;
720 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100721
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100722 start_pos = *pos; // remember start pos for detecting no match
723 found = 0; // default: not found
724 at_first_line = TRUE; // default: start in first line
725 if (pos->lnum == 0) // correct lnum for when starting in line 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000726 {
727 pos->lnum = 1;
728 pos->col = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100729 at_first_line = FALSE; // not in first line now
Bram Moolenaar071d4272004-06-13 20:20:40 +0000730 }
731
732 /*
733 * Start searching in current line, unless searching backwards and
734 * we're in column 0.
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000735 * If we are searching backwards, in column 0, and not including the
736 * current position, gain some efficiency by skipping back a line.
737 * Otherwise begin the search in the current line.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000738 */
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000739 if (dir == BACKWARD && start_pos.col == 0
740 && (options & SEARCH_START) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000741 {
742 lnum = pos->lnum - 1;
743 at_first_line = FALSE;
744 }
745 else
746 lnum = pos->lnum;
747
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100748 for (loop = 0; loop <= 1; ++loop) // loop twice if 'wrapscan' set
Bram Moolenaar071d4272004-06-13 20:20:40 +0000749 {
750 for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
751 lnum += dir, at_first_line = FALSE)
752 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100753 // Stop after checking "stop_lnum", if it's set.
Bram Moolenaara23ccb82006-02-27 00:08:02 +0000754 if (stop_lnum != 0 && (dir == FORWARD
755 ? lnum > stop_lnum : lnum < stop_lnum))
756 break;
Paul Ollis65745772022-06-05 16:55:54 +0100757 // Stop after passing the time limit.
758 if (*timed_out)
Bram Moolenaar76929292008-01-06 19:07:36 +0000759 break;
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,
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +0100767 lnum, col, timed_out);
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200768 // vim_regexec_multi() may clear "regprog"
769 if (regmatch.regprog == NULL)
770 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100771 // Abort searching on an error (e.g., out of stack).
Paul Ollis65745772022-06-05 16:55:54 +0100772 if (called_emsg > called_emsg_before || *timed_out)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000773 break;
774 if (nmatched > 0)
775 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100776 // match may actually be in another line when using \zs
Bram Moolenaar677ee682005-01-27 14:41:15 +0000777 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000778 endpos = regmatch.endpos[0];
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000779#ifdef FEAT_EVAL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000780 submatch = first_submatch(&regmatch);
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000781#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100782 // "lnum" may be past end of buffer for "\n\zs".
Bram Moolenaar32466aa2006-02-24 23:53:04 +0000783 if (lnum + matchpos.lnum > buf->b_ml.ml_line_count)
784 ptr = (char_u *)"";
785 else
786 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000787
788 /*
789 * Forward search in the first line: match should be after
790 * the start position. If not, continue at the end of the
791 * match (this is vi compatible) or on the next char.
792 */
793 if (dir == FORWARD && at_first_line)
794 {
795 match_ok = TRUE;
Bram Moolenaarc96311b2022-11-25 21:13:47 +0000796 matchcol = col;
797
Bram Moolenaar071d4272004-06-13 20:20:40 +0000798 /*
Bram Moolenaar677ee682005-01-27 14:41:15 +0000799 * When the match starts in a next line it's certainly
800 * past the start position.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000801 * When match lands on a NUL the cursor will be put
802 * one back afterwards, compare with that position,
803 * otherwise "/$" will get stuck on end of line.
804 */
Bram Moolenaar677ee682005-01-27 14:41:15 +0000805 while (matchpos.lnum == 0
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100806 && ((options & SEARCH_END) && first_match
Bram Moolenaar677ee682005-01-27 14:41:15 +0000807 ? (nmatched == 1
808 && (int)endpos.col - 1
Bram Moolenaar071d4272004-06-13 20:20:40 +0000809 < (int)start_pos.col + extra_col)
Bram Moolenaar677ee682005-01-27 14:41:15 +0000810 : ((int)matchpos.col
811 - (ptr[matchpos.col] == NUL)
812 < (int)start_pos.col + extra_col)))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000813 {
814 /*
815 * If vi-compatible searching, continue at the end
816 * of the match, otherwise continue one position
817 * forward.
818 */
819 if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
820 {
821 if (nmatched > 1)
822 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100823 // end is in next line, thus no match in
824 // this line
Bram Moolenaar071d4272004-06-13 20:20:40 +0000825 match_ok = FALSE;
826 break;
827 }
828 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100829 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000830 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000831 && ptr[matchcol] != NUL)
832 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000833 if (has_mbyte)
834 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000835 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000836 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000837 ++matchcol;
838 }
839 }
840 else
841 {
Bram Moolenaarc96311b2022-11-25 21:13:47 +0000842 // Advance "matchcol" to the next character.
843 // This does not use matchpos.col, because
844 // "\zs" may have have set it.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000845 if (ptr[matchcol] != NUL)
846 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000847 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000848 matchcol += (*mb_ptr2len)(ptr
Bram Moolenaar071d4272004-06-13 20:20:40 +0000849 + matchcol);
850 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000851 ++matchcol;
852 }
853 }
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +0200854 if (matchcol == 0 && (options & SEARCH_START))
Bram Moolenaardb333a52013-03-19 15:27:48 +0100855 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000856 if (ptr[matchcol] == NUL
857 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +0000858 win, buf, lnum + matchpos.lnum,
Paul Ollis65745772022-06-05 16:55:54 +0100859 matchcol, timed_out)) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000860 {
861 match_ok = FALSE;
862 break;
863 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200864 // vim_regexec_multi() may clear "regprog"
865 if (regmatch.regprog == NULL)
866 break;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000867 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000868 endpos = regmatch.endpos[0];
869# ifdef FEAT_EVAL
870 submatch = first_submatch(&regmatch);
871# endif
872
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100873 // Need to get the line pointer again, a
874 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000875 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000876 }
877 if (!match_ok)
878 continue;
879 }
880 if (dir == BACKWARD)
881 {
882 /*
883 * Now, if there are multiple matches on this line,
884 * we have to get the last one. Or the last one before
885 * the cursor, if we're on that line.
886 * When putting the new cursor at the end, compare
887 * relative to the end of the match.
888 */
889 match_ok = FALSE;
890 for (;;)
891 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100892 // Remember a position that is before the start
893 // position, we use it if it's the last match in
894 // the line. Always accept a position after
895 // wrapping around.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000896 if (loop
897 || ((options & SEARCH_END)
898 ? (lnum + regmatch.endpos[0].lnum
899 < start_pos.lnum
900 || (lnum + regmatch.endpos[0].lnum
901 == start_pos.lnum
902 && (int)regmatch.endpos[0].col - 1
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200903 < (int)start_pos.col
904 + extra_col))
Bram Moolenaar677ee682005-01-27 14:41:15 +0000905 : (lnum + regmatch.startpos[0].lnum
906 < start_pos.lnum
907 || (lnum + regmatch.startpos[0].lnum
908 == start_pos.lnum
909 && (int)regmatch.startpos[0].col
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200910 < (int)start_pos.col
911 + extra_col))))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000912 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000913 match_ok = TRUE;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000914 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000915 endpos = regmatch.endpos[0];
916# ifdef FEAT_EVAL
917 submatch = first_submatch(&regmatch);
918# endif
919 }
920 else
921 break;
922
923 /*
924 * We found a valid match, now check if there is
925 * another one after it.
926 * If vi-compatible searching, continue at the end
927 * of the match, otherwise continue one position
928 * forward.
929 */
930 if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
931 {
932 if (nmatched > 1)
933 break;
934 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100935 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000936 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000937 && ptr[matchcol] != NUL)
938 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000939 if (has_mbyte)
940 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000941 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000942 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000943 ++matchcol;
944 }
945 }
946 else
947 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100948 // Stop when the match is in a next line.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000949 if (matchpos.lnum > 0)
950 break;
951 matchcol = matchpos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000952 if (ptr[matchcol] != NUL)
953 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000954 if (has_mbyte)
955 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000956 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000957 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000958 ++matchcol;
959 }
960 }
961 if (ptr[matchcol] == NUL
962 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +0000963 win, buf, lnum + matchpos.lnum,
Paul Ollis65745772022-06-05 16:55:54 +0100964 matchcol, timed_out)) == 0)
Bram Moolenaar9d322762018-02-09 16:04:25 +0100965 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100966 // If the search timed out, we did find a match
967 // but it might be the wrong one, so that's not
968 // OK.
Paul Ollis65745772022-06-05 16:55:54 +0100969 if (*timed_out)
Bram Moolenaar9d322762018-02-09 16:04:25 +0100970 match_ok = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000971 break;
Bram Moolenaar9d322762018-02-09 16:04:25 +0100972 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200973 // vim_regexec_multi() may clear "regprog"
974 if (regmatch.regprog == NULL)
975 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000976
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100977 // Need to get the line pointer again, a
978 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000979 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000980 }
981
982 /*
983 * If there is only a match after the cursor, skip
984 * this match.
985 */
986 if (!match_ok)
987 continue;
988 }
989
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100990 // With the SEARCH_END option move to the last character
991 // of the match. Don't do it for an empty match, end
992 // should be same as start then.
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +0200993 if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
Bram Moolenaar5bcbd532008-02-20 12:43:01 +0000994 && !(matchpos.lnum == endpos.lnum
995 && matchpos.col == endpos.col))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000996 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100997 // For a match in the first column, set the position
998 // on the NUL in the previous line.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000999 pos->lnum = lnum + endpos.lnum;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001000 pos->col = endpos.col;
1001 if (endpos.col == 0)
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001002 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001003 if (pos->lnum > 1) // just in case
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001004 {
1005 --pos->lnum;
1006 pos->col = (colnr_T)STRLEN(ml_get_buf(buf,
1007 pos->lnum, FALSE));
1008 }
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001009 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001010 else
1011 {
1012 --pos->col;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001013 if (has_mbyte
1014 && pos->lnum <= buf->b_ml.ml_line_count)
1015 {
1016 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1017 pos->col -= (*mb_head_off)(ptr, ptr + pos->col);
1018 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001019 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001020 if (end_pos != NULL)
1021 {
1022 end_pos->lnum = lnum + matchpos.lnum;
1023 end_pos->col = matchpos.col;
1024 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001025 }
1026 else
1027 {
Bram Moolenaar677ee682005-01-27 14:41:15 +00001028 pos->lnum = lnum + matchpos.lnum;
1029 pos->col = matchpos.col;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001030 if (end_pos != NULL)
1031 {
1032 end_pos->lnum = lnum + endpos.lnum;
1033 end_pos->col = endpos.col;
1034 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001035 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001036 pos->coladd = 0;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001037 if (end_pos != NULL)
1038 end_pos->coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001039 found = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +01001040 first_match = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001041
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001042 // Set variables used for 'incsearch' highlighting.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001043 search_match_lines = endpos.lnum - matchpos.lnum;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001044 search_match_endcol = endpos.col;
1045 break;
1046 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001047 line_breakcheck(); // stop if ctrl-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001048 if (got_int)
1049 break;
1050
1051#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001052 // Cancel searching if a character was typed. Used for
1053 // 'incsearch'. Don't check too often, that would slowdown
1054 // searching too much.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001055 if ((options & SEARCH_PEEK)
1056 && ((lnum - pos->lnum) & 0x3f) == 0
1057 && char_avail())
1058 {
1059 break_loop = TRUE;
1060 break;
1061 }
1062#endif
1063
1064 if (loop && lnum == start_pos.lnum)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001065 break; // if second loop, stop where started
Bram Moolenaar071d4272004-06-13 20:20:40 +00001066 }
1067 at_first_line = FALSE;
1068
Bram Moolenaar795aaa12020-10-02 20:36:01 +02001069 // vim_regexec_multi() may clear "regprog"
1070 if (regmatch.regprog == NULL)
1071 break;
1072
Bram Moolenaar071d4272004-06-13 20:20:40 +00001073 /*
Bram Moolenaara23ccb82006-02-27 00:08:02 +00001074 * Stop the search if wrapscan isn't set, "stop_lnum" is
1075 * specified, after an interrupt, after a match and after looping
1076 * twice.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001077 */
Bram Moolenaar53989552019-12-23 22:59:18 +01001078 if (!p_ws || stop_lnum != 0 || got_int
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001079 || called_emsg > called_emsg_before || *timed_out
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001080#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001081 || break_loop
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001082#endif
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001083 || found || loop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001084 break;
1085
1086 /*
1087 * If 'wrapscan' is set we continue at the other end of the file.
1088 * If 'shortmess' does not contain 's', we give a message.
1089 * This message is also remembered in keep_msg for when the screen
1090 * is redrawn. The keep_msg is cleared whenever another message is
1091 * written.
1092 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001093 if (dir == BACKWARD) // start second loop at the other end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001094 lnum = buf->b_ml.ml_line_count;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001095 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001096 lnum = 1;
Bram Moolenaar92d640f2005-09-05 22:11:52 +00001097 if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
1098 give_warning((char_u *)_(dir == BACKWARD
1099 ? top_bot_msg : bot_top_msg), TRUE);
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001100 if (extra_arg != NULL)
1101 extra_arg->sa_wrapped = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001102 }
Paul Ollis65745772022-06-05 16:55:54 +01001103 if (got_int || called_emsg > called_emsg_before || *timed_out
Bram Moolenaar78a15312009-05-15 19:33:18 +00001104#ifdef FEAT_SEARCH_EXTRA
1105 || break_loop
1106#endif
1107 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00001108 break;
1109 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001110 while (--count > 0 && found); // stop after count matches or no match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001111
Bram Moolenaar5ea38d12022-06-16 21:20:48 +01001112#ifdef FEAT_RELTIME
1113 if (extra_arg != NULL && extra_arg->sa_tm > 0)
1114 disable_regexp_timeout();
1115#endif
Bram Moolenaar473de612013-06-08 18:19:48 +02001116 vim_regfree(regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001117
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001118 if (!found) // did not find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00001119 {
1120 if (got_int)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00001121 emsg(_(e_interrupted));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001122 else if ((options & SEARCH_MSG) == SEARCH_MSG)
1123 {
1124 if (p_ws)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001125 semsg(_(e_pattern_not_found_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001126 else if (lnum == 0)
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001127 semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001128 else
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001129 semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001130 }
1131 return FAIL;
1132 }
1133
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001134 // A pattern like "\n\zs" may go past the last line.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001135 if (pos->lnum > buf->b_ml.ml_line_count)
1136 {
1137 pos->lnum = buf->b_ml.ml_line_count;
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00001138 pos->col = (int)STRLEN(ml_get_buf(buf, pos->lnum, FALSE));
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001139 if (pos->col > 0)
1140 --pos->col;
1141 }
1142
Bram Moolenaar071d4272004-06-13 20:20:40 +00001143 return submatch + 1;
1144}
1145
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00001146#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001147 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001148set_search_direction(int cdir)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001149{
1150 spats[0].off.dir = cdir;
1151}
1152
1153 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001154set_vv_searchforward(void)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001155{
1156 set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
1157}
1158
Bram Moolenaar071d4272004-06-13 20:20:40 +00001159/*
1160 * Return the number of the first subpat that matched.
Bram Moolenaarad4d8a12015-12-28 19:20:36 +01001161 * Return zero if none of them matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001162 */
1163 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001164first_submatch(regmmatch_T *rp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001165{
1166 int submatch;
1167
1168 for (submatch = 1; ; ++submatch)
1169 {
1170 if (rp->startpos[submatch].lnum >= 0)
1171 break;
1172 if (submatch == 9)
1173 {
1174 submatch = 0;
1175 break;
1176 }
1177 }
1178 return submatch;
1179}
1180#endif
1181
1182/*
1183 * Highest level string search function.
Bram Moolenaarb8017e72007-05-10 18:59:07 +00001184 * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001185 * If 'dirc' is 0: use previous dir.
1186 * If 'pat' is NULL or empty : use previous string.
1187 * If 'options & SEARCH_REV' : go in reverse of previous dir.
1188 * If 'options & SEARCH_ECHO': echo the search command and handle options
1189 * If 'options & SEARCH_MSG' : may give error message
1190 * If 'options & SEARCH_OPT' : interpret optional flags
1191 * If 'options & SEARCH_HIS' : put search pattern in history
1192 * If 'options & SEARCH_NOOF': don't add offset to position
1193 * If 'options & SEARCH_MARK': set previous context mark
1194 * If 'options & SEARCH_KEEP': keep previous search pattern
1195 * If 'options & SEARCH_START': accept match at curpos itself
1196 * If 'options & SEARCH_PEEK': check for typed char, cancel search
1197 *
1198 * Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
1199 * makes the movement linewise without moving the match position.
1200 *
Bram Moolenaarb6c27352015-03-05 19:57:49 +01001201 * Return 0 for failure, 1 for found, 2 for found and line offset added.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001202 */
1203 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001204do_search(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001205 oparg_T *oap, // can be NULL
1206 int dirc, // '/' or '?'
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001207 int search_delim, // the delimiter for the search, e.g. '%' in
1208 // s%regex%replacement%
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001209 char_u *pat,
1210 long count,
1211 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001212 searchit_arg_T *sia) // optional arguments or NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001213{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001214 pos_T pos; // position of the last match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001215 char_u *searchstr;
Bram Moolenaarc3328162019-07-23 22:15:25 +02001216 soffset_T old_off;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001217 int retval; // Return value
Bram Moolenaar071d4272004-06-13 20:20:40 +00001218 char_u *p;
1219 long c;
1220 char_u *dircp;
1221 char_u *strcopy = NULL;
1222 char_u *ps;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001223 char_u *msgbuf = NULL;
1224 size_t len;
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001225 int has_offset = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001226
1227 /*
1228 * A line offset is not remembered, this is vi compatible.
1229 */
1230 if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
1231 {
1232 spats[0].off.line = FALSE;
1233 spats[0].off.off = 0;
1234 }
1235
1236 /*
1237 * Save the values for when (options & SEARCH_KEEP) is used.
1238 * (there is no "if ()" around this because gcc wants them initialized)
1239 */
1240 old_off = spats[0].off;
1241
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001242 pos = curwin->w_cursor; // start searching at the cursor position
Bram Moolenaar071d4272004-06-13 20:20:40 +00001243
1244 /*
1245 * Find out the direction of the search.
1246 */
1247 if (dirc == 0)
1248 dirc = spats[0].off.dir;
1249 else
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001250 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001251 spats[0].off.dir = dirc;
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001252#if defined(FEAT_EVAL)
1253 set_vv_searchforward();
1254#endif
1255 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001256 if (options & SEARCH_REV)
1257 {
Bram Moolenaar4f974752019-02-17 17:44:42 +01001258#ifdef MSWIN
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001259 // There is a bug in the Visual C++ 2.2 compiler which means that
1260 // dirc always ends up being '/'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001261 dirc = (dirc == '/') ? '?' : '/';
1262#else
1263 if (dirc == '/')
1264 dirc = '?';
1265 else
1266 dirc = '/';
1267#endif
1268 }
1269
1270#ifdef FEAT_FOLDING
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001271 // If the cursor is in a closed fold, don't find another match in the same
1272 // fold.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001273 if (dirc == '/')
1274 {
1275 if (hasFolding(pos.lnum, NULL, &pos.lnum))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001276 pos.col = MAXCOL - 2; // avoid overflow when adding 1
Bram Moolenaar071d4272004-06-13 20:20:40 +00001277 }
1278 else
1279 {
1280 if (hasFolding(pos.lnum, &pos.lnum, NULL))
1281 pos.col = 0;
1282 }
1283#endif
1284
1285#ifdef FEAT_SEARCH_EXTRA
1286 /*
1287 * Turn 'hlsearch' highlighting back on.
1288 */
1289 if (no_hlsearch && !(options & SEARCH_KEEP))
1290 {
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001291 redraw_all_later(UPD_SOME_VALID);
Bram Moolenaar451fc7b2018-04-27 22:53:07 +02001292 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001293 }
1294#endif
1295
1296 /*
1297 * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
1298 */
1299 for (;;)
1300 {
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001301 int show_top_bot_msg = FALSE;
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001302
Bram Moolenaar071d4272004-06-13 20:20:40 +00001303 searchstr = pat;
1304 dircp = NULL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001305 // use previous pattern
Bram Moolenaarc036e872020-02-21 21:30:52 +01001306 if (pat == NULL || *pat == NUL || *pat == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001307 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001308 if (spats[RE_SEARCH].pat == NULL) // no previous pattern
Bram Moolenaar071d4272004-06-13 20:20:40 +00001309 {
Bram Moolenaarea683da2016-09-09 21:41:34 +02001310 searchstr = spats[RE_SUBST].pat;
1311 if (searchstr == NULL)
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001312 {
Bram Moolenaare29a27f2021-07-20 21:07:36 +02001313 emsg(_(e_no_previous_regular_expression));
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001314 retval = 0;
1315 goto end_do_search;
1316 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001317 }
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001318 else
1319 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001320 // make search_regcomp() use spats[RE_SEARCH].pat
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001321 searchstr = (char_u *)"";
1322 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001323 }
1324
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001325 if (pat != NULL && *pat != NUL) // look for (new) offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001326 {
1327 /*
1328 * Find end of regular expression.
1329 * If there is a matching '/' or '?', toss it.
1330 */
1331 ps = strcopy;
Bram Moolenaarf4e20992020-12-21 19:59:08 +01001332 p = skip_regexp_ex(pat, search_delim, magic_isset(),
Bram Moolenaard93a7fc2021-01-04 12:42:13 +01001333 &strcopy, NULL, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001334 if (strcopy != ps)
1335 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001336 // made a copy of "pat" to change "\?" to "?"
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00001337 searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001338 pat = strcopy;
1339 searchstr = strcopy;
1340 }
Bram Moolenaarc036e872020-02-21 21:30:52 +01001341 if (*p == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001342 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001343 dircp = p; // remember where we put the NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001344 *p++ = NUL;
1345 }
1346 spats[0].off.line = FALSE;
1347 spats[0].off.end = FALSE;
1348 spats[0].off.off = 0;
1349 /*
1350 * Check for a line offset or a character offset.
1351 * For get_address (echo off) we don't check for a character
1352 * offset, because it is meaningless and the 's' could be a
1353 * substitute command.
1354 */
1355 if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p))
1356 spats[0].off.line = TRUE;
Dominique Pelle7765f5c2022-04-10 11:26:53 +01001357 else if ((options & SEARCH_OPT)
1358 && (*p == 'e' || *p == 's' || *p == 'b'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001359 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001360 if (*p == 'e') // end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001361 spats[0].off.end = SEARCH_END;
1362 ++p;
1363 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001364 if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001365 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001366 // 'nr' or '+nr' or '-nr'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001367 if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
1368 spats[0].off.off = atol((char *)p);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001369 else if (*p == '-') // single '-'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001370 spats[0].off.off = -1;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001371 else // single '+'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001372 spats[0].off.off = 1;
1373 ++p;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001374 while (VIM_ISDIGIT(*p)) // skip number
Bram Moolenaar071d4272004-06-13 20:20:40 +00001375 ++p;
1376 }
1377
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001378 // compute length of search command for get_address()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001379 searchcmdlen += (int)(p - pat);
1380
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001381 pat = p; // put pat after search command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001382 }
1383
Dominique Pelle7765f5c2022-04-10 11:26:53 +01001384 if ((options & SEARCH_ECHO) && messaging()
1385 && !msg_silent
1386 && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001387 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001388 char_u *trunc;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001389 char_u off_buf[40];
Bram Moolenaard33a7642019-05-24 17:56:14 +02001390 size_t off_len = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001391
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001392 // Compute msg_row early.
1393 msg_start();
1394
Bram Moolenaar984f0312019-05-24 13:11:47 +02001395 // Get the offset, so we know how long it is.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001396 if (!cmd_silent &&
1397 (spats[0].off.line || spats[0].off.end || spats[0].off.off))
Bram Moolenaar984f0312019-05-24 13:11:47 +02001398 {
1399 p = off_buf;
1400 *p++ = dirc;
1401 if (spats[0].off.end)
1402 *p++ = 'e';
1403 else if (!spats[0].off.line)
1404 *p++ = 's';
1405 if (spats[0].off.off > 0 || spats[0].off.line)
1406 *p++ = '+';
1407 *p = NUL;
1408 if (spats[0].off.off != 0 || spats[0].off.line)
1409 sprintf((char *)p, "%ld", spats[0].off.off);
1410 off_len = STRLEN(off_buf);
1411 }
1412
Bram Moolenaar071d4272004-06-13 20:20:40 +00001413 if (*searchstr == NUL)
Bram Moolenaar2fb8f682018-12-01 13:14:45 +01001414 p = spats[0].pat;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001415 else
1416 p = searchstr;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001417
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001418 if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001419 {
1420 // Reserve enough space for the search pattern + offset +
Bram Moolenaar984f0312019-05-24 13:11:47 +02001421 // search stat. Use all the space available, so that the
1422 // search state is right aligned. If there is not enough space
1423 // msg_strtrunc() will shorten in the middle.
Bram Moolenaar19e8ac72019-09-03 22:23:38 +02001424 if (msg_scrolled != 0 && !cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001425 // Use all the columns.
1426 len = (int)(Rows - msg_row) * Columns - 1;
1427 else
1428 // Use up to 'showcmd' column.
1429 len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001430 if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3)
1431 len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001432 }
1433 else
1434 // Reserve enough space for the search pattern + offset.
Bram Moolenaar984f0312019-05-24 13:11:47 +02001435 len = STRLEN(p) + off_len + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001436
Bram Moolenaar880e4d92020-04-11 21:31:28 +02001437 vim_free(msgbuf);
Bram Moolenaar51e14382019-05-25 20:21:28 +02001438 msgbuf = alloc(len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001439 if (msgbuf != NULL)
1440 {
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001441 vim_memset(msgbuf, ' ', len);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001442 msgbuf[len - 1] = NUL;
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001443 // do not fill the msgbuf buffer, if cmd_silent is set, leave it
1444 // empty for the search_stat feature.
1445 if (!cmd_silent)
Bram Moolenaarcafda4f2005-09-06 19:25:11 +00001446 {
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001447 msgbuf[0] = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001448
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001449 if (enc_utf8 && utf_iscomposing(utf_ptr2char(p)))
1450 {
1451 // Use a space to draw the composing char on.
1452 msgbuf[1] = ' ';
1453 mch_memmove(msgbuf + 2, p, STRLEN(p));
1454 }
1455 else
1456 mch_memmove(msgbuf + 1, p, STRLEN(p));
1457 if (off_len > 0)
1458 mch_memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001459
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001460 trunc = msg_strtrunc(msgbuf, TRUE);
1461 if (trunc != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001462 {
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001463 vim_free(msgbuf);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001464 msgbuf = trunc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001465 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001466
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001467#ifdef FEAT_RIGHTLEFT
1468 // The search pattern could be shown on the right in
1469 // rightleft mode, but the 'ruler' and 'showcmd' area use
1470 // it too, thus it would be blanked out again very soon.
1471 // Show it on the left, but do reverse the text.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001472 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
1473 {
1474 char_u *r;
1475 size_t pat_len;
1476
1477 r = reverse_text(msgbuf);
1478 if (r != NULL)
1479 {
1480 vim_free(msgbuf);
1481 msgbuf = r;
1482 // move reversed text to beginning of buffer
1483 while (*r != NUL && *r == ' ')
1484 r++;
1485 pat_len = msgbuf + STRLEN(msgbuf) - r;
1486 mch_memmove(msgbuf, r, pat_len);
1487 // overwrite old text
1488 if ((size_t)(r - msgbuf) >= pat_len)
1489 vim_memset(r, ' ', pat_len);
1490 else
1491 vim_memset(msgbuf + pat_len, ' ', r - msgbuf);
1492 }
1493 }
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001494#endif
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001495 msg_outtrans(msgbuf);
1496 msg_clr_eos();
1497 msg_check();
1498
1499 gotocmdline(FALSE);
1500 out_flush();
1501 msg_nowait = TRUE; // don't wait for this message
1502 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001503 }
1504 }
1505
1506 /*
1507 * If there is a character offset, subtract it from the current
1508 * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
Bram Moolenaared203462004-06-16 11:19:22 +00001509 * Skip this if pos.col is near MAXCOL (closed fold).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001510 * This is not done for a line offset, because then we would not be vi
1511 * compatible.
1512 */
Bram Moolenaared203462004-06-16 11:19:22 +00001513 if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001514 {
1515 if (spats[0].off.off > 0)
1516 {
1517 for (c = spats[0].off.off; c; --c)
1518 if (decl(&pos) == -1)
1519 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001520 if (c) // at start of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001521 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001522 pos.lnum = 0; // allow lnum == 0 here
Bram Moolenaar071d4272004-06-13 20:20:40 +00001523 pos.col = MAXCOL;
1524 }
1525 }
1526 else
1527 {
1528 for (c = spats[0].off.off; c; ++c)
1529 if (incl(&pos) == -1)
1530 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001531 if (c) // at end of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001532 {
1533 pos.lnum = curbuf->b_ml.ml_line_count + 1;
1534 pos.col = 0;
1535 }
1536 }
1537 }
1538
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001539 /*
1540 * The actual search.
1541 */
Bram Moolenaar14184a32019-02-16 15:10:30 +01001542 c = searchit(curwin, curbuf, &pos, NULL,
1543 dirc == '/' ? FORWARD : BACKWARD,
Bram Moolenaar071d4272004-06-13 20:20:40 +00001544 searchstr, count, spats[0].off.end + (options &
1545 (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS
1546 + SEARCH_MSG + SEARCH_START
1547 + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001548 RE_LAST, sia);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001549
1550 if (dircp != NULL)
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001551 *dircp = search_delim; // restore second '/' or '?' for normal_cmd()
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001552
1553 if (!shortmess(SHM_SEARCH)
1554 && ((dirc == '/' && LT_POS(pos, curwin->w_cursor))
1555 || (dirc == '?' && LT_POS(curwin->w_cursor, pos))))
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001556 show_top_bot_msg = TRUE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001557
Bram Moolenaar071d4272004-06-13 20:20:40 +00001558 if (c == FAIL)
1559 {
1560 retval = 0;
1561 goto end_do_search;
1562 }
1563 if (spats[0].off.end && oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001564 oap->inclusive = TRUE; // 'e' includes last character
Bram Moolenaar071d4272004-06-13 20:20:40 +00001565
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001566 retval = 1; // pattern found
Bram Moolenaar071d4272004-06-13 20:20:40 +00001567
1568 /*
1569 * Add character and/or line offset
1570 */
Bram Moolenaar9160f302006-08-29 15:58:12 +00001571 if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001572 {
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001573 pos_T org_pos = pos;
1574
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001575 if (spats[0].off.line) // Add the offset to the line number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001576 {
1577 c = pos.lnum + spats[0].off.off;
1578 if (c < 1)
1579 pos.lnum = 1;
1580 else if (c > curbuf->b_ml.ml_line_count)
1581 pos.lnum = curbuf->b_ml.ml_line_count;
1582 else
1583 pos.lnum = c;
1584 pos.col = 0;
1585
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001586 retval = 2; // pattern found, line offset added
Bram Moolenaar071d4272004-06-13 20:20:40 +00001587 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001588 else if (pos.col < MAXCOL - 2) // just in case
Bram Moolenaar071d4272004-06-13 20:20:40 +00001589 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001590 // to the right, check for end of file
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001591 c = spats[0].off.off;
1592 if (c > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001593 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001594 while (c-- > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001595 if (incl(&pos) == -1)
1596 break;
1597 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001598 // to the left, check for start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00001599 else
1600 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001601 while (c++ < 0)
1602 if (decl(&pos) == -1)
1603 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001604 }
1605 }
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001606 if (!EQUAL_POS(pos, org_pos))
1607 has_offset = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001608 }
1609
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001610 // Show [1/15] if 'S' is not in 'shortmess'.
1611 if ((options & SEARCH_ECHO)
1612 && messaging()
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001613 && !msg_silent
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001614 && c != FAIL
1615 && !shortmess(SHM_SEARCHCOUNT)
1616 && msgbuf != NULL)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001617 cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
1618 show_top_bot_msg, msgbuf,
1619 (count != 1 || has_offset
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001620#ifdef FEAT_FOLDING
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001621 || (!(fdo_flags & FDO_SEARCH)
1622 && hasFolding(curwin->w_cursor.lnum,
1623 NULL, NULL))
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001624#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001625 ),
1626 SEARCH_STAT_DEF_MAX_COUNT,
1627 SEARCH_STAT_DEF_TIMEOUT);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001628
Bram Moolenaar071d4272004-06-13 20:20:40 +00001629 /*
1630 * The search command can be followed by a ';' to do another search.
1631 * For example: "/pat/;/foo/+3;?bar"
1632 * This is like doing another search command, except:
1633 * - The remembered direction '/' or '?' is from the first search.
1634 * - When an error happens the cursor isn't moved at all.
1635 * Don't do this when called by get_address() (it handles ';' itself).
1636 */
1637 if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';')
1638 break;
1639
1640 dirc = *++pat;
Bram Moolenaarc036e872020-02-21 21:30:52 +01001641 search_delim = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001642 if (dirc != '?' && dirc != '/')
1643 {
1644 retval = 0;
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001645 emsg(_(e_expected_question_or_slash_after_semicolon));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001646 goto end_do_search;
1647 }
1648 ++pat;
1649 }
1650
1651 if (options & SEARCH_MARK)
1652 setpcmark();
1653 curwin->w_cursor = pos;
1654 curwin->w_set_curswant = TRUE;
1655
1656end_do_search:
Bram Moolenaare1004402020-10-24 20:49:43 +02001657 if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001658 spats[0].off = old_off;
1659 vim_free(strcopy);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001660 vim_free(msgbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001661
1662 return retval;
1663}
1664
Bram Moolenaar071d4272004-06-13 20:20:40 +00001665/*
1666 * search_for_exact_line(buf, pos, dir, pat)
1667 *
1668 * Search for a line starting with the given pattern (ignoring leading
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001669 * white-space), starting from pos and going in direction "dir". "pos" will
Bram Moolenaar071d4272004-06-13 20:20:40 +00001670 * contain the position of the match found. Blank lines match only if
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001671 * ADDING is set. If p_ic is set then the pattern must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001672 * Return OK for success, or FAIL if no line found.
1673 */
1674 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001675search_for_exact_line(
1676 buf_T *buf,
1677 pos_T *pos,
1678 int dir,
1679 char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001680{
1681 linenr_T start = 0;
1682 char_u *ptr;
1683 char_u *p;
1684
1685 if (buf->b_ml.ml_line_count == 0)
1686 return FAIL;
1687 for (;;)
1688 {
1689 pos->lnum += dir;
1690 if (pos->lnum < 1)
1691 {
1692 if (p_ws)
1693 {
1694 pos->lnum = buf->b_ml.ml_line_count;
1695 if (!shortmess(SHM_SEARCH))
1696 give_warning((char_u *)_(top_bot_msg), TRUE);
1697 }
1698 else
1699 {
1700 pos->lnum = 1;
1701 break;
1702 }
1703 }
1704 else if (pos->lnum > buf->b_ml.ml_line_count)
1705 {
1706 if (p_ws)
1707 {
1708 pos->lnum = 1;
1709 if (!shortmess(SHM_SEARCH))
1710 give_warning((char_u *)_(bot_top_msg), TRUE);
1711 }
1712 else
1713 {
1714 pos->lnum = 1;
1715 break;
1716 }
1717 }
1718 if (pos->lnum == start)
1719 break;
1720 if (start == 0)
1721 start = pos->lnum;
1722 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1723 p = skipwhite(ptr);
1724 pos->col = (colnr_T) (p - ptr);
1725
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001726 // when adding lines the matching line may be empty but it is not
1727 // ignored because we are interested in the next line -- Acevedo
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00001728 if (compl_status_adding() && !compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00001729 {
1730 if ((p_ic ? MB_STRICMP(p, pat) : STRCMP(p, pat)) == 0)
1731 return OK;
1732 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001733 else if (*p != NUL) // ignore empty lines
1734 { // expanding lines or words
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00001735 if ((p_ic ? MB_STRNICMP(p, pat, ins_compl_len())
1736 : STRNCMP(p, pat, ins_compl_len())) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001737 return OK;
1738 }
1739 }
1740 return FAIL;
1741}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001742
1743/*
1744 * Character Searches
1745 */
1746
1747/*
1748 * Search for a character in a line. If "t_cmd" is FALSE, move to the
1749 * position of the character, otherwise move to just before the char.
1750 * Do this "cap->count1" times.
1751 * Return FAIL or OK.
1752 */
1753 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001754searchc(cmdarg_T *cap, int t_cmd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001755{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001756 int c = cap->nchar; // char to search for
1757 int dir = cap->arg; // TRUE for searching forward
1758 long count = cap->count1; // repeat count
Bram Moolenaar071d4272004-06-13 20:20:40 +00001759 int col;
1760 char_u *p;
1761 int len;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001762 int stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001763
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001764 if (c != NUL) // normal search: remember args for repeat
Bram Moolenaar071d4272004-06-13 20:20:40 +00001765 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001766 if (!KeyStuffed) // don't remember when redoing
Bram Moolenaar071d4272004-06-13 20:20:40 +00001767 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001768 *lastc = c;
1769 set_csearch_direction(dir);
1770 set_csearch_until(t_cmd);
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001771 lastc_bytelen = (*mb_char2bytes)(c, lastc_bytes);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001772 if (cap->ncharC1 != 0)
1773 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001774 lastc_bytelen += (*mb_char2bytes)(cap->ncharC1,
1775 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001776 if (cap->ncharC2 != 0)
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001777 lastc_bytelen += (*mb_char2bytes)(cap->ncharC2,
1778 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001779 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001780 }
1781 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001782 else // repeat previous search
Bram Moolenaar071d4272004-06-13 20:20:40 +00001783 {
Bram Moolenaar264b74f2019-01-24 17:18:42 +01001784 if (*lastc == NUL && lastc_bytelen == 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001785 return FAIL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001786 if (dir) // repeat in opposite direction
Bram Moolenaar071d4272004-06-13 20:20:40 +00001787 dir = -lastcdir;
1788 else
1789 dir = lastcdir;
1790 t_cmd = last_t_cmd;
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001791 c = *lastc;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001792 // For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001793
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001794 // Force a move of at least one char, so ";" and "," will move the
1795 // cursor, even if the cursor is right in front of char we are looking
1796 // at.
Bram Moolenaar19fd09a2011-07-15 13:21:30 +02001797 if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd)
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001798 stop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001799 }
1800
Bram Moolenaar60a795a2005-09-16 21:55:43 +00001801 if (dir == BACKWARD)
1802 cap->oap->inclusive = FALSE;
1803 else
1804 cap->oap->inclusive = TRUE;
1805
Bram Moolenaar071d4272004-06-13 20:20:40 +00001806 p = ml_get_curline();
1807 col = curwin->w_cursor.col;
1808 len = (int)STRLEN(p);
1809
1810 while (count--)
1811 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001812 if (has_mbyte)
1813 {
1814 for (;;)
1815 {
1816 if (dir > 0)
1817 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00001818 col += (*mb_ptr2len)(p + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001819 if (col >= len)
1820 return FAIL;
1821 }
1822 else
1823 {
1824 if (col == 0)
1825 return FAIL;
1826 col -= (*mb_head_off)(p, p + col - 1) + 1;
1827 }
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001828 if (lastc_bytelen == 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001829 {
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001830 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001831 break;
1832 }
Bram Moolenaar66727e12017-03-01 22:17:05 +01001833 else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0
Bram Moolenaarb129a442016-12-01 17:25:20 +01001834 && stop)
Bram Moolenaar66727e12017-03-01 22:17:05 +01001835 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001836 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001837 }
1838 }
1839 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001840 {
1841 for (;;)
1842 {
1843 if ((col += dir) < 0 || col >= len)
1844 return FAIL;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001845 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001846 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001847 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001848 }
1849 }
1850 }
1851
1852 if (t_cmd)
1853 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001854 // backup to before the character (possibly double-byte)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001855 col -= dir;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001856 if (has_mbyte)
1857 {
1858 if (dir < 0)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001859 // Landed on the search char which is lastc_bytelen long
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001860 col += lastc_bytelen - 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001861 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001862 // To previous char, which may be multi-byte.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001863 col -= (*mb_head_off)(p, p + col);
1864 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001865 }
1866 curwin->w_cursor.col = col;
1867
1868 return OK;
1869}
1870
1871/*
1872 * "Other" Searches
1873 */
1874
1875/*
1876 * findmatch - find the matching paren or brace
1877 *
1878 * Improvement over vi: Braces inside quotes are ignored.
1879 */
1880 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001881findmatch(oparg_T *oap, int initc)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001882{
1883 return findmatchlimit(oap, initc, 0, 0);
1884}
1885
1886/*
1887 * Return TRUE if the character before "linep[col]" equals "ch".
1888 * Return FALSE if "col" is zero.
1889 * Update "*prevcol" to the column of the previous character, unless "prevcol"
1890 * is NULL.
1891 * Handles multibyte string correctly.
1892 */
1893 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001894check_prevcol(
1895 char_u *linep,
1896 int col,
1897 int ch,
1898 int *prevcol)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001899{
1900 --col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001901 if (col > 0 && has_mbyte)
1902 col -= (*mb_head_off)(linep, linep + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001903 if (prevcol)
1904 *prevcol = col;
1905 return (col >= 0 && linep[col] == ch) ? TRUE : FALSE;
1906}
1907
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001908/*
1909 * Raw string start is found at linep[startpos.col - 1].
1910 * Return TRUE if the matching end can be found between startpos and endpos.
1911 */
1912 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001913find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001914{
1915 char_u *p;
1916 char_u *delim_copy;
1917 size_t delim_len;
1918 linenr_T lnum;
1919 int found = FALSE;
1920
1921 for (p = linep + startpos->col + 1; *p && *p != '('; ++p)
1922 ;
1923 delim_len = (p - linep) - startpos->col - 1;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001924 delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len);
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001925 if (delim_copy == NULL)
1926 return FALSE;
1927 for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum)
1928 {
1929 char_u *line = ml_get(lnum);
1930
1931 for (p = line + (lnum == startpos->lnum
1932 ? startpos->col + 1 : 0); *p; ++p)
1933 {
1934 if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col)
1935 break;
Bram Moolenaar282f9c62020-08-04 21:46:18 +02001936 if (*p == ')' && STRNCMP(delim_copy, p + 1, delim_len) == 0
1937 && p[delim_len + 1] == '"')
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001938 {
1939 found = TRUE;
1940 break;
1941 }
1942 }
1943 if (found)
1944 break;
1945 }
1946 vim_free(delim_copy);
1947 return found;
1948}
1949
Bram Moolenaar071d4272004-06-13 20:20:40 +00001950/*
Bram Moolenaar556ae8e2019-11-21 22:27:22 +01001951 * Check matchpairs option for "*initc".
1952 * If there is a match set "*initc" to the matching character and "*findc" to
1953 * the opposite character. Set "*backwards" to the direction.
1954 * When "switchit" is TRUE swap the direction.
1955 */
1956 static void
1957find_mps_values(
1958 int *initc,
1959 int *findc,
1960 int *backwards,
1961 int switchit)
1962{
1963 char_u *ptr;
1964
1965 ptr = curbuf->b_p_mps;
1966 while (*ptr != NUL)
1967 {
1968 if (has_mbyte)
1969 {
1970 char_u *prev;
1971
1972 if (mb_ptr2char(ptr) == *initc)
1973 {
1974 if (switchit)
1975 {
1976 *findc = *initc;
1977 *initc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
1978 *backwards = TRUE;
1979 }
1980 else
1981 {
1982 *findc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
1983 *backwards = FALSE;
1984 }
1985 return;
1986 }
1987 prev = ptr;
1988 ptr += mb_ptr2len(ptr) + 1;
1989 if (mb_ptr2char(ptr) == *initc)
1990 {
1991 if (switchit)
1992 {
1993 *findc = *initc;
1994 *initc = mb_ptr2char(prev);
1995 *backwards = FALSE;
1996 }
1997 else
1998 {
1999 *findc = mb_ptr2char(prev);
2000 *backwards = TRUE;
2001 }
2002 return;
2003 }
2004 ptr += mb_ptr2len(ptr);
2005 }
2006 else
2007 {
2008 if (*ptr == *initc)
2009 {
2010 if (switchit)
2011 {
2012 *backwards = TRUE;
2013 *findc = *initc;
2014 *initc = ptr[2];
2015 }
2016 else
2017 {
2018 *backwards = FALSE;
2019 *findc = ptr[2];
2020 }
2021 return;
2022 }
2023 ptr += 2;
2024 if (*ptr == *initc)
2025 {
2026 if (switchit)
2027 {
2028 *backwards = FALSE;
2029 *findc = *initc;
2030 *initc = ptr[-2];
2031 }
2032 else
2033 {
2034 *backwards = TRUE;
2035 *findc = ptr[-2];
2036 }
2037 return;
2038 }
2039 ++ptr;
2040 }
2041 if (*ptr == ',')
2042 ++ptr;
2043 }
2044}
2045
2046/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002047 * findmatchlimit -- find the matching paren or brace, if it exists within
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002048 * maxtravel lines of the cursor. A maxtravel of 0 means search until falling
2049 * off the edge of the file.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002050 *
2051 * "initc" is the character to find a match for. NUL means to find the
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002052 * character at or after the cursor. Special values:
2053 * '*' look for C-style comment / *
2054 * '/' look for C-style comment / *, ignoring comment-end
2055 * '#' look for preprocessor directives
2056 * 'R' look for raw string start: R"delim(text)delim" (only backwards)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002057 *
2058 * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
2059 * FM_FORWARD search forwards (when initc is '/', '*' or '#')
2060 * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
2061 * FM_SKIPCOMM skip comments (not implemented yet!)
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002062 *
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002063 * "oap" is only used to set oap->motion_type for a linewise motion, it can be
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002064 * NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002065 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002066 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002067findmatchlimit(
2068 oparg_T *oap,
2069 int initc,
2070 int flags,
2071 int maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002072{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002073 static pos_T pos; // current search position
2074 int findc = 0; // matching brace
Bram Moolenaar071d4272004-06-13 20:20:40 +00002075 int c;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002076 int count = 0; // cumulative number of braces
2077 int backwards = FALSE; // init for gcc
2078 int raw_string = FALSE; // search for raw string
2079 int inquote = FALSE; // TRUE when inside quotes
2080 char_u *linep; // pointer to current line
Bram Moolenaar071d4272004-06-13 20:20:40 +00002081 char_u *ptr;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002082 int do_quotes; // check for quotes in current line
2083 int at_start; // do_quotes value at start position
2084 int hash_dir = 0; // Direction searched for # things
2085 int comment_dir = 0; // Direction searched for comments
2086 pos_T match_pos; // Where last slash-star was found
2087 int start_in_quotes; // start position is in quotes
2088 int traveled = 0; // how far we've searched so far
2089 int ignore_cend = FALSE; // ignore comment end
2090 int cpo_match; // vi compatible matching
2091 int cpo_bsl; // don't recognize backslashes
2092 int match_escaped = 0; // search for escaped match
2093 int dir; // Direction to search
2094 int comment_col = MAXCOL; // start of / / comment
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002095 int lispcomm = FALSE; // inside of Lisp-style comment
2096 int lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002097
2098 pos = curwin->w_cursor;
Bram Moolenaarc56c4592013-08-14 17:45:29 +02002099 pos.coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002100 linep = ml_get(pos.lnum);
2101
2102 cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
2103 cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
2104
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002105 // Direction to search when initc is '/', '*' or '#'
Bram Moolenaar071d4272004-06-13 20:20:40 +00002106 if (flags & FM_BACKWARD)
2107 dir = BACKWARD;
2108 else if (flags & FM_FORWARD)
2109 dir = FORWARD;
2110 else
2111 dir = 0;
2112
2113 /*
2114 * if initc given, look in the table for the matching character
2115 * '/' and '*' are special cases: look for start or end of comment.
2116 * When '/' is used, we ignore running backwards into an star-slash, for
2117 * "[*" command, we just want to find any comment.
2118 */
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002119 if (initc == '/' || initc == '*' || initc == 'R')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002120 {
2121 comment_dir = dir;
2122 if (initc == '/')
2123 ignore_cend = TRUE;
2124 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002125 raw_string = (initc == 'R');
Bram Moolenaar071d4272004-06-13 20:20:40 +00002126 initc = NUL;
2127 }
2128 else if (initc != '#' && initc != NUL)
2129 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002130 find_mps_values(&initc, &findc, &backwards, TRUE);
Connor Lane Smithb9115da2021-07-31 13:31:42 +02002131 if (dir)
2132 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002133 if (findc == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002134 return NULL;
2135 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002136 else
2137 {
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002138 /*
2139 * Either initc is '#', or no initc was given and we need to look
2140 * under the cursor.
2141 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002142 if (initc == '#')
2143 {
2144 hash_dir = dir;
2145 }
2146 else
2147 {
2148 /*
2149 * initc was not given, must look for something to match under
2150 * or near the cursor.
2151 * Only check for special things when 'cpo' doesn't have '%'.
2152 */
2153 if (!cpo_match)
2154 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002155 // Are we before or at #if, #else etc.?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002156 ptr = skipwhite(linep);
2157 if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
2158 {
2159 ptr = skipwhite(ptr + 1);
2160 if ( STRNCMP(ptr, "if", 2) == 0
2161 || STRNCMP(ptr, "endif", 5) == 0
2162 || STRNCMP(ptr, "el", 2) == 0)
2163 hash_dir = 1;
2164 }
2165
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002166 // Are we on a comment?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002167 else if (linep[pos.col] == '/')
2168 {
2169 if (linep[pos.col + 1] == '*')
2170 {
2171 comment_dir = FORWARD;
2172 backwards = FALSE;
2173 pos.col++;
2174 }
2175 else if (pos.col > 0 && linep[pos.col - 1] == '*')
2176 {
2177 comment_dir = BACKWARD;
2178 backwards = TRUE;
2179 pos.col--;
2180 }
2181 }
2182 else if (linep[pos.col] == '*')
2183 {
2184 if (linep[pos.col + 1] == '/')
2185 {
2186 comment_dir = BACKWARD;
2187 backwards = TRUE;
2188 }
2189 else if (pos.col > 0 && linep[pos.col - 1] == '/')
2190 {
2191 comment_dir = FORWARD;
2192 backwards = FALSE;
2193 }
2194 }
2195 }
2196
2197 /*
2198 * If we are not on a comment or the # at the start of a line, then
2199 * look for brace anywhere on this line after the cursor.
2200 */
2201 if (!hash_dir && !comment_dir)
2202 {
2203 /*
2204 * Find the brace under or after the cursor.
2205 * If beyond the end of the line, use the last character in
2206 * the line.
2207 */
2208 if (linep[pos.col] == NUL && pos.col)
2209 --pos.col;
2210 for (;;)
2211 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002212 initc = PTR2CHAR(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002213 if (initc == NUL)
2214 break;
2215
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002216 find_mps_values(&initc, &findc, &backwards, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002217 if (findc)
2218 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002219 pos.col += mb_ptr2len(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002220 }
2221 if (!findc)
2222 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002223 // no brace in the line, maybe use " #if" then
Bram Moolenaar071d4272004-06-13 20:20:40 +00002224 if (!cpo_match && *skipwhite(linep) == '#')
2225 hash_dir = 1;
2226 else
2227 return NULL;
2228 }
2229 else if (!cpo_bsl)
2230 {
2231 int col, bslcnt = 0;
2232
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002233 // Set "match_escaped" if there are an odd number of
2234 // backslashes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002235 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2236 bslcnt++;
2237 match_escaped = (bslcnt & 1);
2238 }
2239 }
2240 }
2241 if (hash_dir)
2242 {
2243 /*
2244 * Look for matching #if, #else, #elif, or #endif
2245 */
2246 if (oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002247 oap->motion_type = MLINE; // Linewise for this case only
Bram Moolenaar071d4272004-06-13 20:20:40 +00002248 if (initc != '#')
2249 {
2250 ptr = skipwhite(skipwhite(linep) + 1);
2251 if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0)
2252 hash_dir = 1;
2253 else if (STRNCMP(ptr, "endif", 5) == 0)
2254 hash_dir = -1;
2255 else
2256 return NULL;
2257 }
2258 pos.col = 0;
2259 while (!got_int)
2260 {
2261 if (hash_dir > 0)
2262 {
2263 if (pos.lnum == curbuf->b_ml.ml_line_count)
2264 break;
2265 }
2266 else if (pos.lnum == 1)
2267 break;
2268 pos.lnum += hash_dir;
2269 linep = ml_get(pos.lnum);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002270 line_breakcheck(); // check for CTRL-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00002271 ptr = skipwhite(linep);
2272 if (*ptr != '#')
2273 continue;
2274 pos.col = (colnr_T) (ptr - linep);
2275 ptr = skipwhite(ptr + 1);
2276 if (hash_dir > 0)
2277 {
2278 if (STRNCMP(ptr, "if", 2) == 0)
2279 count++;
2280 else if (STRNCMP(ptr, "el", 2) == 0)
2281 {
2282 if (count == 0)
2283 return &pos;
2284 }
2285 else if (STRNCMP(ptr, "endif", 5) == 0)
2286 {
2287 if (count == 0)
2288 return &pos;
2289 count--;
2290 }
2291 }
2292 else
2293 {
2294 if (STRNCMP(ptr, "if", 2) == 0)
2295 {
2296 if (count == 0)
2297 return &pos;
2298 count--;
2299 }
2300 else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0)
2301 {
2302 if (count == 0)
2303 return &pos;
2304 }
2305 else if (STRNCMP(ptr, "endif", 5) == 0)
2306 count++;
2307 }
2308 }
2309 return NULL;
2310 }
2311 }
2312
2313#ifdef FEAT_RIGHTLEFT
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002314 // This is just guessing: when 'rightleft' is set, search for a matching
2315 // paren/brace in the other direction.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002316 if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>", initc) != NULL)
2317 backwards = !backwards;
2318#endif
2319
2320 do_quotes = -1;
2321 start_in_quotes = MAYBE;
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01002322 CLEAR_POS(&match_pos);
Bram Moolenaarfd2ac762006-03-01 22:09:21 +00002323
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002324 // backward search: Check if this line contains a single-line comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002325 if ((backwards && comment_dir) || lisp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002326 comment_col = check_linecomment(linep);
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002327 if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002328 lispcomm = TRUE; // find match inside this comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002329
Bram Moolenaar071d4272004-06-13 20:20:40 +00002330 while (!got_int)
2331 {
2332 /*
2333 * Go to the next position, forward or backward. We could use
2334 * inc() and dec() here, but that is much slower
2335 */
2336 if (backwards)
2337 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002338 // char to match is inside of comment, don't search outside
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002339 if (lispcomm && pos.col < (colnr_T)comment_col)
2340 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002341 if (pos.col == 0) // at start of line, go to prev. one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002342 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002343 if (pos.lnum == 1) // start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00002344 break;
2345 --pos.lnum;
2346
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00002347 if (maxtravel > 0 && ++traveled > maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002348 break;
2349
2350 linep = ml_get(pos.lnum);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002351 pos.col = (colnr_T)STRLEN(linep); // pos.col on trailing NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002352 do_quotes = -1;
2353 line_breakcheck();
2354
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002355 // Check if this line contains a single-line comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002356 if (comment_dir || lisp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002357 comment_col = check_linecomment(linep);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002358 // skip comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002359 if (lisp && comment_col != MAXCOL)
2360 pos.col = comment_col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002361 }
2362 else
2363 {
2364 --pos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002365 if (has_mbyte)
2366 pos.col -= (*mb_head_off)(linep, linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002367 }
2368 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002369 else // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002370 {
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002371 if (linep[pos.col] == NUL
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002372 // at end of line, go to next one
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002373 // For lisp don't search for match in comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002374 || (lisp && comment_col != MAXCOL
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002375 && pos.col == (colnr_T)comment_col))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002376 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002377 if (pos.lnum == curbuf->b_ml.ml_line_count // end of file
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002378 // line is exhausted and comment with it,
2379 // don't search for match in code
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002380 || lispcomm)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002381 break;
2382 ++pos.lnum;
2383
2384 if (maxtravel && traveled++ > maxtravel)
2385 break;
2386
2387 linep = ml_get(pos.lnum);
2388 pos.col = 0;
2389 do_quotes = -1;
2390 line_breakcheck();
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002391 if (lisp) // find comment pos in new line
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002392 comment_col = check_linecomment(linep);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002393 }
2394 else
2395 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00002396 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00002397 pos.col += (*mb_ptr2len)(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002398 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002399 ++pos.col;
2400 }
2401 }
2402
2403 /*
2404 * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
2405 */
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002406 if (pos.col == 0 && (flags & FM_BLOCKSTOP)
2407 && (linep[0] == '{' || linep[0] == '}'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002408 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002409 if (linep[0] == findc && count == 0) // match!
Bram Moolenaar071d4272004-06-13 20:20:40 +00002410 return &pos;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002411 break; // out of scope
Bram Moolenaar071d4272004-06-13 20:20:40 +00002412 }
2413
2414 if (comment_dir)
2415 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002416 // Note: comments do not nest, and we ignore quotes in them
2417 // TODO: ignore comment brackets inside strings
Bram Moolenaar071d4272004-06-13 20:20:40 +00002418 if (comment_dir == FORWARD)
2419 {
2420 if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
2421 {
2422 pos.col++;
2423 return &pos;
2424 }
2425 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002426 else // Searching backwards
Bram Moolenaar071d4272004-06-13 20:20:40 +00002427 {
2428 /*
2429 * A comment may contain / * or / /, it may also start or end
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002430 * with / * /. Ignore a / * after / / and after *.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002431 */
2432 if (pos.col == 0)
2433 continue;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002434 else if (raw_string)
2435 {
2436 if (linep[pos.col - 1] == 'R'
2437 && linep[pos.col] == '"'
2438 && vim_strchr(linep + pos.col + 1, '(') != NULL)
2439 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002440 // Possible start of raw string. Now that we have the
2441 // delimiter we can check if it ends before where we
2442 // started searching, or before the previously found
2443 // raw string start.
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002444 if (!find_rawstring_end(linep, &pos,
2445 count > 0 ? &match_pos : &curwin->w_cursor))
2446 {
2447 count++;
2448 match_pos = pos;
2449 match_pos.col--;
2450 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002451 linep = ml_get(pos.lnum); // may have been released
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002452 }
2453 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002454 else if ( linep[pos.col - 1] == '/'
2455 && linep[pos.col] == '*'
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002456 && (pos.col == 1 || linep[pos.col - 2] != '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002457 && (int)pos.col < comment_col)
2458 {
2459 count++;
2460 match_pos = pos;
2461 match_pos.col--;
2462 }
2463 else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
2464 {
2465 if (count > 0)
2466 pos = match_pos;
2467 else if (pos.col > 1 && linep[pos.col - 2] == '/'
2468 && (int)pos.col <= comment_col)
2469 pos.col -= 2;
2470 else if (ignore_cend)
2471 continue;
2472 else
2473 return NULL;
2474 return &pos;
2475 }
2476 }
2477 continue;
2478 }
2479
2480 /*
2481 * If smart matching ('cpoptions' does not contain '%'), braces inside
2482 * of quotes are ignored, but only if there is an even number of
2483 * quotes in the line.
2484 */
2485 if (cpo_match)
2486 do_quotes = 0;
2487 else if (do_quotes == -1)
2488 {
2489 /*
2490 * Count the number of quotes in the line, skipping \" and '"'.
2491 * Watch out for "\\".
2492 */
2493 at_start = do_quotes;
2494 for (ptr = linep; *ptr; ++ptr)
2495 {
2496 if (ptr == linep + pos.col + backwards)
2497 at_start = (do_quotes & 1);
2498 if (*ptr == '"'
2499 && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
2500 ++do_quotes;
2501 if (*ptr == '\\' && ptr[1] != NUL)
2502 ++ptr;
2503 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002504 do_quotes &= 1; // result is 1 with even number of quotes
Bram Moolenaar071d4272004-06-13 20:20:40 +00002505
2506 /*
2507 * If we find an uneven count, check current line and previous
2508 * one for a '\' at the end.
2509 */
2510 if (!do_quotes)
2511 {
2512 inquote = FALSE;
2513 if (ptr[-1] == '\\')
2514 {
2515 do_quotes = 1;
2516 if (start_in_quotes == MAYBE)
2517 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002518 // Do we need to use at_start here?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002519 inquote = TRUE;
2520 start_in_quotes = TRUE;
2521 }
2522 else if (backwards)
2523 inquote = TRUE;
2524 }
2525 if (pos.lnum > 1)
2526 {
2527 ptr = ml_get(pos.lnum - 1);
2528 if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
2529 {
2530 do_quotes = 1;
2531 if (start_in_quotes == MAYBE)
2532 {
2533 inquote = at_start;
2534 if (inquote)
2535 start_in_quotes = TRUE;
2536 }
2537 else if (!backwards)
2538 inquote = TRUE;
2539 }
Bram Moolenaaraec11792007-07-10 11:09:36 +00002540
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002541 // ml_get() only keeps one line, need to get linep again
Bram Moolenaaraec11792007-07-10 11:09:36 +00002542 linep = ml_get(pos.lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002543 }
2544 }
2545 }
2546 if (start_in_quotes == MAYBE)
2547 start_in_quotes = FALSE;
2548
2549 /*
2550 * If 'smartmatch' is set:
2551 * Things inside quotes are ignored by setting 'inquote'. If we
2552 * find a quote without a preceding '\' invert 'inquote'. At the
2553 * end of a line not ending in '\' we reset 'inquote'.
2554 *
2555 * In lines with an uneven number of quotes (without preceding '\')
2556 * we do not know which part to ignore. Therefore we only set
2557 * inquote if the number of quotes in a line is even, unless this
2558 * line or the previous one ends in a '\'. Complicated, isn't it?
2559 */
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002560 c = PTR2CHAR(linep + pos.col);
2561 switch (c)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002562 {
2563 case NUL:
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002564 // at end of line without trailing backslash, reset inquote
Bram Moolenaar071d4272004-06-13 20:20:40 +00002565 if (pos.col == 0 || linep[pos.col - 1] != '\\')
2566 {
2567 inquote = FALSE;
2568 start_in_quotes = FALSE;
2569 }
2570 break;
2571
2572 case '"':
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002573 // a quote that is preceded with an odd number of backslashes is
2574 // ignored
Bram Moolenaar071d4272004-06-13 20:20:40 +00002575 if (do_quotes)
2576 {
2577 int col;
2578
2579 for (col = pos.col - 1; col >= 0; --col)
2580 if (linep[col] != '\\')
2581 break;
2582 if ((((int)pos.col - 1 - col) & 1) == 0)
2583 {
2584 inquote = !inquote;
2585 start_in_quotes = FALSE;
2586 }
2587 }
2588 break;
2589
2590 /*
2591 * If smart matching ('cpoptions' does not contain '%'):
2592 * Skip things in single quotes: 'x' or '\x'. Be careful for single
2593 * single quotes, eg jon's. Things like '\233' or '\x3f' are not
2594 * skipped, there is never a brace in them.
2595 * Ignore this when finding matches for `'.
2596 */
2597 case '\'':
2598 if (!cpo_match && initc != '\'' && findc != '\'')
2599 {
2600 if (backwards)
2601 {
2602 if (pos.col > 1)
2603 {
2604 if (linep[pos.col - 2] == '\'')
2605 {
2606 pos.col -= 2;
2607 break;
2608 }
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002609 else if (linep[pos.col - 2] == '\\'
2610 && pos.col > 2 && linep[pos.col - 3] == '\'')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002611 {
2612 pos.col -= 3;
2613 break;
2614 }
2615 }
2616 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002617 else if (linep[pos.col + 1]) // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002618 {
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002619 if (linep[pos.col + 1] == '\\'
2620 && linep[pos.col + 2] && linep[pos.col + 3] == '\'')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002621 {
2622 pos.col += 3;
2623 break;
2624 }
2625 else if (linep[pos.col + 2] == '\'')
2626 {
2627 pos.col += 2;
2628 break;
2629 }
2630 }
2631 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002632 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00002633
2634 default:
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002635 /*
2636 * For Lisp skip over backslashed (), {} and [].
2637 * (actually, we skip #\( et al)
2638 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002639 if (curbuf->b_p_lisp
2640 && vim_strchr((char_u *)"(){}[]", c) != NULL
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002641 && pos.col > 1
2642 && check_prevcol(linep, pos.col, '\\', NULL)
2643 && check_prevcol(linep, pos.col - 1, '#', NULL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002644 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002645
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002646 // Check for match outside of quotes, and inside of
2647 // quotes when the start is also inside of quotes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002648 if ((!inquote || start_in_quotes == TRUE)
2649 && (c == initc || c == findc))
2650 {
2651 int col, bslcnt = 0;
2652
2653 if (!cpo_bsl)
2654 {
2655 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2656 bslcnt++;
2657 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002658 // Only accept a match when 'M' is in 'cpo' or when escaping
2659 // is what we expect.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002660 if (cpo_bsl || (bslcnt & 1) == match_escaped)
2661 {
2662 if (c == initc)
2663 count++;
2664 else
2665 {
2666 if (count == 0)
2667 return &pos;
2668 count--;
2669 }
2670 }
2671 }
2672 }
2673 }
2674
2675 if (comment_dir == BACKWARD && count > 0)
2676 {
2677 pos = match_pos;
2678 return &pos;
2679 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002680 return (pos_T *)NULL; // never found it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002681}
2682
2683/*
2684 * Check if line[] contains a / / comment.
2685 * Return MAXCOL if not, otherwise return the column.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002686 */
Bram Moolenaar6e371ec2021-12-12 14:16:39 +00002687 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002688check_linecomment(char_u *line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002689{
2690 char_u *p;
2691
2692 p = line;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002693 // skip Lispish one-line comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002694 if (curbuf->b_p_lisp)
2695 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002696 if (vim_strchr(p, ';') != NULL) // there may be comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002697 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002698 int in_str = FALSE; // inside of string
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002699
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002700 p = line; // scan from start
Bram Moolenaar520470a2005-06-16 21:59:56 +00002701 while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002702 {
2703 if (*p == '"')
2704 {
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002705 if (in_str)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002706 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002707 if (*(p - 1) != '\\') // skip escaped quote
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002708 in_str = FALSE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002709 }
2710 else if (p == line || ((p - line) >= 2
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002711 // skip #\" form
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002712 && *(p - 1) != '\\' && *(p - 2) != '#'))
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002713 in_str = TRUE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002714 }
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002715 else if (!in_str && ((p - line) < 2
Bram Moolenaarba263672021-12-29 18:09:13 +00002716 || (*(p - 1) != '\\' && *(p - 2) != '#'))
2717 && !is_pos_in_string(line, (colnr_T)(p - line)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002718 break; // found!
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002719 ++p;
2720 }
2721 }
2722 else
2723 p = NULL;
2724 }
2725 else
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002726 while ((p = vim_strchr(p, '/')) != NULL)
2727 {
2728 // Accept a double /, unless it's preceded with * and followed by
2729 // *, because * / / * is an end and start of a C comment. Only
2730 // accept the position if it is not inside a string.
2731 if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
Bram Moolenaarba263672021-12-29 18:09:13 +00002732 && !is_pos_in_string(line, (colnr_T)(p - line)))
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002733 break;
2734 ++p;
2735 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002736
2737 if (p == NULL)
2738 return MAXCOL;
2739 return (int)(p - line);
2740}
2741
2742/*
2743 * Move cursor briefly to character matching the one under the cursor.
2744 * Used for Insert mode and "r" command.
2745 * Show the match only if it is visible on the screen.
2746 * If there isn't a match, then beep.
2747 */
2748 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002749showmatch(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002750 int c) // char to show match for
Bram Moolenaar071d4272004-06-13 20:20:40 +00002751{
2752 pos_T *lpos, save_cursor;
2753 pos_T mpos;
2754 colnr_T vcol;
2755 long save_so;
2756 long save_siso;
2757#ifdef CURSOR_SHAPE
2758 int save_state;
2759#endif
2760 colnr_T save_dollar_vcol;
2761 char_u *p;
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01002762 long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
2763 long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002764
2765 /*
2766 * Only show match for chars in the 'matchpairs' option.
2767 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002768 // 'matchpairs' is "x:y,x:y"
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002769 for (p = curbuf->b_p_mps; *p != NUL; ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002770 {
2771#ifdef FEAT_RIGHTLEFT
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002772 if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002773 break;
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002774#endif
Bram Moolenaar1614a142019-10-06 22:00:13 +02002775 p += mb_ptr2len(p) + 1;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002776 if (PTR2CHAR(p) == c
Bram Moolenaar071d4272004-06-13 20:20:40 +00002777#ifdef FEAT_RIGHTLEFT
2778 && !(curwin->w_p_rl ^ p_ri)
2779#endif
2780 )
2781 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002782 p += mb_ptr2len(p);
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002783 if (*p == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002784 return;
2785 }
Bram Moolenaar5b8cabf2021-04-02 18:55:57 +02002786 if (*p == NUL)
2787 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002788
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002789 if ((lpos = findmatch(NULL, NUL)) == NULL) // no match, so beep
Bram Moolenaar165bc692015-07-21 17:53:25 +02002790 vim_beep(BO_MATCH);
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002791 else if (lpos->lnum >= curwin->w_topline && lpos->lnum < curwin->w_botline)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002792 {
2793 if (!curwin->w_p_wrap)
2794 getvcol(curwin, lpos, NULL, &vcol, NULL);
2795 if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol
Bram Moolenaar02631462017-09-22 15:20:32 +02002796 && vcol < curwin->w_leftcol + curwin->w_width))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002797 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002798 mpos = *lpos; // save the pos, update_screen() may change it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002799 save_cursor = curwin->w_cursor;
Bram Moolenaar375e3392019-01-31 18:26:10 +01002800 save_so = *so;
2801 save_siso = *siso;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002802 // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
2803 // stop displaying the "$".
Bram Moolenaar76b9b362012-02-04 23:35:00 +01002804 if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
2805 dollar_vcol = -1;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002806 ++curwin->w_virtcol; // do display ')' just before "$"
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002807 update_screen(UPD_VALID); // show the new char first
Bram Moolenaar071d4272004-06-13 20:20:40 +00002808
2809 save_dollar_vcol = dollar_vcol;
2810#ifdef CURSOR_SHAPE
2811 save_state = State;
Bram Moolenaar24959102022-05-07 20:01:16 +01002812 State = MODE_SHOWMATCH;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002813 ui_cursor_shape(); // may show different cursor shape
Bram Moolenaar071d4272004-06-13 20:20:40 +00002814#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002815 curwin->w_cursor = mpos; // move to matching char
2816 *so = 0; // don't use 'scrolloff' here
2817 *siso = 0; // don't use 'sidescrolloff' here
Bram Moolenaar071d4272004-06-13 20:20:40 +00002818 showruler(FALSE);
2819 setcursor();
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002820 cursor_on(); // make sure that the cursor is shown
Bram Moolenaara338adc2018-01-31 20:51:47 +01002821 out_flush_cursor(TRUE, FALSE);
2822
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002823 // Restore dollar_vcol(), because setcursor() may call curs_rows()
2824 // which resets it if the matching position is in a previous line
2825 // and has a higher column number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002826 dollar_vcol = save_dollar_vcol;
2827
2828 /*
2829 * brief pause, unless 'm' is present in 'cpo' and a character is
2830 * available.
2831 */
2832 if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
Bram Moolenaareda1da02019-11-17 17:06:33 +01002833 ui_delay(p_mat * 100L + 8, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002834 else if (!char_avail())
Bram Moolenaareda1da02019-11-17 17:06:33 +01002835 ui_delay(p_mat * 100L + 9, FALSE);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002836 curwin->w_cursor = save_cursor; // restore cursor position
Bram Moolenaar375e3392019-01-31 18:26:10 +01002837 *so = save_so;
2838 *siso = save_siso;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002839#ifdef CURSOR_SHAPE
2840 State = save_state;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002841 ui_cursor_shape(); // may show different cursor shape
Bram Moolenaar071d4272004-06-13 20:20:40 +00002842#endif
2843 }
2844 }
2845}
2846
2847/*
Bram Moolenaar453c1922019-10-26 14:42:09 +02002848 * Check if the pattern is zero-width.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002849 * If move is TRUE, check from the beginning of the buffer, else from position
2850 * "cur".
2851 * "direction" is FORWARD or BACKWARD.
2852 * Returns TRUE, FALSE or -1 for failure.
2853 */
2854 static int
2855is_zero_width(char_u *pattern, int move, pos_T *cur, int direction)
2856{
2857 regmmatch_T regmatch;
2858 int nmatched = 0;
2859 int result = -1;
2860 pos_T pos;
Bram Moolenaar53989552019-12-23 22:59:18 +01002861 int called_emsg_before = called_emsg;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002862 int flag = 0;
2863
2864 if (pattern == NULL)
2865 pattern = spats[last_idx].pat;
2866
2867 if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH,
2868 SEARCH_KEEP, &regmatch) == FAIL)
2869 return -1;
2870
2871 // init startcol correctly
2872 regmatch.startpos[0].col = -1;
2873 // move to match
2874 if (move)
2875 {
2876 CLEAR_POS(&pos);
2877 }
2878 else
2879 {
2880 pos = *cur;
2881 // accept a match at the cursor position
2882 flag = SEARCH_START;
2883 }
2884
2885 if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
2886 SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL)
2887 {
2888 // Zero-width pattern should match somewhere, then we can check if
2889 // start and end are in the same position.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002890 do
2891 {
2892 regmatch.startpos[0].col++;
2893 nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
Paul Ollis65745772022-06-05 16:55:54 +01002894 pos.lnum, regmatch.startpos[0].col, NULL);
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002895 if (nmatched != 0)
2896 break;
Bram Moolenaar795aaa12020-10-02 20:36:01 +02002897 } while (regmatch.regprog != NULL
2898 && direction == FORWARD ? regmatch.startpos[0].col < pos.col
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002899 : regmatch.startpos[0].col > pos.col);
2900
Bram Moolenaar53989552019-12-23 22:59:18 +01002901 if (called_emsg == called_emsg_before)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002902 {
2903 result = (nmatched != 0
2904 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
2905 && regmatch.startpos[0].col == regmatch.endpos[0].col);
2906 }
2907 }
2908
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002909 vim_regfree(regmatch.regprog);
2910 return result;
2911}
2912
Bram Moolenaardde0efe2012-08-23 15:53:05 +02002913
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002914/*
2915 * Find next search match under cursor, cursor at end.
2916 * Used while an operator is pending, and in Visual mode.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002917 */
2918 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002919current_search(
2920 long count,
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002921 int forward) // TRUE for forward, FALSE for backward
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002922{
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002923 pos_T start_pos; // start position of the pattern match
2924 pos_T end_pos; // end position of the pattern match
2925 pos_T orig_pos; // position of the cursor at beginning
2926 pos_T pos; // position after the pattern
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002927 int i;
2928 int dir;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002929 int result; // result of various function calls
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002930 char_u old_p_ws = p_ws;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002931 int flags = 0;
Bram Moolenaarde9149e2013-07-17 19:22:13 +02002932 pos_T save_VIsual = VIsual;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002933 int zero_width;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002934 int skip_first_backward;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002935
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002936 // Correct cursor when 'selection' is exclusive
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01002937 if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002938 dec_cursor();
2939
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002940 // When searching forward and the cursor is at the start of the Visual
2941 // area, skip the first search backward, otherwise it doesn't move.
2942 skip_first_backward = forward && VIsual_active
2943 && LT_POS(curwin->w_cursor, VIsual);
2944
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002945 orig_pos = pos = curwin->w_cursor;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002946 if (VIsual_active)
2947 {
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002948 if (forward)
2949 incl(&pos);
2950 else
2951 decl(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002952 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002953
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002954 // Is the pattern is zero-width?, this time, don't care about the direction
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002955 zero_width = is_zero_width(spats[last_idx].pat, TRUE, &curwin->w_cursor,
Bram Moolenaar22ab5472017-09-26 12:28:45 +02002956 FORWARD);
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002957 if (zero_width == -1)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002958 return FAIL; // pattern not found
Bram Moolenaarba6ba362012-08-08 15:27:57 +02002959
Bram Moolenaarba6ba362012-08-08 15:27:57 +02002960 /*
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002961 * The trick is to first search backwards and then search forward again,
2962 * so that a match at the current cursor position will be correctly
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002963 * captured. When "forward" is false do it the other way around.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002964 */
2965 for (i = 0; i < 2; i++)
2966 {
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002967 if (forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002968 {
2969 if (i == 0 && skip_first_backward)
2970 continue;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002971 dir = i;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02002972 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002973 else
2974 dir = !i;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02002975
2976 flags = 0;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002977 if (!dir && !zero_width)
Bram Moolenaarba6ba362012-08-08 15:27:57 +02002978 flags = SEARCH_END;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002979 end_pos = pos;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02002980
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01002981 // wrapping should not occur in the first round
2982 if (i == 0)
2983 p_ws = FALSE;
2984
Bram Moolenaar5d24a222018-12-23 19:10:09 +01002985 result = searchit(curwin, curbuf, &pos, &end_pos,
2986 (dir ? FORWARD : BACKWARD),
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002987 spats[last_idx].pat, (long) (i ? count : 1),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02002988 SEARCH_KEEP | flags, RE_SEARCH, NULL);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002989
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01002990 p_ws = old_p_ws;
2991
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002992 // First search may fail, but then start searching from the
2993 // beginning of the file (cursor might be on the search match)
2994 // except when Visual mode is active, so that extending the visual
2995 // selection works.
2996 if (i == 1 && !result) // not found, abort
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002997 {
2998 curwin->w_cursor = orig_pos;
2999 if (VIsual_active)
3000 VIsual = save_VIsual;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003001 return FAIL;
3002 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003003 else if (i == 0 && !result)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003004 {
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003005 if (forward)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003006 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003007 // try again from start of buffer
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003008 CLEAR_POS(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003009 }
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003010 else
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003011 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003012 // try again from end of buffer
3013 // searching backwards, so set pos to last line and col
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003014 pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
Bram Moolenaar09168a72012-08-02 21:24:42 +02003015 pos.col = (colnr_T)STRLEN(
3016 ml_get(curwin->w_buffer->b_ml.ml_line_count));
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003017 }
3018 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003019 }
3020
3021 start_pos = pos;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003022
3023 if (!VIsual_active)
3024 VIsual = start_pos;
3025
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003026 // put the cursor after the match
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003027 curwin->w_cursor = end_pos;
Bram Moolenaar453c1922019-10-26 14:42:09 +02003028 if (LT_POS(VIsual, end_pos) && forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003029 {
3030 if (skip_first_backward)
3031 // put the cursor on the start of the match
3032 curwin->w_cursor = pos;
3033 else
3034 // put the cursor on last character of match
3035 dec_cursor();
3036 }
Bram Moolenaar28f224b2020-10-10 16:45:25 +02003037 else if (VIsual_active && LT_POS(curwin->w_cursor, VIsual) && forward)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003038 curwin->w_cursor = pos; // put the cursor on the start of the match
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003039 VIsual_active = TRUE;
3040 VIsual_mode = 'v';
3041
Bram Moolenaarb7633612019-02-10 21:48:25 +01003042 if (*p_sel == 'e')
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003043 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003044 // Correction for exclusive selection depends on the direction.
Bram Moolenaarb7633612019-02-10 21:48:25 +01003045 if (forward && LTOREQ_POS(VIsual, curwin->w_cursor))
3046 inc_cursor();
3047 else if (!forward && LTOREQ_POS(curwin->w_cursor, VIsual))
3048 inc(&VIsual);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003049 }
3050
3051#ifdef FEAT_FOLDING
3052 if (fdo_flags & FDO_SEARCH && KeyTyped)
3053 foldOpenCursor();
3054#endif
3055
3056 may_start_select('c');
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003057 setmouse();
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003058#ifdef FEAT_CLIPBOARD
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003059 // Make sure the clipboard gets updated. Needed because start and
3060 // end are still the same, and the selection needs to be owned
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003061 clip_star.vmode = NUL;
3062#endif
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003063 redraw_curbuf_later(UPD_INVERTED);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003064 showmode();
3065
3066 return OK;
3067}
Bram Moolenaardde0efe2012-08-23 15:53:05 +02003068
Bram Moolenaar071d4272004-06-13 20:20:40 +00003069/*
3070 * return TRUE if line 'lnum' is empty or has white chars only.
3071 */
3072 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003073linewhite(linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003074{
3075 char_u *p;
3076
3077 p = skipwhite(ml_get(lnum));
3078 return (*p == NUL);
3079}
Bram Moolenaar071d4272004-06-13 20:20:40 +00003080
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003081/*
3082 * Add the search count "[3/19]" to "msgbuf".
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003083 * See update_search_stat() for other arguments.
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003084 */
3085 static void
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003086cmdline_search_stat(
3087 int dirc,
3088 pos_T *pos,
3089 pos_T *cursor_pos,
3090 int show_top_bot_msg,
3091 char_u *msgbuf,
3092 int recompute,
3093 int maxcount,
3094 long timeout)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003095{
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003096 searchstat_T stat;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003097
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003098 update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
3099 timeout);
3100 if (stat.cur > 0)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003101 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003102 char t[SEARCH_STAT_BUF_LEN];
Bram Moolenaare2ad8262019-05-24 13:22:22 +02003103 size_t len;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003104
3105#ifdef FEAT_RIGHTLEFT
3106 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
3107 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003108 if (stat.incomplete == 1)
Bram Moolenaarb6cb26f2019-05-07 21:34:37 +02003109 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003110 else if (stat.cnt > maxcount && stat.cur > maxcount)
3111 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3112 maxcount, maxcount);
3113 else if (stat.cnt > maxcount)
3114 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
3115 maxcount, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003116 else
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003117 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3118 stat.cnt, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003119 }
3120 else
3121#endif
3122 {
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003123 if (stat.incomplete == 1)
Bram Moolenaarb6cb26f2019-05-07 21:34:37 +02003124 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003125 else if (stat.cnt > maxcount && stat.cur > maxcount)
3126 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
3127 maxcount, maxcount);
3128 else if (stat.cnt > maxcount)
3129 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
3130 stat.cur, maxcount);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003131 else
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003132 vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
3133 stat.cur, stat.cnt);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003134 }
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003135
3136 len = STRLEN(t);
Bram Moolenaardc6855a2019-05-18 19:26:29 +02003137 if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003138 {
Bram Moolenaar16b58ae2019-09-06 20:40:21 +02003139 mch_memmove(t + 2, t, len);
3140 t[0] = 'W';
3141 t[1] = ' ';
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02003142 len += 2;
3143 }
3144
3145 mch_memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003146 if (dirc == '?' && stat.cur == maxcount + 1)
3147 stat.cur = -1;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003148
Bram Moolenaar984f0312019-05-24 13:11:47 +02003149 // keep the message even after redraw, but don't put in history
3150 msg_hist_off = TRUE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003151 give_warning(msgbuf, FALSE);
Bram Moolenaar984f0312019-05-24 13:11:47 +02003152 msg_hist_off = FALSE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003153 }
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003154}
3155
3156/*
3157 * Add the search count information to "stat".
3158 * "stat" must not be NULL.
3159 * When "recompute" is TRUE always recompute the numbers.
3160 * dirc == 0: don't find the next/previous match (only set the result to "stat")
3161 * dirc == '/': find the next match
3162 * dirc == '?': find the previous match
3163 */
3164 static void
3165update_search_stat(
3166 int dirc,
3167 pos_T *pos,
3168 pos_T *cursor_pos,
3169 searchstat_T *stat,
3170 int recompute,
3171 int maxcount,
Bram Moolenaarf9ca08e2020-06-01 18:56:03 +02003172 long timeout UNUSED)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003173{
3174 int save_ws = p_ws;
3175 int wraparound = FALSE;
3176 pos_T p = (*pos);
Bram Moolenaar14681622020-06-03 22:57:39 +02003177 static pos_T lastpos = {0, 0, 0};
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003178 static int cur = 0;
3179 static int cnt = 0;
3180 static int exact_match = FALSE;
3181 static int incomplete = 0;
3182 static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
3183 static int chgtick = 0;
3184 static char_u *lastpat = NULL;
3185 static buf_T *lbuf = NULL;
3186#ifdef FEAT_RELTIME
3187 proftime_T start;
3188#endif
3189
3190 vim_memset(stat, 0, sizeof(searchstat_T));
3191
3192 if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
3193 {
3194 stat->cur = cur;
3195 stat->cnt = cnt;
3196 stat->exact_match = exact_match;
3197 stat->incomplete = incomplete;
3198 stat->last_maxcount = last_maxcount;
3199 return;
3200 }
3201 last_maxcount = maxcount;
3202
3203 wraparound = ((dirc == '?' && LT_POS(lastpos, p))
3204 || (dirc == '/' && LT_POS(p, lastpos)));
3205
3206 // If anything relevant changed the count has to be recomputed.
3207 // MB_STRNICMP ignores case, but we should not ignore case.
3208 // Unfortunately, there is no MB_STRNICMP function.
3209 // XXX: above comment should be "no MB_STRCMP function" ?
3210 if (!(chgtick == CHANGEDTICK(curbuf)
3211 && MB_STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
3212 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
3213 && EQUAL_POS(lastpos, *cursor_pos)
3214 && lbuf == curbuf) || wraparound || cur < 0
3215 || (maxcount > 0 && cur > maxcount) || recompute)
3216 {
3217 cur = 0;
3218 cnt = 0;
3219 exact_match = FALSE;
3220 incomplete = 0;
3221 CLEAR_POS(&lastpos);
3222 lbuf = curbuf;
3223 }
3224
3225 if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
3226 && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0))
3227 cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
3228 else
3229 {
3230 int done_search = FALSE;
3231 pos_T endpos = {0, 0, 0};
3232
3233 p_ws = FALSE;
3234#ifdef FEAT_RELTIME
3235 if (timeout > 0)
3236 profile_setlimit(timeout, &start);
3237#endif
3238 while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
3239 FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
3240 {
3241 done_search = TRUE;
3242#ifdef FEAT_RELTIME
3243 // Stop after passing the time limit.
3244 if (timeout > 0 && profile_passed_limit(&start))
3245 {
3246 incomplete = 1;
3247 break;
3248 }
3249#endif
3250 cnt++;
3251 if (LTOREQ_POS(lastpos, p))
3252 {
3253 cur = cnt;
Bram Moolenaar57f75a52020-06-02 22:06:21 +02003254 if (LT_POS(p, endpos))
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003255 exact_match = TRUE;
3256 }
3257 fast_breakcheck();
3258 if (maxcount > 0 && cnt > maxcount)
3259 {
3260 incomplete = 2; // max count exceeded
3261 break;
3262 }
3263 }
3264 if (got_int)
3265 cur = -1; // abort
3266 if (done_search)
3267 {
3268 vim_free(lastpat);
3269 lastpat = vim_strsave(spats[last_idx].pat);
3270 chgtick = CHANGEDTICK(curbuf);
3271 lbuf = curbuf;
3272 lastpos = p;
3273 }
3274 }
3275 stat->cur = cur;
3276 stat->cnt = cnt;
3277 stat->exact_match = exact_match;
3278 stat->incomplete = incomplete;
3279 stat->last_maxcount = last_maxcount;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003280 p_ws = save_ws;
3281}
3282
Bram Moolenaar071d4272004-06-13 20:20:40 +00003283#if defined(FEAT_FIND_ID) || defined(PROTO)
Bram Moolenaar409510c2022-06-01 15:23:13 +01003284
3285/*
3286 * Get line "lnum" and copy it into "buf[LSIZE]".
3287 * The copy is made because the regexp may make the line invalid when using a
3288 * mark.
3289 */
3290 static char_u *
3291get_line_and_copy(linenr_T lnum, char_u *buf)
3292{
3293 char_u *line = ml_get(lnum);
3294
3295 vim_strncpy(buf, line, LSIZE - 1);
3296 return buf;
3297}
3298
Bram Moolenaar071d4272004-06-13 20:20:40 +00003299/*
3300 * Find identifiers or defines in included files.
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003301 * If p_ic && compl_status_sol() then ptr must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003302 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00003303 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003304find_pattern_in_path(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003305 char_u *ptr, // pointer to search pattern
3306 int dir UNUSED, // direction of expansion
3307 int len, // length of search pattern
3308 int whole, // match whole words only
3309 int skip_comments, // don't match inside comments
3310 int type, // Type of search; are we looking for a type?
3311 // a macro?
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003312 long count,
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003313 int action, // What to do when we find it
3314 linenr_T start_lnum, // first line to start searching
3315 linenr_T end_lnum) // last line for searching
Bram Moolenaar071d4272004-06-13 20:20:40 +00003316{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003317 SearchedFile *files; // Stack of included files
3318 SearchedFile *bigger; // When we need more space
Bram Moolenaar071d4272004-06-13 20:20:40 +00003319 int max_path_depth = 50;
3320 long match_count = 1;
3321
3322 char_u *pat;
3323 char_u *new_fname;
3324 char_u *curr_fname = curbuf->b_fname;
3325 char_u *prev_fname = NULL;
3326 linenr_T lnum;
3327 int depth;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003328 int depth_displayed; // For type==CHECK_PATH
Bram Moolenaar071d4272004-06-13 20:20:40 +00003329 int old_files;
3330 int already_searched;
3331 char_u *file_line;
3332 char_u *line;
3333 char_u *p;
3334 char_u save_char;
3335 int define_matched;
3336 regmatch_T regmatch;
3337 regmatch_T incl_regmatch;
3338 regmatch_T def_regmatch;
3339 int matched = FALSE;
3340 int did_show = FALSE;
3341 int found = FALSE;
3342 int i;
3343 char_u *already = NULL;
3344 char_u *startp = NULL;
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003345 char_u *inc_opt = NULL;
Bram Moolenaar4033c552017-09-16 20:54:51 +02003346#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003347 win_T *curwin_save = NULL;
3348#endif
3349
3350 regmatch.regprog = NULL;
3351 incl_regmatch.regprog = NULL;
3352 def_regmatch.regprog = NULL;
3353
3354 file_line = alloc(LSIZE);
3355 if (file_line == NULL)
3356 return;
3357
Bram Moolenaar071d4272004-06-13 20:20:40 +00003358 if (type != CHECK_PATH && type != FIND_DEFINE
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003359 // when CONT_SOL is set compare "ptr" with the beginning of the
3360 // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
3361 && !compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003362 {
3363 pat = alloc(len + 5);
3364 if (pat == NULL)
3365 goto fpip_end;
3366 sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003367 // ignore case according to p_ic, p_scs and pat
Bram Moolenaar071d4272004-06-13 20:20:40 +00003368 regmatch.rm_ic = ignorecase(pat);
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003369 regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003370 vim_free(pat);
3371 if (regmatch.regprog == NULL)
3372 goto fpip_end;
3373 }
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003374 inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
3375 if (*inc_opt != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003376 {
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003377 incl_regmatch.regprog = vim_regcomp(inc_opt,
3378 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003379 if (incl_regmatch.regprog == NULL)
3380 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003381 incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003382 }
3383 if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL))
3384 {
3385 def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003386 ? p_def : curbuf->b_p_def,
3387 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003388 if (def_regmatch.regprog == NULL)
3389 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003390 def_regmatch.rm_ic = FALSE; // don't ignore case in define pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003391 }
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003392 files = lalloc_clear(max_path_depth * sizeof(SearchedFile), TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003393 if (files == NULL)
3394 goto fpip_end;
3395 old_files = max_path_depth;
3396 depth = depth_displayed = -1;
3397
3398 lnum = start_lnum;
3399 if (end_lnum > curbuf->b_ml.ml_line_count)
3400 end_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003401 if (lnum > end_lnum) // do at least one line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003402 lnum = end_lnum;
Bram Moolenaar409510c2022-06-01 15:23:13 +01003403 line = get_line_and_copy(lnum, file_line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003404
3405 for (;;)
3406 {
3407 if (incl_regmatch.regprog != NULL
3408 && vim_regexec(&incl_regmatch, line, (colnr_T)0))
3409 {
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003410 char_u *p_fname = (curr_fname == curbuf->b_fname)
3411 ? curbuf->b_ffname : curr_fname;
3412
3413 if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003414 // Use text from '\zs' to '\ze' (or end) of 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003415 new_fname = find_file_name_in_path(incl_regmatch.startp[0],
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003416 (int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]),
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003417 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname);
3418 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003419 // Use text after match with 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003420 new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
Bram Moolenaard1f56e62006-02-22 21:25:37 +00003421 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003422 already_searched = FALSE;
3423 if (new_fname != NULL)
3424 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003425 // Check whether we have already searched in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003426 for (i = 0;; i++)
3427 {
3428 if (i == depth + 1)
3429 i = old_files;
3430 if (i == max_path_depth)
3431 break;
Bram Moolenaar99499b12019-05-23 21:35:48 +02003432 if (fullpathcmp(new_fname, files[i].name, TRUE, TRUE)
3433 & FPC_SAME)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003434 {
Dominique Pelle7765f5c2022-04-10 11:26:53 +01003435 if (type != CHECK_PATH
3436 && action == ACTION_SHOW_ALL
3437 && files[i].matched)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003438 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003439 msg_putchar('\n'); // cursor below last one
3440 if (!got_int) // don't display if 'q'
3441 // typed at "--more--"
3442 // message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003443 {
3444 msg_home_replace_hl(new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003445 msg_puts(_(" (includes previously listed match)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003446 prev_fname = NULL;
3447 }
3448 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01003449 VIM_CLEAR(new_fname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003450 already_searched = TRUE;
3451 break;
3452 }
3453 }
3454 }
3455
3456 if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
3457 || (new_fname == NULL && !already_searched)))
3458 {
3459 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003460 msg_putchar('\n'); // cursor below last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00003461 else
3462 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003463 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar32526b32019-01-19 17:43:09 +01003464 msg_puts_title(_("--- Included files "));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003465 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003466 msg_puts_title(_("not found "));
3467 msg_puts_title(_("in path ---\n"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003468 }
3469 did_show = TRUE;
3470 while (depth_displayed < depth && !got_int)
3471 {
3472 ++depth_displayed;
3473 for (i = 0; i < depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003474 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003475 msg_home_replace(files[depth_displayed].name);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003476 msg_puts(" -->\n");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003477 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003478 if (!got_int) // don't display if 'q' typed
3479 // for "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003480 {
3481 for (i = 0; i <= depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003482 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003483 if (new_fname != NULL)
3484 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003485 // using "new_fname" is more reliable, e.g., when
3486 // 'includeexpr' is set.
Bram Moolenaar8820b482017-03-16 17:23:31 +01003487 msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003488 }
3489 else
3490 {
3491 /*
3492 * Isolate the file name.
3493 * Include the surrounding "" or <> if present.
3494 */
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003495 if (inc_opt != NULL
3496 && strstr((char *)inc_opt, "\\zs") != NULL)
3497 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003498 // pattern contains \zs, use the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003499 p = incl_regmatch.startp[0];
3500 i = (int)(incl_regmatch.endp[0]
3501 - incl_regmatch.startp[0]);
3502 }
3503 else
3504 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003505 // find the file name after the end of the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003506 for (p = incl_regmatch.endp[0];
3507 *p && !vim_isfilec(*p); p++)
3508 ;
3509 for (i = 0; vim_isfilec(p[i]); i++)
3510 ;
3511 }
3512
Bram Moolenaar071d4272004-06-13 20:20:40 +00003513 if (i == 0)
3514 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003515 // Nothing found, use the rest of the line.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003516 p = incl_regmatch.endp[0];
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00003517 i = (int)STRLEN(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003518 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003519 // Avoid checking before the start of the line, can
3520 // happen if \zs appears in the regexp.
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003521 else if (p > line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003522 {
3523 if (p[-1] == '"' || p[-1] == '<')
3524 {
3525 --p;
3526 ++i;
3527 }
3528 if (p[i] == '"' || p[i] == '>')
3529 ++i;
3530 }
3531 save_char = p[i];
3532 p[i] = NUL;
Bram Moolenaar8820b482017-03-16 17:23:31 +01003533 msg_outtrans_attr(p, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003534 p[i] = save_char;
3535 }
3536
3537 if (new_fname == NULL && action == ACTION_SHOW_ALL)
3538 {
3539 if (already_searched)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003540 msg_puts(_(" (Already listed)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003541 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01003542 msg_puts(_(" NOT FOUND"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003543 }
3544 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003545 out_flush(); // output each line directly
Bram Moolenaar071d4272004-06-13 20:20:40 +00003546 }
3547
3548 if (new_fname != NULL)
3549 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003550 // Push the new file onto the file stack
Bram Moolenaar071d4272004-06-13 20:20:40 +00003551 if (depth + 1 == old_files)
3552 {
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003553 bigger = ALLOC_MULT(SearchedFile, max_path_depth * 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003554 if (bigger != NULL)
3555 {
3556 for (i = 0; i <= depth; i++)
3557 bigger[i] = files[i];
3558 for (i = depth + 1; i < old_files + max_path_depth; i++)
3559 {
3560 bigger[i].fp = NULL;
3561 bigger[i].name = NULL;
3562 bigger[i].lnum = 0;
3563 bigger[i].matched = FALSE;
3564 }
3565 for (i = old_files; i < max_path_depth; i++)
3566 bigger[i + max_path_depth] = files[i];
3567 old_files += max_path_depth;
3568 max_path_depth *= 2;
3569 vim_free(files);
3570 files = bigger;
3571 }
3572 }
3573 if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
3574 == NULL)
3575 vim_free(new_fname);
3576 else
3577 {
3578 if (++depth == old_files)
3579 {
3580 /*
3581 * lalloc() for 'bigger' must have failed above. We
3582 * will forget one of our already visited files now.
3583 */
3584 vim_free(files[old_files].name);
3585 ++old_files;
3586 }
3587 files[depth].name = curr_fname = new_fname;
3588 files[depth].lnum = 0;
3589 files[depth].matched = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003590 if (action == ACTION_EXPAND)
3591 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003592 msg_hist_off = TRUE; // reset in msg_trunc_attr()
Bram Moolenaar555b2802005-05-19 21:08:39 +00003593 vim_snprintf((char*)IObuff, IOSIZE,
3594 _("Scanning included file: %s"),
3595 (char *)new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003596 msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003597 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003598 else if (p_verbose >= 5)
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003599 {
3600 verbose_enter();
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003601 smsg(_("Searching included file %s"),
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003602 (char *)new_fname);
3603 verbose_leave();
3604 }
3605
Bram Moolenaar071d4272004-06-13 20:20:40 +00003606 }
3607 }
3608 }
3609 else
3610 {
3611 /*
3612 * Check if the line is a define (type == FIND_DEFINE)
3613 */
3614 p = line;
3615search_line:
3616 define_matched = FALSE;
3617 if (def_regmatch.regprog != NULL
3618 && vim_regexec(&def_regmatch, line, (colnr_T)0))
3619 {
3620 /*
3621 * Pattern must be first identifier after 'define', so skip
3622 * to that position before checking for match of pattern. Also
3623 * don't let it match beyond the end of this identifier.
3624 */
3625 p = def_regmatch.endp[0];
3626 while (*p && !vim_iswordc(*p))
3627 p++;
3628 define_matched = TRUE;
3629 }
3630
3631 /*
3632 * Look for a match. Don't do this if we are looking for a
3633 * define and this line didn't match define_prog above.
3634 */
3635 if (def_regmatch.regprog == NULL || define_matched)
3636 {
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003637 if (define_matched || compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003638 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003639 // compare the first "len" chars from "ptr"
Bram Moolenaar071d4272004-06-13 20:20:40 +00003640 startp = skipwhite(p);
3641 if (p_ic)
3642 matched = !MB_STRNICMP(startp, ptr, len);
3643 else
3644 matched = !STRNCMP(startp, ptr, len);
3645 if (matched && define_matched && whole
3646 && vim_iswordc(startp[len]))
3647 matched = FALSE;
3648 }
3649 else if (regmatch.regprog != NULL
3650 && vim_regexec(&regmatch, line, (colnr_T)(p - line)))
3651 {
3652 matched = TRUE;
3653 startp = regmatch.startp[0];
3654 /*
3655 * Check if the line is not a comment line (unless we are
3656 * looking for a define). A line starting with "# define"
3657 * is not considered to be a comment line.
3658 */
3659 if (!define_matched && skip_comments)
3660 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003661 if ((*line != '#' ||
3662 STRNCMP(skipwhite(line + 1), "define", 6) != 0)
Bram Moolenaar81340392012-06-06 16:12:59 +02003663 && get_leader_len(line, NULL, FALSE, TRUE))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003664 matched = FALSE;
3665
3666 /*
3667 * Also check for a "/ *" or "/ /" before the match.
3668 * Skips lines like "int backwards; / * normal index
3669 * * /" when looking for "normal".
3670 * Note: Doesn't skip "/ *" in comments.
3671 */
3672 p = skipwhite(line);
3673 if (matched
3674 || (p[0] == '/' && p[1] == '*') || p[0] == '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00003675 for (p = line; *p && p < startp; ++p)
3676 {
3677 if (matched
3678 && p[0] == '/'
3679 && (p[1] == '*' || p[1] == '/'))
3680 {
3681 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003682 // After "//" all text is comment
Bram Moolenaar071d4272004-06-13 20:20:40 +00003683 if (p[1] == '/')
3684 break;
3685 ++p;
3686 }
3687 else if (!matched && p[0] == '*' && p[1] == '/')
3688 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003689 // Can find match after "* /".
Bram Moolenaar071d4272004-06-13 20:20:40 +00003690 matched = TRUE;
3691 ++p;
3692 }
3693 }
3694 }
3695 }
3696 }
3697 }
3698 if (matched)
3699 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003700 if (action == ACTION_EXPAND)
3701 {
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003702 int cont_s_ipos = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003703 int add_r;
3704 char_u *aux;
3705
3706 if (depth == -1 && lnum == curwin->w_cursor.lnum)
3707 break;
3708 found = TRUE;
3709 aux = p = startp;
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003710 if (compl_status_adding())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003711 {
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003712 p += ins_compl_len();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003713 if (vim_iswordp(p))
3714 goto exit_matched;
3715 p = find_word_start(p);
3716 }
3717 p = find_word_end(p);
3718 i = (int)(p - aux);
3719
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003720 if (compl_status_adding() && i == ins_compl_len())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003721 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003722 // IOSIZE > compl_length, so the STRNCPY works
Bram Moolenaar071d4272004-06-13 20:20:40 +00003723 STRNCPY(IObuff, aux, i);
Bram Moolenaar89d40322006-08-29 15:30:07 +00003724
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003725 // Get the next line: when "depth" < 0 from the current
3726 // buffer, otherwise from the included file. Jump to
3727 // exit_matched when past the last line.
Bram Moolenaar89d40322006-08-29 15:30:07 +00003728 if (depth < 0)
3729 {
3730 if (lnum >= end_lnum)
3731 goto exit_matched;
Bram Moolenaar409510c2022-06-01 15:23:13 +01003732 line = get_line_and_copy(++lnum, file_line);
Bram Moolenaar89d40322006-08-29 15:30:07 +00003733 }
3734 else if (vim_fgets(line = file_line,
3735 LSIZE, files[depth].fp))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003736 goto exit_matched;
3737
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003738 // we read a line, set "already" to check this "line" later
3739 // if depth >= 0 we'll increase files[depth].lnum far
Bram Moolenaar8e7d6222020-12-18 19:49:56 +01003740 // below -- Acevedo
Bram Moolenaar071d4272004-06-13 20:20:40 +00003741 already = aux = p = skipwhite(line);
3742 p = find_word_start(p);
3743 p = find_word_end(p);
3744 if (p > aux)
3745 {
3746 if (*aux != ')' && IObuff[i-1] != TAB)
3747 {
3748 if (IObuff[i-1] != ' ')
3749 IObuff[i++] = ' ';
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003750 // IObuf =~ "\(\k\|\i\).* ", thus i >= 2
Bram Moolenaar071d4272004-06-13 20:20:40 +00003751 if (p_js
3752 && (IObuff[i-2] == '.'
3753 || (vim_strchr(p_cpo, CPO_JOINSP) == NULL
3754 && (IObuff[i-2] == '?'
3755 || IObuff[i-2] == '!'))))
3756 IObuff[i++] = ' ';
3757 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003758 // copy as much as possible of the new word
Bram Moolenaar071d4272004-06-13 20:20:40 +00003759 if (p - aux >= IOSIZE - i)
3760 p = aux + IOSIZE - i - 1;
3761 STRNCPY(IObuff + i, aux, p - aux);
3762 i += (int)(p - aux);
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003763 cont_s_ipos = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003764 }
3765 IObuff[i] = NUL;
3766 aux = IObuff;
3767
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003768 if (i == ins_compl_len())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003769 goto exit_matched;
3770 }
3771
Bram Moolenaare8c3a142006-08-29 14:30:35 +00003772 add_r = ins_compl_add_infercase(aux, i, p_ic,
Bram Moolenaar071d4272004-06-13 20:20:40 +00003773 curr_fname == curbuf->b_fname ? NULL : curr_fname,
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003774 dir, cont_s_ipos);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003775 if (add_r == OK)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003776 // if dir was BACKWARD then honor it just once
Bram Moolenaar071d4272004-06-13 20:20:40 +00003777 dir = FORWARD;
Bram Moolenaar572cb562005-08-05 21:35:02 +00003778 else if (add_r == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003779 break;
3780 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003781 else if (action == ACTION_SHOW_ALL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003782 {
3783 found = TRUE;
3784 if (!did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003785 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003786 if (curr_fname != prev_fname)
3787 {
3788 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003789 msg_putchar('\n'); // cursor below last one
3790 if (!got_int) // don't display if 'q' typed
3791 // at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003792 msg_home_replace_hl(curr_fname);
3793 prev_fname = curr_fname;
3794 }
3795 did_show = TRUE;
3796 if (!got_int)
3797 show_pat_in_path(line, type, TRUE, action,
3798 (depth == -1) ? NULL : files[depth].fp,
3799 (depth == -1) ? &lnum : &files[depth].lnum,
3800 match_count++);
3801
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003802 // Set matched flag for this file and all the ones that
3803 // include it
Bram Moolenaar071d4272004-06-13 20:20:40 +00003804 for (i = 0; i <= depth; ++i)
3805 files[i].matched = TRUE;
3806 }
3807 else if (--count <= 0)
3808 {
3809 found = TRUE;
3810 if (depth == -1 && lnum == curwin->w_cursor.lnum
Bram Moolenaar4033c552017-09-16 20:54:51 +02003811#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003812 && g_do_tagpreview == 0
3813#endif
3814 )
Bram Moolenaarac78dd42022-01-02 19:25:26 +00003815 emsg(_(e_match_is_on_current_line));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003816 else if (action == ACTION_SHOW)
3817 {
3818 show_pat_in_path(line, type, did_show, action,
3819 (depth == -1) ? NULL : files[depth].fp,
3820 (depth == -1) ? &lnum : &files[depth].lnum, 1L);
3821 did_show = TRUE;
3822 }
3823 else
3824 {
3825#ifdef FEAT_GUI
3826 need_mouse_correct = TRUE;
3827#endif
Bram Moolenaar4033c552017-09-16 20:54:51 +02003828#if defined(FEAT_QUICKFIX)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003829 // ":psearch" uses the preview window
Bram Moolenaar071d4272004-06-13 20:20:40 +00003830 if (g_do_tagpreview != 0)
3831 {
3832 curwin_save = curwin;
Bram Moolenaar576a4a62019-08-18 15:25:17 +02003833 prepare_tagpreview(TRUE, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003834 }
3835#endif
3836 if (action == ACTION_SPLIT)
3837 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003838 if (win_split(0, 0) == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003839 break;
Bram Moolenaar3368ea22010-09-21 16:56:35 +02003840 RESET_BINDING(curwin);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003841 }
3842 if (depth == -1)
3843 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003844 // match in current file
Bram Moolenaar4033c552017-09-16 20:54:51 +02003845#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003846 if (g_do_tagpreview != 0)
3847 {
Bram Moolenaar92bb83e2021-02-03 23:04:46 +01003848 if (!win_valid(curwin_save))
3849 break;
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003850 if (!GETFILE_SUCCESS(getfile(
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003851 curwin_save->w_buffer->b_fnum, NULL,
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003852 NULL, TRUE, lnum, FALSE)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003853 break; // failed to jump to file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003854 }
3855 else
3856#endif
3857 setpcmark();
3858 curwin->w_cursor.lnum = lnum;
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003859 check_cursor();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003860 }
3861 else
3862 {
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003863 if (!GETFILE_SUCCESS(getfile(
3864 0, files[depth].name, NULL, TRUE,
3865 files[depth].lnum, FALSE)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003866 break; // failed to jump to file
3867 // autocommands may have changed the lnum, we don't
3868 // want that here
Bram Moolenaar071d4272004-06-13 20:20:40 +00003869 curwin->w_cursor.lnum = files[depth].lnum;
3870 }
3871 }
3872 if (action != ACTION_SHOW)
3873 {
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003874 curwin->w_cursor.col = (colnr_T)(startp - line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003875 curwin->w_set_curswant = TRUE;
3876 }
3877
Bram Moolenaar4033c552017-09-16 20:54:51 +02003878#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003879 if (g_do_tagpreview != 0
Bram Moolenaar997fb4b2006-02-17 21:53:23 +00003880 && curwin != curwin_save && win_valid(curwin_save))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003881 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003882 // Return cursor to where we were
Bram Moolenaar071d4272004-06-13 20:20:40 +00003883 validate_cursor();
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003884 redraw_later(UPD_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003885 win_enter(curwin_save, TRUE);
3886 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01003887# ifdef FEAT_PROP_POPUP
Bram Moolenaar1b6d9c42019-08-05 21:52:04 +02003888 else if (WIN_IS_POPUP(curwin))
3889 // can't keep focus in popup window
3890 win_enter(firstwin, TRUE);
3891# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003892#endif
3893 break;
3894 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003895exit_matched:
Bram Moolenaar071d4272004-06-13 20:20:40 +00003896 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003897 // look for other matches in the rest of the line if we
3898 // are not at the end of it already
Bram Moolenaar071d4272004-06-13 20:20:40 +00003899 if (def_regmatch.regprog == NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00003900 && action == ACTION_EXPAND
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003901 && !compl_status_sol()
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003902 && *startp != NUL
Bram Moolenaar1614a142019-10-06 22:00:13 +02003903 && *(p = startp + mb_ptr2len(startp)) != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003904 goto search_line;
3905 }
3906 line_breakcheck();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003907 if (action == ACTION_EXPAND)
Bram Moolenaar472e8592016-10-15 17:06:47 +02003908 ins_compl_check_keys(30, FALSE);
Bram Moolenaar7591bb32019-03-30 13:53:47 +01003909 if (got_int || ins_compl_interrupted())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003910 break;
3911
3912 /*
3913 * Read the next line. When reading an included file and encountering
3914 * end-of-file, close the file and continue in the file that included
3915 * it.
3916 */
3917 while (depth >= 0 && !already
3918 && vim_fgets(line = file_line, LSIZE, files[depth].fp))
3919 {
3920 fclose(files[depth].fp);
3921 --old_files;
3922 files[old_files].name = files[depth].name;
3923 files[old_files].matched = files[depth].matched;
3924 --depth;
3925 curr_fname = (depth == -1) ? curbuf->b_fname
3926 : files[depth].name;
3927 if (depth < depth_displayed)
3928 depth_displayed = depth;
3929 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003930 if (depth >= 0) // we could read the line
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003931 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003932 files[depth].lnum++;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003933 // Remove any CR and LF from the line.
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003934 i = (int)STRLEN(line);
3935 if (i > 0 && line[i - 1] == '\n')
3936 line[--i] = NUL;
3937 if (i > 0 && line[i - 1] == '\r')
3938 line[--i] = NUL;
3939 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003940 else if (!already)
3941 {
3942 if (++lnum > end_lnum)
3943 break;
Bram Moolenaar409510c2022-06-01 15:23:13 +01003944 line = get_line_and_copy(lnum, file_line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003945 }
3946 already = NULL;
3947 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003948 // End of big for (;;) loop.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003949
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003950 // Close any files that are still open.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003951 for (i = 0; i <= depth; i++)
3952 {
3953 fclose(files[i].fp);
3954 vim_free(files[i].name);
3955 }
3956 for (i = old_files; i < max_path_depth; i++)
3957 vim_free(files[i].name);
3958 vim_free(files);
3959
3960 if (type == CHECK_PATH)
3961 {
3962 if (!did_show)
3963 {
3964 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003965 msg(_("All included files were found"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003966 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01003967 msg(_("No included files"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003968 }
3969 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003970 else if (!found && action != ACTION_EXPAND)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003971 {
Bram Moolenaar7591bb32019-03-30 13:53:47 +01003972 if (got_int || ins_compl_interrupted())
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00003973 emsg(_(e_interrupted));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003974 else if (type == FIND_DEFINE)
Bram Moolenaarac78dd42022-01-02 19:25:26 +00003975 emsg(_(e_couldnt_find_definition));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003976 else
Bram Moolenaarac78dd42022-01-02 19:25:26 +00003977 emsg(_(e_couldnt_find_pattern));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003978 }
3979 if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
3980 msg_end();
3981
3982fpip_end:
3983 vim_free(file_line);
Bram Moolenaar473de612013-06-08 18:19:48 +02003984 vim_regfree(regmatch.regprog);
3985 vim_regfree(incl_regmatch.regprog);
3986 vim_regfree(def_regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003987}
3988
3989 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003990show_pat_in_path(
3991 char_u *line,
3992 int type,
3993 int did_show,
3994 int action,
3995 FILE *fp,
3996 linenr_T *lnum,
3997 long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003998{
3999 char_u *p;
4000
4001 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004002 msg_putchar('\n'); // cursor below last one
Bram Moolenaar91170f82006-05-05 21:15:17 +00004003 else if (!msg_silent)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004004 gotocmdline(TRUE); // cursor at status line
4005 if (got_int) // 'q' typed at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00004006 return;
4007 for (;;)
4008 {
4009 p = line + STRLEN(line) - 1;
4010 if (fp != NULL)
4011 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004012 // We used fgets(), so get rid of newline at end
Bram Moolenaar071d4272004-06-13 20:20:40 +00004013 if (p >= line && *p == '\n')
4014 --p;
4015 if (p >= line && *p == '\r')
4016 --p;
4017 *(p + 1) = NUL;
4018 }
4019 if (action == ACTION_SHOW_ALL)
4020 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004021 sprintf((char *)IObuff, "%3ld: ", count); // show match nr
Bram Moolenaar32526b32019-01-19 17:43:09 +01004022 msg_puts((char *)IObuff);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004023 sprintf((char *)IObuff, "%4ld", *lnum); // show line nr
4024 // Highlight line numbers
Bram Moolenaar32526b32019-01-19 17:43:09 +01004025 msg_puts_attr((char *)IObuff, HL_ATTR(HLF_N));
4026 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00004027 }
Bram Moolenaar26a60b42005-02-22 08:49:11 +00004028 msg_prt_line(line, FALSE);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004029 out_flush(); // show one line at a time
Bram Moolenaar071d4272004-06-13 20:20:40 +00004030
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004031 // Definition continues until line that doesn't end with '\'
Bram Moolenaar071d4272004-06-13 20:20:40 +00004032 if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
4033 break;
4034
4035 if (fp != NULL)
4036 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004037 if (vim_fgets(line, LSIZE, fp)) // end of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00004038 break;
4039 ++*lnum;
4040 }
4041 else
4042 {
4043 if (++*lnum > curbuf->b_ml.ml_line_count)
4044 break;
4045 line = ml_get(*lnum);
4046 }
4047 msg_putchar('\n');
4048 }
4049}
4050#endif
4051
4052#ifdef FEAT_VIMINFO
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004053/*
4054 * Return the last used search pattern at "idx".
4055 */
Bram Moolenaarc3328162019-07-23 22:15:25 +02004056 spat_T *
4057get_spat(int idx)
4058{
4059 return &spats[idx];
4060}
4061
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004062/*
4063 * Return the last used search pattern index.
4064 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004065 int
Bram Moolenaarc3328162019-07-23 22:15:25 +02004066get_spat_last_idx(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004067{
Bram Moolenaarc3328162019-07-23 22:15:25 +02004068 return last_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004069}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004070#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004071
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004072#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004073/*
4074 * "searchcount()" function
4075 */
4076 void
4077f_searchcount(typval_T *argvars, typval_T *rettv)
4078{
4079 pos_T pos = curwin->w_cursor;
4080 char_u *pattern = NULL;
4081 int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
4082 long timeout = SEARCH_STAT_DEF_TIMEOUT;
Bram Moolenaar4140c4f2020-09-05 23:16:00 +02004083 int recompute = TRUE;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004084 searchstat_T stat;
4085
4086 if (rettv_dict_alloc(rettv) == FAIL)
4087 return;
4088
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02004089 if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
4090 return;
4091
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004092 if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
4093 recompute = TRUE;
4094
4095 if (argvars[0].v_type != VAR_UNKNOWN)
4096 {
Bram Moolenaar14681622020-06-03 22:57:39 +02004097 dict_T *dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004098 dictitem_T *di;
4099 listitem_T *li;
4100 int error = FALSE;
4101
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01004102 if (check_for_nonnull_dict_arg(argvars, 0) == FAIL)
Bram Moolenaar14681622020-06-03 22:57:39 +02004103 return;
Bram Moolenaar14681622020-06-03 22:57:39 +02004104 dict = argvars[0].vval.v_dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004105 di = dict_find(dict, (char_u *)"timeout", -1);
4106 if (di != NULL)
4107 {
4108 timeout = (long)tv_get_number_chk(&di->di_tv, &error);
4109 if (error)
4110 return;
4111 }
4112 di = dict_find(dict, (char_u *)"maxcount", -1);
4113 if (di != NULL)
4114 {
4115 maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
4116 if (error)
4117 return;
4118 }
Bram Moolenaard61efa52022-07-23 09:52:04 +01004119 recompute = dict_get_bool(dict, "recompute", recompute);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004120 di = dict_find(dict, (char_u *)"pattern", -1);
4121 if (di != NULL)
4122 {
4123 pattern = tv_get_string_chk(&di->di_tv);
4124 if (pattern == NULL)
4125 return;
4126 }
4127 di = dict_find(dict, (char_u *)"pos", -1);
4128 if (di != NULL)
4129 {
4130 if (di->di_tv.v_type != VAR_LIST)
4131 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004132 semsg(_(e_invalid_argument_str), "pos");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004133 return;
4134 }
4135 if (list_len(di->di_tv.vval.v_list) != 3)
4136 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004137 semsg(_(e_invalid_argument_str), "List format should be [lnum, col, off]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004138 return;
4139 }
4140 li = list_find(di->di_tv.vval.v_list, 0L);
4141 if (li != NULL)
4142 {
4143 pos.lnum = tv_get_number_chk(&li->li_tv, &error);
4144 if (error)
4145 return;
4146 }
4147 li = list_find(di->di_tv.vval.v_list, 1L);
4148 if (li != NULL)
4149 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004150 pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004151 if (error)
4152 return;
4153 }
4154 li = list_find(di->di_tv.vval.v_list, 2L);
4155 if (li != NULL)
4156 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004157 pos.coladd = tv_get_number_chk(&li->li_tv, &error);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004158 if (error)
4159 return;
4160 }
4161 }
4162 }
4163
4164 save_last_search_pattern();
Christian Brabandt6dd74242022-02-14 12:44:32 +00004165#ifdef FEAT_SEARCH_EXTRA
4166 save_incsearch_state();
4167#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004168 if (pattern != NULL)
4169 {
4170 if (*pattern == NUL)
4171 goto the_end;
Bram Moolenaar109aece2020-06-01 19:08:54 +02004172 vim_free(spats[last_idx].pat);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004173 spats[last_idx].pat = vim_strsave(pattern);
4174 }
4175 if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
4176 goto the_end; // the previous pattern was never defined
4177
4178 update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
4179
4180 dict_add_number(rettv->vval.v_dict, "current", stat.cur);
4181 dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
4182 dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
4183 dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
4184 dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
4185
4186the_end:
4187 restore_last_search_pattern();
Christian Brabandt6dd74242022-02-14 12:44:32 +00004188#ifdef FEAT_SEARCH_EXTRA
4189 restore_incsearch_state();
4190#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004191}
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004192#endif
Bram Moolenaar635414d2020-09-11 22:25:15 +02004193
4194/*
4195 * Fuzzy string matching
4196 *
4197 * Ported from the lib_fts library authored by Forrest Smith.
4198 * https://github.com/forrestthewoods/lib_fts/tree/master/code
4199 *
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004200 * The following blog describes the fuzzy matching algorithm:
Bram Moolenaar635414d2020-09-11 22:25:15 +02004201 * https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
4202 *
4203 * Each matching string is assigned a score. The following factors are checked:
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004204 * - Matched letter
4205 * - Unmatched letter
4206 * - Consecutively matched letters
4207 * - Proximity to start
4208 * - Letter following a separator (space, underscore)
4209 * - Uppercase letter following lowercase (aka CamelCase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004210 *
4211 * Matched letters are good. Unmatched letters are bad. Matching near the start
4212 * is good. Matching the first letter in the middle of a phrase is good.
4213 * Matching the uppercase letters in camel case entries is good.
4214 *
4215 * The score assigned for each factor is explained below.
4216 * File paths are different from file names. File extensions may be ignorable.
4217 * Single words care about consecutive matches but not separators or camel
4218 * case.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004219 * Score starts at 100
Bram Moolenaar635414d2020-09-11 22:25:15 +02004220 * Matched letter: +0 points
4221 * Unmatched letter: -1 point
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004222 * Consecutive match bonus: +15 points
4223 * First letter bonus: +15 points
4224 * Separator bonus: +30 points
4225 * Camel case bonus: +30 points
4226 * Unmatched leading letter: -5 points (max: -15)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004227 *
4228 * There is some nuance to this. Scores don’t have an intrinsic meaning. The
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004229 * score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
Bram Moolenaar635414d2020-09-11 22:25:15 +02004230 * lower minimum score due to unmatched letter penalty. Longer search patterns
4231 * have a higher maximum score due to match bonuses.
4232 *
4233 * Separator and camel case bonus is worth a LOT. Consecutive matches are worth
4234 * quite a bit.
4235 *
4236 * There is a penalty if you DON’T match the first three letters. Which
4237 * effectively rewards matching near the start. However there’s no difference
4238 * in matching between the middle and end.
4239 *
4240 * There is not an explicit bonus for an exact match. Unmatched letters receive
4241 * a penalty. So shorter strings and closer matches are worth more.
4242 */
4243typedef struct
4244{
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004245 int idx; // used for stable sort
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004246 listitem_T *item;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004247 int score;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004248 list_T *lmatchpos;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004249} fuzzyItem_T;
4250
Bram Moolenaare9f9f162020-10-20 19:01:30 +02004251// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
4252// matching a whole word is preferred.
4253#define SEQUENTIAL_BONUS 40
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004254// bonus if match occurs after a path separator
4255#define PATH_SEPARATOR_BONUS 30
4256// bonus if match occurs after a word separator
4257#define WORD_SEPARATOR_BONUS 25
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004258// bonus if match is uppercase and prev is lower
4259#define CAMEL_BONUS 30
4260// bonus if the first letter is matched
4261#define FIRST_LETTER_BONUS 15
4262// penalty applied for every letter in str before the first match
kylo252ae6f1d82022-02-16 19:24:07 +00004263#define LEADING_LETTER_PENALTY (-5)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004264// maximum penalty for leading letters
kylo252ae6f1d82022-02-16 19:24:07 +00004265#define MAX_LEADING_LETTER_PENALTY (-15)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004266// penalty for every letter that doesn't match
kylo252ae6f1d82022-02-16 19:24:07 +00004267#define UNMATCHED_LETTER_PENALTY (-1)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004268// penalty for gap in matching positions (-2 * k)
kylo252ae6f1d82022-02-16 19:24:07 +00004269#define GAP_PENALTY (-2)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004270// Score for a string that doesn't fuzzy match the pattern
kylo252ae6f1d82022-02-16 19:24:07 +00004271#define SCORE_NONE (-9999)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004272
4273#define FUZZY_MATCH_RECURSION_LIMIT 10
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004274
4275/*
4276 * Compute a score for a fuzzy matched string. The matching character locations
4277 * are in 'matches'.
4278 */
4279 static int
4280fuzzy_match_compute_score(
4281 char_u *str,
4282 int strSz,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004283 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004284 int numMatches)
4285{
4286 int score;
4287 int penalty;
4288 int unmatched;
4289 int i;
4290 char_u *p = str;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004291 int_u sidx = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004292
4293 // Initialize score
4294 score = 100;
4295
4296 // Apply leading letter penalty
4297 penalty = LEADING_LETTER_PENALTY * matches[0];
4298 if (penalty < MAX_LEADING_LETTER_PENALTY)
4299 penalty = MAX_LEADING_LETTER_PENALTY;
4300 score += penalty;
4301
4302 // Apply unmatched penalty
4303 unmatched = strSz - numMatches;
4304 score += UNMATCHED_LETTER_PENALTY * unmatched;
4305
4306 // Apply ordering bonuses
4307 for (i = 0; i < numMatches; ++i)
4308 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004309 int_u currIdx = matches[i];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004310
4311 if (i > 0)
4312 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004313 int_u prevIdx = matches[i - 1];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004314
4315 // Sequential
4316 if (currIdx == (prevIdx + 1))
4317 score += SEQUENTIAL_BONUS;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004318 else
4319 score += GAP_PENALTY * (currIdx - prevIdx);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004320 }
4321
4322 // Check for bonuses based on neighbor character value
4323 if (currIdx > 0)
4324 {
4325 // Camel case
Bram Moolenaarc53e9c52020-09-22 22:08:32 +02004326 int neighbor = ' ';
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004327 int curr;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004328
4329 if (has_mbyte)
4330 {
4331 while (sidx < currIdx)
4332 {
4333 neighbor = (*mb_ptr2char)(p);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004334 MB_PTR_ADV(p);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004335 sidx++;
4336 }
4337 curr = (*mb_ptr2char)(p);
4338 }
4339 else
4340 {
4341 neighbor = str[currIdx - 1];
4342 curr = str[currIdx];
4343 }
4344
4345 if (vim_islower(neighbor) && vim_isupper(curr))
4346 score += CAMEL_BONUS;
4347
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004348 // Bonus if the match follows a separator character
4349 if (neighbor == '/' || neighbor == '\\')
4350 score += PATH_SEPARATOR_BONUS;
4351 else if (neighbor == ' ' || neighbor == '_')
4352 score += WORD_SEPARATOR_BONUS;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004353 }
4354 else
4355 {
4356 // First letter
4357 score += FIRST_LETTER_BONUS;
4358 }
4359 }
4360 return score;
4361}
4362
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004363/*
4364 * Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
4365 * Return the number of matching characters.
4366 */
Bram Moolenaar635414d2020-09-11 22:25:15 +02004367 static int
4368fuzzy_match_recursive(
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004369 char_u *fuzpat,
4370 char_u *str,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004371 int_u strIdx,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004372 int *outScore,
4373 char_u *strBegin,
4374 int strLen,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004375 int_u *srcMatches,
4376 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004377 int maxMatches,
4378 int nextMatch,
4379 int *recursionCount)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004380{
4381 // Recursion params
4382 int recursiveMatch = FALSE;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004383 int_u bestRecursiveMatches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004384 int bestRecursiveScore = 0;
4385 int first_match;
4386 int matched;
4387
4388 // Count recursions
4389 ++*recursionCount;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004390 if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004391 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004392
4393 // Detect end of strings
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004394 if (*fuzpat == NUL || *str == NUL)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004395 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004396
4397 // Loop through fuzpat and str looking for a match
4398 first_match = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004399 while (*fuzpat != NUL && *str != NUL)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004400 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004401 int c1;
4402 int c2;
4403
4404 c1 = PTR2CHAR(fuzpat);
4405 c2 = PTR2CHAR(str);
4406
Bram Moolenaar635414d2020-09-11 22:25:15 +02004407 // Found match
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004408 if (vim_tolower(c1) == vim_tolower(c2))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004409 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004410 int_u recursiveMatches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004411 int recursiveScore = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004412 char_u *next_char;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004413
4414 // Supplied matches buffer was too short
4415 if (nextMatch >= maxMatches)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004416 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004417
4418 // "Copy-on-Write" srcMatches into matches
4419 if (first_match && srcMatches)
4420 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004421 memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004422 first_match = FALSE;
4423 }
4424
4425 // Recursive call that "skips" this match
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004426 if (has_mbyte)
4427 next_char = str + (*mb_ptr2len)(str);
4428 else
4429 next_char = str + 1;
4430 if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
4431 &recursiveScore, strBegin, strLen, matches,
4432 recursiveMatches,
K.Takataeeec2542021-06-02 13:28:16 +02004433 ARRAY_LENGTH(recursiveMatches),
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004434 nextMatch, recursionCount))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004435 {
4436 // Pick best recursive score
4437 if (!recursiveMatch || recursiveScore > bestRecursiveScore)
4438 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004439 memcpy(bestRecursiveMatches, recursiveMatches,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004440 MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004441 bestRecursiveScore = recursiveScore;
4442 }
4443 recursiveMatch = TRUE;
4444 }
4445
4446 // Advance
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004447 matches[nextMatch++] = strIdx;
4448 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004449 MB_PTR_ADV(fuzpat);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004450 else
4451 ++fuzpat;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004452 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004453 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004454 MB_PTR_ADV(str);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004455 else
4456 ++str;
4457 strIdx++;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004458 }
4459
4460 // Determine if full fuzpat was matched
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004461 matched = *fuzpat == NUL ? TRUE : FALSE;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004462
4463 // Calculate score
4464 if (matched)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004465 *outScore = fuzzy_match_compute_score(strBegin, strLen, matches,
4466 nextMatch);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004467
4468 // Return best result
4469 if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
4470 {
4471 // Recursive score is better than "this"
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004472 memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004473 *outScore = bestRecursiveScore;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004474 return nextMatch;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004475 }
4476 else if (matched)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004477 return nextMatch; // "this" score is better than recursive
Bram Moolenaar635414d2020-09-11 22:25:15 +02004478
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004479 return 0; // no match
Bram Moolenaar635414d2020-09-11 22:25:15 +02004480}
4481
4482/*
4483 * fuzzy_match()
4484 *
4485 * Performs exhaustive search via recursion to find all possible matches and
4486 * match with highest score.
4487 * Scores values have no intrinsic meaning. Possible score range is not
4488 * normalized and varies with pattern.
4489 * Recursion is limited internally (default=10) to prevent degenerate cases
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004490 * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004491 * Uses char_u for match indices. Therefore patterns are limited to
4492 * MAX_FUZZY_MATCHES characters.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004493 *
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004494 * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004495 * 'outScore' and the matching character positions in 'matches'.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004496 */
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004497 int
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004498fuzzy_match(
4499 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004500 char_u *pat_arg,
4501 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004502 int *outScore,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004503 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004504 int maxMatches)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004505{
Bram Moolenaar635414d2020-09-11 22:25:15 +02004506 int recursionCount = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004507 int len = MB_CHARLEN(str);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004508 char_u *save_pat;
4509 char_u *pat;
4510 char_u *p;
4511 int complete = FALSE;
4512 int score = 0;
4513 int numMatches = 0;
4514 int matchCount;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004515
4516 *outScore = 0;
4517
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004518 save_pat = vim_strsave(pat_arg);
4519 if (save_pat == NULL)
4520 return FALSE;
4521 pat = save_pat;
4522 p = pat;
4523
4524 // Try matching each word in 'pat_arg' in 'str'
4525 while (TRUE)
4526 {
4527 if (matchseq)
4528 complete = TRUE;
4529 else
4530 {
4531 // Extract one word from the pattern (separated by space)
4532 p = skipwhite(p);
4533 if (*p == NUL)
4534 break;
4535 pat = p;
4536 while (*p != NUL && !VIM_ISWHITE(PTR2CHAR(p)))
4537 {
4538 if (has_mbyte)
4539 MB_PTR_ADV(p);
4540 else
4541 ++p;
4542 }
4543 if (*p == NUL) // processed all the words
4544 complete = TRUE;
4545 *p = NUL;
4546 }
4547
4548 score = 0;
4549 recursionCount = 0;
4550 matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
4551 matches + numMatches, maxMatches - numMatches,
4552 0, &recursionCount);
4553 if (matchCount == 0)
4554 {
4555 numMatches = 0;
4556 break;
4557 }
4558
4559 // Accumulate the match score and the number of matches
4560 *outScore += score;
4561 numMatches += matchCount;
4562
4563 if (complete)
4564 break;
4565
4566 // try matching the next word
4567 ++p;
4568 }
4569
4570 vim_free(save_pat);
4571 return numMatches != 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004572}
4573
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004574#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004575/*
4576 * Sort the fuzzy matches in the descending order of the match score.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004577 * For items with same score, retain the order using the index (stable sort)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004578 */
4579 static int
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004580fuzzy_match_item_compare(const void *s1, const void *s2)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004581{
4582 int v1 = ((fuzzyItem_T *)s1)->score;
4583 int v2 = ((fuzzyItem_T *)s2)->score;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004584 int idx1 = ((fuzzyItem_T *)s1)->idx;
4585 int idx2 = ((fuzzyItem_T *)s2)->idx;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004586
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004587 return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004588}
4589
4590/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004591 * Fuzzy search the string 'str' in a list of 'items' and return the matching
4592 * strings in 'fmatchlist'.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004593 * If 'matchseq' is TRUE, then for multi-word search strings, match all the
4594 * words in sequence.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004595 * If 'items' is a list of strings, then search for 'str' in the list.
4596 * If 'items' is a list of dicts, then either use 'key' to lookup the string
4597 * for each item or use 'item_cb' Funcref function to get the string.
4598 * If 'retmatchpos' is TRUE, then return a list of positions where 'str'
4599 * matches for each item.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004600 */
4601 static void
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004602fuzzy_match_in_list(
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004603 list_T *l,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004604 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004605 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004606 char_u *key,
4607 callback_T *item_cb,
4608 int retmatchpos,
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004609 list_T *fmatchlist,
4610 long max_matches)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004611{
4612 long len;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004613 fuzzyItem_T *items;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004614 listitem_T *li;
4615 long i = 0;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004616 long match_count = 0;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004617 int_u matches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004618
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004619 len = list_len(l);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004620 if (len == 0)
4621 return;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004622 if (max_matches > 0 && len > max_matches)
4623 len = max_matches;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004624
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004625 items = ALLOC_CLEAR_MULT(fuzzyItem_T, len);
4626 if (items == NULL)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004627 return;
4628
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004629 // For all the string items in items, get the fuzzy matching score
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004630 FOR_ALL_LIST_ITEMS(l, li)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004631 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004632 int score;
4633 char_u *itemstr;
4634 typval_T rettv;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004635
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004636 if (max_matches > 0 && match_count >= max_matches)
4637 break;
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004638
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004639 itemstr = NULL;
4640 rettv.v_type = VAR_UNKNOWN;
4641 if (li->li_tv.v_type == VAR_STRING) // list of strings
4642 itemstr = li->li_tv.vval.v_string;
Dominique Pelle7765f5c2022-04-10 11:26:53 +01004643 else if (li->li_tv.v_type == VAR_DICT
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004644 && (key != NULL || item_cb->cb_name != NULL))
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004645 {
4646 // For a dict, either use the specified key to lookup the string or
4647 // use the specified callback function to get the string.
4648 if (key != NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004649 itemstr = dict_get_string(li->li_tv.vval.v_dict,
4650 (char *)key, FALSE);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004651 else
Bram Moolenaar635414d2020-09-11 22:25:15 +02004652 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004653 typval_T argv[2];
4654
4655 // Invoke the supplied callback (if any) to get the dict item
4656 li->li_tv.vval.v_dict->dv_refcount++;
4657 argv[0].v_type = VAR_DICT;
4658 argv[0].vval.v_dict = li->li_tv.vval.v_dict;
4659 argv[1].v_type = VAR_UNKNOWN;
4660 if (call_callback(item_cb, -1, &rettv, 1, argv) != FAIL)
4661 {
4662 if (rettv.v_type == VAR_STRING)
4663 itemstr = rettv.vval.v_string;
4664 }
4665 dict_unref(li->li_tv.vval.v_dict);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004666 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004667 }
4668
4669 if (itemstr != NULL
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004670 && fuzzy_match(itemstr, str, matchseq, &score, matches,
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004671 MAX_FUZZY_MATCHES))
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004672 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004673 items[match_count].idx = match_count;
4674 items[match_count].item = li;
4675 items[match_count].score = score;
4676
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004677 // Copy the list of matching positions in itemstr to a list, if
4678 // 'retmatchpos' is set.
4679 if (retmatchpos)
4680 {
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004681 int j = 0;
4682 char_u *p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004683
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004684 items[match_count].lmatchpos = list_alloc();
4685 if (items[match_count].lmatchpos == NULL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004686 goto done;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004687
4688 p = str;
4689 while (*p != NUL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004690 {
zeertzjq9af2bc02022-05-11 14:15:37 +01004691 if (!VIM_ISWHITE(PTR2CHAR(p)) || matchseq)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004692 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004693 if (list_append_number(items[match_count].lmatchpos,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004694 matches[j]) == FAIL)
4695 goto done;
4696 j++;
4697 }
4698 if (has_mbyte)
4699 MB_PTR_ADV(p);
4700 else
4701 ++p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004702 }
4703 }
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004704 ++match_count;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004705 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004706 clear_tv(&rettv);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004707 }
4708
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004709 if (match_count > 0)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004710 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004711 list_T *retlist;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004712
Bram Moolenaar635414d2020-09-11 22:25:15 +02004713 // Sort the list by the descending order of the match score
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004714 qsort((void *)items, (size_t)match_count, sizeof(fuzzyItem_T),
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004715 fuzzy_match_item_compare);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004716
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004717 // For matchfuzzy(), return a list of matched strings.
4718 // ['str1', 'str2', 'str3']
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004719 // For matchfuzzypos(), return a list with three items.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004720 // The first item is a list of matched strings. The second item
4721 // is a list of lists where each list item is a list of matched
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004722 // character positions. The third item is a list of matching scores.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004723 // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
4724 if (retmatchpos)
4725 {
4726 li = list_find(fmatchlist, 0);
4727 if (li == NULL || li->li_tv.vval.v_list == NULL)
4728 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004729 retlist = li->li_tv.vval.v_list;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004730 }
4731 else
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004732 retlist = fmatchlist;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004733
4734 // Copy the matching strings with a valid score to the return list
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004735 for (i = 0; i < match_count; i++)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004736 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004737 if (items[i].score == SCORE_NONE)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004738 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004739 list_append_tv(retlist, &items[i].item->li_tv);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004740 }
4741
4742 // next copy the list of matching positions
4743 if (retmatchpos)
4744 {
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004745 li = list_find(fmatchlist, -2);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004746 if (li == NULL || li->li_tv.vval.v_list == NULL)
4747 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004748 retlist = li->li_tv.vval.v_list;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004749
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004750 for (i = 0; i < match_count; i++)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004751 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004752 if (items[i].score == SCORE_NONE)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004753 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004754 if (items[i].lmatchpos != NULL
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004755 && list_append_list(retlist, items[i].lmatchpos) == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004756 goto done;
4757 }
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004758
4759 // copy the matching scores
4760 li = list_find(fmatchlist, -1);
4761 if (li == NULL || li->li_tv.vval.v_list == NULL)
4762 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004763 retlist = li->li_tv.vval.v_list;
4764 for (i = 0; i < match_count; i++)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004765 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004766 if (items[i].score == SCORE_NONE)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004767 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004768 if (list_append_number(retlist, items[i].score) == FAIL)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004769 goto done;
4770 }
Bram Moolenaar635414d2020-09-11 22:25:15 +02004771 }
4772 }
4773
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004774done:
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004775 vim_free(items);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004776}
4777
4778/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004779 * Do fuzzy matching. Returns the list of matched strings in 'rettv'.
4780 * If 'retmatchpos' is TRUE, also returns the matching character positions.
4781 */
4782 static void
4783do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
4784{
4785 callback_T cb;
4786 char_u *key = NULL;
4787 int ret;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004788 int matchseq = FALSE;
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004789 long max_matches = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004790
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02004791 if (in_vim9script()
4792 && (check_for_list_arg(argvars, 0) == FAIL
4793 || check_for_string_arg(argvars, 1) == FAIL
4794 || check_for_opt_dict_arg(argvars, 2) == FAIL))
4795 return;
4796
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004797 CLEAR_POINTER(&cb);
4798
4799 // validate and get the arguments
4800 if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
4801 {
Bram Moolenaar3a846e62022-01-01 16:21:00 +00004802 semsg(_(e_argument_of_str_must_be_list),
4803 retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004804 return;
4805 }
4806 if (argvars[1].v_type != VAR_STRING
4807 || argvars[1].vval.v_string == NULL)
4808 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004809 semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004810 return;
4811 }
4812
4813 if (argvars[2].v_type != VAR_UNKNOWN)
4814 {
4815 dict_T *d;
4816 dictitem_T *di;
4817
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01004818 if (check_for_nonnull_dict_arg(argvars, 2) == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004819 return;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004820
4821 // To search a dict, either a callback function or a key can be
4822 // specified.
4823 d = argvars[2].vval.v_dict;
4824 if ((di = dict_find(d, (char_u *)"key", -1)) != NULL)
4825 {
4826 if (di->di_tv.v_type != VAR_STRING
4827 || di->di_tv.vval.v_string == NULL
4828 || *di->di_tv.vval.v_string == NUL)
4829 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004830 semsg(_(e_invalid_argument_str), tv_get_string(&di->di_tv));
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004831 return;
4832 }
4833 key = tv_get_string(&di->di_tv);
4834 }
4835 else if ((di = dict_find(d, (char_u *)"text_cb", -1)) != NULL)
4836 {
4837 cb = get_callback(&di->di_tv);
4838 if (cb.cb_name == NULL)
4839 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004840 semsg(_(e_invalid_value_for_argument_str), "text_cb");
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004841 return;
4842 }
4843 }
Kazuyuki Miyagi47f1a552022-06-17 18:30:03 +01004844
4845 if ((di = dict_find(d, (char_u *)"limit", -1)) != NULL)
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004846 {
4847 if (di->di_tv.v_type != VAR_NUMBER)
4848 {
4849 semsg(_(e_invalid_argument_str), tv_get_string(&di->di_tv));
4850 return;
4851 }
4852 max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
4853 }
4854
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004855 if (dict_has_key(d, "matchseq"))
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004856 matchseq = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004857 }
4858
4859 // get the fuzzy matches
4860 ret = rettv_list_alloc(rettv);
Bram Moolenaar5ea38d12022-06-16 21:20:48 +01004861 if (ret == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004862 goto done;
4863 if (retmatchpos)
4864 {
4865 list_T *l;
4866
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004867 // For matchfuzzypos(), a list with three items are returned. First
4868 // item is a list of matching strings, the second item is a list of
4869 // lists with matching positions within each string and the third item
4870 // is the list of scores of the matches.
4871 l = list_alloc();
4872 if (l == NULL)
4873 goto done;
4874 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004875 {
4876 vim_free(l);
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004877 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004878 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004879 l = list_alloc();
4880 if (l == NULL)
4881 goto done;
4882 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004883 {
4884 vim_free(l);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004885 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004886 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004887 l = list_alloc();
4888 if (l == NULL)
4889 goto done;
4890 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004891 {
4892 vim_free(l);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004893 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004894 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004895 }
4896
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004897 fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004898 matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004899
4900done:
4901 free_callback(&cb);
4902}
4903
4904/*
Bram Moolenaar635414d2020-09-11 22:25:15 +02004905 * "matchfuzzy()" function
4906 */
4907 void
4908f_matchfuzzy(typval_T *argvars, typval_T *rettv)
4909{
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004910 do_fuzzymatch(argvars, rettv, FALSE);
4911}
4912
4913/*
4914 * "matchfuzzypos()" function
4915 */
4916 void
4917f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
4918{
4919 do_fuzzymatch(argvars, rettv, TRUE);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004920}
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004921#endif
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004922
4923/*
4924 * Same as fuzzy_match_item_compare() except for use with a string match
4925 */
4926 static int
4927fuzzy_match_str_compare(const void *s1, const void *s2)
4928{
4929 int v1 = ((fuzmatch_str_T *)s1)->score;
4930 int v2 = ((fuzmatch_str_T *)s2)->score;
4931 int idx1 = ((fuzmatch_str_T *)s1)->idx;
4932 int idx2 = ((fuzmatch_str_T *)s2)->idx;
4933
4934 return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
4935}
4936
4937/*
4938 * Sort fuzzy matches by score
4939 */
4940 static void
4941fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz)
4942{
4943 // Sort the list by the descending order of the match score
4944 qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
4945 fuzzy_match_str_compare);
4946}
4947
4948/*
4949 * Same as fuzzy_match_item_compare() except for use with a function name
4950 * string match. <SNR> functions should be sorted to the end.
4951 */
4952 static int
4953fuzzy_match_func_compare(const void *s1, const void *s2)
4954{
4955 int v1 = ((fuzmatch_str_T *)s1)->score;
4956 int v2 = ((fuzmatch_str_T *)s2)->score;
4957 int idx1 = ((fuzmatch_str_T *)s1)->idx;
4958 int idx2 = ((fuzmatch_str_T *)s2)->idx;
4959 char_u *str1 = ((fuzmatch_str_T *)s1)->str;
4960 char_u *str2 = ((fuzmatch_str_T *)s2)->str;
4961
4962 if (*str1 != '<' && *str2 == '<') return -1;
4963 if (*str1 == '<' && *str2 != '<') return 1;
4964 return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1;
4965}
4966
4967/*
4968 * Sort fuzzy matches of function names by score.
4969 * <SNR> functions should be sorted to the end.
4970 */
4971 static void
4972fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz)
4973{
4974 // Sort the list by the descending order of the match score
4975 qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
4976 fuzzy_match_func_compare);
4977}
4978
4979/*
4980 * Fuzzy match 'pat' in 'str'. Returns 0 if there is no match. Otherwise,
4981 * returns the match score.
4982 */
4983 int
4984fuzzy_match_str(char_u *str, char_u *pat)
4985{
4986 int score = 0;
Yegappan Lakshmanan5ec633b2022-02-25 15:24:24 +00004987 int_u matchpos[MAX_FUZZY_MATCHES];
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004988
4989 if (str == NULL || pat == NULL)
4990 return 0;
4991
Yegappan Lakshmanan6caeda22022-02-27 12:07:30 +00004992 fuzzy_match(str, pat, TRUE, &score, matchpos,
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004993 sizeof(matchpos) / sizeof(matchpos[0]));
4994
4995 return score;
4996}
4997
4998/*
Bram Moolenaarc6e0a5e2022-04-10 18:09:06 +01004999 * Free an array of fuzzy string matches "fuzmatch[count]".
5000 */
5001 void
5002fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count)
5003{
5004 int i;
5005
5006 if (fuzmatch == NULL)
5007 return;
5008 for (i = 0; i < count; ++i)
5009 vim_free(fuzmatch[i].str);
5010 vim_free(fuzmatch);
5011}
5012
5013/*
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005014 * Copy a list of fuzzy matches into a string list after sorting the matches by
5015 * the fuzzy score. Frees the memory allocated for 'fuzmatch'.
5016 * Returns OK on success and FAIL on memory allocation failure.
5017 */
5018 int
5019fuzzymatches_to_strmatches(
5020 fuzmatch_str_T *fuzmatch,
5021 char_u ***matches,
5022 int count,
5023 int funcsort)
5024{
5025 int i;
5026
5027 if (count <= 0)
5028 return OK;
5029
5030 *matches = ALLOC_MULT(char_u *, count);
5031 if (*matches == NULL)
5032 {
Bram Moolenaarc6e0a5e2022-04-10 18:09:06 +01005033 fuzmatch_str_free(fuzmatch, count);
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005034 return FAIL;
5035 }
5036
5037 // Sort the list by the descending order of the match score
5038 if (funcsort)
5039 fuzzy_match_func_sort((void *)fuzmatch, (size_t)count);
5040 else
5041 fuzzy_match_str_sort((void *)fuzmatch, (size_t)count);
5042
5043 for (i = 0; i < count; i++)
5044 (*matches)[i] = fuzmatch[i].str;
5045 vim_free(fuzmatch);
5046
5047 return OK;
5048}