blob: 425d0d3b5ee4c9c4ddb2d6637fb9b6c62f2b8c8e [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
John Marriott8c85a2a2024-05-20 19:18:26 +020020static char_u *get_line_and_copy(linenr_T lnum, char_u *buf);
21static void show_pat_in_path(char_u *, int, 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
John Marriott8c85a2a2024-05-20 19:18:26 +020035#ifdef FEAT_SEARCH_EXTRA
36static void save_incsearch_state(void);
37static void restore_incsearch_state(void);
38#endif
39static int check_prevcol(char_u *linep, int col, int ch, int *prevcol);
40static int find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos);
41static void find_mps_values(int *initc, int *findc, int *backwards, int switchit);
42static int is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction);
43static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, int show_top_bot_msg, char_u *msgbuf, size_t msgbuflen, int recompute, int maxcount, long timeout);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020044static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
glepnir28e40a72025-03-16 21:24:22 +010045static int fuzzy_match_compute_score(char_u *fuzpat, char_u *str, int strSz, int_u *matches, int numMatches, int camelcase);
46static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, int *outScore, char_u *strBegin, int strLen, int_u *srcMatches, int_u *matches, int maxMatches, int nextMatch, int *recursionCount, int camelcase);
John Marriott8c85a2a2024-05-20 19:18:26 +020047#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
48static int fuzzy_match_item_compare(const void *s1, const void *s2);
glepnir28e40a72025-03-16 21:24:22 +010049static void fuzzy_match_in_list(list_T *l, char_u *str, int matchseq, char_u *key, callback_T *item_cb, int retmatchpos, list_T *fmatchlist, long max_matches, int camelcase);
John Marriott8c85a2a2024-05-20 19:18:26 +020050static void do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos);
51#endif
52static int fuzzy_match_str_compare(const void *s1, const void *s2);
53static void fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz);
54static int fuzzy_match_func_compare(const void *s1, const void *s2);
55static void fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020056
Bram Moolenaarea6561a2020-06-01 21:32:45 +020057#define SEARCH_STAT_DEF_TIMEOUT 40L
Bram Moolenaare8f5ec02020-06-01 17:28:35 +020058#define SEARCH_STAT_DEF_MAX_COUNT 99
59#define SEARCH_STAT_BUF_LEN 12
Bram Moolenaar071d4272004-06-13 20:20:40 +000060
Bram Moolenaar071d4272004-06-13 20:20:40 +000061/*
62 * This file contains various searching-related routines. These fall into
63 * three groups:
64 * 1. string searches (for /, ?, n, and N)
65 * 2. character searches within a single line (for f, F, t, T, etc)
66 * 3. "other" kinds of searches like the '%' command, and 'word' searches.
67 */
68
69/*
70 * String searches
71 *
72 * The string search functions are divided into two levels:
73 * lowest: searchit(); uses an pos_T for starting position and found match.
74 * Highest: do_search(); uses curwin->w_cursor; calls searchit().
75 *
76 * The last search pattern is remembered for repeating the same search.
77 * This pattern is shared between the :g, :s, ? and / commands.
78 * This is in search_regcomp().
79 *
80 * The actual string matching is done using a heavily modified version of
81 * Henry Spencer's regular expression library. See regexp.c.
82 */
83
Bram Moolenaar071d4272004-06-13 20:20:40 +000084/*
85 * Two search patterns are remembered: One for the :substitute command and
86 * one for other searches. last_idx points to the one that was used the last
87 * time.
88 */
Bram Moolenaarc3328162019-07-23 22:15:25 +020089static spat_T spats[2] =
Bram Moolenaar071d4272004-06-13 20:20:40 +000090{
John Marriott8c85a2a2024-05-20 19:18:26 +020091 {NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat
92 {NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat
Bram Moolenaar071d4272004-06-13 20:20:40 +000093};
94
Bram Moolenaar63d9e732019-12-05 21:10:38 +010095static int last_idx = 0; // index in spats[] for RE_LAST
Bram Moolenaar071d4272004-06-13 20:20:40 +000096
Bram Moolenaar63d9e732019-12-05 21:10:38 +010097static char_u lastc[2] = {NUL, NUL}; // last character searched for
98static int lastcdir = FORWARD; // last direction of character search
99static int last_t_cmd = TRUE; // last search t_cmd
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200100static char_u lastc_bytes[MB_MAXBYTES + 1];
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100101static int lastc_bytelen = 1; // >1 for multi-byte char
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200102
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100103// copy of spats[], for keeping the search patterns while executing autocmds
John Marriott8c85a2a2024-05-20 19:18:26 +0200104static spat_T saved_spats[ARRAY_LENGTH(spats)];
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100105static char_u *saved_mr_pattern = NULL;
John Marriott8c85a2a2024-05-20 19:18:26 +0200106static size_t saved_mr_patternlen = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000107# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100108static int saved_spats_last_idx = 0;
109static int saved_spats_no_hlsearch = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000110# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000111
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100112// allocated copy of pattern used by search_regcomp()
113static char_u *mr_pattern = NULL;
John Marriott8c85a2a2024-05-20 19:18:26 +0200114static size_t mr_patternlen = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000115
116#ifdef FEAT_FIND_ID
117/*
118 * Type used by find_pattern_in_path() to remember which included files have
119 * been searched already.
120 */
121typedef struct SearchedFile
122{
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100123 FILE *fp; // File pointer
124 char_u *name; // Full name of file
125 linenr_T lnum; // Line we were up to in file
126 int matched; // Found a match in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +0000127} SearchedFile;
128#endif
129
130/*
131 * translate search pattern for vim_regcomp()
132 *
133 * pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
134 * pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
135 * pat_save == RE_BOTH: save pat in both patterns (:global command)
136 * pat_use == RE_SEARCH: use previous search pattern if "pat" is NULL
Bram Moolenaarb8017e72007-05-10 18:59:07 +0000137 * pat_use == RE_SUBST: use previous substitute pattern if "pat" is NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000138 * pat_use == RE_LAST: use last used pattern if "pat" is NULL
139 * options & SEARCH_HIS: put search string in history
140 * options & SEARCH_KEEP: keep previous search pattern
141 *
142 * returns FAIL if failed, OK otherwise.
143 */
144 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100145search_regcomp(
146 char_u *pat,
John Marriott8c85a2a2024-05-20 19:18:26 +0200147 size_t patlen,
Rob Pillinge86190e2022-12-23 19:06:04 +0000148 char_u **used_pat,
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100149 int pat_save,
150 int pat_use,
151 int options,
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100152 regmmatch_T *regmatch) // return: pattern and ignore-case flag
Bram Moolenaar071d4272004-06-13 20:20:40 +0000153{
154 int magic;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000155
156 rc_did_emsg = FALSE;
Bram Moolenaarf4e20992020-12-21 19:59:08 +0100157 magic = magic_isset();
Bram Moolenaar071d4272004-06-13 20:20:40 +0000158
159 /*
160 * If no pattern given, use a previously defined pattern.
161 */
162 if (pat == NULL || *pat == NUL)
163 {
John Marriott8c85a2a2024-05-20 19:18:26 +0200164 int i;
165
Bram Moolenaar071d4272004-06-13 20:20:40 +0000166 if (pat_use == RE_LAST)
167 i = last_idx;
168 else
169 i = pat_use;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100170 if (spats[i].pat == NULL) // pattern was never defined
Bram Moolenaar071d4272004-06-13 20:20:40 +0000171 {
172 if (pat_use == RE_SUBST)
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200173 emsg(_(e_no_previous_substitute_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000174 else
Bram Moolenaare29a27f2021-07-20 21:07:36 +0200175 emsg(_(e_no_previous_regular_expression));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000176 rc_did_emsg = TRUE;
177 return FAIL;
178 }
179 pat = spats[i].pat;
John Marriott8c85a2a2024-05-20 19:18:26 +0200180 patlen = spats[i].patlen;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000181 magic = spats[i].magic;
182 no_smartcase = spats[i].no_scs;
183 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100184 else if (options & SEARCH_HIS) // put new pattern in history
John Marriott8c85a2a2024-05-20 19:18:26 +0200185 add_to_history(HIST_SEARCH, pat, patlen, TRUE, NUL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000186
Rob Pillinge86190e2022-12-23 19:06:04 +0000187 if (used_pat)
Bram Moolenaarebfec1c2023-01-22 21:14:53 +0000188 *used_pat = pat;
Rob Pillinge86190e2022-12-23 19:06:04 +0000189
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100190 vim_free(mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000191#ifdef FEAT_RIGHTLEFT
Bram Moolenaar071d4272004-06-13 20:20:40 +0000192 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100193 mr_pattern = reverse_text(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000194 else
195#endif
John Marriott8c85a2a2024-05-20 19:18:26 +0200196 mr_pattern = vim_strnsave(pat, patlen);
197 if (mr_pattern == NULL)
198 mr_patternlen = 0;
199 else
200 mr_patternlen = patlen;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000201
202 /*
203 * Save the currently used pattern in the appropriate place,
204 * unless the pattern should not be remembered.
205 */
Bram Moolenaare1004402020-10-24 20:49:43 +0200206 if (!(options & SEARCH_KEEP)
207 && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000208 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100209 // search or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000210 if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
John Marriott8c85a2a2024-05-20 19:18:26 +0200211 save_re_pat(RE_SEARCH, pat, patlen, magic);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100212 // substitute or global command
Bram Moolenaar071d4272004-06-13 20:20:40 +0000213 if (pat_save == RE_SUBST || pat_save == RE_BOTH)
John Marriott8c85a2a2024-05-20 19:18:26 +0200214 save_re_pat(RE_SUBST, pat, patlen, magic);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000215 }
216
217 regmatch->rmm_ic = ignorecase(pat);
Bram Moolenaar3b56eb32005-07-11 22:40:32 +0000218 regmatch->rmm_maxcol = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000219 regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
220 if (regmatch->regprog == NULL)
221 return FAIL;
222 return OK;
223}
224
225/*
226 * Get search pattern used by search_regcomp().
227 */
228 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100229get_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000230{
231 return mr_pattern;
232}
233
Bram Moolenaarcc2b9d52014-12-13 03:17:11 +0100234 void
John Marriott8c85a2a2024-05-20 19:18:26 +0200235save_re_pat(int idx, char_u *pat, size_t patlen, int magic)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000236{
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000237 if (spats[idx].pat == pat)
238 return;
239
240 vim_free(spats[idx].pat);
John Marriott8c85a2a2024-05-20 19:18:26 +0200241 spats[idx].pat = vim_strnsave(pat, patlen);
242 if (spats[idx].pat == NULL)
243 spats[idx].patlen = 0;
244 else
245 spats[idx].patlen = patlen;
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000246 spats[idx].magic = magic;
247 spats[idx].no_scs = no_smartcase;
248 last_idx = idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000249#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000250 // If 'hlsearch' set and search pat changed: need redraw.
251 if (p_hls)
252 redraw_all_later(UPD_SOME_VALID);
253 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000254#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000255}
256
Bram Moolenaar071d4272004-06-13 20:20:40 +0000257/*
258 * Save the search patterns, so they can be restored later.
259 * Used before/after executing autocommands and user functions.
260 */
261static int save_level = 0;
262
263 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100264save_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000265{
John Marriott8c85a2a2024-05-20 19:18:26 +0200266 int i;
267
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000268 if (save_level++ != 0)
269 return;
270
John Marriott8c85a2a2024-05-20 19:18:26 +0200271 for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
272 {
273 saved_spats[i] = spats[i];
274 if (spats[i].pat != NULL)
275 {
276 saved_spats[i].pat = vim_strnsave(spats[i].pat, spats[i].patlen);
277 if (saved_spats[i].pat == NULL)
278 saved_spats[i].patlen = 0;
279 else
280 saved_spats[i].patlen = spats[i].patlen;
281 }
282 }
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000283 if (mr_pattern == NULL)
284 saved_mr_pattern = NULL;
285 else
John Marriott8c85a2a2024-05-20 19:18:26 +0200286 saved_mr_pattern = vim_strnsave(mr_pattern, mr_patternlen);
287 if (saved_mr_pattern == NULL)
288 saved_mr_patternlen = 0;
289 else
290 saved_mr_patternlen = mr_patternlen;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100291#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000292 saved_spats_last_idx = last_idx;
293 saved_spats_no_hlsearch = no_hlsearch;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100294#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000295}
296
297 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100298restore_search_patterns(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000299{
John Marriott8c85a2a2024-05-20 19:18:26 +0200300 int i;
301
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000302 if (--save_level != 0)
303 return;
304
John Marriott8c85a2a2024-05-20 19:18:26 +0200305 for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
306 {
307 vim_free(spats[i].pat);
308 spats[i] = saved_spats[i];
309 }
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100310#if defined(FEAT_EVAL)
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000311 set_vv_searchforward();
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100312#endif
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000313 vim_free(mr_pattern);
314 mr_pattern = saved_mr_pattern;
John Marriott8c85a2a2024-05-20 19:18:26 +0200315 mr_patternlen = saved_mr_patternlen;
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100316#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +0000317 last_idx = saved_spats_last_idx;
318 set_no_hlsearch(saved_spats_no_hlsearch);
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +0100319#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000320}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000321
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000322#if defined(EXITFREE) || defined(PROTO)
323 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100324free_search_patterns(void)
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000325{
John Marriott8c85a2a2024-05-20 19:18:26 +0200326 int i;
327
328 for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
329 {
330 VIM_CLEAR(spats[i].pat);
331 spats[i].patlen = 0;
332 }
Bram Moolenaara2cff1d2021-10-15 12:51:29 +0100333 VIM_CLEAR(mr_pattern);
John Marriott8c85a2a2024-05-20 19:18:26 +0200334 mr_patternlen = 0;
Bram Moolenaarf461c8e2005-06-25 23:04:51 +0000335}
336#endif
337
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100338#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100339// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
340// searching
Bram Moolenaarc3328162019-07-23 22:15:25 +0200341static spat_T saved_last_search_spat;
Bram Moolenaared8bc782018-12-01 21:08:21 +0100342static int did_save_last_search_spat = 0;
343static int saved_last_idx = 0;
344static int saved_no_hlsearch = 0;
Christian Brabandt6dd74242022-02-14 12:44:32 +0000345static int saved_search_match_endcol;
346static int saved_search_match_lines;
Bram Moolenaared8bc782018-12-01 21:08:21 +0100347
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100348/*
349 * Save and restore the search pattern for incremental highlight search
350 * feature.
351 *
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100352 * It's similar to but different from save_search_patterns() and
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100353 * restore_search_patterns(), because the search pattern must be restored when
Bram Moolenaarc4568ab2018-11-16 16:21:05 +0100354 * canceling incremental searching even if it's called inside user functions.
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100355 */
356 void
357save_last_search_pattern(void)
358{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200359 if (++did_save_last_search_spat != 1)
360 // nested call, nothing to do
361 return;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100362
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100363 saved_last_search_spat = spats[RE_SEARCH];
364 if (spats[RE_SEARCH].pat != NULL)
John Marriott8c85a2a2024-05-20 19:18:26 +0200365 {
366 saved_last_search_spat.pat = vim_strnsave(spats[RE_SEARCH].pat, spats[RE_SEARCH].patlen);
367 if (saved_last_search_spat.pat == NULL)
368 saved_last_search_spat.patlen = 0;
369 else
370 saved_last_search_spat.patlen = spats[RE_SEARCH].patlen;
371 }
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100372 saved_last_idx = last_idx;
373 saved_no_hlsearch = no_hlsearch;
374}
375
376 void
377restore_last_search_pattern(void)
378{
Bram Moolenaar442a8532020-06-04 20:56:09 +0200379 if (--did_save_last_search_spat > 0)
380 // nested call, nothing to do
381 return;
382 if (did_save_last_search_spat != 0)
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100383 {
Bram Moolenaar442a8532020-06-04 20:56:09 +0200384 iemsg("restore_last_search_pattern() called more often than save_last_search_pattern()");
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100385 return;
386 }
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100387
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100388 vim_free(spats[RE_SEARCH].pat);
389 spats[RE_SEARCH] = saved_last_search_spat;
Bram Moolenaar01a060d2018-11-30 21:57:55 +0100390 saved_last_search_spat.pat = NULL;
John Marriott8c85a2a2024-05-20 19:18:26 +0200391 saved_last_search_spat.patlen = 0;
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100392# if defined(FEAT_EVAL)
393 set_vv_searchforward();
394# endif
395 last_idx = saved_last_idx;
Bram Moolenaar451fc7b2018-04-27 22:53:07 +0200396 set_no_hlsearch(saved_no_hlsearch);
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100397}
Bram Moolenaard0480092017-11-16 22:20:39 +0100398
Christian Brabandt6dd74242022-02-14 12:44:32 +0000399/*
400 * Save and restore the incsearch highlighting variables.
401 * This is required so that calling searchcount() at does not invalidate the
402 * incsearch highlighting.
403 */
404 static void
405save_incsearch_state(void)
406{
407 saved_search_match_endcol = search_match_endcol;
408 saved_search_match_lines = search_match_lines;
409}
410
411 static void
412restore_incsearch_state(void)
413{
414 search_match_endcol = saved_search_match_endcol;
415 search_match_lines = saved_search_match_lines;
416}
417
Bram Moolenaard0480092017-11-16 22:20:39 +0100418 char_u *
419last_search_pattern(void)
420{
421 return spats[RE_SEARCH].pat;
422}
John Marriottccf89072024-10-07 21:40:39 +0200423
424 size_t
425last_search_pattern_len(void)
426{
427 return spats[RE_SEARCH].patlen;
428}
Bram Moolenaar2e51d9a2017-10-29 16:40:30 +0100429#endif
430
Bram Moolenaar071d4272004-06-13 20:20:40 +0000431/*
432 * Return TRUE when case should be ignored for search pattern "pat".
433 * Uses the 'ignorecase' and 'smartcase' options.
434 */
435 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100436ignorecase(char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000437{
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200438 return ignorecase_opt(pat, p_ic, p_scs);
439}
Bram Moolenaar071d4272004-06-13 20:20:40 +0000440
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200441/*
Girish Palyadc314052025-05-08 23:28:52 +0200442 * As ignorecase() but pass the "ic" and "scs" flags.
Bram Moolenaar66e29d72016-08-20 16:57:02 +0200443 */
444 int
445ignorecase_opt(char_u *pat, int ic_in, int scs)
446{
447 int ic = ic_in;
448
449 if (ic && !no_smartcase && scs
Bram Moolenaare2c453d2019-08-21 14:37:09 +0200450 && !(ctrl_x_mode_not_default() && curbuf->b_p_inf))
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200451 ic = !pat_has_uppercase(pat);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000452 no_smartcase = FALSE;
453
454 return ic;
455}
456
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200457/*
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200458 * Return TRUE if pattern "pat" has an uppercase character.
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200459 */
460 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100461pat_has_uppercase(char_u *pat)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200462{
463 char_u *p = pat;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200464 magic_T magic_val = MAGIC_ON;
465
466 // get the magicness of the pattern
467 (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200468
469 while (*p != NUL)
470 {
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200471 int l;
472
473 if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
474 {
475 if (enc_utf8 && utf_isupper(utf_ptr2char(p)))
476 return TRUE;
477 p += l;
478 }
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200479 else if (*p == '\\' && magic_val <= MAGIC_ON)
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200480 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100481 if (p[1] == '_' && p[2] != NUL) // skip "\_X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200482 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100483 else if (p[1] == '%' && p[2] != NUL) // skip "\%X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200484 p += 3;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100485 else if (p[1] != NUL) // skip "\X"
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200486 p += 2;
487 else
488 p += 1;
489 }
Christian Brabandt78ba9332021-08-01 12:44:37 +0200490 else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL)
491 {
492 if (p[1] != NUL) // skip "_X" and %X
493 p += 2;
Christian Brabandtbc67e5a2021-08-05 15:24:59 +0200494 else
495 p++;
Christian Brabandt78ba9332021-08-01 12:44:37 +0200496 }
Bram Moolenaara9dc3752010-07-11 20:46:53 +0200497 else if (MB_ISUPPER(*p))
498 return TRUE;
499 else
500 ++p;
501 }
502 return FALSE;
503}
504
Bram Moolenaar113e1072019-01-20 15:30:40 +0100505#if defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000506 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100507last_csearch(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200508{
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200509 return lastc_bytes;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200510}
511
512 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100513last_csearch_forward(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200514{
515 return lastcdir == FORWARD;
516}
517
518 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100519last_csearch_until(void)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200520{
521 return last_t_cmd == TRUE;
522}
523
524 void
zeertzjqe5d91ba2023-05-14 17:39:18 +0100525set_last_csearch(int c, char_u *s, int len)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200526{
527 *lastc = c;
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200528 lastc_bytelen = len;
529 if (len)
530 memcpy(lastc_bytes, s, len);
531 else
Bram Moolenaara80faa82020-04-12 19:37:17 +0200532 CLEAR_FIELD(lastc_bytes);
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200533}
Bram Moolenaar113e1072019-01-20 15:30:40 +0100534#endif
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200535
536 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100537set_csearch_direction(int cdir)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200538{
539 lastcdir = cdir;
540}
541
542 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100543set_csearch_until(int t_cmd)
Bram Moolenaardbd24b52015-08-11 14:26:19 +0200544{
545 last_t_cmd = t_cmd;
546}
547
548 char_u *
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100549last_search_pat(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000550{
551 return spats[last_idx].pat;
552}
553
554/*
555 * Reset search direction to forward. For "gd" and "gD" commands.
556 */
557 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100558reset_search_dir(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000559{
560 spats[0].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000561#if defined(FEAT_EVAL)
562 set_vv_searchforward();
563#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000564}
565
566#if defined(FEAT_EVAL) || defined(FEAT_VIMINFO)
567/*
568 * Set the last search pattern. For ":let @/ =" and viminfo.
569 * Also set the saved search pattern, so that this works in an autocommand.
570 */
571 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100572set_last_search_pat(
573 char_u *s,
574 int idx,
575 int magic,
576 int setlast)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000577{
578 vim_free(spats[idx].pat);
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100579 // An empty string means that nothing should be matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000580 if (*s == NUL)
581 spats[idx].pat = NULL;
582 else
John Marriott8c85a2a2024-05-20 19:18:26 +0200583 {
584 spats[idx].patlen = STRLEN(s);
585 spats[idx].pat = vim_strnsave(s, spats[idx].patlen);
586 }
587 if (spats[idx].pat == NULL)
588 spats[idx].patlen = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000589 spats[idx].magic = magic;
590 spats[idx].no_scs = FALSE;
591 spats[idx].off.dir = '/';
Bram Moolenaar8c8de832008-06-24 22:58:06 +0000592#if defined(FEAT_EVAL)
593 set_vv_searchforward();
594#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000595 spats[idx].off.line = FALSE;
596 spats[idx].off.end = FALSE;
597 spats[idx].off.off = 0;
598 if (setlast)
599 last_idx = idx;
600 if (save_level)
601 {
602 vim_free(saved_spats[idx].pat);
603 saved_spats[idx] = spats[0];
604 if (spats[idx].pat == NULL)
605 saved_spats[idx].pat = NULL;
606 else
John Marriott8c85a2a2024-05-20 19:18:26 +0200607 saved_spats[idx].pat = vim_strnsave(spats[idx].pat, spats[idx].patlen);
608 if (saved_spats[idx].pat == NULL)
609 saved_spats[idx].patlen = 0;
610 else
611 saved_spats[idx].patlen = spats[idx].patlen;
Bram Moolenaar975880b2019-03-03 14:42:11 +0100612# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaared8bc782018-12-01 21:08:21 +0100613 saved_spats_last_idx = last_idx;
Bram Moolenaar975880b2019-03-03 14:42:11 +0100614# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000615 }
616# ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100617 // If 'hlsearch' set and search pat changed: need redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000618 if (p_hls && idx == last_idx && !no_hlsearch)
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100619 redraw_all_later(UPD_SOME_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000620# endif
621}
622#endif
623
624#ifdef FEAT_SEARCH_EXTRA
625/*
626 * Get a regexp program for the last used search pattern.
627 * This is used for highlighting all matches in a window.
628 * Values returned in regmatch->regprog and regmatch->rmm_ic.
629 */
630 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100631last_pat_prog(regmmatch_T *regmatch)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000632{
633 if (spats[last_idx].pat == NULL)
634 {
635 regmatch->regprog = NULL;
636 return;
637 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100638 ++emsg_off; // So it doesn't beep if bad expr
John Marriott8c85a2a2024-05-20 19:18:26 +0200639 (void)search_regcomp((char_u *)"", 0, NULL, 0, last_idx, SEARCH_KEEP, regmatch);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000640 --emsg_off;
641}
642#endif
643
644/*
Bram Moolenaarf7ff6e82014-03-23 15:13:05 +0100645 * Lowest level search function.
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100646 * Search for 'count'th occurrence of pattern "pat" in direction "dir".
647 * Start at position "pos" and return the found position in "pos".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000648 *
649 * if (options & SEARCH_MSG) == 0 don't give any messages
650 * if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
651 * if (options & SEARCH_MSG) == SEARCH_MSG give all messages
652 * if (options & SEARCH_HIS) put search pattern in history
653 * if (options & SEARCH_END) return position at end of match
654 * if (options & SEARCH_START) accept match at pos itself
655 * if (options & SEARCH_KEEP) keep previous search pattern
656 * if (options & SEARCH_FOLD) match only once in a closed fold
657 * if (options & SEARCH_PEEK) check for typed char, cancel search
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100658 * if (options & SEARCH_COL) start at pos->col instead of zero
Bram Moolenaar071d4272004-06-13 20:20:40 +0000659 *
660 * Return FAIL (zero) for failure, non-zero for success.
661 * When FEAT_EVAL is defined, returns the index of the first matching
662 * subpattern plus one; one if there was none.
663 */
664 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100665searchit(
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200666 win_T *win, // window to search in; can be NULL for a
667 // buffer without a window!
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100668 buf_T *buf,
669 pos_T *pos,
Bram Moolenaar5d24a222018-12-23 19:10:09 +0100670 pos_T *end_pos, // set to end of the match, unless NULL
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100671 int dir,
672 char_u *pat,
John Marriott8c85a2a2024-05-20 19:18:26 +0200673 size_t patlen,
Bram Moolenaar764b23c2016-01-30 21:10:09 +0100674 long count,
675 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200676 int pat_use, // which pattern to use when "pat" is empty
677 searchit_arg_T *extra_arg) // optional extra arguments, can be NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000678{
679 int found;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100680 linenr_T lnum; // no init to shut up Apollo cc
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100681 colnr_T col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000682 regmmatch_T regmatch;
683 char_u *ptr;
684 colnr_T matchcol;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000685 lpos_T endpos;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000686 lpos_T matchpos;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000687 int loop;
688 pos_T start_pos;
689 int at_first_line;
690 int extra_col;
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200691 int start_char_len;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000692 int match_ok;
693 long nmatched;
694 int submatch = 0;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100695 int first_match = TRUE;
Bram Moolenaar53989552019-12-23 22:59:18 +0100696 int called_emsg_before = called_emsg;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000697#ifdef FEAT_SEARCH_EXTRA
698 int break_loop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000699#endif
Bram Moolenaar92ea26b2019-10-18 20:53:34 +0200700 linenr_T stop_lnum = 0; // stop after this line number when != 0
Paul Ollis65745772022-06-05 16:55:54 +0100701 int unused_timeout_flag = FALSE;
702 int *timed_out = &unused_timeout_flag; // set when timed out.
John Marriott8c85a2a2024-05-20 19:18:26 +0200703 int search_from_match_end; // vi-compatible search?
Bram Moolenaar071d4272004-06-13 20:20:40 +0000704
John Marriott8c85a2a2024-05-20 19:18:26 +0200705 if (search_regcomp(pat, patlen, NULL, RE_SEARCH, pat_use,
Bram Moolenaar071d4272004-06-13 20:20:40 +0000706 (options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL)
707 {
708 if ((options & SEARCH_MSG) && !rc_did_emsg)
Bram Moolenaarac78dd42022-01-02 19:25:26 +0000709 semsg(_(e_invalid_search_string_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000710 return FAIL;
711 }
712
John Marriott8c85a2a2024-05-20 19:18:26 +0200713 search_from_match_end = vim_strchr(p_cpo, CPO_SEARCH) != NULL;
714
Paul Ollis65745772022-06-05 16:55:54 +0100715 if (extra_arg != NULL)
716 {
717 stop_lnum = extra_arg->sa_stop_lnum;
718#ifdef FEAT_RELTIME
719 if (extra_arg->sa_tm > 0)
Paul Ollis65745772022-06-05 16:55:54 +0100720 init_regexp_timeout(extra_arg->sa_tm);
Bram Moolenaar5ea38d12022-06-16 21:20:48 +0100721 // Also set the pointer when sa_tm is zero, the caller may have set the
722 // timeout.
723 timed_out = &extra_arg->sa_timed_out;
Paul Ollis65745772022-06-05 16:55:54 +0100724#endif
725 }
726
Bram Moolenaar280f1262006-01-30 00:14:18 +0000727 /*
728 * find the string
729 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100730 do // loop for count
Bram Moolenaar071d4272004-06-13 20:20:40 +0000731 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100732 // When not accepting a match at the start position set "extra_col" to
733 // a non-zero value. Don't do that when starting at MAXCOL, since
734 // MAXCOL + 1 is zero.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200735 if (pos->col == MAXCOL)
736 start_char_len = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100737 // Watch out for the "col" being MAXCOL - 2, used in a closed fold.
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200738 else if (has_mbyte
739 && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count
740 && pos->col < MAXCOL - 2)
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100741 {
Bram Moolenaar82846a02018-02-09 18:09:54 +0100742 ptr = ml_get_buf(buf, pos->lnum, FALSE);
zeertzjq94b7c322024-03-12 21:50:32 +0100743 if (ml_get_buf_len(buf, pos->lnum) <= pos->col)
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200744 start_char_len = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100745 else
Bram Moolenaar82846a02018-02-09 18:09:54 +0100746 start_char_len = (*mb_ptr2len)(ptr + pos->col);
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100747 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100748 else
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200749 start_char_len = 1;
750 if (dir == FORWARD)
751 {
752 if (options & SEARCH_START)
753 extra_col = 0;
754 else
755 extra_col = start_char_len;
756 }
757 else
758 {
759 if (options & SEARCH_START)
760 extra_col = start_char_len;
761 else
762 extra_col = 0;
763 }
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100764
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100765 start_pos = *pos; // remember start pos for detecting no match
766 found = 0; // default: not found
767 at_first_line = TRUE; // default: start in first line
768 if (pos->lnum == 0) // correct lnum for when starting in line 0
Bram Moolenaar071d4272004-06-13 20:20:40 +0000769 {
770 pos->lnum = 1;
771 pos->col = 0;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100772 at_first_line = FALSE; // not in first line now
Bram Moolenaar071d4272004-06-13 20:20:40 +0000773 }
774
775 /*
776 * Start searching in current line, unless searching backwards and
777 * we're in column 0.
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000778 * If we are searching backwards, in column 0, and not including the
779 * current position, gain some efficiency by skipping back a line.
780 * Otherwise begin the search in the current line.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000781 */
Bram Moolenaar7a42fa32007-07-10 11:28:55 +0000782 if (dir == BACKWARD && start_pos.col == 0
783 && (options & SEARCH_START) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000784 {
785 lnum = pos->lnum - 1;
786 at_first_line = FALSE;
787 }
788 else
789 lnum = pos->lnum;
790
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100791 for (loop = 0; loop <= 1; ++loop) // loop twice if 'wrapscan' set
Bram Moolenaar071d4272004-06-13 20:20:40 +0000792 {
793 for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
794 lnum += dir, at_first_line = FALSE)
795 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100796 // Stop after checking "stop_lnum", if it's set.
Bram Moolenaara23ccb82006-02-27 00:08:02 +0000797 if (stop_lnum != 0 && (dir == FORWARD
798 ? lnum > stop_lnum : lnum < stop_lnum))
799 break;
Paul Ollis65745772022-06-05 16:55:54 +0100800 // Stop after passing the time limit.
801 if (*timed_out)
Bram Moolenaar76929292008-01-06 19:07:36 +0000802 break;
Bram Moolenaara23ccb82006-02-27 00:08:02 +0000803
Bram Moolenaar071d4272004-06-13 20:20:40 +0000804 /*
Bram Moolenaar677ee682005-01-27 14:41:15 +0000805 * Look for a match somewhere in line "lnum".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000806 */
Bram Moolenaarad4d8a12015-12-28 19:20:36 +0100807 col = at_first_line && (options & SEARCH_COL) ? pos->col
808 : (colnr_T)0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000809 nmatched = vim_regexec_multi(&regmatch, win, buf,
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +0100810 lnum, col, timed_out);
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200811 // vim_regexec_multi() may clear "regprog"
812 if (regmatch.regprog == NULL)
813 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100814 // Abort searching on an error (e.g., out of stack).
Paul Ollis65745772022-06-05 16:55:54 +0100815 if (called_emsg > called_emsg_before || *timed_out)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000816 break;
817 if (nmatched > 0)
818 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100819 // match may actually be in another line when using \zs
Bram Moolenaar677ee682005-01-27 14:41:15 +0000820 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000821 endpos = regmatch.endpos[0];
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000822#ifdef FEAT_EVAL
Bram Moolenaar071d4272004-06-13 20:20:40 +0000823 submatch = first_submatch(&regmatch);
Bram Moolenaar91a4e822008-01-19 14:59:58 +0000824#endif
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100825 // "lnum" may be past end of buffer for "\n\zs".
Bram Moolenaar32466aa2006-02-24 23:53:04 +0000826 if (lnum + matchpos.lnum > buf->b_ml.ml_line_count)
827 ptr = (char_u *)"";
828 else
829 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000830
831 /*
832 * Forward search in the first line: match should be after
833 * the start position. If not, continue at the end of the
834 * match (this is vi compatible) or on the next char.
835 */
836 if (dir == FORWARD && at_first_line)
837 {
838 match_ok = TRUE;
Bram Moolenaarc96311b2022-11-25 21:13:47 +0000839
Bram Moolenaar071d4272004-06-13 20:20:40 +0000840 /*
Bram Moolenaar677ee682005-01-27 14:41:15 +0000841 * When the match starts in a next line it's certainly
842 * past the start position.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000843 * When match lands on a NUL the cursor will be put
844 * one back afterwards, compare with that position,
845 * otherwise "/$" will get stuck on end of line.
846 */
Bram Moolenaar677ee682005-01-27 14:41:15 +0000847 while (matchpos.lnum == 0
Bram Moolenaara3dfccc2014-11-27 17:29:56 +0100848 && ((options & SEARCH_END) && first_match
Bram Moolenaar677ee682005-01-27 14:41:15 +0000849 ? (nmatched == 1
850 && (int)endpos.col - 1
Bram Moolenaar071d4272004-06-13 20:20:40 +0000851 < (int)start_pos.col + extra_col)
Bram Moolenaar677ee682005-01-27 14:41:15 +0000852 : ((int)matchpos.col
853 - (ptr[matchpos.col] == NUL)
854 < (int)start_pos.col + extra_col)))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000855 {
856 /*
857 * If vi-compatible searching, continue at the end
858 * of the match, otherwise continue one position
859 * forward.
860 */
John Marriott8c85a2a2024-05-20 19:18:26 +0200861 if (search_from_match_end)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000862 {
863 if (nmatched > 1)
864 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100865 // end is in next line, thus no match in
866 // this line
Bram Moolenaar071d4272004-06-13 20:20:40 +0000867 match_ok = FALSE;
868 break;
869 }
870 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100871 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000872 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000873 && ptr[matchcol] != NUL)
874 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000875 if (has_mbyte)
876 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000877 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000878 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000879 ++matchcol;
880 }
881 }
882 else
883 {
Bram Moolenaarc96311b2022-11-25 21:13:47 +0000884 // Advance "matchcol" to the next character.
Bram Moolenaar837ca8f2022-11-26 18:59:19 +0000885 // This uses rmm_matchcol, the actual start of
886 // the match, ignoring "\zs".
887 matchcol = regmatch.rmm_matchcol;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000888 if (ptr[matchcol] != NUL)
889 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000890 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000891 matchcol += (*mb_ptr2len)(ptr
Bram Moolenaar071d4272004-06-13 20:20:40 +0000892 + matchcol);
893 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000894 ++matchcol;
895 }
896 }
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +0200897 if (matchcol == 0 && (options & SEARCH_START))
Bram Moolenaardb333a52013-03-19 15:27:48 +0100898 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000899 if (ptr[matchcol] == NUL
900 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +0000901 win, buf, lnum + matchpos.lnum,
Paul Ollis65745772022-06-05 16:55:54 +0100902 matchcol, timed_out)) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000903 {
904 match_ok = FALSE;
905 break;
906 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +0200907 // vim_regexec_multi() may clear "regprog"
908 if (regmatch.regprog == NULL)
909 break;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000910 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000911 endpos = regmatch.endpos[0];
912# ifdef FEAT_EVAL
913 submatch = first_submatch(&regmatch);
914# endif
915
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100916 // Need to get the line pointer again, a
917 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000918 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000919 }
920 if (!match_ok)
921 continue;
922 }
923 if (dir == BACKWARD)
924 {
925 /*
926 * Now, if there are multiple matches on this line,
927 * we have to get the last one. Or the last one before
928 * the cursor, if we're on that line.
929 * When putting the new cursor at the end, compare
930 * relative to the end of the match.
931 */
932 match_ok = FALSE;
933 for (;;)
934 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100935 // Remember a position that is before the start
936 // position, we use it if it's the last match in
937 // the line. Always accept a position after
938 // wrapping around.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000939 if (loop
940 || ((options & SEARCH_END)
941 ? (lnum + regmatch.endpos[0].lnum
942 < start_pos.lnum
943 || (lnum + regmatch.endpos[0].lnum
944 == start_pos.lnum
945 && (int)regmatch.endpos[0].col - 1
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200946 < (int)start_pos.col
947 + extra_col))
Bram Moolenaar677ee682005-01-27 14:41:15 +0000948 : (lnum + regmatch.startpos[0].lnum
949 < start_pos.lnum
950 || (lnum + regmatch.startpos[0].lnum
951 == start_pos.lnum
952 && (int)regmatch.startpos[0].col
Bram Moolenaar5f1e68b2015-07-10 14:43:35 +0200953 < (int)start_pos.col
954 + extra_col))))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000955 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000956 match_ok = TRUE;
Bram Moolenaar677ee682005-01-27 14:41:15 +0000957 matchpos = regmatch.startpos[0];
Bram Moolenaar071d4272004-06-13 20:20:40 +0000958 endpos = regmatch.endpos[0];
959# ifdef FEAT_EVAL
960 submatch = first_submatch(&regmatch);
961# endif
962 }
963 else
964 break;
965
966 /*
967 * We found a valid match, now check if there is
968 * another one after it.
969 * If vi-compatible searching, continue at the end
970 * of the match, otherwise continue one position
971 * forward.
972 */
John Marriott8c85a2a2024-05-20 19:18:26 +0200973 if (search_from_match_end)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000974 {
975 if (nmatched > 1)
976 break;
977 matchcol = endpos.col;
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100978 // for empty match: advance one char
Bram Moolenaar677ee682005-01-27 14:41:15 +0000979 if (matchcol == matchpos.col
Bram Moolenaar071d4272004-06-13 20:20:40 +0000980 && ptr[matchcol] != NUL)
981 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000982 if (has_mbyte)
983 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000984 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000985 else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000986 ++matchcol;
987 }
988 }
989 else
990 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +0100991 // Stop when the match is in a next line.
Bram Moolenaar677ee682005-01-27 14:41:15 +0000992 if (matchpos.lnum > 0)
993 break;
994 matchcol = matchpos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000995 if (ptr[matchcol] != NUL)
996 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000997 if (has_mbyte)
998 matchcol +=
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000999 (*mb_ptr2len)(ptr + matchcol);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001000 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001001 ++matchcol;
1002 }
1003 }
1004 if (ptr[matchcol] == NUL
1005 || (nmatched = vim_regexec_multi(&regmatch,
Bram Moolenaar677ee682005-01-27 14:41:15 +00001006 win, buf, lnum + matchpos.lnum,
Paul Ollis65745772022-06-05 16:55:54 +01001007 matchcol, timed_out)) == 0)
Bram Moolenaar9d322762018-02-09 16:04:25 +01001008 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001009 // If the search timed out, we did find a match
1010 // but it might be the wrong one, so that's not
1011 // OK.
Paul Ollis65745772022-06-05 16:55:54 +01001012 if (*timed_out)
Bram Moolenaar9d322762018-02-09 16:04:25 +01001013 match_ok = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001014 break;
Bram Moolenaar9d322762018-02-09 16:04:25 +01001015 }
Bram Moolenaar795aaa12020-10-02 20:36:01 +02001016 // vim_regexec_multi() may clear "regprog"
1017 if (regmatch.regprog == NULL)
1018 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001019
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001020 // Need to get the line pointer again, a
1021 // multi-line search may have made it invalid.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001022 ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001023 }
1024
1025 /*
1026 * If there is only a match after the cursor, skip
1027 * this match.
1028 */
1029 if (!match_ok)
1030 continue;
1031 }
1032
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001033 // With the SEARCH_END option move to the last character
1034 // of the match. Don't do it for an empty match, end
1035 // should be same as start then.
Bram Moolenaar7bcb30e2013-04-03 21:14:29 +02001036 if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001037 && !(matchpos.lnum == endpos.lnum
1038 && matchpos.col == endpos.col))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001039 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001040 // For a match in the first column, set the position
1041 // on the NUL in the previous line.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001042 pos->lnum = lnum + endpos.lnum;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001043 pos->col = endpos.col;
1044 if (endpos.col == 0)
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001045 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001046 if (pos->lnum > 1) // just in case
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001047 {
1048 --pos->lnum;
zeertzjq94b7c322024-03-12 21:50:32 +01001049 pos->col = ml_get_buf_len(buf, pos->lnum);
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001050 }
Bram Moolenaar910f66f2006-04-05 20:41:53 +00001051 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001052 else
1053 {
1054 --pos->col;
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001055 if (has_mbyte
1056 && pos->lnum <= buf->b_ml.ml_line_count)
1057 {
1058 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1059 pos->col -= (*mb_head_off)(ptr, ptr + pos->col);
1060 }
Bram Moolenaar5bcbd532008-02-20 12:43:01 +00001061 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001062 if (end_pos != NULL)
1063 {
1064 end_pos->lnum = lnum + matchpos.lnum;
1065 end_pos->col = matchpos.col;
1066 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001067 }
1068 else
1069 {
Bram Moolenaar677ee682005-01-27 14:41:15 +00001070 pos->lnum = lnum + matchpos.lnum;
1071 pos->col = matchpos.col;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001072 if (end_pos != NULL)
1073 {
1074 end_pos->lnum = lnum + endpos.lnum;
1075 end_pos->col = endpos.col;
1076 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001077 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001078 pos->coladd = 0;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01001079 if (end_pos != NULL)
1080 end_pos->coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001081 found = 1;
Bram Moolenaara3dfccc2014-11-27 17:29:56 +01001082 first_match = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001083
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001084 // Set variables used for 'incsearch' highlighting.
Bram Moolenaar677ee682005-01-27 14:41:15 +00001085 search_match_lines = endpos.lnum - matchpos.lnum;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001086 search_match_endcol = endpos.col;
1087 break;
1088 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001089 line_breakcheck(); // stop if ctrl-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00001090 if (got_int)
1091 break;
1092
1093#ifdef FEAT_SEARCH_EXTRA
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001094 // Cancel searching if a character was typed. Used for
1095 // 'incsearch'. Don't check too often, that would slowdown
1096 // searching too much.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001097 if ((options & SEARCH_PEEK)
1098 && ((lnum - pos->lnum) & 0x3f) == 0
1099 && char_avail())
1100 {
1101 break_loop = TRUE;
1102 break;
1103 }
1104#endif
1105
1106 if (loop && lnum == start_pos.lnum)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001107 break; // if second loop, stop where started
Bram Moolenaar071d4272004-06-13 20:20:40 +00001108 }
1109 at_first_line = FALSE;
1110
Bram Moolenaar795aaa12020-10-02 20:36:01 +02001111 // vim_regexec_multi() may clear "regprog"
1112 if (regmatch.regprog == NULL)
1113 break;
1114
Bram Moolenaar071d4272004-06-13 20:20:40 +00001115 /*
Bram Moolenaara23ccb82006-02-27 00:08:02 +00001116 * Stop the search if wrapscan isn't set, "stop_lnum" is
1117 * specified, after an interrupt, after a match and after looping
1118 * twice.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001119 */
Bram Moolenaar53989552019-12-23 22:59:18 +01001120 if (!p_ws || stop_lnum != 0 || got_int
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001121 || called_emsg > called_emsg_before || *timed_out
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001122#ifdef FEAT_SEARCH_EXTRA
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001123 || break_loop
Bram Moolenaarfbd0b0a2017-06-17 18:44:21 +02001124#endif
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01001125 || found || loop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001126 break;
1127
1128 /*
1129 * If 'wrapscan' is set we continue at the other end of the file.
Christian Brabandt34a6a362023-05-06 19:20:20 +01001130 * If 'shortmess' does not contain 's', we give a message, but
1131 * only, if we won't show the search stat later anyhow,
1132 * (so SEARCH_COUNT must be absent).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001133 * This message is also remembered in keep_msg for when the screen
1134 * is redrawn. The keep_msg is cleared whenever another message is
1135 * written.
1136 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001137 if (dir == BACKWARD) // start second loop at the other end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001138 lnum = buf->b_ml.ml_line_count;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001139 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001140 lnum = 1;
Christian Brabandt34a6a362023-05-06 19:20:20 +01001141 if (!shortmess(SHM_SEARCH)
1142 && shortmess(SHM_SEARCHCOUNT)
1143 && (options & SEARCH_MSG))
Bram Moolenaar92d640f2005-09-05 22:11:52 +00001144 give_warning((char_u *)_(dir == BACKWARD
1145 ? top_bot_msg : bot_top_msg), TRUE);
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001146 if (extra_arg != NULL)
1147 extra_arg->sa_wrapped = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001148 }
Paul Ollis65745772022-06-05 16:55:54 +01001149 if (got_int || called_emsg > called_emsg_before || *timed_out
Bram Moolenaar78a15312009-05-15 19:33:18 +00001150#ifdef FEAT_SEARCH_EXTRA
1151 || break_loop
1152#endif
1153 )
Bram Moolenaar071d4272004-06-13 20:20:40 +00001154 break;
1155 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001156 while (--count > 0 && found); // stop after count matches or no match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001157
Bram Moolenaar5ea38d12022-06-16 21:20:48 +01001158#ifdef FEAT_RELTIME
1159 if (extra_arg != NULL && extra_arg->sa_tm > 0)
1160 disable_regexp_timeout();
1161#endif
Bram Moolenaar473de612013-06-08 18:19:48 +02001162 vim_regfree(regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001163
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001164 if (!found) // did not find it
Bram Moolenaar071d4272004-06-13 20:20:40 +00001165 {
1166 if (got_int)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00001167 emsg(_(e_interrupted));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001168 else if ((options & SEARCH_MSG) == SEARCH_MSG)
1169 {
1170 if (p_ws)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001171 semsg(_(e_pattern_not_found_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001172 else if (lnum == 0)
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001173 semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001174 else
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001175 semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001176 }
1177 return FAIL;
1178 }
1179
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001180 // A pattern like "\n\zs" may go past the last line.
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001181 if (pos->lnum > buf->b_ml.ml_line_count)
1182 {
1183 pos->lnum = buf->b_ml.ml_line_count;
zeertzjq94b7c322024-03-12 21:50:32 +01001184 pos->col = ml_get_buf_len(buf, pos->lnum);
Bram Moolenaar32466aa2006-02-24 23:53:04 +00001185 if (pos->col > 0)
1186 --pos->col;
1187 }
1188
Bram Moolenaar071d4272004-06-13 20:20:40 +00001189 return submatch + 1;
1190}
1191
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00001192#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001193 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001194set_search_direction(int cdir)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001195{
1196 spats[0].off.dir = cdir;
1197}
1198
1199 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001200set_vv_searchforward(void)
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001201{
1202 set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
1203}
1204
Bram Moolenaar071d4272004-06-13 20:20:40 +00001205/*
1206 * Return the number of the first subpat that matched.
Bram Moolenaarad4d8a12015-12-28 19:20:36 +01001207 * Return zero if none of them matched.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001208 */
1209 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001210first_submatch(regmmatch_T *rp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001211{
1212 int submatch;
1213
1214 for (submatch = 1; ; ++submatch)
1215 {
1216 if (rp->startpos[submatch].lnum >= 0)
1217 break;
1218 if (submatch == 9)
1219 {
1220 submatch = 0;
1221 break;
1222 }
1223 }
1224 return submatch;
1225}
1226#endif
1227
1228/*
1229 * Highest level string search function.
Bram Moolenaarb8017e72007-05-10 18:59:07 +00001230 * Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001231 * If 'dirc' is 0: use previous dir.
1232 * If 'pat' is NULL or empty : use previous string.
1233 * If 'options & SEARCH_REV' : go in reverse of previous dir.
1234 * If 'options & SEARCH_ECHO': echo the search command and handle options
1235 * If 'options & SEARCH_MSG' : may give error message
1236 * If 'options & SEARCH_OPT' : interpret optional flags
1237 * If 'options & SEARCH_HIS' : put search pattern in history
1238 * If 'options & SEARCH_NOOF': don't add offset to position
1239 * If 'options & SEARCH_MARK': set previous context mark
1240 * If 'options & SEARCH_KEEP': keep previous search pattern
1241 * If 'options & SEARCH_START': accept match at curpos itself
1242 * If 'options & SEARCH_PEEK': check for typed char, cancel search
1243 *
1244 * Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
1245 * makes the movement linewise without moving the match position.
1246 *
Bram Moolenaarb6c27352015-03-05 19:57:49 +01001247 * Return 0 for failure, 1 for found, 2 for found and line offset added.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001248 */
1249 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001250do_search(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001251 oparg_T *oap, // can be NULL
1252 int dirc, // '/' or '?'
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001253 int search_delim, // the delimiter for the search, e.g. '%' in
1254 // s%regex%replacement%
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001255 char_u *pat,
John Marriott8c85a2a2024-05-20 19:18:26 +02001256 size_t patlen,
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001257 long count,
1258 int options,
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001259 searchit_arg_T *sia) // optional arguments or NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001260{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001261 pos_T pos; // position of the last match
Bram Moolenaar071d4272004-06-13 20:20:40 +00001262 char_u *searchstr;
John Marriott8c85a2a2024-05-20 19:18:26 +02001263 size_t searchstrlen;
Bram Moolenaarc3328162019-07-23 22:15:25 +02001264 soffset_T old_off;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001265 int retval; // Return value
Bram Moolenaar071d4272004-06-13 20:20:40 +00001266 char_u *p;
1267 long c;
1268 char_u *dircp;
1269 char_u *strcopy = NULL;
1270 char_u *ps;
John Marriott8c85a2a2024-05-20 19:18:26 +02001271 int show_search_stats;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001272 char_u *msgbuf = NULL;
John Marriott8c85a2a2024-05-20 19:18:26 +02001273 size_t msgbuflen = 0;
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001274 int has_offset = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001275
John Marriott8c85a2a2024-05-20 19:18:26 +02001276 searchcmdlen = 0;
1277
Bram Moolenaar071d4272004-06-13 20:20:40 +00001278 /*
1279 * A line offset is not remembered, this is vi compatible.
1280 */
1281 if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
1282 {
1283 spats[0].off.line = FALSE;
1284 spats[0].off.off = 0;
1285 }
1286
1287 /*
1288 * Save the values for when (options & SEARCH_KEEP) is used.
1289 * (there is no "if ()" around this because gcc wants them initialized)
1290 */
1291 old_off = spats[0].off;
1292
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001293 pos = curwin->w_cursor; // start searching at the cursor position
Bram Moolenaar071d4272004-06-13 20:20:40 +00001294
1295 /*
1296 * Find out the direction of the search.
1297 */
1298 if (dirc == 0)
1299 dirc = spats[0].off.dir;
1300 else
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001301 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001302 spats[0].off.dir = dirc;
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001303#if defined(FEAT_EVAL)
1304 set_vv_searchforward();
1305#endif
1306 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001307 if (options & SEARCH_REV)
1308 {
Bram Moolenaar4f974752019-02-17 17:44:42 +01001309#ifdef MSWIN
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001310 // There is a bug in the Visual C++ 2.2 compiler which means that
1311 // dirc always ends up being '/'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001312 dirc = (dirc == '/') ? '?' : '/';
1313#else
1314 if (dirc == '/')
1315 dirc = '?';
1316 else
1317 dirc = '/';
1318#endif
1319 }
1320
1321#ifdef FEAT_FOLDING
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001322 // If the cursor is in a closed fold, don't find another match in the same
1323 // fold.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001324 if (dirc == '/')
1325 {
1326 if (hasFolding(pos.lnum, NULL, &pos.lnum))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001327 pos.col = MAXCOL - 2; // avoid overflow when adding 1
Bram Moolenaar071d4272004-06-13 20:20:40 +00001328 }
1329 else
1330 {
1331 if (hasFolding(pos.lnum, &pos.lnum, NULL))
1332 pos.col = 0;
1333 }
1334#endif
1335
1336#ifdef FEAT_SEARCH_EXTRA
1337 /*
1338 * Turn 'hlsearch' highlighting back on.
1339 */
1340 if (no_hlsearch && !(options & SEARCH_KEEP))
1341 {
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001342 redraw_all_later(UPD_SOME_VALID);
Bram Moolenaar451fc7b2018-04-27 22:53:07 +02001343 set_no_hlsearch(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001344 }
1345#endif
1346
1347 /*
1348 * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
1349 */
1350 for (;;)
1351 {
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001352 int show_top_bot_msg = FALSE;
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001353
Bram Moolenaar071d4272004-06-13 20:20:40 +00001354 searchstr = pat;
John Marriott8c85a2a2024-05-20 19:18:26 +02001355 searchstrlen = patlen;
1356
Bram Moolenaar071d4272004-06-13 20:20:40 +00001357 dircp = NULL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001358 // use previous pattern
Bram Moolenaarc036e872020-02-21 21:30:52 +01001359 if (pat == NULL || *pat == NUL || *pat == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001360 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001361 if (spats[RE_SEARCH].pat == NULL) // no previous pattern
Bram Moolenaar071d4272004-06-13 20:20:40 +00001362 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001363 if (spats[RE_SUBST].pat == NULL)
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001364 {
Bram Moolenaare29a27f2021-07-20 21:07:36 +02001365 emsg(_(e_no_previous_regular_expression));
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001366 retval = 0;
1367 goto end_do_search;
1368 }
John Marriott8c85a2a2024-05-20 19:18:26 +02001369 searchstr = spats[RE_SUBST].pat;
1370 searchstrlen = spats[RE_SUBST].patlen;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001371 }
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001372 else
1373 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001374 // make search_regcomp() use spats[RE_SEARCH].pat
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001375 searchstr = (char_u *)"";
John Marriott8c85a2a2024-05-20 19:18:26 +02001376 searchstrlen = 0;
Bram Moolenaarb4b0a082011-02-25 18:38:36 +01001377 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001378 }
1379
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001380 if (pat != NULL && *pat != NUL) // look for (new) offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001381 {
1382 /*
1383 * Find end of regular expression.
1384 * If there is a matching '/' or '?', toss it.
1385 */
1386 ps = strcopy;
Bram Moolenaarf4e20992020-12-21 19:59:08 +01001387 p = skip_regexp_ex(pat, search_delim, magic_isset(),
Bram Moolenaard93a7fc2021-01-04 12:42:13 +01001388 &strcopy, NULL, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001389 if (strcopy != ps)
1390 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001391 size_t len = STRLEN(strcopy);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001392 // made a copy of "pat" to change "\?" to "?"
John Marriott8c85a2a2024-05-20 19:18:26 +02001393 searchcmdlen += (int)(patlen - len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001394 pat = strcopy;
John Marriott8c85a2a2024-05-20 19:18:26 +02001395 patlen = len;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001396 searchstr = strcopy;
John Marriott8c85a2a2024-05-20 19:18:26 +02001397 searchstrlen = len;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001398 }
Bram Moolenaarc036e872020-02-21 21:30:52 +01001399 if (*p == search_delim)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001400 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001401 searchstrlen = p - pat;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001402 dircp = p; // remember where we put the NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001403 *p++ = NUL;
1404 }
1405 spats[0].off.line = FALSE;
1406 spats[0].off.end = FALSE;
1407 spats[0].off.off = 0;
1408 /*
1409 * Check for a line offset or a character offset.
1410 * For get_address (echo off) we don't check for a character
1411 * offset, because it is meaningless and the 's' could be a
1412 * substitute command.
1413 */
1414 if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p))
1415 spats[0].off.line = TRUE;
Dominique Pelle7765f5c2022-04-10 11:26:53 +01001416 else if ((options & SEARCH_OPT)
1417 && (*p == 'e' || *p == 's' || *p == 'b'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001418 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001419 if (*p == 'e') // end
Bram Moolenaar071d4272004-06-13 20:20:40 +00001420 spats[0].off.end = SEARCH_END;
1421 ++p;
1422 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001423 if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
Bram Moolenaar071d4272004-06-13 20:20:40 +00001424 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001425 // 'nr' or '+nr' or '-nr'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001426 if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
1427 spats[0].off.off = atol((char *)p);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001428 else if (*p == '-') // single '-'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001429 spats[0].off.off = -1;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001430 else // single '+'
Bram Moolenaar071d4272004-06-13 20:20:40 +00001431 spats[0].off.off = 1;
1432 ++p;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001433 while (VIM_ISDIGIT(*p)) // skip number
Bram Moolenaar071d4272004-06-13 20:20:40 +00001434 ++p;
1435 }
1436
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001437 // compute length of search command for get_address()
Bram Moolenaar071d4272004-06-13 20:20:40 +00001438 searchcmdlen += (int)(p - pat);
1439
John Marriott8c85a2a2024-05-20 19:18:26 +02001440 patlen -= p - pat;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001441 pat = p; // put pat after search command
Bram Moolenaar071d4272004-06-13 20:20:40 +00001442 }
1443
John Marriott8c85a2a2024-05-20 19:18:26 +02001444 show_search_stats = FALSE;
Dominique Pelle7765f5c2022-04-10 11:26:53 +01001445 if ((options & SEARCH_ECHO) && messaging()
1446 && !msg_silent
1447 && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT)))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001448 {
Bram Moolenaar984f0312019-05-24 13:11:47 +02001449 char_u off_buf[40];
Bram Moolenaard33a7642019-05-24 17:56:14 +02001450 size_t off_len = 0;
John Marriott8c85a2a2024-05-20 19:18:26 +02001451 size_t plen;
1452 size_t msgbufsize;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001453
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001454 // Compute msg_row early.
1455 msg_start();
1456
Bram Moolenaar984f0312019-05-24 13:11:47 +02001457 // Get the offset, so we know how long it is.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001458 if (!cmd_silent &&
1459 (spats[0].off.line || spats[0].off.end || spats[0].off.off))
Bram Moolenaar984f0312019-05-24 13:11:47 +02001460 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001461 off_buf[off_len++] = dirc;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001462 if (spats[0].off.end)
John Marriott8c85a2a2024-05-20 19:18:26 +02001463 off_buf[off_len++] = 'e';
Bram Moolenaar984f0312019-05-24 13:11:47 +02001464 else if (!spats[0].off.line)
John Marriott8c85a2a2024-05-20 19:18:26 +02001465 off_buf[off_len++] = 's';
Bram Moolenaar984f0312019-05-24 13:11:47 +02001466 if (spats[0].off.off > 0 || spats[0].off.line)
John Marriott8c85a2a2024-05-20 19:18:26 +02001467 off_buf[off_len++] = '+';
1468 off_buf[off_len] = NUL;
Bram Moolenaar984f0312019-05-24 13:11:47 +02001469 if (spats[0].off.off != 0 || spats[0].off.line)
John Marriott8c85a2a2024-05-20 19:18:26 +02001470 off_len += vim_snprintf((char *)off_buf + off_len, sizeof(off_buf) - off_len, "%ld", spats[0].off.off);
Bram Moolenaar984f0312019-05-24 13:11:47 +02001471 }
1472
Bram Moolenaar071d4272004-06-13 20:20:40 +00001473 if (*searchstr == NUL)
John Marriott8c85a2a2024-05-20 19:18:26 +02001474 {
Bram Moolenaar2fb8f682018-12-01 13:14:45 +01001475 p = spats[0].pat;
John Marriott8c85a2a2024-05-20 19:18:26 +02001476 plen = spats[0].patlen;
1477 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001478 else
John Marriott8c85a2a2024-05-20 19:18:26 +02001479 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001480 p = searchstr;
John Marriott8c85a2a2024-05-20 19:18:26 +02001481 plen = searchstrlen;
1482 }
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001483
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001484 if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001485 {
1486 // Reserve enough space for the search pattern + offset +
Bram Moolenaar984f0312019-05-24 13:11:47 +02001487 // search stat. Use all the space available, so that the
1488 // search state is right aligned. If there is not enough space
1489 // msg_strtrunc() will shorten in the middle.
Bram Moolenaar19e8ac72019-09-03 22:23:38 +02001490 if (msg_scrolled != 0 && !cmd_silent)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001491 // Use all the columns.
John Marriott8c85a2a2024-05-20 19:18:26 +02001492 msgbufsize = (int)(Rows - msg_row) * Columns - 1;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001493 else
1494 // Use up to 'showcmd' column.
John Marriott8c85a2a2024-05-20 19:18:26 +02001495 msgbufsize = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
1496 if (msgbufsize < plen + off_len + SEARCH_STAT_BUF_LEN + 3)
1497 msgbufsize = plen + off_len + SEARCH_STAT_BUF_LEN + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001498 }
1499 else
1500 // Reserve enough space for the search pattern + offset.
John Marriott8c85a2a2024-05-20 19:18:26 +02001501 msgbufsize = plen + off_len + 3;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001502
Bram Moolenaar880e4d92020-04-11 21:31:28 +02001503 vim_free(msgbuf);
John Marriott8c85a2a2024-05-20 19:18:26 +02001504 msgbuf = alloc(msgbufsize);
1505 if (msgbuf == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001506 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001507 msgbuflen = 0;
1508 }
1509 else
1510 {
1511 vim_memset(msgbuf, ' ', msgbufsize);
1512 msgbuflen = msgbufsize - 1;
1513 msgbuf[msgbuflen] = NUL;
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001514 // do not fill the msgbuf buffer, if cmd_silent is set, leave it
1515 // empty for the search_stat feature.
1516 if (!cmd_silent)
Bram Moolenaarcafda4f2005-09-06 19:25:11 +00001517 {
John Marriott8c85a2a2024-05-20 19:18:26 +02001518 char_u *trunc;
1519
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001520 msgbuf[0] = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001521
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001522 if (enc_utf8 && utf_iscomposing(utf_ptr2char(p)))
1523 {
1524 // Use a space to draw the composing char on.
1525 msgbuf[1] = ' ';
John Marriott8c85a2a2024-05-20 19:18:26 +02001526 mch_memmove(msgbuf + 2, p, plen);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001527 }
1528 else
John Marriott8c85a2a2024-05-20 19:18:26 +02001529 mch_memmove(msgbuf + 1, p, plen);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001530 if (off_len > 0)
John Marriott8c85a2a2024-05-20 19:18:26 +02001531 mch_memmove(msgbuf + plen + 1, off_buf, off_len);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001532
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001533 trunc = msg_strtrunc(msgbuf, TRUE);
1534 if (trunc != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001535 {
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001536 vim_free(msgbuf);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001537 msgbuf = trunc;
John Marriott8c85a2a2024-05-20 19:18:26 +02001538 msgbuflen = STRLEN(msgbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001539 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001540
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001541#ifdef FEAT_RIGHTLEFT
1542 // The search pattern could be shown on the right in
1543 // rightleft mode, but the 'ruler' and 'showcmd' area use
1544 // it too, thus it would be blanked out again very soon.
1545 // Show it on the left, but do reverse the text.
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001546 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
1547 {
1548 char_u *r;
1549 size_t pat_len;
1550
1551 r = reverse_text(msgbuf);
1552 if (r != NULL)
1553 {
1554 vim_free(msgbuf);
1555 msgbuf = r;
Christian Brabandtcacb6692024-08-22 21:40:14 +02001556 msgbuflen = STRLEN(msgbuf);
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001557 // move reversed text to beginning of buffer
1558 while (*r != NUL && *r == ' ')
1559 r++;
John Marriott8c85a2a2024-05-20 19:18:26 +02001560 pat_len = msgbuf + msgbuflen - r;
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001561 mch_memmove(msgbuf, r, pat_len);
1562 // overwrite old text
1563 if ((size_t)(r - msgbuf) >= pat_len)
1564 vim_memset(r, ' ', pat_len);
1565 else
1566 vim_memset(msgbuf + pat_len, ' ', r - msgbuf);
1567 }
1568 }
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001569#endif
Bram Moolenaar359ad1a2019-09-02 21:44:59 +02001570 msg_outtrans(msgbuf);
1571 msg_clr_eos();
1572 msg_check();
1573
1574 gotocmdline(FALSE);
1575 out_flush();
1576 msg_nowait = TRUE; // don't wait for this message
1577 }
John Marriott8c85a2a2024-05-20 19:18:26 +02001578
1579 if (!shortmess(SHM_SEARCHCOUNT))
1580 show_search_stats = TRUE;
1581 } // msgbuf != NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001582 }
1583
1584 /*
1585 * If there is a character offset, subtract it from the current
1586 * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
Bram Moolenaared203462004-06-16 11:19:22 +00001587 * Skip this if pos.col is near MAXCOL (closed fold).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001588 * This is not done for a line offset, because then we would not be vi
1589 * compatible.
1590 */
Bram Moolenaared203462004-06-16 11:19:22 +00001591 if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001592 {
1593 if (spats[0].off.off > 0)
1594 {
1595 for (c = spats[0].off.off; c; --c)
1596 if (decl(&pos) == -1)
1597 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001598 if (c) // at start of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001599 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001600 pos.lnum = 0; // allow lnum == 0 here
Bram Moolenaar071d4272004-06-13 20:20:40 +00001601 pos.col = MAXCOL;
1602 }
1603 }
1604 else
1605 {
1606 for (c = spats[0].off.off; c; ++c)
1607 if (incl(&pos) == -1)
1608 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001609 if (c) // at end of buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001610 {
1611 pos.lnum = curbuf->b_ml.ml_line_count + 1;
1612 pos.col = 0;
1613 }
1614 }
1615 }
1616
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001617 /*
1618 * The actual search.
1619 */
Bram Moolenaar14184a32019-02-16 15:10:30 +01001620 c = searchit(curwin, curbuf, &pos, NULL,
1621 dirc == '/' ? FORWARD : BACKWARD,
John Marriott8c85a2a2024-05-20 19:18:26 +02001622 searchstr, searchstrlen, count, spats[0].off.end + (options &
Bram Moolenaar071d4272004-06-13 20:20:40 +00001623 (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS
1624 + SEARCH_MSG + SEARCH_START
1625 + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02001626 RE_LAST, sia);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001627
1628 if (dircp != NULL)
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02001629 *dircp = search_delim; // restore second '/' or '?' for normal_cmd()
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001630
1631 if (!shortmess(SHM_SEARCH)
1632 && ((dirc == '/' && LT_POS(pos, curwin->w_cursor))
1633 || (dirc == '?' && LT_POS(curwin->w_cursor, pos))))
Bram Moolenaarc7a10b32019-05-06 21:37:18 +02001634 show_top_bot_msg = TRUE;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001635
Bram Moolenaar071d4272004-06-13 20:20:40 +00001636 if (c == FAIL)
1637 {
1638 retval = 0;
1639 goto end_do_search;
1640 }
1641 if (spats[0].off.end && oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001642 oap->inclusive = TRUE; // 'e' includes last character
Bram Moolenaar071d4272004-06-13 20:20:40 +00001643
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001644 retval = 1; // pattern found
Bram Moolenaar071d4272004-06-13 20:20:40 +00001645
1646 /*
1647 * Add character and/or line offset
1648 */
Bram Moolenaar9160f302006-08-29 15:58:12 +00001649 if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001650 {
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001651 pos_T org_pos = pos;
1652
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001653 if (spats[0].off.line) // Add the offset to the line number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001654 {
1655 c = pos.lnum + spats[0].off.off;
1656 if (c < 1)
1657 pos.lnum = 1;
1658 else if (c > curbuf->b_ml.ml_line_count)
1659 pos.lnum = curbuf->b_ml.ml_line_count;
1660 else
1661 pos.lnum = c;
1662 pos.col = 0;
1663
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001664 retval = 2; // pattern found, line offset added
Bram Moolenaar071d4272004-06-13 20:20:40 +00001665 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001666 else if (pos.col < MAXCOL - 2) // just in case
Bram Moolenaar071d4272004-06-13 20:20:40 +00001667 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001668 // to the right, check for end of file
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001669 c = spats[0].off.off;
1670 if (c > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001671 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001672 while (c-- > 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001673 if (incl(&pos) == -1)
1674 break;
1675 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001676 // to the left, check for start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00001677 else
1678 {
Bram Moolenaar8c8de832008-06-24 22:58:06 +00001679 while (c++ < 0)
1680 if (decl(&pos) == -1)
1681 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001682 }
1683 }
Bram Moolenaar8f46e4c2019-05-24 22:08:15 +02001684 if (!EQUAL_POS(pos, org_pos))
1685 has_offset = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001686 }
1687
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001688 // Show [1/15] if 'S' is not in 'shortmess'.
John Marriott8c85a2a2024-05-20 19:18:26 +02001689 if (show_search_stats)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001690 cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
John Marriott8c85a2a2024-05-20 19:18:26 +02001691 show_top_bot_msg, msgbuf, msgbuflen,
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001692 (count != 1 || has_offset
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001693#ifdef FEAT_FOLDING
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001694 || (!(fdo_flags & FDO_SEARCH)
1695 && hasFolding(curwin->w_cursor.lnum,
1696 NULL, NULL))
Bram Moolenaar6cb07262020-05-29 22:49:43 +02001697#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02001698 ),
1699 SEARCH_STAT_DEF_MAX_COUNT,
1700 SEARCH_STAT_DEF_TIMEOUT);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001701
Bram Moolenaar071d4272004-06-13 20:20:40 +00001702 /*
1703 * The search command can be followed by a ';' to do another search.
1704 * For example: "/pat/;/foo/+3;?bar"
1705 * This is like doing another search command, except:
1706 * - The remembered direction '/' or '?' is from the first search.
1707 * - When an error happens the cursor isn't moved at all.
1708 * Don't do this when called by get_address() (it handles ';' itself).
1709 */
1710 if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';')
1711 break;
1712
1713 dirc = *++pat;
Bram Moolenaarc036e872020-02-21 21:30:52 +01001714 search_delim = dirc;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001715 if (dirc != '?' && dirc != '/')
1716 {
1717 retval = 0;
Bram Moolenaarac78dd42022-01-02 19:25:26 +00001718 emsg(_(e_expected_question_or_slash_after_semicolon));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001719 goto end_do_search;
1720 }
1721 ++pat;
John Marriott8c85a2a2024-05-20 19:18:26 +02001722 --patlen;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001723 }
1724
1725 if (options & SEARCH_MARK)
1726 setpcmark();
1727 curwin->w_cursor = pos;
1728 curwin->w_set_curswant = TRUE;
1729
1730end_do_search:
Bram Moolenaare1004402020-10-24 20:49:43 +02001731 if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001732 spats[0].off = old_off;
1733 vim_free(strcopy);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02001734 vim_free(msgbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001735
1736 return retval;
1737}
1738
Bram Moolenaar071d4272004-06-13 20:20:40 +00001739/*
1740 * search_for_exact_line(buf, pos, dir, pat)
1741 *
1742 * Search for a line starting with the given pattern (ignoring leading
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001743 * white-space), starting from pos and going in direction "dir". "pos" will
Bram Moolenaar071d4272004-06-13 20:20:40 +00001744 * contain the position of the match found. Blank lines match only if
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02001745 * ADDING is set. If p_ic is set then the pattern must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001746 * Return OK for success, or FAIL if no line found.
1747 */
1748 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001749search_for_exact_line(
1750 buf_T *buf,
1751 pos_T *pos,
1752 int dir,
1753 char_u *pat)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001754{
1755 linenr_T start = 0;
1756 char_u *ptr;
1757 char_u *p;
1758
1759 if (buf->b_ml.ml_line_count == 0)
1760 return FAIL;
1761 for (;;)
1762 {
1763 pos->lnum += dir;
1764 if (pos->lnum < 1)
1765 {
1766 if (p_ws)
1767 {
1768 pos->lnum = buf->b_ml.ml_line_count;
1769 if (!shortmess(SHM_SEARCH))
1770 give_warning((char_u *)_(top_bot_msg), TRUE);
1771 }
1772 else
1773 {
1774 pos->lnum = 1;
1775 break;
1776 }
1777 }
1778 else if (pos->lnum > buf->b_ml.ml_line_count)
1779 {
1780 if (p_ws)
1781 {
1782 pos->lnum = 1;
1783 if (!shortmess(SHM_SEARCH))
1784 give_warning((char_u *)_(bot_top_msg), TRUE);
1785 }
1786 else
1787 {
1788 pos->lnum = 1;
1789 break;
1790 }
1791 }
1792 if (pos->lnum == start)
1793 break;
1794 if (start == 0)
1795 start = pos->lnum;
1796 ptr = ml_get_buf(buf, pos->lnum, FALSE);
1797 p = skipwhite(ptr);
1798 pos->col = (colnr_T) (p - ptr);
1799
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001800 // when adding lines the matching line may be empty but it is not
1801 // ignored because we are interested in the next line -- Acevedo
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00001802 if (compl_status_adding() && !compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00001803 {
1804 if ((p_ic ? MB_STRICMP(p, pat) : STRCMP(p, pat)) == 0)
1805 return OK;
1806 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001807 else if (*p != NUL) // ignore empty lines
1808 { // expanding lines or words
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00001809 if ((p_ic ? MB_STRNICMP(p, pat, ins_compl_len())
1810 : STRNCMP(p, pat, ins_compl_len())) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001811 return OK;
1812 }
1813 }
1814 return FAIL;
1815}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001816
1817/*
1818 * Character Searches
1819 */
1820
1821/*
1822 * Search for a character in a line. If "t_cmd" is FALSE, move to the
1823 * position of the character, otherwise move to just before the char.
1824 * Do this "cap->count1" times.
1825 * Return FAIL or OK.
1826 */
1827 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001828searchc(cmdarg_T *cap, int t_cmd)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001829{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001830 int c = cap->nchar; // char to search for
1831 int dir = cap->arg; // TRUE for searching forward
1832 long count = cap->count1; // repeat count
Bram Moolenaar071d4272004-06-13 20:20:40 +00001833 int col;
1834 char_u *p;
1835 int len;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001836 int stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001837
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001838 if (c != NUL) // normal search: remember args for repeat
Bram Moolenaar071d4272004-06-13 20:20:40 +00001839 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001840 if (!KeyStuffed) // don't remember when redoing
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001842 *lastc = c;
1843 set_csearch_direction(dir);
1844 set_csearch_until(t_cmd);
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001845 lastc_bytelen = (*mb_char2bytes)(c, lastc_bytes);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001846 if (cap->ncharC1 != 0)
1847 {
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001848 lastc_bytelen += (*mb_char2bytes)(cap->ncharC1,
1849 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001850 if (cap->ncharC2 != 0)
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001851 lastc_bytelen += (*mb_char2bytes)(cap->ncharC2,
1852 lastc_bytes + lastc_bytelen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001853 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001854 }
1855 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001856 else // repeat previous search
Bram Moolenaar071d4272004-06-13 20:20:40 +00001857 {
zeertzjqe5d91ba2023-05-14 17:39:18 +01001858 if (*lastc == NUL && lastc_bytelen <= 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001859 return FAIL;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001860 if (dir) // repeat in opposite direction
Bram Moolenaar071d4272004-06-13 20:20:40 +00001861 dir = -lastcdir;
1862 else
1863 dir = lastcdir;
1864 t_cmd = last_t_cmd;
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001865 c = *lastc;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001866 // For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001867
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001868 // Force a move of at least one char, so ";" and "," will move the
1869 // cursor, even if the cursor is right in front of char we are looking
1870 // at.
Bram Moolenaar19fd09a2011-07-15 13:21:30 +02001871 if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd)
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001872 stop = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001873 }
1874
Bram Moolenaar60a795a2005-09-16 21:55:43 +00001875 if (dir == BACKWARD)
1876 cap->oap->inclusive = FALSE;
1877 else
1878 cap->oap->inclusive = TRUE;
1879
Bram Moolenaar071d4272004-06-13 20:20:40 +00001880 p = ml_get_curline();
1881 col = curwin->w_cursor.col;
zeertzjq94b7c322024-03-12 21:50:32 +01001882 len = ml_get_curline_len();
Bram Moolenaar071d4272004-06-13 20:20:40 +00001883
1884 while (count--)
1885 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001886 if (has_mbyte)
1887 {
1888 for (;;)
1889 {
1890 if (dir > 0)
1891 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00001892 col += (*mb_ptr2len)(p + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001893 if (col >= len)
1894 return FAIL;
1895 }
1896 else
1897 {
1898 if (col == 0)
1899 return FAIL;
1900 col -= (*mb_head_off)(p, p + col - 1) + 1;
1901 }
zeertzjqe5d91ba2023-05-14 17:39:18 +01001902 if (lastc_bytelen <= 1)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001903 {
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001904 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001905 break;
1906 }
Bram Moolenaar66727e12017-03-01 22:17:05 +01001907 else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0
Bram Moolenaarb129a442016-12-01 17:25:20 +01001908 && stop)
Bram Moolenaar66727e12017-03-01 22:17:05 +01001909 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001910 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001911 }
1912 }
1913 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001914 {
1915 for (;;)
1916 {
1917 if ((col += dir) < 0 || col >= len)
1918 return FAIL;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001919 if (p[col] == c && stop)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001920 break;
Bram Moolenaar8b3e0332011-06-26 05:36:34 +02001921 stop = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001922 }
1923 }
1924 }
1925
1926 if (t_cmd)
1927 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001928 // backup to before the character (possibly double-byte)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001929 col -= dir;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001930 if (has_mbyte)
1931 {
1932 if (dir < 0)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001933 // Landed on the search char which is lastc_bytelen long
Bram Moolenaardbd24b52015-08-11 14:26:19 +02001934 col += lastc_bytelen - 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001935 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01001936 // To previous char, which may be multi-byte.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001937 col -= (*mb_head_off)(p, p + col);
1938 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001939 }
1940 curwin->w_cursor.col = col;
1941
1942 return OK;
1943}
1944
1945/*
1946 * "Other" Searches
1947 */
1948
1949/*
1950 * findmatch - find the matching paren or brace
1951 *
1952 * Improvement over vi: Braces inside quotes are ignored.
1953 */
1954 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001955findmatch(oparg_T *oap, int initc)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001956{
1957 return findmatchlimit(oap, initc, 0, 0);
1958}
1959
1960/*
1961 * Return TRUE if the character before "linep[col]" equals "ch".
1962 * Return FALSE if "col" is zero.
1963 * Update "*prevcol" to the column of the previous character, unless "prevcol"
1964 * is NULL.
1965 * Handles multibyte string correctly.
1966 */
1967 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001968check_prevcol(
1969 char_u *linep,
1970 int col,
1971 int ch,
1972 int *prevcol)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001973{
1974 --col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001975 if (col > 0 && has_mbyte)
1976 col -= (*mb_head_off)(linep, linep + col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001977 if (prevcol)
1978 *prevcol = col;
1979 return (col >= 0 && linep[col] == ch) ? TRUE : FALSE;
1980}
1981
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001982/*
1983 * Raw string start is found at linep[startpos.col - 1].
1984 * Return TRUE if the matching end can be found between startpos and endpos.
1985 */
1986 static int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01001987find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001988{
1989 char_u *p;
1990 char_u *delim_copy;
1991 size_t delim_len;
1992 linenr_T lnum;
1993 int found = FALSE;
1994
1995 for (p = linep + startpos->col + 1; *p && *p != '('; ++p)
1996 ;
1997 delim_len = (p - linep) - startpos->col - 1;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001998 delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len);
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02001999 if (delim_copy == NULL)
2000 return FALSE;
2001 for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum)
2002 {
2003 char_u *line = ml_get(lnum);
2004
2005 for (p = line + (lnum == startpos->lnum
2006 ? startpos->col + 1 : 0); *p; ++p)
2007 {
2008 if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col)
2009 break;
Bram Moolenaar282f9c62020-08-04 21:46:18 +02002010 if (*p == ')' && STRNCMP(delim_copy, p + 1, delim_len) == 0
2011 && p[delim_len + 1] == '"')
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002012 {
2013 found = TRUE;
2014 break;
2015 }
2016 }
2017 if (found)
2018 break;
2019 }
2020 vim_free(delim_copy);
2021 return found;
2022}
2023
Bram Moolenaar071d4272004-06-13 20:20:40 +00002024/*
Bram Moolenaar556ae8e2019-11-21 22:27:22 +01002025 * Check matchpairs option for "*initc".
2026 * If there is a match set "*initc" to the matching character and "*findc" to
2027 * the opposite character. Set "*backwards" to the direction.
2028 * When "switchit" is TRUE swap the direction.
2029 */
2030 static void
2031find_mps_values(
2032 int *initc,
2033 int *findc,
2034 int *backwards,
2035 int switchit)
2036{
2037 char_u *ptr;
2038
2039 ptr = curbuf->b_p_mps;
2040 while (*ptr != NUL)
2041 {
2042 if (has_mbyte)
2043 {
2044 char_u *prev;
2045
2046 if (mb_ptr2char(ptr) == *initc)
2047 {
2048 if (switchit)
2049 {
2050 *findc = *initc;
2051 *initc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
2052 *backwards = TRUE;
2053 }
2054 else
2055 {
2056 *findc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
2057 *backwards = FALSE;
2058 }
2059 return;
2060 }
2061 prev = ptr;
2062 ptr += mb_ptr2len(ptr) + 1;
2063 if (mb_ptr2char(ptr) == *initc)
2064 {
2065 if (switchit)
2066 {
2067 *findc = *initc;
2068 *initc = mb_ptr2char(prev);
2069 *backwards = FALSE;
2070 }
2071 else
2072 {
2073 *findc = mb_ptr2char(prev);
2074 *backwards = TRUE;
2075 }
2076 return;
2077 }
2078 ptr += mb_ptr2len(ptr);
2079 }
2080 else
2081 {
2082 if (*ptr == *initc)
2083 {
2084 if (switchit)
2085 {
2086 *backwards = TRUE;
2087 *findc = *initc;
2088 *initc = ptr[2];
2089 }
2090 else
2091 {
2092 *backwards = FALSE;
2093 *findc = ptr[2];
2094 }
2095 return;
2096 }
2097 ptr += 2;
2098 if (*ptr == *initc)
2099 {
2100 if (switchit)
2101 {
2102 *backwards = FALSE;
2103 *findc = *initc;
2104 *initc = ptr[-2];
2105 }
2106 else
2107 {
2108 *backwards = TRUE;
2109 *findc = ptr[-2];
2110 }
2111 return;
2112 }
2113 ++ptr;
2114 }
2115 if (*ptr == ',')
2116 ++ptr;
2117 }
2118}
2119
2120/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002121 * findmatchlimit -- find the matching paren or brace, if it exists within
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002122 * maxtravel lines of the cursor. A maxtravel of 0 means search until falling
2123 * off the edge of the file.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002124 *
2125 * "initc" is the character to find a match for. NUL means to find the
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002126 * character at or after the cursor. Special values:
2127 * '*' look for C-style comment / *
2128 * '/' look for C-style comment / *, ignoring comment-end
2129 * '#' look for preprocessor directives
2130 * 'R' look for raw string start: R"delim(text)delim" (only backwards)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002131 *
2132 * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
2133 * FM_FORWARD search forwards (when initc is '/', '*' or '#')
2134 * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
2135 * FM_SKIPCOMM skip comments (not implemented yet!)
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002136 *
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002137 * "oap" is only used to set oap->motion_type for a linewise motion, it can be
Bram Moolenaarf75a9632005-09-13 21:20:47 +00002138 * NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002139 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002140 pos_T *
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002141findmatchlimit(
2142 oparg_T *oap,
2143 int initc,
2144 int flags,
2145 int maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002146{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002147 static pos_T pos; // current search position
2148 int findc = 0; // matching brace
Bram Moolenaar071d4272004-06-13 20:20:40 +00002149 int c;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002150 int count = 0; // cumulative number of braces
2151 int backwards = FALSE; // init for gcc
2152 int raw_string = FALSE; // search for raw string
2153 int inquote = FALSE; // TRUE when inside quotes
2154 char_u *linep; // pointer to current line
Bram Moolenaar071d4272004-06-13 20:20:40 +00002155 char_u *ptr;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002156 int do_quotes; // check for quotes in current line
2157 int at_start; // do_quotes value at start position
2158 int hash_dir = 0; // Direction searched for # things
2159 int comment_dir = 0; // Direction searched for comments
2160 pos_T match_pos; // Where last slash-star was found
2161 int start_in_quotes; // start position is in quotes
2162 int traveled = 0; // how far we've searched so far
2163 int ignore_cend = FALSE; // ignore comment end
2164 int cpo_match; // vi compatible matching
2165 int cpo_bsl; // don't recognize backslashes
2166 int match_escaped = 0; // search for escaped match
2167 int dir; // Direction to search
2168 int comment_col = MAXCOL; // start of / / comment
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002169 int lispcomm = FALSE; // inside of Lisp-style comment
2170 int lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002171
2172 pos = curwin->w_cursor;
Bram Moolenaarc56c4592013-08-14 17:45:29 +02002173 pos.coladd = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002174 linep = ml_get(pos.lnum);
2175
2176 cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
2177 cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
2178
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002179 // Direction to search when initc is '/', '*' or '#'
Bram Moolenaar071d4272004-06-13 20:20:40 +00002180 if (flags & FM_BACKWARD)
2181 dir = BACKWARD;
2182 else if (flags & FM_FORWARD)
2183 dir = FORWARD;
2184 else
2185 dir = 0;
2186
2187 /*
2188 * if initc given, look in the table for the matching character
2189 * '/' and '*' are special cases: look for start or end of comment.
2190 * When '/' is used, we ignore running backwards into an star-slash, for
2191 * "[*" command, we just want to find any comment.
2192 */
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002193 if (initc == '/' || initc == '*' || initc == 'R')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002194 {
2195 comment_dir = dir;
2196 if (initc == '/')
2197 ignore_cend = TRUE;
2198 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002199 raw_string = (initc == 'R');
Bram Moolenaar071d4272004-06-13 20:20:40 +00002200 initc = NUL;
2201 }
2202 else if (initc != '#' && initc != NUL)
2203 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002204 find_mps_values(&initc, &findc, &backwards, TRUE);
Connor Lane Smithb9115da2021-07-31 13:31:42 +02002205 if (dir)
2206 backwards = (dir == FORWARD) ? FALSE : TRUE;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002207 if (findc == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002208 return NULL;
2209 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002210 else
2211 {
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002212 /*
2213 * Either initc is '#', or no initc was given and we need to look
2214 * under the cursor.
2215 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002216 if (initc == '#')
2217 {
2218 hash_dir = dir;
2219 }
2220 else
2221 {
2222 /*
2223 * initc was not given, must look for something to match under
2224 * or near the cursor.
2225 * Only check for special things when 'cpo' doesn't have '%'.
2226 */
2227 if (!cpo_match)
2228 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002229 // Are we before or at #if, #else etc.?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002230 ptr = skipwhite(linep);
2231 if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
2232 {
2233 ptr = skipwhite(ptr + 1);
2234 if ( STRNCMP(ptr, "if", 2) == 0
2235 || STRNCMP(ptr, "endif", 5) == 0
2236 || STRNCMP(ptr, "el", 2) == 0)
2237 hash_dir = 1;
2238 }
2239
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002240 // Are we on a comment?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002241 else if (linep[pos.col] == '/')
2242 {
2243 if (linep[pos.col + 1] == '*')
2244 {
2245 comment_dir = FORWARD;
2246 backwards = FALSE;
2247 pos.col++;
2248 }
2249 else if (pos.col > 0 && linep[pos.col - 1] == '*')
2250 {
2251 comment_dir = BACKWARD;
2252 backwards = TRUE;
2253 pos.col--;
2254 }
2255 }
2256 else if (linep[pos.col] == '*')
2257 {
2258 if (linep[pos.col + 1] == '/')
2259 {
2260 comment_dir = BACKWARD;
2261 backwards = TRUE;
2262 }
2263 else if (pos.col > 0 && linep[pos.col - 1] == '/')
2264 {
2265 comment_dir = FORWARD;
2266 backwards = FALSE;
2267 }
2268 }
2269 }
2270
2271 /*
2272 * If we are not on a comment or the # at the start of a line, then
2273 * look for brace anywhere on this line after the cursor.
2274 */
2275 if (!hash_dir && !comment_dir)
2276 {
2277 /*
2278 * Find the brace under or after the cursor.
2279 * If beyond the end of the line, use the last character in
2280 * the line.
2281 */
2282 if (linep[pos.col] == NUL && pos.col)
2283 --pos.col;
2284 for (;;)
2285 {
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002286 initc = PTR2CHAR(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002287 if (initc == NUL)
2288 break;
2289
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002290 find_mps_values(&initc, &findc, &backwards, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002291 if (findc)
2292 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002293 pos.col += mb_ptr2len(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002294 }
2295 if (!findc)
2296 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002297 // no brace in the line, maybe use " #if" then
Bram Moolenaar071d4272004-06-13 20:20:40 +00002298 if (!cpo_match && *skipwhite(linep) == '#')
2299 hash_dir = 1;
2300 else
2301 return NULL;
2302 }
2303 else if (!cpo_bsl)
2304 {
2305 int col, bslcnt = 0;
2306
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002307 // Set "match_escaped" if there are an odd number of
2308 // backslashes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002309 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2310 bslcnt++;
2311 match_escaped = (bslcnt & 1);
2312 }
2313 }
2314 }
2315 if (hash_dir)
2316 {
2317 /*
2318 * Look for matching #if, #else, #elif, or #endif
2319 */
2320 if (oap != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002321 oap->motion_type = MLINE; // Linewise for this case only
Bram Moolenaar071d4272004-06-13 20:20:40 +00002322 if (initc != '#')
2323 {
2324 ptr = skipwhite(skipwhite(linep) + 1);
2325 if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0)
2326 hash_dir = 1;
2327 else if (STRNCMP(ptr, "endif", 5) == 0)
2328 hash_dir = -1;
2329 else
2330 return NULL;
2331 }
2332 pos.col = 0;
2333 while (!got_int)
2334 {
2335 if (hash_dir > 0)
2336 {
2337 if (pos.lnum == curbuf->b_ml.ml_line_count)
2338 break;
2339 }
2340 else if (pos.lnum == 1)
2341 break;
2342 pos.lnum += hash_dir;
2343 linep = ml_get(pos.lnum);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002344 line_breakcheck(); // check for CTRL-C typed
Bram Moolenaar071d4272004-06-13 20:20:40 +00002345 ptr = skipwhite(linep);
2346 if (*ptr != '#')
2347 continue;
2348 pos.col = (colnr_T) (ptr - linep);
2349 ptr = skipwhite(ptr + 1);
2350 if (hash_dir > 0)
2351 {
2352 if (STRNCMP(ptr, "if", 2) == 0)
2353 count++;
2354 else if (STRNCMP(ptr, "el", 2) == 0)
2355 {
2356 if (count == 0)
2357 return &pos;
2358 }
2359 else if (STRNCMP(ptr, "endif", 5) == 0)
2360 {
2361 if (count == 0)
2362 return &pos;
2363 count--;
2364 }
2365 }
2366 else
2367 {
2368 if (STRNCMP(ptr, "if", 2) == 0)
2369 {
2370 if (count == 0)
2371 return &pos;
2372 count--;
2373 }
2374 else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0)
2375 {
2376 if (count == 0)
2377 return &pos;
2378 }
2379 else if (STRNCMP(ptr, "endif", 5) == 0)
2380 count++;
2381 }
2382 }
2383 return NULL;
2384 }
2385 }
2386
2387#ifdef FEAT_RIGHTLEFT
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002388 // This is just guessing: when 'rightleft' is set, search for a matching
2389 // paren/brace in the other direction.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002390 if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>", initc) != NULL)
2391 backwards = !backwards;
2392#endif
2393
2394 do_quotes = -1;
2395 start_in_quotes = MAYBE;
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01002396 CLEAR_POS(&match_pos);
Bram Moolenaarfd2ac762006-03-01 22:09:21 +00002397
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002398 // backward search: Check if this line contains a single-line comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002399 if ((backwards && comment_dir) || lisp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002400 comment_col = check_linecomment(linep);
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002401 if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002402 lispcomm = TRUE; // find match inside this comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002403
Bram Moolenaar071d4272004-06-13 20:20:40 +00002404 while (!got_int)
2405 {
2406 /*
2407 * Go to the next position, forward or backward. We could use
2408 * inc() and dec() here, but that is much slower
2409 */
2410 if (backwards)
2411 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002412 // char to match is inside of comment, don't search outside
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002413 if (lispcomm && pos.col < (colnr_T)comment_col)
2414 break;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002415 if (pos.col == 0) // at start of line, go to prev. one
Bram Moolenaar071d4272004-06-13 20:20:40 +00002416 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002417 if (pos.lnum == 1) // start of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00002418 break;
2419 --pos.lnum;
2420
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00002421 if (maxtravel > 0 && ++traveled > maxtravel)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002422 break;
2423
2424 linep = ml_get(pos.lnum);
zeertzjq94b7c322024-03-12 21:50:32 +01002425 pos.col = ml_get_len(pos.lnum); // pos.col on trailing NUL
Bram Moolenaar071d4272004-06-13 20:20:40 +00002426 do_quotes = -1;
2427 line_breakcheck();
2428
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002429 // Check if this line contains a single-line comment
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002430 if (comment_dir || lisp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002431 comment_col = check_linecomment(linep);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002432 // skip comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002433 if (lisp && comment_col != MAXCOL)
2434 pos.col = comment_col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002435 }
2436 else
2437 {
2438 --pos.col;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002439 if (has_mbyte)
2440 pos.col -= (*mb_head_off)(linep, linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002441 }
2442 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002443 else // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002444 {
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002445 if (linep[pos.col] == NUL
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002446 // at end of line, go to next one
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002447 // For lisp don't search for match in comment
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002448 || (lisp && comment_col != MAXCOL
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002449 && pos.col == (colnr_T)comment_col))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002450 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002451 if (pos.lnum == curbuf->b_ml.ml_line_count // end of file
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002452 // line is exhausted and comment with it,
2453 // don't search for match in code
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002454 || lispcomm)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002455 break;
2456 ++pos.lnum;
2457
2458 if (maxtravel && traveled++ > maxtravel)
2459 break;
2460
2461 linep = ml_get(pos.lnum);
2462 pos.col = 0;
2463 do_quotes = -1;
2464 line_breakcheck();
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002465 if (lisp) // find comment pos in new line
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002466 comment_col = check_linecomment(linep);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002467 }
2468 else
2469 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00002470 if (has_mbyte)
Bram Moolenaar0fa313a2005-08-10 21:07:57 +00002471 pos.col += (*mb_ptr2len)(linep + pos.col);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002472 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00002473 ++pos.col;
2474 }
2475 }
2476
2477 /*
2478 * If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
2479 */
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002480 if (pos.col == 0 && (flags & FM_BLOCKSTOP)
2481 && (linep[0] == '{' || linep[0] == '}'))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002482 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002483 if (linep[0] == findc && count == 0) // match!
Bram Moolenaar071d4272004-06-13 20:20:40 +00002484 return &pos;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002485 break; // out of scope
Bram Moolenaar071d4272004-06-13 20:20:40 +00002486 }
2487
2488 if (comment_dir)
2489 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002490 // Note: comments do not nest, and we ignore quotes in them
2491 // TODO: ignore comment brackets inside strings
Bram Moolenaar071d4272004-06-13 20:20:40 +00002492 if (comment_dir == FORWARD)
2493 {
2494 if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
2495 {
2496 pos.col++;
2497 return &pos;
2498 }
2499 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002500 else // Searching backwards
Bram Moolenaar071d4272004-06-13 20:20:40 +00002501 {
2502 /*
2503 * A comment may contain / * or / /, it may also start or end
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002504 * with / * /. Ignore a / * after / / and after *.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002505 */
2506 if (pos.col == 0)
2507 continue;
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002508 else if (raw_string)
2509 {
2510 if (linep[pos.col - 1] == 'R'
2511 && linep[pos.col] == '"'
2512 && vim_strchr(linep + pos.col + 1, '(') != NULL)
2513 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002514 // Possible start of raw string. Now that we have the
2515 // delimiter we can check if it ends before where we
2516 // started searching, or before the previously found
2517 // raw string start.
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002518 if (!find_rawstring_end(linep, &pos,
2519 count > 0 ? &match_pos : &curwin->w_cursor))
2520 {
2521 count++;
2522 match_pos = pos;
2523 match_pos.col--;
2524 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002525 linep = ml_get(pos.lnum); // may have been released
Bram Moolenaarf7bb86d2015-07-28 21:17:36 +02002526 }
2527 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002528 else if ( linep[pos.col - 1] == '/'
2529 && linep[pos.col] == '*'
Bram Moolenaarf8c53d32017-11-12 15:36:38 +01002530 && (pos.col == 1 || linep[pos.col - 2] != '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002531 && (int)pos.col < comment_col)
2532 {
2533 count++;
2534 match_pos = pos;
2535 match_pos.col--;
2536 }
2537 else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
2538 {
2539 if (count > 0)
2540 pos = match_pos;
2541 else if (pos.col > 1 && linep[pos.col - 2] == '/'
2542 && (int)pos.col <= comment_col)
2543 pos.col -= 2;
2544 else if (ignore_cend)
2545 continue;
2546 else
2547 return NULL;
2548 return &pos;
2549 }
2550 }
2551 continue;
2552 }
2553
2554 /*
2555 * If smart matching ('cpoptions' does not contain '%'), braces inside
2556 * of quotes are ignored, but only if there is an even number of
2557 * quotes in the line.
2558 */
2559 if (cpo_match)
2560 do_quotes = 0;
2561 else if (do_quotes == -1)
2562 {
2563 /*
2564 * Count the number of quotes in the line, skipping \" and '"'.
2565 * Watch out for "\\".
2566 */
2567 at_start = do_quotes;
2568 for (ptr = linep; *ptr; ++ptr)
2569 {
2570 if (ptr == linep + pos.col + backwards)
2571 at_start = (do_quotes & 1);
2572 if (*ptr == '"'
2573 && (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
2574 ++do_quotes;
2575 if (*ptr == '\\' && ptr[1] != NUL)
2576 ++ptr;
2577 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002578 do_quotes &= 1; // result is 1 with even number of quotes
Bram Moolenaar071d4272004-06-13 20:20:40 +00002579
2580 /*
2581 * If we find an uneven count, check current line and previous
2582 * one for a '\' at the end.
2583 */
2584 if (!do_quotes)
2585 {
2586 inquote = FALSE;
2587 if (ptr[-1] == '\\')
2588 {
2589 do_quotes = 1;
2590 if (start_in_quotes == MAYBE)
2591 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002592 // Do we need to use at_start here?
Bram Moolenaar071d4272004-06-13 20:20:40 +00002593 inquote = TRUE;
2594 start_in_quotes = TRUE;
2595 }
2596 else if (backwards)
2597 inquote = TRUE;
2598 }
2599 if (pos.lnum > 1)
2600 {
2601 ptr = ml_get(pos.lnum - 1);
zeertzjq94b7c322024-03-12 21:50:32 +01002602 if (*ptr && *(ptr + ml_get_len(pos.lnum - 1) - 1) == '\\')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002603 {
2604 do_quotes = 1;
2605 if (start_in_quotes == MAYBE)
2606 {
2607 inquote = at_start;
2608 if (inquote)
2609 start_in_quotes = TRUE;
2610 }
2611 else if (!backwards)
2612 inquote = TRUE;
2613 }
Bram Moolenaaraec11792007-07-10 11:09:36 +00002614
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002615 // ml_get() only keeps one line, need to get linep again
Bram Moolenaaraec11792007-07-10 11:09:36 +00002616 linep = ml_get(pos.lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002617 }
2618 }
2619 }
2620 if (start_in_quotes == MAYBE)
2621 start_in_quotes = FALSE;
2622
2623 /*
2624 * If 'smartmatch' is set:
2625 * Things inside quotes are ignored by setting 'inquote'. If we
2626 * find a quote without a preceding '\' invert 'inquote'. At the
2627 * end of a line not ending in '\' we reset 'inquote'.
2628 *
2629 * In lines with an uneven number of quotes (without preceding '\')
2630 * we do not know which part to ignore. Therefore we only set
2631 * inquote if the number of quotes in a line is even, unless this
2632 * line or the previous one ends in a '\'. Complicated, isn't it?
2633 */
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002634 c = PTR2CHAR(linep + pos.col);
2635 switch (c)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002636 {
2637 case NUL:
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002638 // at end of line without trailing backslash, reset inquote
Bram Moolenaar071d4272004-06-13 20:20:40 +00002639 if (pos.col == 0 || linep[pos.col - 1] != '\\')
2640 {
2641 inquote = FALSE;
2642 start_in_quotes = FALSE;
2643 }
2644 break;
2645
2646 case '"':
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002647 // a quote that is preceded with an odd number of backslashes is
2648 // ignored
Bram Moolenaar071d4272004-06-13 20:20:40 +00002649 if (do_quotes)
2650 {
2651 int col;
2652
2653 for (col = pos.col - 1; col >= 0; --col)
2654 if (linep[col] != '\\')
2655 break;
2656 if ((((int)pos.col - 1 - col) & 1) == 0)
2657 {
2658 inquote = !inquote;
2659 start_in_quotes = FALSE;
2660 }
2661 }
2662 break;
2663
2664 /*
2665 * If smart matching ('cpoptions' does not contain '%'):
2666 * Skip things in single quotes: 'x' or '\x'. Be careful for single
2667 * single quotes, eg jon's. Things like '\233' or '\x3f' are not
2668 * skipped, there is never a brace in them.
2669 * Ignore this when finding matches for `'.
2670 */
2671 case '\'':
2672 if (!cpo_match && initc != '\'' && findc != '\'')
2673 {
2674 if (backwards)
2675 {
2676 if (pos.col > 1)
2677 {
2678 if (linep[pos.col - 2] == '\'')
2679 {
2680 pos.col -= 2;
2681 break;
2682 }
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002683 else if (linep[pos.col - 2] == '\\'
2684 && pos.col > 2 && linep[pos.col - 3] == '\'')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002685 {
2686 pos.col -= 3;
2687 break;
2688 }
2689 }
2690 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002691 else if (linep[pos.col + 1]) // forward search
Bram Moolenaar071d4272004-06-13 20:20:40 +00002692 {
Dominique Pelle7765f5c2022-04-10 11:26:53 +01002693 if (linep[pos.col + 1] == '\\'
2694 && linep[pos.col + 2] && linep[pos.col + 3] == '\'')
Bram Moolenaar071d4272004-06-13 20:20:40 +00002695 {
2696 pos.col += 3;
2697 break;
2698 }
2699 else if (linep[pos.col + 2] == '\'')
2700 {
2701 pos.col += 2;
2702 break;
2703 }
2704 }
2705 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002706 // FALLTHROUGH
Bram Moolenaar071d4272004-06-13 20:20:40 +00002707
2708 default:
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002709 /*
2710 * For Lisp skip over backslashed (), {} and [].
2711 * (actually, we skip #\( et al)
2712 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00002713 if (curbuf->b_p_lisp
Bram Moolenaarebfec1c2023-01-22 21:14:53 +00002714 && vim_strchr((char_u *)"{}()[]", c) != NULL
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002715 && pos.col > 1
2716 && check_prevcol(linep, pos.col, '\\', NULL)
2717 && check_prevcol(linep, pos.col - 1, '#', NULL))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002718 break;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002719
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002720 // Check for match outside of quotes, and inside of
2721 // quotes when the start is also inside of quotes.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002722 if ((!inquote || start_in_quotes == TRUE)
2723 && (c == initc || c == findc))
2724 {
2725 int col, bslcnt = 0;
2726
2727 if (!cpo_bsl)
2728 {
2729 for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
2730 bslcnt++;
2731 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002732 // Only accept a match when 'M' is in 'cpo' or when escaping
2733 // is what we expect.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002734 if (cpo_bsl || (bslcnt & 1) == match_escaped)
2735 {
2736 if (c == initc)
2737 count++;
2738 else
2739 {
2740 if (count == 0)
2741 return &pos;
2742 count--;
2743 }
2744 }
2745 }
2746 }
2747 }
2748
2749 if (comment_dir == BACKWARD && count > 0)
2750 {
2751 pos = match_pos;
2752 return &pos;
2753 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002754 return (pos_T *)NULL; // never found it
Bram Moolenaar071d4272004-06-13 20:20:40 +00002755}
2756
2757/*
2758 * Check if line[] contains a / / comment.
2759 * Return MAXCOL if not, otherwise return the column.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002760 */
Bram Moolenaar6e371ec2021-12-12 14:16:39 +00002761 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002762check_linecomment(char_u *line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002763{
2764 char_u *p;
2765
2766 p = line;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002767 // skip Lispish one-line comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002768 if (curbuf->b_p_lisp)
2769 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002770 if (vim_strchr(p, ';') != NULL) // there may be comments
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002771 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002772 int in_str = FALSE; // inside of string
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002773
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002774 p = line; // scan from start
Bram Moolenaar520470a2005-06-16 21:59:56 +00002775 while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002776 {
2777 if (*p == '"')
2778 {
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002779 if (in_str)
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002780 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002781 if (*(p - 1) != '\\') // skip escaped quote
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002782 in_str = FALSE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002783 }
2784 else if (p == line || ((p - line) >= 2
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002785 // skip #\" form
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002786 && *(p - 1) != '\\' && *(p - 2) != '#'))
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002787 in_str = TRUE;
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002788 }
Bram Moolenaar70b2a562012-01-10 22:26:17 +01002789 else if (!in_str && ((p - line) < 2
Bram Moolenaarba263672021-12-29 18:09:13 +00002790 || (*(p - 1) != '\\' && *(p - 2) != '#'))
2791 && !is_pos_in_string(line, (colnr_T)(p - line)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002792 break; // found!
Bram Moolenaar325b7a22004-07-05 15:58:32 +00002793 ++p;
2794 }
2795 }
2796 else
2797 p = NULL;
2798 }
2799 else
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002800 while ((p = vim_strchr(p, '/')) != NULL)
2801 {
2802 // Accept a double /, unless it's preceded with * and followed by
2803 // *, because * / / * is an end and start of a C comment. Only
2804 // accept the position if it is not inside a string.
2805 if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
Bram Moolenaarba263672021-12-29 18:09:13 +00002806 && !is_pos_in_string(line, (colnr_T)(p - line)))
Bram Moolenaar8e145b82022-05-21 20:17:31 +01002807 break;
2808 ++p;
2809 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002810
2811 if (p == NULL)
2812 return MAXCOL;
2813 return (int)(p - line);
2814}
2815
2816/*
2817 * Move cursor briefly to character matching the one under the cursor.
2818 * Used for Insert mode and "r" command.
2819 * Show the match only if it is visible on the screen.
2820 * If there isn't a match, then beep.
2821 */
2822 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01002823showmatch(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002824 int c) // char to show match for
Bram Moolenaar071d4272004-06-13 20:20:40 +00002825{
2826 pos_T *lpos, save_cursor;
2827 pos_T mpos;
2828 colnr_T vcol;
2829 long save_so;
2830 long save_siso;
2831#ifdef CURSOR_SHAPE
2832 int save_state;
2833#endif
2834 colnr_T save_dollar_vcol;
2835 char_u *p;
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01002836 long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
2837 long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002838
2839 /*
2840 * Only show match for chars in the 'matchpairs' option.
2841 */
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002842 // 'matchpairs' is "x:y,x:y"
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002843 for (p = curbuf->b_p_mps; *p != NUL; ++p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002844 {
2845#ifdef FEAT_RIGHTLEFT
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002846 if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002847 break;
Bram Moolenaar187d3ac2013-02-20 18:39:13 +01002848#endif
Bram Moolenaar1614a142019-10-06 22:00:13 +02002849 p += mb_ptr2len(p) + 1;
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002850 if (PTR2CHAR(p) == c
Bram Moolenaar071d4272004-06-13 20:20:40 +00002851#ifdef FEAT_RIGHTLEFT
2852 && !(curwin->w_p_rl ^ p_ri)
2853#endif
2854 )
2855 break;
Bram Moolenaar1614a142019-10-06 22:00:13 +02002856 p += mb_ptr2len(p);
Bram Moolenaar8c7694a2013-01-17 17:02:05 +01002857 if (*p == NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002858 return;
2859 }
Bram Moolenaar5b8cabf2021-04-02 18:55:57 +02002860 if (*p == NUL)
2861 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002862
Bram Moolenaar63d9e732019-12-05 21:10:38 +01002863 if ((lpos = findmatch(NULL, NUL)) == NULL) // no match, so beep
Bram Moolenaar071d4272004-06-13 20:20:40 +00002864 {
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00002865 vim_beep(BO_MATCH);
2866 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002867 }
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00002868
2869 if (lpos->lnum < curwin->w_topline || lpos->lnum >= curwin->w_botline)
2870 return;
2871
2872 if (!curwin->w_p_wrap)
2873 getvcol(curwin, lpos, NULL, &vcol, NULL);
2874
2875 int col_visible = (curwin->w_p_wrap
2876 || (vcol >= curwin->w_leftcol
2877 && vcol < curwin->w_leftcol + curwin->w_width));
2878 if (!col_visible)
2879 return;
2880
2881 mpos = *lpos; // save the pos, update_screen() may change it
2882 save_cursor = curwin->w_cursor;
2883 save_so = *so;
2884 save_siso = *siso;
2885 // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
2886 // stop displaying the "$".
2887 if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
2888 dollar_vcol = -1;
2889 ++curwin->w_virtcol; // do display ')' just before "$"
2890 update_screen(UPD_VALID); // show the new char first
2891
2892 save_dollar_vcol = dollar_vcol;
2893#ifdef CURSOR_SHAPE
2894 save_state = State;
2895 State = MODE_SHOWMATCH;
2896 ui_cursor_shape(); // may show different cursor shape
2897#endif
2898 curwin->w_cursor = mpos; // move to matching char
2899 *so = 0; // don't use 'scrolloff' here
2900 *siso = 0; // don't use 'sidescrolloff' here
2901 showruler(FALSE);
2902 setcursor();
2903 cursor_on(); // make sure that the cursor is shown
2904 out_flush_cursor(TRUE, FALSE);
2905
2906 // Restore dollar_vcol(), because setcursor() may call curs_rows()
2907 // which resets it if the matching position is in a previous line
2908 // and has a higher column number.
2909 dollar_vcol = save_dollar_vcol;
2910
2911 /*
2912 * brief pause, unless 'm' is present in 'cpo' and a character is
2913 * available.
2914 */
2915 if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
2916 ui_delay(p_mat * 100L + 8, TRUE);
2917 else if (!char_avail())
2918 ui_delay(p_mat * 100L + 9, FALSE);
2919 curwin->w_cursor = save_cursor; // restore cursor position
2920 *so = save_so;
2921 *siso = save_siso;
2922#ifdef CURSOR_SHAPE
2923 State = save_state;
2924 ui_cursor_shape(); // may show different cursor shape
2925#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00002926}
2927
2928/*
Bram Moolenaar453c1922019-10-26 14:42:09 +02002929 * Check if the pattern is zero-width.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002930 * If move is TRUE, check from the beginning of the buffer, else from position
2931 * "cur".
2932 * "direction" is FORWARD or BACKWARD.
2933 * Returns TRUE, FALSE or -1 for failure.
2934 */
2935 static int
John Marriott8c85a2a2024-05-20 19:18:26 +02002936is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002937{
2938 regmmatch_T regmatch;
2939 int nmatched = 0;
2940 int result = -1;
2941 pos_T pos;
Bram Moolenaar53989552019-12-23 22:59:18 +01002942 int called_emsg_before = called_emsg;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002943 int flag = 0;
2944
2945 if (pattern == NULL)
John Marriott8c85a2a2024-05-20 19:18:26 +02002946 {
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002947 pattern = spats[last_idx].pat;
John Marriott8c85a2a2024-05-20 19:18:26 +02002948 patternlen = spats[last_idx].patlen;
2949 }
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002950
John Marriott8c85a2a2024-05-20 19:18:26 +02002951 if (search_regcomp(pattern, patternlen, NULL, RE_SEARCH, RE_SEARCH,
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002952 SEARCH_KEEP, &regmatch) == FAIL)
2953 return -1;
2954
2955 // init startcol correctly
2956 regmatch.startpos[0].col = -1;
2957 // move to match
2958 if (move)
2959 {
2960 CLEAR_POS(&pos);
2961 }
2962 else
2963 {
2964 pos = *cur;
2965 // accept a match at the cursor position
2966 flag = SEARCH_START;
2967 }
2968
John Marriott8c85a2a2024-05-20 19:18:26 +02002969 if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, patternlen, 1,
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002970 SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL)
2971 {
2972 // Zero-width pattern should match somewhere, then we can check if
2973 // start and end are in the same position.
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002974 do
2975 {
2976 regmatch.startpos[0].col++;
2977 nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
Paul Ollis65745772022-06-05 16:55:54 +01002978 pos.lnum, regmatch.startpos[0].col, NULL);
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002979 if (nmatched != 0)
2980 break;
Bram Moolenaar795aaa12020-10-02 20:36:01 +02002981 } while (regmatch.regprog != NULL
2982 && direction == FORWARD ? regmatch.startpos[0].col < pos.col
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002983 : regmatch.startpos[0].col > pos.col);
2984
Bram Moolenaar53989552019-12-23 22:59:18 +01002985 if (called_emsg == called_emsg_before)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002986 {
2987 result = (nmatched != 0
2988 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
2989 && regmatch.startpos[0].col == regmatch.endpos[0].col);
2990 }
2991 }
2992
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02002993 vim_regfree(regmatch.regprog);
2994 return result;
2995}
2996
Bram Moolenaardde0efe2012-08-23 15:53:05 +02002997
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02002998/*
2999 * Find next search match under cursor, cursor at end.
3000 * Used while an operator is pending, and in Visual mode.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003001 */
3002 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003003current_search(
3004 long count,
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003005 int forward) // TRUE for forward, FALSE for backward
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003006{
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003007 pos_T start_pos; // start position of the pattern match
3008 pos_T end_pos; // end position of the pattern match
3009 pos_T orig_pos; // position of the cursor at beginning
3010 pos_T pos; // position after the pattern
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003011 int i;
3012 int dir;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003013 int result; // result of various function calls
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003014 char_u old_p_ws = p_ws;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003015 int flags = 0;
Bram Moolenaarde9149e2013-07-17 19:22:13 +02003016 pos_T save_VIsual = VIsual;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003017 int zero_width;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003018 int skip_first_backward;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003019
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003020 // Correct cursor when 'selection' is exclusive
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003021 if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003022 dec_cursor();
3023
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003024 // When searching forward and the cursor is at the start of the Visual
3025 // area, skip the first search backward, otherwise it doesn't move.
3026 skip_first_backward = forward && VIsual_active
3027 && LT_POS(curwin->w_cursor, VIsual);
3028
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003029 orig_pos = pos = curwin->w_cursor;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003030 if (VIsual_active)
3031 {
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003032 if (forward)
3033 incl(&pos);
3034 else
3035 decl(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003036 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003037
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003038 // Is the pattern is zero-width?, this time, don't care about the direction
John Marriott8c85a2a2024-05-20 19:18:26 +02003039 zero_width = is_zero_width(spats[last_idx].pat, spats[last_idx].patlen,
3040 TRUE, &curwin->w_cursor, FORWARD);
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003041 if (zero_width == -1)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003042 return FAIL; // pattern not found
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003043
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003044 /*
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003045 * The trick is to first search backwards and then search forward again,
3046 * so that a match at the current cursor position will be correctly
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003047 * captured. When "forward" is false do it the other way around.
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003048 */
3049 for (i = 0; i < 2; i++)
3050 {
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003051 if (forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003052 {
3053 if (i == 0 && skip_first_backward)
3054 continue;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003055 dir = i;
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003056 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003057 else
3058 dir = !i;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003059
3060 flags = 0;
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003061 if (!dir && !zero_width)
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003062 flags = SEARCH_END;
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003063 end_pos = pos;
Bram Moolenaarba6ba362012-08-08 15:27:57 +02003064
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01003065 // wrapping should not occur in the first round
3066 if (i == 0)
3067 p_ws = FALSE;
3068
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003069 result = searchit(curwin, curbuf, &pos, &end_pos,
3070 (dir ? FORWARD : BACKWARD),
John Marriott8c85a2a2024-05-20 19:18:26 +02003071 spats[last_idx].pat, spats[last_idx].patlen, (long) (i ? count : 1),
Bram Moolenaar92ea26b2019-10-18 20:53:34 +02003072 SEARCH_KEEP | flags, RE_SEARCH, NULL);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003073
Bram Moolenaar82cf7f62019-11-02 23:22:47 +01003074 p_ws = old_p_ws;
3075
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003076 // First search may fail, but then start searching from the
3077 // beginning of the file (cursor might be on the search match)
3078 // except when Visual mode is active, so that extending the visual
3079 // selection works.
3080 if (i == 1 && !result) // not found, abort
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003081 {
3082 curwin->w_cursor = orig_pos;
3083 if (VIsual_active)
3084 VIsual = save_VIsual;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003085 return FAIL;
3086 }
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003087 else if (i == 0 && !result)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003088 {
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003089 if (forward)
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003090 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003091 // try again from start of buffer
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003092 CLEAR_POS(&pos);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003093 }
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003094 else
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003095 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003096 // try again from end of buffer
3097 // searching backwards, so set pos to last line and col
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003098 pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
zeertzjq94b7c322024-03-12 21:50:32 +01003099 pos.col = ml_get_len(curwin->w_buffer->b_ml.ml_line_count);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003100 }
3101 }
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003102 }
3103
3104 start_pos = pos;
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003105
3106 if (!VIsual_active)
3107 VIsual = start_pos;
3108
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003109 // put the cursor after the match
Bram Moolenaar5d24a222018-12-23 19:10:09 +01003110 curwin->w_cursor = end_pos;
Bram Moolenaar453c1922019-10-26 14:42:09 +02003111 if (LT_POS(VIsual, end_pos) && forward)
Bram Moolenaarc07b7f72020-10-11 20:44:15 +02003112 {
3113 if (skip_first_backward)
3114 // put the cursor on the start of the match
3115 curwin->w_cursor = pos;
3116 else
3117 // put the cursor on last character of match
3118 dec_cursor();
3119 }
Bram Moolenaar28f224b2020-10-10 16:45:25 +02003120 else if (VIsual_active && LT_POS(curwin->w_cursor, VIsual) && forward)
Bram Moolenaaredaad6e2019-10-24 15:23:37 +02003121 curwin->w_cursor = pos; // put the cursor on the start of the match
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003122 VIsual_active = TRUE;
3123 VIsual_mode = 'v';
3124
Bram Moolenaarb7633612019-02-10 21:48:25 +01003125 if (*p_sel == 'e')
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003126 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003127 // Correction for exclusive selection depends on the direction.
Bram Moolenaarb7633612019-02-10 21:48:25 +01003128 if (forward && LTOREQ_POS(VIsual, curwin->w_cursor))
3129 inc_cursor();
3130 else if (!forward && LTOREQ_POS(curwin->w_cursor, VIsual))
3131 inc(&VIsual);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003132 }
3133
3134#ifdef FEAT_FOLDING
3135 if (fdo_flags & FDO_SEARCH && KeyTyped)
3136 foldOpenCursor();
3137#endif
3138
3139 may_start_select('c');
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003140 setmouse();
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003141#ifdef FEAT_CLIPBOARD
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003142 // Make sure the clipboard gets updated. Needed because start and
3143 // end are still the same, and the selection needs to be owned
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003144 clip_star.vmode = NUL;
3145#endif
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003146 redraw_curbuf_later(UPD_INVERTED);
Bram Moolenaar8a0f3c72012-07-29 12:55:32 +02003147 showmode();
3148
3149 return OK;
3150}
Bram Moolenaardde0efe2012-08-23 15:53:05 +02003151
Bram Moolenaar071d4272004-06-13 20:20:40 +00003152/*
3153 * return TRUE if line 'lnum' is empty or has white chars only.
3154 */
3155 int
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003156linewhite(linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003157{
3158 char_u *p;
3159
3160 p = skipwhite(ml_get(lnum));
3161 return (*p == NUL);
3162}
Bram Moolenaar071d4272004-06-13 20:20:40 +00003163
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003164/*
3165 * Add the search count "[3/19]" to "msgbuf".
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003166 * See update_search_stat() for other arguments.
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003167 */
3168 static void
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003169cmdline_search_stat(
3170 int dirc,
3171 pos_T *pos,
3172 pos_T *cursor_pos,
3173 int show_top_bot_msg,
3174 char_u *msgbuf,
John Marriott8c85a2a2024-05-20 19:18:26 +02003175 size_t msgbuflen,
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003176 int recompute,
3177 int maxcount,
3178 long timeout)
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003179{
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003180 searchstat_T stat;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003181
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003182 update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
3183 timeout);
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003184 if (stat.cur <= 0)
3185 return;
3186
3187 char t[SEARCH_STAT_BUF_LEN];
3188 size_t len;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003189
3190#ifdef FEAT_RIGHTLEFT
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003191 if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
3192 {
3193 if (stat.incomplete == 1)
John Marriott8c85a2a2024-05-20 19:18:26 +02003194 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003195 else if (stat.cnt > maxcount && stat.cur > maxcount)
John Marriott8c85a2a2024-05-20 19:18:26 +02003196 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003197 maxcount, maxcount);
3198 else if (stat.cnt > maxcount)
John Marriott8c85a2a2024-05-20 19:18:26 +02003199 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003200 maxcount, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003201 else
John Marriott8c85a2a2024-05-20 19:18:26 +02003202 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003203 stat.cnt, stat.cur);
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003204 }
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003205 else
3206#endif
3207 {
3208 if (stat.incomplete == 1)
John Marriott8c85a2a2024-05-20 19:18:26 +02003209 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003210 else if (stat.cnt > maxcount && stat.cur > maxcount)
John Marriott8c85a2a2024-05-20 19:18:26 +02003211 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003212 maxcount, maxcount);
3213 else if (stat.cnt > maxcount)
John Marriott8c85a2a2024-05-20 19:18:26 +02003214 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003215 stat.cur, maxcount);
3216 else
John Marriott8c85a2a2024-05-20 19:18:26 +02003217 len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003218 stat.cur, stat.cnt);
3219 }
3220
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003221 if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
3222 {
3223 mch_memmove(t + 2, t, len);
3224 t[0] = 'W';
3225 t[1] = ' ';
3226 len += 2;
3227 }
3228
John Marriott8c85a2a2024-05-20 19:18:26 +02003229 if (len > msgbuflen)
3230 len = msgbuflen;
3231 mch_memmove(msgbuf + msgbuflen - len, t, len);
zeertzjqa7d36b72023-01-31 21:13:38 +00003232
Yegappan Lakshmanan6ec66662023-01-23 20:46:21 +00003233 if (dirc == '?' && stat.cur == maxcount + 1)
3234 stat.cur = -1;
3235
3236 // keep the message even after redraw, but don't put in history
3237 msg_hist_off = TRUE;
3238 give_warning(msgbuf, FALSE);
3239 msg_hist_off = FALSE;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003240}
3241
3242/*
3243 * Add the search count information to "stat".
3244 * "stat" must not be NULL.
3245 * When "recompute" is TRUE always recompute the numbers.
3246 * dirc == 0: don't find the next/previous match (only set the result to "stat")
3247 * dirc == '/': find the next match
3248 * dirc == '?': find the previous match
3249 */
3250 static void
3251update_search_stat(
3252 int dirc,
3253 pos_T *pos,
3254 pos_T *cursor_pos,
3255 searchstat_T *stat,
3256 int recompute,
3257 int maxcount,
Bram Moolenaarf9ca08e2020-06-01 18:56:03 +02003258 long timeout UNUSED)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003259{
3260 int save_ws = p_ws;
3261 int wraparound = FALSE;
3262 pos_T p = (*pos);
Bram Moolenaar14681622020-06-03 22:57:39 +02003263 static pos_T lastpos = {0, 0, 0};
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003264 static int cur = 0;
3265 static int cnt = 0;
3266 static int exact_match = FALSE;
3267 static int incomplete = 0;
3268 static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
3269 static int chgtick = 0;
3270 static char_u *lastpat = NULL;
John Marriottb79fa3d2025-02-21 19:59:56 +01003271 static size_t lastpatlen = 0;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003272 static buf_T *lbuf = NULL;
3273#ifdef FEAT_RELTIME
3274 proftime_T start;
3275#endif
3276
Yegappan Lakshmanan960dcbd2023-03-07 17:45:11 +00003277 CLEAR_POINTER(stat);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003278
3279 if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
3280 {
3281 stat->cur = cur;
3282 stat->cnt = cnt;
3283 stat->exact_match = exact_match;
3284 stat->incomplete = incomplete;
3285 stat->last_maxcount = last_maxcount;
3286 return;
3287 }
3288 last_maxcount = maxcount;
3289
3290 wraparound = ((dirc == '?' && LT_POS(lastpos, p))
3291 || (dirc == '/' && LT_POS(p, lastpos)));
3292
3293 // If anything relevant changed the count has to be recomputed.
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003294 if (!(chgtick == CHANGEDTICK(curbuf)
John Marriottb79fa3d2025-02-21 19:59:56 +01003295 && (lastpat != NULL
Christian Brabandt670d0c12025-05-16 19:38:50 +02003296 && STRNCMP(lastpat, spats[last_idx].pat, lastpatlen) == 0
John Marriottb79fa3d2025-02-21 19:59:56 +01003297 && lastpatlen == spats[last_idx].patlen
3298 )
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003299 && EQUAL_POS(lastpos, *cursor_pos)
3300 && lbuf == curbuf) || wraparound || cur < 0
3301 || (maxcount > 0 && cur > maxcount) || recompute)
3302 {
3303 cur = 0;
3304 cnt = 0;
3305 exact_match = FALSE;
3306 incomplete = 0;
3307 CLEAR_POS(&lastpos);
3308 lbuf = curbuf;
3309 }
3310
Christian Brabandt34a6a362023-05-06 19:20:20 +01003311 // when searching backwards and having jumped to the first occurrence,
3312 // cur must remain greater than 1
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003313 if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
Christian Brabandt34a6a362023-05-06 19:20:20 +01003314 && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 1))
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003315 cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
3316 else
3317 {
3318 int done_search = FALSE;
3319 pos_T endpos = {0, 0, 0};
3320
3321 p_ws = FALSE;
3322#ifdef FEAT_RELTIME
3323 if (timeout > 0)
3324 profile_setlimit(timeout, &start);
3325#endif
3326 while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
John Marriott8c85a2a2024-05-20 19:18:26 +02003327 FORWARD, NULL, 0, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003328 {
3329 done_search = TRUE;
3330#ifdef FEAT_RELTIME
3331 // Stop after passing the time limit.
3332 if (timeout > 0 && profile_passed_limit(&start))
3333 {
3334 incomplete = 1;
3335 break;
3336 }
3337#endif
3338 cnt++;
3339 if (LTOREQ_POS(lastpos, p))
3340 {
3341 cur = cnt;
Bram Moolenaar57f75a52020-06-02 22:06:21 +02003342 if (LT_POS(p, endpos))
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003343 exact_match = TRUE;
3344 }
3345 fast_breakcheck();
3346 if (maxcount > 0 && cnt > maxcount)
3347 {
3348 incomplete = 2; // max count exceeded
3349 break;
3350 }
3351 }
3352 if (got_int)
3353 cur = -1; // abort
3354 if (done_search)
3355 {
3356 vim_free(lastpat);
John Marriottb79fa3d2025-02-21 19:59:56 +01003357 lastpat = vim_strnsave(spats[last_idx].pat, spats[last_idx].patlen);
3358 if (lastpat == NULL)
3359 lastpatlen = 0;
3360 else
3361 lastpatlen = spats[last_idx].patlen;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02003362 chgtick = CHANGEDTICK(curbuf);
3363 lbuf = curbuf;
3364 lastpos = p;
3365 }
3366 }
3367 stat->cur = cur;
3368 stat->cnt = cnt;
3369 stat->exact_match = exact_match;
3370 stat->incomplete = incomplete;
3371 stat->last_maxcount = last_maxcount;
Bram Moolenaar9dfa3132019-05-04 21:08:40 +02003372 p_ws = save_ws;
3373}
3374
Bram Moolenaar071d4272004-06-13 20:20:40 +00003375#if defined(FEAT_FIND_ID) || defined(PROTO)
Bram Moolenaar409510c2022-06-01 15:23:13 +01003376
3377/*
3378 * Get line "lnum" and copy it into "buf[LSIZE]".
3379 * The copy is made because the regexp may make the line invalid when using a
3380 * mark.
3381 */
3382 static char_u *
3383get_line_and_copy(linenr_T lnum, char_u *buf)
3384{
3385 char_u *line = ml_get(lnum);
3386
3387 vim_strncpy(buf, line, LSIZE - 1);
3388 return buf;
3389}
3390
Bram Moolenaar071d4272004-06-13 20:20:40 +00003391/*
3392 * Find identifiers or defines in included files.
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003393 * If p_ic && compl_status_sol() then ptr must be in lowercase.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003394 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00003395 void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003396find_pattern_in_path(
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003397 char_u *ptr, // pointer to search pattern
3398 int dir UNUSED, // direction of expansion
3399 int len, // length of search pattern
3400 int whole, // match whole words only
3401 int skip_comments, // don't match inside comments
3402 int type, // Type of search; are we looking for a type?
3403 // a macro?
Bram Moolenaar764b23c2016-01-30 21:10:09 +01003404 long count,
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003405 int action, // What to do when we find it
3406 linenr_T start_lnum, // first line to start searching
Colin Kennedy21570352024-03-03 16:16:47 +01003407 linenr_T end_lnum, // last line for searching
3408 int forceit) // If true, always switch to the found path
Bram Moolenaar071d4272004-06-13 20:20:40 +00003409{
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003410 SearchedFile *files; // Stack of included files
3411 SearchedFile *bigger; // When we need more space
Bram Moolenaar071d4272004-06-13 20:20:40 +00003412 int max_path_depth = 50;
3413 long match_count = 1;
3414
3415 char_u *pat;
3416 char_u *new_fname;
3417 char_u *curr_fname = curbuf->b_fname;
3418 char_u *prev_fname = NULL;
3419 linenr_T lnum;
3420 int depth;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003421 int depth_displayed; // For type==CHECK_PATH
Bram Moolenaar071d4272004-06-13 20:20:40 +00003422 int old_files;
3423 int already_searched;
3424 char_u *file_line;
3425 char_u *line;
3426 char_u *p;
3427 char_u save_char;
3428 int define_matched;
3429 regmatch_T regmatch;
3430 regmatch_T incl_regmatch;
3431 regmatch_T def_regmatch;
3432 int matched = FALSE;
3433 int did_show = FALSE;
3434 int found = FALSE;
3435 int i;
3436 char_u *already = NULL;
3437 char_u *startp = NULL;
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003438 char_u *inc_opt = NULL;
Bram Moolenaar4033c552017-09-16 20:54:51 +02003439#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003440 win_T *curwin_save = NULL;
3441#endif
3442
3443 regmatch.regprog = NULL;
3444 incl_regmatch.regprog = NULL;
3445 def_regmatch.regprog = NULL;
3446
3447 file_line = alloc(LSIZE);
3448 if (file_line == NULL)
3449 return;
3450
Bram Moolenaar071d4272004-06-13 20:20:40 +00003451 if (type != CHECK_PATH && type != FIND_DEFINE
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003452 // when CONT_SOL is set compare "ptr" with the beginning of the
3453 // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
3454 && !compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003455 {
3456 pat = alloc(len + 5);
3457 if (pat == NULL)
3458 goto fpip_end;
John Marriott8c85a2a2024-05-20 19:18:26 +02003459 vim_snprintf((char *)pat, len + 5, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003460 // ignore case according to p_ic, p_scs and pat
Bram Moolenaar071d4272004-06-13 20:20:40 +00003461 regmatch.rm_ic = ignorecase(pat);
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003462 regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003463 vim_free(pat);
3464 if (regmatch.regprog == NULL)
3465 goto fpip_end;
3466 }
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003467 inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
3468 if (*inc_opt != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003469 {
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003470 incl_regmatch.regprog = vim_regcomp(inc_opt,
3471 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003472 if (incl_regmatch.regprog == NULL)
3473 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003474 incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003475 }
3476 if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL))
3477 {
John Marriott8c85a2a2024-05-20 19:18:26 +02003478 def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL ? p_def : curbuf->b_p_def,
Bram Moolenaarf4e20992020-12-21 19:59:08 +01003479 magic_isset() ? RE_MAGIC : 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003480 if (def_regmatch.regprog == NULL)
3481 goto fpip_end;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003482 def_regmatch.rm_ic = FALSE; // don't ignore case in define pat.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003483 }
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003484 files = lalloc_clear(max_path_depth * sizeof(SearchedFile), TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003485 if (files == NULL)
3486 goto fpip_end;
3487 old_files = max_path_depth;
3488 depth = depth_displayed = -1;
3489
3490 lnum = start_lnum;
3491 if (end_lnum > curbuf->b_ml.ml_line_count)
3492 end_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003493 if (lnum > end_lnum) // do at least one line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003494 lnum = end_lnum;
Bram Moolenaar409510c2022-06-01 15:23:13 +01003495 line = get_line_and_copy(lnum, file_line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003496
3497 for (;;)
3498 {
3499 if (incl_regmatch.regprog != NULL
3500 && vim_regexec(&incl_regmatch, line, (colnr_T)0))
3501 {
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003502 char_u *p_fname = (curr_fname == curbuf->b_fname)
3503 ? curbuf->b_ffname : curr_fname;
3504
3505 if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003506 // Use text from '\zs' to '\ze' (or end) of 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003507 new_fname = find_file_name_in_path(incl_regmatch.startp[0],
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02003508 (int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]),
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003509 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname);
3510 else
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003511 // Use text after match with 'include'.
Bram Moolenaar482aaeb2005-09-29 18:26:07 +00003512 new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
Bram Moolenaard1f56e62006-02-22 21:25:37 +00003513 FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003514 already_searched = FALSE;
3515 if (new_fname != NULL)
3516 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003517 // Check whether we have already searched in this file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003518 for (i = 0;; i++)
3519 {
3520 if (i == depth + 1)
3521 i = old_files;
3522 if (i == max_path_depth)
3523 break;
Bram Moolenaar99499b12019-05-23 21:35:48 +02003524 if (fullpathcmp(new_fname, files[i].name, TRUE, TRUE)
3525 & FPC_SAME)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003526 {
Dominique Pelle7765f5c2022-04-10 11:26:53 +01003527 if (type != CHECK_PATH
3528 && action == ACTION_SHOW_ALL
3529 && files[i].matched)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003530 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003531 msg_putchar('\n'); // cursor below last one
3532 if (!got_int) // don't display if 'q'
3533 // typed at "--more--"
3534 // message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003535 {
3536 msg_home_replace_hl(new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003537 msg_puts(_(" (includes previously listed match)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003538 prev_fname = NULL;
3539 }
3540 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01003541 VIM_CLEAR(new_fname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003542 already_searched = TRUE;
3543 break;
3544 }
3545 }
3546 }
3547
3548 if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
3549 || (new_fname == NULL && !already_searched)))
3550 {
3551 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003552 msg_putchar('\n'); // cursor below last one
Bram Moolenaar071d4272004-06-13 20:20:40 +00003553 else
3554 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003555 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar32526b32019-01-19 17:43:09 +01003556 msg_puts_title(_("--- Included files "));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003557 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003558 msg_puts_title(_("not found "));
3559 msg_puts_title(_("in path ---\n"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003560 }
3561 did_show = TRUE;
3562 while (depth_displayed < depth && !got_int)
3563 {
3564 ++depth_displayed;
3565 for (i = 0; i < depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003566 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003567 msg_home_replace(files[depth_displayed].name);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003568 msg_puts(" -->\n");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003569 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003570 if (!got_int) // don't display if 'q' typed
3571 // for "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003572 {
3573 for (i = 0; i <= depth_displayed; i++)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003574 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00003575 if (new_fname != NULL)
3576 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003577 // using "new_fname" is more reliable, e.g., when
3578 // 'includeexpr' is set.
Bram Moolenaar8820b482017-03-16 17:23:31 +01003579 msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003580 }
3581 else
3582 {
3583 /*
3584 * Isolate the file name.
3585 * Include the surrounding "" or <> if present.
3586 */
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003587 if (inc_opt != NULL
3588 && strstr((char *)inc_opt, "\\zs") != NULL)
3589 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003590 // pattern contains \zs, use the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003591 p = incl_regmatch.startp[0];
3592 i = (int)(incl_regmatch.endp[0]
3593 - incl_regmatch.startp[0]);
3594 }
3595 else
3596 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003597 // find the file name after the end of the match
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003598 for (p = incl_regmatch.endp[0];
3599 *p && !vim_isfilec(*p); p++)
3600 ;
3601 for (i = 0; vim_isfilec(p[i]); i++)
3602 ;
3603 }
3604
Bram Moolenaar071d4272004-06-13 20:20:40 +00003605 if (i == 0)
3606 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003607 // Nothing found, use the rest of the line.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003608 p = incl_regmatch.endp[0];
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00003609 i = (int)STRLEN(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003610 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003611 // Avoid checking before the start of the line, can
3612 // happen if \zs appears in the regexp.
Bram Moolenaar058bdcf2012-07-25 13:46:30 +02003613 else if (p > line)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003614 {
3615 if (p[-1] == '"' || p[-1] == '<')
3616 {
3617 --p;
3618 ++i;
3619 }
3620 if (p[i] == '"' || p[i] == '>')
3621 ++i;
3622 }
3623 save_char = p[i];
3624 p[i] = NUL;
Bram Moolenaar8820b482017-03-16 17:23:31 +01003625 msg_outtrans_attr(p, HL_ATTR(HLF_D));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003626 p[i] = save_char;
3627 }
3628
3629 if (new_fname == NULL && action == ACTION_SHOW_ALL)
3630 {
3631 if (already_searched)
Bram Moolenaar32526b32019-01-19 17:43:09 +01003632 msg_puts(_(" (Already listed)"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003633 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01003634 msg_puts(_(" NOT FOUND"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003635 }
3636 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003637 out_flush(); // output each line directly
Bram Moolenaar071d4272004-06-13 20:20:40 +00003638 }
3639
3640 if (new_fname != NULL)
3641 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003642 // Push the new file onto the file stack
Bram Moolenaar071d4272004-06-13 20:20:40 +00003643 if (depth + 1 == old_files)
3644 {
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003645 bigger = ALLOC_MULT(SearchedFile, max_path_depth * 2);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003646 if (bigger != NULL)
3647 {
3648 for (i = 0; i <= depth; i++)
3649 bigger[i] = files[i];
3650 for (i = depth + 1; i < old_files + max_path_depth; i++)
3651 {
3652 bigger[i].fp = NULL;
3653 bigger[i].name = NULL;
3654 bigger[i].lnum = 0;
3655 bigger[i].matched = FALSE;
3656 }
3657 for (i = old_files; i < max_path_depth; i++)
3658 bigger[i + max_path_depth] = files[i];
3659 old_files += max_path_depth;
3660 max_path_depth *= 2;
3661 vim_free(files);
3662 files = bigger;
3663 }
3664 }
3665 if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
3666 == NULL)
3667 vim_free(new_fname);
3668 else
3669 {
3670 if (++depth == old_files)
3671 {
3672 /*
3673 * lalloc() for 'bigger' must have failed above. We
3674 * will forget one of our already visited files now.
3675 */
3676 vim_free(files[old_files].name);
3677 ++old_files;
3678 }
3679 files[depth].name = curr_fname = new_fname;
3680 files[depth].lnum = 0;
3681 files[depth].matched = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003682 if (action == ACTION_EXPAND)
3683 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003684 msg_hist_off = TRUE; // reset in msg_trunc_attr()
Bram Moolenaar555b2802005-05-19 21:08:39 +00003685 vim_snprintf((char*)IObuff, IOSIZE,
3686 _("Scanning included file: %s"),
3687 (char *)new_fname);
Bram Moolenaar32526b32019-01-19 17:43:09 +01003688 msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003689 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003690 else if (p_verbose >= 5)
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003691 {
3692 verbose_enter();
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003693 smsg(_("Searching included file %s"),
Bram Moolenaar87b5ca52006-03-04 21:55:31 +00003694 (char *)new_fname);
3695 verbose_leave();
3696 }
3697
Bram Moolenaar071d4272004-06-13 20:20:40 +00003698 }
3699 }
3700 }
3701 else
3702 {
3703 /*
3704 * Check if the line is a define (type == FIND_DEFINE)
3705 */
3706 p = line;
3707search_line:
3708 define_matched = FALSE;
3709 if (def_regmatch.regprog != NULL
3710 && vim_regexec(&def_regmatch, line, (colnr_T)0))
3711 {
3712 /*
3713 * Pattern must be first identifier after 'define', so skip
3714 * to that position before checking for match of pattern. Also
3715 * don't let it match beyond the end of this identifier.
3716 */
3717 p = def_regmatch.endp[0];
3718 while (*p && !vim_iswordc(*p))
3719 p++;
3720 define_matched = TRUE;
3721 }
3722
3723 /*
3724 * Look for a match. Don't do this if we are looking for a
3725 * define and this line didn't match define_prog above.
3726 */
3727 if (def_regmatch.regprog == NULL || define_matched)
3728 {
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003729 if (define_matched || compl_status_sol())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003730 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003731 // compare the first "len" chars from "ptr"
Bram Moolenaar071d4272004-06-13 20:20:40 +00003732 startp = skipwhite(p);
3733 if (p_ic)
3734 matched = !MB_STRNICMP(startp, ptr, len);
3735 else
3736 matched = !STRNCMP(startp, ptr, len);
3737 if (matched && define_matched && whole
3738 && vim_iswordc(startp[len]))
3739 matched = FALSE;
3740 }
3741 else if (regmatch.regprog != NULL
3742 && vim_regexec(&regmatch, line, (colnr_T)(p - line)))
3743 {
3744 matched = TRUE;
3745 startp = regmatch.startp[0];
3746 /*
3747 * Check if the line is not a comment line (unless we are
3748 * looking for a define). A line starting with "# define"
3749 * is not considered to be a comment line.
3750 */
3751 if (!define_matched && skip_comments)
3752 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003753 if ((*line != '#' ||
3754 STRNCMP(skipwhite(line + 1), "define", 6) != 0)
Bram Moolenaar81340392012-06-06 16:12:59 +02003755 && get_leader_len(line, NULL, FALSE, TRUE))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003756 matched = FALSE;
3757
3758 /*
3759 * Also check for a "/ *" or "/ /" before the match.
3760 * Skips lines like "int backwards; / * normal index
3761 * * /" when looking for "normal".
3762 * Note: Doesn't skip "/ *" in comments.
3763 */
3764 p = skipwhite(line);
3765 if (matched
3766 || (p[0] == '/' && p[1] == '*') || p[0] == '*')
Bram Moolenaar071d4272004-06-13 20:20:40 +00003767 for (p = line; *p && p < startp; ++p)
3768 {
3769 if (matched
3770 && p[0] == '/'
3771 && (p[1] == '*' || p[1] == '/'))
3772 {
3773 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003774 // After "//" all text is comment
Bram Moolenaar071d4272004-06-13 20:20:40 +00003775 if (p[1] == '/')
3776 break;
3777 ++p;
3778 }
3779 else if (!matched && p[0] == '*' && p[1] == '/')
3780 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003781 // Can find match after "* /".
Bram Moolenaar071d4272004-06-13 20:20:40 +00003782 matched = TRUE;
3783 ++p;
3784 }
3785 }
3786 }
3787 }
3788 }
3789 }
3790 if (matched)
3791 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003792 if (action == ACTION_EXPAND)
3793 {
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003794 int cont_s_ipos = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003795 int add_r;
3796 char_u *aux;
3797
3798 if (depth == -1 && lnum == curwin->w_cursor.lnum)
3799 break;
3800 found = TRUE;
3801 aux = p = startp;
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003802 if (compl_status_adding())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003803 {
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003804 p += ins_compl_len();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003805 if (vim_iswordp(p))
3806 goto exit_matched;
3807 p = find_word_start(p);
3808 }
3809 p = find_word_end(p);
3810 i = (int)(p - aux);
3811
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003812 if (compl_status_adding() && i == ins_compl_len())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003813 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003814 // IOSIZE > compl_length, so the STRNCPY works
Bram Moolenaar071d4272004-06-13 20:20:40 +00003815 STRNCPY(IObuff, aux, i);
Bram Moolenaar89d40322006-08-29 15:30:07 +00003816
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003817 // Get the next line: when "depth" < 0 from the current
3818 // buffer, otherwise from the included file. Jump to
3819 // exit_matched when past the last line.
Bram Moolenaar89d40322006-08-29 15:30:07 +00003820 if (depth < 0)
3821 {
3822 if (lnum >= end_lnum)
3823 goto exit_matched;
Bram Moolenaar409510c2022-06-01 15:23:13 +01003824 line = get_line_and_copy(++lnum, file_line);
Bram Moolenaar89d40322006-08-29 15:30:07 +00003825 }
3826 else if (vim_fgets(line = file_line,
3827 LSIZE, files[depth].fp))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003828 goto exit_matched;
3829
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003830 // we read a line, set "already" to check this "line" later
3831 // if depth >= 0 we'll increase files[depth].lnum far
Bram Moolenaar8e7d6222020-12-18 19:49:56 +01003832 // below -- Acevedo
Bram Moolenaar071d4272004-06-13 20:20:40 +00003833 already = aux = p = skipwhite(line);
3834 p = find_word_start(p);
3835 p = find_word_end(p);
3836 if (p > aux)
3837 {
3838 if (*aux != ')' && IObuff[i-1] != TAB)
3839 {
3840 if (IObuff[i-1] != ' ')
3841 IObuff[i++] = ' ';
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003842 // IObuf =~ "\(\k\|\i\).* ", thus i >= 2
Bram Moolenaar071d4272004-06-13 20:20:40 +00003843 if (p_js
3844 && (IObuff[i-2] == '.'
3845 || (vim_strchr(p_cpo, CPO_JOINSP) == NULL
3846 && (IObuff[i-2] == '?'
3847 || IObuff[i-2] == '!'))))
3848 IObuff[i++] = ' ';
3849 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003850 // copy as much as possible of the new word
Bram Moolenaar071d4272004-06-13 20:20:40 +00003851 if (p - aux >= IOSIZE - i)
3852 p = aux + IOSIZE - i - 1;
3853 STRNCPY(IObuff + i, aux, p - aux);
3854 i += (int)(p - aux);
Bram Moolenaard9eefe32019-04-06 14:22:21 +02003855 cont_s_ipos = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003856 }
3857 IObuff[i] = NUL;
3858 aux = IObuff;
3859
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003860 if (i == ins_compl_len())
Bram Moolenaar071d4272004-06-13 20:20:40 +00003861 goto exit_matched;
3862 }
3863
Bram Moolenaare8c3a142006-08-29 14:30:35 +00003864 add_r = ins_compl_add_infercase(aux, i, p_ic,
Bram Moolenaar071d4272004-06-13 20:20:40 +00003865 curr_fname == curbuf->b_fname ? NULL : curr_fname,
glepnirf31cfa22025-03-06 21:59:13 +01003866 dir, cont_s_ipos, 0);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003867 if (add_r == OK)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003868 // if dir was BACKWARD then honor it just once
Bram Moolenaar071d4272004-06-13 20:20:40 +00003869 dir = FORWARD;
Bram Moolenaar572cb562005-08-05 21:35:02 +00003870 else if (add_r == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003871 break;
3872 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02003873 else if (action == ACTION_SHOW_ALL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003874 {
3875 found = TRUE;
3876 if (!did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003877 gotocmdline(TRUE); // cursor at status line
Bram Moolenaar071d4272004-06-13 20:20:40 +00003878 if (curr_fname != prev_fname)
3879 {
3880 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003881 msg_putchar('\n'); // cursor below last one
3882 if (!got_int) // don't display if 'q' typed
3883 // at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00003884 msg_home_replace_hl(curr_fname);
3885 prev_fname = curr_fname;
3886 }
3887 did_show = TRUE;
3888 if (!got_int)
3889 show_pat_in_path(line, type, TRUE, action,
3890 (depth == -1) ? NULL : files[depth].fp,
3891 (depth == -1) ? &lnum : &files[depth].lnum,
3892 match_count++);
3893
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003894 // Set matched flag for this file and all the ones that
3895 // include it
Bram Moolenaar071d4272004-06-13 20:20:40 +00003896 for (i = 0; i <= depth; ++i)
3897 files[i].matched = TRUE;
3898 }
3899 else if (--count <= 0)
3900 {
3901 found = TRUE;
3902 if (depth == -1 && lnum == curwin->w_cursor.lnum
Bram Moolenaar4033c552017-09-16 20:54:51 +02003903#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003904 && g_do_tagpreview == 0
3905#endif
3906 )
Bram Moolenaarac78dd42022-01-02 19:25:26 +00003907 emsg(_(e_match_is_on_current_line));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003908 else if (action == ACTION_SHOW)
3909 {
3910 show_pat_in_path(line, type, did_show, action,
3911 (depth == -1) ? NULL : files[depth].fp,
3912 (depth == -1) ? &lnum : &files[depth].lnum, 1L);
3913 did_show = TRUE;
3914 }
3915 else
3916 {
3917#ifdef FEAT_GUI
3918 need_mouse_correct = TRUE;
3919#endif
Bram Moolenaar4033c552017-09-16 20:54:51 +02003920#if defined(FEAT_QUICKFIX)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003921 // ":psearch" uses the preview window
Bram Moolenaar071d4272004-06-13 20:20:40 +00003922 if (g_do_tagpreview != 0)
3923 {
3924 curwin_save = curwin;
Bram Moolenaar576a4a62019-08-18 15:25:17 +02003925 prepare_tagpreview(TRUE, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003926 }
3927#endif
3928 if (action == ACTION_SPLIT)
3929 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00003930 if (win_split(0, 0) == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003931 break;
Bram Moolenaar3368ea22010-09-21 16:56:35 +02003932 RESET_BINDING(curwin);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003933 }
3934 if (depth == -1)
3935 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003936 // match in current file
Bram Moolenaar4033c552017-09-16 20:54:51 +02003937#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003938 if (g_do_tagpreview != 0)
3939 {
Bram Moolenaar92bb83e2021-02-03 23:04:46 +01003940 if (!win_valid(curwin_save))
3941 break;
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003942 if (!GETFILE_SUCCESS(getfile(
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003943 curwin_save->w_buffer->b_fnum, NULL,
Colin Kennedy21570352024-03-03 16:16:47 +01003944 NULL, TRUE, lnum, forceit)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003945 break; // failed to jump to file
Bram Moolenaar071d4272004-06-13 20:20:40 +00003946 }
3947 else
3948#endif
3949 setpcmark();
3950 curwin->w_cursor.lnum = lnum;
Bram Moolenaarc31f9ae2017-07-23 22:02:02 +02003951 check_cursor();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003952 }
3953 else
3954 {
Bram Moolenaar8ad80de2017-06-05 16:01:59 +02003955 if (!GETFILE_SUCCESS(getfile(
3956 0, files[depth].name, NULL, TRUE,
Colin Kennedy21570352024-03-03 16:16:47 +01003957 files[depth].lnum, forceit)))
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003958 break; // failed to jump to file
3959 // autocommands may have changed the lnum, we don't
3960 // want that here
Bram Moolenaar071d4272004-06-13 20:20:40 +00003961 curwin->w_cursor.lnum = files[depth].lnum;
3962 }
3963 }
3964 if (action != ACTION_SHOW)
3965 {
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003966 curwin->w_cursor.col = (colnr_T)(startp - line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003967 curwin->w_set_curswant = TRUE;
3968 }
3969
Bram Moolenaar4033c552017-09-16 20:54:51 +02003970#if defined(FEAT_QUICKFIX)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003971 if (g_do_tagpreview != 0
Bram Moolenaar997fb4b2006-02-17 21:53:23 +00003972 && curwin != curwin_save && win_valid(curwin_save))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003973 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003974 // Return cursor to where we were
Bram Moolenaar071d4272004-06-13 20:20:40 +00003975 validate_cursor();
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003976 redraw_later(UPD_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003977 win_enter(curwin_save, TRUE);
3978 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01003979# ifdef FEAT_PROP_POPUP
Bram Moolenaar1b6d9c42019-08-05 21:52:04 +02003980 else if (WIN_IS_POPUP(curwin))
3981 // can't keep focus in popup window
3982 win_enter(firstwin, TRUE);
3983# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00003984#endif
3985 break;
3986 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003987exit_matched:
Bram Moolenaar071d4272004-06-13 20:20:40 +00003988 matched = FALSE;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01003989 // look for other matches in the rest of the line if we
3990 // are not at the end of it already
Bram Moolenaar071d4272004-06-13 20:20:40 +00003991 if (def_regmatch.regprog == NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00003992 && action == ACTION_EXPAND
Yegappan Lakshmanand94fbfc2022-01-04 17:01:44 +00003993 && !compl_status_sol()
Bram Moolenaarfe81d452009-04-22 14:44:41 +00003994 && *startp != NUL
John Marriott8c85a2a2024-05-20 19:18:26 +02003995 && *(startp + mb_ptr2len(startp)) != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003996 goto search_line;
3997 }
3998 line_breakcheck();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003999 if (action == ACTION_EXPAND)
Bram Moolenaar472e8592016-10-15 17:06:47 +02004000 ins_compl_check_keys(30, FALSE);
Bram Moolenaar7591bb32019-03-30 13:53:47 +01004001 if (got_int || ins_compl_interrupted())
Bram Moolenaar071d4272004-06-13 20:20:40 +00004002 break;
4003
4004 /*
4005 * Read the next line. When reading an included file and encountering
4006 * end-of-file, close the file and continue in the file that included
4007 * it.
4008 */
4009 while (depth >= 0 && !already
4010 && vim_fgets(line = file_line, LSIZE, files[depth].fp))
4011 {
4012 fclose(files[depth].fp);
4013 --old_files;
4014 files[old_files].name = files[depth].name;
4015 files[old_files].matched = files[depth].matched;
4016 --depth;
4017 curr_fname = (depth == -1) ? curbuf->b_fname
4018 : files[depth].name;
4019 if (depth < depth_displayed)
4020 depth_displayed = depth;
4021 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004022 if (depth >= 0) // we could read the line
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02004023 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00004024 files[depth].lnum++;
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004025 // Remove any CR and LF from the line.
Bram Moolenaarc84e3c12013-07-03 22:28:36 +02004026 i = (int)STRLEN(line);
4027 if (i > 0 && line[i - 1] == '\n')
4028 line[--i] = NUL;
4029 if (i > 0 && line[i - 1] == '\r')
4030 line[--i] = NUL;
4031 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004032 else if (!already)
4033 {
4034 if (++lnum > end_lnum)
4035 break;
Bram Moolenaar409510c2022-06-01 15:23:13 +01004036 line = get_line_and_copy(lnum, file_line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004037 }
4038 already = NULL;
4039 }
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004040 // End of big for (;;) loop.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004041
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004042 // Close any files that are still open.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004043 for (i = 0; i <= depth; i++)
4044 {
4045 fclose(files[i].fp);
4046 vim_free(files[i].name);
4047 }
4048 for (i = old_files; i < max_path_depth; i++)
4049 vim_free(files[i].name);
4050 vim_free(files);
4051
4052 if (type == CHECK_PATH)
4053 {
4054 if (!did_show)
4055 {
4056 if (action != ACTION_SHOW_ALL)
Bram Moolenaar32526b32019-01-19 17:43:09 +01004057 msg(_("All included files were found"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004058 else
Bram Moolenaar32526b32019-01-19 17:43:09 +01004059 msg(_("No included files"));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004060 }
4061 }
Bram Moolenaare2c453d2019-08-21 14:37:09 +02004062 else if (!found && action != ACTION_EXPAND)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004063 {
Bram Moolenaar7591bb32019-03-30 13:53:47 +01004064 if (got_int || ins_compl_interrupted())
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004065 emsg(_(e_interrupted));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004066 else if (type == FIND_DEFINE)
Bram Moolenaarac78dd42022-01-02 19:25:26 +00004067 emsg(_(e_couldnt_find_definition));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004068 else
Bram Moolenaarac78dd42022-01-02 19:25:26 +00004069 emsg(_(e_couldnt_find_pattern));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004070 }
4071 if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
4072 msg_end();
4073
4074fpip_end:
4075 vim_free(file_line);
Bram Moolenaar473de612013-06-08 18:19:48 +02004076 vim_regfree(regmatch.regprog);
4077 vim_regfree(incl_regmatch.regprog);
4078 vim_regfree(def_regmatch.regprog);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004079}
4080
4081 static void
Bram Moolenaar764b23c2016-01-30 21:10:09 +01004082show_pat_in_path(
4083 char_u *line,
4084 int type,
4085 int did_show,
4086 int action,
4087 FILE *fp,
4088 linenr_T *lnum,
4089 long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004090{
4091 char_u *p;
John Marriott8c85a2a2024-05-20 19:18:26 +02004092 size_t linelen;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004093
4094 if (did_show)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004095 msg_putchar('\n'); // cursor below last one
Bram Moolenaar91170f82006-05-05 21:15:17 +00004096 else if (!msg_silent)
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004097 gotocmdline(TRUE); // cursor at status line
4098 if (got_int) // 'q' typed at "--more--" message
Bram Moolenaar071d4272004-06-13 20:20:40 +00004099 return;
John Marriott8c85a2a2024-05-20 19:18:26 +02004100 linelen = STRLEN(line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004101 for (;;)
4102 {
John Marriott8c85a2a2024-05-20 19:18:26 +02004103 p = line + linelen - 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004104 if (fp != NULL)
4105 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004106 // We used fgets(), so get rid of newline at end
Bram Moolenaar071d4272004-06-13 20:20:40 +00004107 if (p >= line && *p == '\n')
4108 --p;
4109 if (p >= line && *p == '\r')
4110 --p;
4111 *(p + 1) = NUL;
4112 }
4113 if (action == ACTION_SHOW_ALL)
4114 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004115 sprintf((char *)IObuff, "%3ld: ", count); // show match nr
Bram Moolenaar32526b32019-01-19 17:43:09 +01004116 msg_puts((char *)IObuff);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004117 sprintf((char *)IObuff, "%4ld", *lnum); // show line nr
4118 // Highlight line numbers
Bram Moolenaar32526b32019-01-19 17:43:09 +01004119 msg_puts_attr((char *)IObuff, HL_ATTR(HLF_N));
4120 msg_puts(" ");
Bram Moolenaar071d4272004-06-13 20:20:40 +00004121 }
Bram Moolenaar26a60b42005-02-22 08:49:11 +00004122 msg_prt_line(line, FALSE);
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004123 out_flush(); // show one line at a time
Bram Moolenaar071d4272004-06-13 20:20:40 +00004124
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004125 // Definition continues until line that doesn't end with '\'
Bram Moolenaar071d4272004-06-13 20:20:40 +00004126 if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
4127 break;
4128
4129 if (fp != NULL)
4130 {
Bram Moolenaar63d9e732019-12-05 21:10:38 +01004131 if (vim_fgets(line, LSIZE, fp)) // end of file
Bram Moolenaar071d4272004-06-13 20:20:40 +00004132 break;
John Marriott8c85a2a2024-05-20 19:18:26 +02004133 linelen = STRLEN(line);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004134 ++*lnum;
4135 }
4136 else
4137 {
4138 if (++*lnum > curbuf->b_ml.ml_line_count)
4139 break;
4140 line = ml_get(*lnum);
John Marriott8c85a2a2024-05-20 19:18:26 +02004141 linelen = ml_get_len(*lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004142 }
4143 msg_putchar('\n');
4144 }
4145}
4146#endif
4147
4148#ifdef FEAT_VIMINFO
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004149/*
4150 * Return the last used search pattern at "idx".
4151 */
Bram Moolenaarc3328162019-07-23 22:15:25 +02004152 spat_T *
4153get_spat(int idx)
4154{
4155 return &spats[idx];
4156}
4157
Bram Moolenaar6bd1d772019-10-09 22:01:25 +02004158/*
4159 * Return the last used search pattern index.
4160 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00004161 int
Bram Moolenaarc3328162019-07-23 22:15:25 +02004162get_spat_last_idx(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004163{
Bram Moolenaarc3328162019-07-23 22:15:25 +02004164 return last_idx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004165}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004166#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004167
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004168#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004169/*
4170 * "searchcount()" function
4171 */
4172 void
4173f_searchcount(typval_T *argvars, typval_T *rettv)
4174{
4175 pos_T pos = curwin->w_cursor;
4176 char_u *pattern = NULL;
4177 int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
4178 long timeout = SEARCH_STAT_DEF_TIMEOUT;
Bram Moolenaar4140c4f2020-09-05 23:16:00 +02004179 int recompute = TRUE;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004180 searchstat_T stat;
4181
4182 if (rettv_dict_alloc(rettv) == FAIL)
4183 return;
4184
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02004185 if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
4186 return;
4187
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004188 if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
4189 recompute = TRUE;
4190
4191 if (argvars[0].v_type != VAR_UNKNOWN)
4192 {
Bram Moolenaar14681622020-06-03 22:57:39 +02004193 dict_T *dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004194 dictitem_T *di;
4195 listitem_T *li;
4196 int error = FALSE;
4197
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01004198 if (check_for_nonnull_dict_arg(argvars, 0) == FAIL)
Bram Moolenaar14681622020-06-03 22:57:39 +02004199 return;
Bram Moolenaar14681622020-06-03 22:57:39 +02004200 dict = argvars[0].vval.v_dict;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004201 di = dict_find(dict, (char_u *)"timeout", -1);
4202 if (di != NULL)
4203 {
4204 timeout = (long)tv_get_number_chk(&di->di_tv, &error);
4205 if (error)
4206 return;
4207 }
4208 di = dict_find(dict, (char_u *)"maxcount", -1);
4209 if (di != NULL)
4210 {
4211 maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
4212 if (error)
4213 return;
4214 }
Bram Moolenaard61efa52022-07-23 09:52:04 +01004215 recompute = dict_get_bool(dict, "recompute", recompute);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004216 di = dict_find(dict, (char_u *)"pattern", -1);
4217 if (di != NULL)
4218 {
4219 pattern = tv_get_string_chk(&di->di_tv);
4220 if (pattern == NULL)
4221 return;
4222 }
4223 di = dict_find(dict, (char_u *)"pos", -1);
4224 if (di != NULL)
4225 {
4226 if (di->di_tv.v_type != VAR_LIST)
4227 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004228 semsg(_(e_invalid_argument_str), "pos");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004229 return;
4230 }
4231 if (list_len(di->di_tv.vval.v_list) != 3)
4232 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004233 semsg(_(e_invalid_argument_str), "List format should be [lnum, col, off]");
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004234 return;
4235 }
4236 li = list_find(di->di_tv.vval.v_list, 0L);
4237 if (li != NULL)
4238 {
4239 pos.lnum = tv_get_number_chk(&li->li_tv, &error);
4240 if (error)
4241 return;
4242 }
4243 li = list_find(di->di_tv.vval.v_list, 1L);
4244 if (li != NULL)
4245 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004246 pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004247 if (error)
4248 return;
4249 }
4250 li = list_find(di->di_tv.vval.v_list, 2L);
4251 if (li != NULL)
4252 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004253 pos.coladd = tv_get_number_chk(&li->li_tv, &error);
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004254 if (error)
4255 return;
4256 }
4257 }
4258 }
4259
4260 save_last_search_pattern();
Christian Brabandt6dd74242022-02-14 12:44:32 +00004261#ifdef FEAT_SEARCH_EXTRA
4262 save_incsearch_state();
4263#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004264 if (pattern != NULL)
4265 {
4266 if (*pattern == NUL)
4267 goto the_end;
Bram Moolenaar109aece2020-06-01 19:08:54 +02004268 vim_free(spats[last_idx].pat);
John Marriott8c85a2a2024-05-20 19:18:26 +02004269 spats[last_idx].patlen = STRLEN(pattern);
4270 spats[last_idx].pat = vim_strnsave(pattern, spats[last_idx].patlen);
4271 if (spats[last_idx].pat == NULL)
4272 spats[last_idx].patlen = 0;
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004273 }
4274 if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
4275 goto the_end; // the previous pattern was never defined
4276
4277 update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
4278
4279 dict_add_number(rettv->vval.v_dict, "current", stat.cur);
4280 dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
4281 dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
4282 dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
4283 dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
4284
4285the_end:
4286 restore_last_search_pattern();
Christian Brabandt6dd74242022-02-14 12:44:32 +00004287#ifdef FEAT_SEARCH_EXTRA
4288 restore_incsearch_state();
4289#endif
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02004290}
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004291#endif
Bram Moolenaar635414d2020-09-11 22:25:15 +02004292
4293/*
4294 * Fuzzy string matching
4295 *
4296 * Ported from the lib_fts library authored by Forrest Smith.
4297 * https://github.com/forrestthewoods/lib_fts/tree/master/code
4298 *
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004299 * The following blog describes the fuzzy matching algorithm:
Bram Moolenaar635414d2020-09-11 22:25:15 +02004300 * https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
4301 *
4302 * Each matching string is assigned a score. The following factors are checked:
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004303 * - Matched letter
4304 * - Unmatched letter
4305 * - Consecutively matched letters
4306 * - Proximity to start
4307 * - Letter following a separator (space, underscore)
4308 * - Uppercase letter following lowercase (aka CamelCase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004309 *
4310 * Matched letters are good. Unmatched letters are bad. Matching near the start
4311 * is good. Matching the first letter in the middle of a phrase is good.
4312 * Matching the uppercase letters in camel case entries is good.
4313 *
4314 * The score assigned for each factor is explained below.
4315 * File paths are different from file names. File extensions may be ignorable.
4316 * Single words care about consecutive matches but not separators or camel
4317 * case.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004318 * Score starts at 100
Bram Moolenaar635414d2020-09-11 22:25:15 +02004319 * Matched letter: +0 points
4320 * Unmatched letter: -1 point
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004321 * Consecutive match bonus: +15 points
4322 * First letter bonus: +15 points
4323 * Separator bonus: +30 points
4324 * Camel case bonus: +30 points
4325 * Unmatched leading letter: -5 points (max: -15)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004326 *
4327 * There is some nuance to this. Scores don’t have an intrinsic meaning. The
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004328 * score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a
Bram Moolenaar635414d2020-09-11 22:25:15 +02004329 * lower minimum score due to unmatched letter penalty. Longer search patterns
4330 * have a higher maximum score due to match bonuses.
4331 *
4332 * Separator and camel case bonus is worth a LOT. Consecutive matches are worth
4333 * quite a bit.
4334 *
4335 * There is a penalty if you DON’T match the first three letters. Which
4336 * effectively rewards matching near the start. However there’s no difference
4337 * in matching between the middle and end.
4338 *
4339 * There is not an explicit bonus for an exact match. Unmatched letters receive
4340 * a penalty. So shorter strings and closer matches are worth more.
4341 */
4342typedef struct
4343{
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004344 int idx; // used for stable sort
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004345 listitem_T *item;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004346 int score;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004347 list_T *lmatchpos;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004348} fuzzyItem_T;
4349
Bram Moolenaare9f9f162020-10-20 19:01:30 +02004350// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
4351// matching a whole word is preferred.
4352#define SEQUENTIAL_BONUS 40
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004353// bonus if match occurs after a path separator
4354#define PATH_SEPARATOR_BONUS 30
4355// bonus if match occurs after a word separator
4356#define WORD_SEPARATOR_BONUS 25
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004357// bonus if match is uppercase and prev is lower
4358#define CAMEL_BONUS 30
4359// bonus if the first letter is matched
4360#define FIRST_LETTER_BONUS 15
glepnir9dfc7e52025-01-21 22:33:13 +01004361// bonus if exact match
4362#define EXACT_MATCH_BONUS 100
4363// bonus if case match when no ignorecase
4364#define CASE_MATCH_BONUS 25
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004365// penalty applied for every letter in str before the first match
kylo252ae6f1d82022-02-16 19:24:07 +00004366#define LEADING_LETTER_PENALTY (-5)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004367// maximum penalty for leading letters
kylo252ae6f1d82022-02-16 19:24:07 +00004368#define MAX_LEADING_LETTER_PENALTY (-15)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004369// penalty for every letter that doesn't match
kylo252ae6f1d82022-02-16 19:24:07 +00004370#define UNMATCHED_LETTER_PENALTY (-1)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004371// penalty for gap in matching positions (-2 * k)
kylo252ae6f1d82022-02-16 19:24:07 +00004372#define GAP_PENALTY (-2)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004373// Score for a string that doesn't fuzzy match the pattern
kylo252ae6f1d82022-02-16 19:24:07 +00004374#define SCORE_NONE (-9999)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004375
4376#define FUZZY_MATCH_RECURSION_LIMIT 10
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004377
4378/*
4379 * Compute a score for a fuzzy matched string. The matching character locations
4380 * are in 'matches'.
4381 */
4382 static int
4383fuzzy_match_compute_score(
glepnir9dfc7e52025-01-21 22:33:13 +01004384 char_u *fuzpat,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004385 char_u *str,
4386 int strSz,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004387 int_u *matches,
glepnir28e40a72025-03-16 21:24:22 +01004388 int numMatches,
4389 int camelcase)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004390{
4391 int score;
4392 int penalty;
4393 int unmatched;
4394 int i;
4395 char_u *p = str;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004396 int_u sidx = 0;
glepnir5a049992024-12-26 15:38:39 +01004397 int is_exact_match = TRUE;
glepnir9dfc7e52025-01-21 22:33:13 +01004398 char_u *orig_fuzpat = fuzpat - numMatches;
4399 char_u *curpat = orig_fuzpat;
4400 int pat_idx = 0;
4401 // Track consecutive camel case matches
4402 int consecutive_camel = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004403
4404 // Initialize score
4405 score = 100;
4406
4407 // Apply leading letter penalty
4408 penalty = LEADING_LETTER_PENALTY * matches[0];
4409 if (penalty < MAX_LEADING_LETTER_PENALTY)
4410 penalty = MAX_LEADING_LETTER_PENALTY;
4411 score += penalty;
4412
4413 // Apply unmatched penalty
4414 unmatched = strSz - numMatches;
4415 score += UNMATCHED_LETTER_PENALTY * unmatched;
Girish Palya328332b2025-06-09 20:43:03 +02004416 // In a long string, not all matches may be found due to the recursion limit.
4417 // If at least one match is found, reset the score to a non-negative value.
4418 if (score < 0 && numMatches > 0)
4419 score = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004420
4421 // Apply ordering bonuses
4422 for (i = 0; i < numMatches; ++i)
4423 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004424 int_u currIdx = matches[i];
glepnir9dfc7e52025-01-21 22:33:13 +01004425 int curr;
4426 int is_camel = FALSE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004427
4428 if (i > 0)
4429 {
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004430 int_u prevIdx = matches[i - 1];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004431
4432 // Sequential
4433 if (currIdx == (prevIdx + 1))
4434 score += SEQUENTIAL_BONUS;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004435 else
glepnir9dfc7e52025-01-21 22:33:13 +01004436 {
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004437 score += GAP_PENALTY * (currIdx - prevIdx);
glepnir9dfc7e52025-01-21 22:33:13 +01004438 // Reset consecutive camel count on gap
4439 consecutive_camel = 0;
4440 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004441 }
4442
4443 // Check for bonuses based on neighbor character value
4444 if (currIdx > 0)
4445 {
4446 // Camel case
glepnir9dfc7e52025-01-21 22:33:13 +01004447 int neighbor = ' ';
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004448
4449 if (has_mbyte)
4450 {
4451 while (sidx < currIdx)
4452 {
4453 neighbor = (*mb_ptr2char)(p);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004454 MB_PTR_ADV(p);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004455 sidx++;
4456 }
4457 curr = (*mb_ptr2char)(p);
4458 }
4459 else
4460 {
4461 neighbor = str[currIdx - 1];
4462 curr = str[currIdx];
4463 }
4464
glepnir9dfc7e52025-01-21 22:33:13 +01004465 // Enhanced camel case scoring
glepnir28e40a72025-03-16 21:24:22 +01004466 if (camelcase && vim_islower(neighbor) && vim_isupper(curr))
glepnir9dfc7e52025-01-21 22:33:13 +01004467 {
4468 score += CAMEL_BONUS * 2; // Double the camel case bonus
4469 is_camel = TRUE;
4470 consecutive_camel++;
4471 // Additional bonus for consecutive camel
4472 if (consecutive_camel > 1)
4473 score += CAMEL_BONUS;
4474 }
4475 else
4476 consecutive_camel = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004477
Bram Moolenaardcdd42a2020-10-29 18:58:01 +01004478 // Bonus if the match follows a separator character
4479 if (neighbor == '/' || neighbor == '\\')
4480 score += PATH_SEPARATOR_BONUS;
4481 else if (neighbor == ' ' || neighbor == '_')
4482 score += WORD_SEPARATOR_BONUS;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004483 }
4484 else
4485 {
4486 // First letter
4487 score += FIRST_LETTER_BONUS;
glepnir9dfc7e52025-01-21 22:33:13 +01004488 curr = has_mbyte ? (*mb_ptr2char)(p) : str[currIdx];
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004489 }
glepnir9dfc7e52025-01-21 22:33:13 +01004490
4491 // Case matching bonus
4492 if (vim_isalpha(curr))
4493 {
4494 while (pat_idx < i && *curpat)
4495 {
4496 if (has_mbyte)
4497 MB_PTR_ADV(curpat);
4498 else
4499 curpat++;
4500 pat_idx++;
4501 }
4502
4503 if (has_mbyte)
4504 {
4505 if (curr == (*mb_ptr2char)(curpat))
4506 {
4507 score += CASE_MATCH_BONUS;
4508 // Extra bonus for exact case match in camel
4509 if (is_camel)
4510 score += CASE_MATCH_BONUS / 2;
4511 }
4512 }
4513 else if (curr == *curpat)
4514 {
4515 score += CASE_MATCH_BONUS;
4516 if (is_camel)
4517 score += CASE_MATCH_BONUS / 2;
4518 }
4519 }
4520
glepnir5a049992024-12-26 15:38:39 +01004521 // Check exact match condition
Naruhiko Nishinoc2a90002025-05-04 20:05:47 +02004522 if (currIdx != (int_u)i)
glepnir5a049992024-12-26 15:38:39 +01004523 is_exact_match = FALSE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004524 }
glepnir9dfc7e52025-01-21 22:33:13 +01004525
glepnir5a049992024-12-26 15:38:39 +01004526 // Boost score for exact matches
4527 if (is_exact_match && numMatches == strSz)
Naruhiko Nishinoc2a90002025-05-04 20:05:47 +02004528 score += EXACT_MATCH_BONUS;
glepnir5a049992024-12-26 15:38:39 +01004529
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004530 return score;
4531}
4532
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004533/*
4534 * Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
4535 * Return the number of matching characters.
4536 */
Bram Moolenaar635414d2020-09-11 22:25:15 +02004537 static int
4538fuzzy_match_recursive(
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004539 char_u *fuzpat,
4540 char_u *str,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004541 int_u strIdx,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004542 int *outScore,
4543 char_u *strBegin,
4544 int strLen,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004545 int_u *srcMatches,
4546 int_u *matches,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004547 int maxMatches,
4548 int nextMatch,
glepnir28e40a72025-03-16 21:24:22 +01004549 int *recursionCount,
4550 int camelcase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004551{
4552 // Recursion params
4553 int recursiveMatch = FALSE;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004554 int_u bestRecursiveMatches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004555 int bestRecursiveScore = 0;
4556 int first_match;
4557 int matched;
4558
4559 // Count recursions
4560 ++*recursionCount;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004561 if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004562 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004563
4564 // Detect end of strings
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004565 if (*fuzpat == NUL || *str == NUL)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004566 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004567
4568 // Loop through fuzpat and str looking for a match
4569 first_match = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004570 while (*fuzpat != NUL && *str != NUL)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004571 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004572 int c1;
4573 int c2;
4574
4575 c1 = PTR2CHAR(fuzpat);
4576 c2 = PTR2CHAR(str);
4577
Bram Moolenaar635414d2020-09-11 22:25:15 +02004578 // Found match
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004579 if (vim_tolower(c1) == vim_tolower(c2))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004580 {
Bram Moolenaar635414d2020-09-11 22:25:15 +02004581 // Supplied matches buffer was too short
4582 if (nextMatch >= maxMatches)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004583 return 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004584
Bram Moolenaarcaf642c2023-04-29 21:38:04 +01004585 int recursiveScore = 0;
4586 int_u recursiveMatches[MAX_FUZZY_MATCHES];
4587 CLEAR_FIELD(recursiveMatches);
4588
Bram Moolenaar635414d2020-09-11 22:25:15 +02004589 // "Copy-on-Write" srcMatches into matches
4590 if (first_match && srcMatches)
4591 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004592 memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004593 first_match = FALSE;
4594 }
4595
4596 // Recursive call that "skips" this match
Bram Moolenaarcaf642c2023-04-29 21:38:04 +01004597 char_u *next_char = str + (has_mbyte ? (*mb_ptr2len)(str) : 1);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004598 if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
4599 &recursiveScore, strBegin, strLen, matches,
4600 recursiveMatches,
K.Takataeeec2542021-06-02 13:28:16 +02004601 ARRAY_LENGTH(recursiveMatches),
glepnir28e40a72025-03-16 21:24:22 +01004602 nextMatch, recursionCount, camelcase))
Bram Moolenaar635414d2020-09-11 22:25:15 +02004603 {
4604 // Pick best recursive score
4605 if (!recursiveMatch || recursiveScore > bestRecursiveScore)
4606 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004607 memcpy(bestRecursiveMatches, recursiveMatches,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004608 MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004609 bestRecursiveScore = recursiveScore;
4610 }
4611 recursiveMatch = TRUE;
4612 }
4613
4614 // Advance
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004615 matches[nextMatch++] = strIdx;
4616 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004617 MB_PTR_ADV(fuzpat);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004618 else
4619 ++fuzpat;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004620 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004621 if (has_mbyte)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004622 MB_PTR_ADV(str);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004623 else
4624 ++str;
4625 strIdx++;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004626 }
4627
4628 // Determine if full fuzpat was matched
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004629 matched = *fuzpat == NUL ? TRUE : FALSE;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004630
4631 // Calculate score
4632 if (matched)
glepnir9dfc7e52025-01-21 22:33:13 +01004633 *outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches,
glepnir28e40a72025-03-16 21:24:22 +01004634 nextMatch, camelcase);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004635
4636 // Return best result
4637 if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
4638 {
4639 // Recursive score is better than "this"
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004640 memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
Bram Moolenaar635414d2020-09-11 22:25:15 +02004641 *outScore = bestRecursiveScore;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004642 return nextMatch;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004643 }
4644 else if (matched)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004645 return nextMatch; // "this" score is better than recursive
Bram Moolenaar635414d2020-09-11 22:25:15 +02004646
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004647 return 0; // no match
Bram Moolenaar635414d2020-09-11 22:25:15 +02004648}
4649
4650/*
4651 * fuzzy_match()
4652 *
4653 * Performs exhaustive search via recursion to find all possible matches and
4654 * match with highest score.
4655 * Scores values have no intrinsic meaning. Possible score range is not
4656 * normalized and varies with pattern.
4657 * Recursion is limited internally (default=10) to prevent degenerate cases
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004658 * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004659 * Uses char_u for match indices. Therefore patterns are limited to
4660 * MAX_FUZZY_MATCHES characters.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004661 *
Bram Moolenaarcaf642c2023-04-29 21:38:04 +01004662 * Returns TRUE if "pat_arg" matches "str". Also returns the match score in
4663 * "outScore" and the matching character positions in "matches".
Bram Moolenaar635414d2020-09-11 22:25:15 +02004664 */
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004665 int
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004666fuzzy_match(
4667 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004668 char_u *pat_arg,
4669 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004670 int *outScore,
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004671 int_u *matches,
glepnir28e40a72025-03-16 21:24:22 +01004672 int maxMatches,
4673 int camelcase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004674{
Bram Moolenaar635414d2020-09-11 22:25:15 +02004675 int recursionCount = 0;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004676 int len = MB_CHARLEN(str);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004677 char_u *save_pat;
4678 char_u *pat;
4679 char_u *p;
4680 int complete = FALSE;
4681 int score = 0;
4682 int numMatches = 0;
4683 int matchCount;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004684
4685 *outScore = 0;
4686
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004687 save_pat = vim_strsave(pat_arg);
4688 if (save_pat == NULL)
4689 return FALSE;
4690 pat = save_pat;
4691 p = pat;
4692
4693 // Try matching each word in 'pat_arg' in 'str'
4694 while (TRUE)
4695 {
4696 if (matchseq)
4697 complete = TRUE;
4698 else
4699 {
4700 // Extract one word from the pattern (separated by space)
4701 p = skipwhite(p);
4702 if (*p == NUL)
4703 break;
4704 pat = p;
4705 while (*p != NUL && !VIM_ISWHITE(PTR2CHAR(p)))
4706 {
4707 if (has_mbyte)
4708 MB_PTR_ADV(p);
4709 else
4710 ++p;
4711 }
4712 if (*p == NUL) // processed all the words
4713 complete = TRUE;
4714 *p = NUL;
4715 }
4716
4717 score = 0;
4718 recursionCount = 0;
4719 matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
4720 matches + numMatches, maxMatches - numMatches,
glepnir28e40a72025-03-16 21:24:22 +01004721 0, &recursionCount, camelcase);
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004722 if (matchCount == 0)
4723 {
4724 numMatches = 0;
4725 break;
4726 }
4727
4728 // Accumulate the match score and the number of matches
4729 *outScore += score;
4730 numMatches += matchCount;
4731
4732 if (complete)
4733 break;
4734
4735 // try matching the next word
4736 ++p;
4737 }
4738
4739 vim_free(save_pat);
4740 return numMatches != 0;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004741}
4742
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00004743#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004744/*
4745 * Sort the fuzzy matches in the descending order of the match score.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004746 * For items with same score, retain the order using the index (stable sort)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004747 */
4748 static int
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004749fuzzy_match_item_compare(const void *s1, const void *s2)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004750{
4751 int v1 = ((fuzzyItem_T *)s1)->score;
4752 int v2 = ((fuzzyItem_T *)s2)->score;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004753 int idx1 = ((fuzzyItem_T *)s1)->idx;
4754 int idx2 = ((fuzzyItem_T *)s2)->idx;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004755
zeertzjq77078272024-02-10 13:24:03 +01004756 if (v1 == v2)
4757 return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
4758 else
4759 return v1 > v2 ? -1 : 1;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004760}
4761
4762/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004763 * Fuzzy search the string 'str' in a list of 'items' and return the matching
4764 * strings in 'fmatchlist'.
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004765 * If 'matchseq' is TRUE, then for multi-word search strings, match all the
4766 * words in sequence.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004767 * If 'items' is a list of strings, then search for 'str' in the list.
4768 * If 'items' is a list of dicts, then either use 'key' to lookup the string
4769 * for each item or use 'item_cb' Funcref function to get the string.
4770 * If 'retmatchpos' is TRUE, then return a list of positions where 'str'
4771 * matches for each item.
Bram Moolenaar635414d2020-09-11 22:25:15 +02004772 */
4773 static void
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004774fuzzy_match_in_list(
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004775 list_T *l,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004776 char_u *str,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004777 int matchseq,
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004778 char_u *key,
4779 callback_T *item_cb,
4780 int retmatchpos,
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004781 list_T *fmatchlist,
glepnir28e40a72025-03-16 21:24:22 +01004782 long max_matches,
4783 int camelcase)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004784{
4785 long len;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004786 fuzzyItem_T *items;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004787 listitem_T *li;
4788 long i = 0;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004789 long match_count = 0;
Yegappan Lakshmananbb01a1e2021-04-26 21:17:52 +02004790 int_u matches[MAX_FUZZY_MATCHES];
Bram Moolenaar635414d2020-09-11 22:25:15 +02004791
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004792 len = list_len(l);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004793 if (len == 0)
4794 return;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004795 if (max_matches > 0 && len > max_matches)
4796 len = max_matches;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004797
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004798 items = ALLOC_CLEAR_MULT(fuzzyItem_T, len);
4799 if (items == NULL)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004800 return;
4801
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004802 // For all the string items in items, get the fuzzy matching score
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004803 FOR_ALL_LIST_ITEMS(l, li)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004804 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004805 int score;
4806 char_u *itemstr;
4807 typval_T rettv;
Bram Moolenaar635414d2020-09-11 22:25:15 +02004808
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004809 if (max_matches > 0 && match_count >= max_matches)
4810 break;
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004811
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004812 itemstr = NULL;
4813 rettv.v_type = VAR_UNKNOWN;
4814 if (li->li_tv.v_type == VAR_STRING) // list of strings
4815 itemstr = li->li_tv.vval.v_string;
Dominique Pelle7765f5c2022-04-10 11:26:53 +01004816 else if (li->li_tv.v_type == VAR_DICT
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004817 && (key != NULL || item_cb->cb_name != NULL))
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004818 {
4819 // For a dict, either use the specified key to lookup the string or
4820 // use the specified callback function to get the string.
4821 if (key != NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004822 itemstr = dict_get_string(li->li_tv.vval.v_dict,
4823 (char *)key, FALSE);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004824 else
Bram Moolenaar635414d2020-09-11 22:25:15 +02004825 {
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004826 typval_T argv[2];
4827
4828 // Invoke the supplied callback (if any) to get the dict item
4829 li->li_tv.vval.v_dict->dv_refcount++;
4830 argv[0].v_type = VAR_DICT;
4831 argv[0].vval.v_dict = li->li_tv.vval.v_dict;
4832 argv[1].v_type = VAR_UNKNOWN;
4833 if (call_callback(item_cb, -1, &rettv, 1, argv) != FAIL)
4834 {
4835 if (rettv.v_type == VAR_STRING)
4836 itemstr = rettv.vval.v_string;
4837 }
4838 dict_unref(li->li_tv.vval.v_dict);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004839 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004840 }
4841
4842 if (itemstr != NULL
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004843 && fuzzy_match(itemstr, str, matchseq, &score, matches,
glepnir28e40a72025-03-16 21:24:22 +01004844 MAX_FUZZY_MATCHES, camelcase))
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004845 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004846 items[match_count].idx = match_count;
4847 items[match_count].item = li;
4848 items[match_count].score = score;
4849
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004850 // Copy the list of matching positions in itemstr to a list, if
4851 // 'retmatchpos' is set.
4852 if (retmatchpos)
4853 {
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004854 int j = 0;
4855 char_u *p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004856
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004857 items[match_count].lmatchpos = list_alloc();
4858 if (items[match_count].lmatchpos == NULL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004859 goto done;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004860
4861 p = str;
4862 while (*p != NUL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004863 {
zeertzjq9af2bc02022-05-11 14:15:37 +01004864 if (!VIM_ISWHITE(PTR2CHAR(p)) || matchseq)
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004865 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004866 if (list_append_number(items[match_count].lmatchpos,
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004867 matches[j]) == FAIL)
4868 goto done;
4869 j++;
4870 }
4871 if (has_mbyte)
4872 MB_PTR_ADV(p);
4873 else
4874 ++p;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004875 }
4876 }
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004877 ++match_count;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004878 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004879 clear_tv(&rettv);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004880 }
4881
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004882 if (match_count > 0)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004883 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004884 list_T *retlist;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004885
Bram Moolenaar635414d2020-09-11 22:25:15 +02004886 // Sort the list by the descending order of the match score
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004887 qsort((void *)items, (size_t)match_count, sizeof(fuzzyItem_T),
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004888 fuzzy_match_item_compare);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004889
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004890 // For matchfuzzy(), return a list of matched strings.
4891 // ['str1', 'str2', 'str3']
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004892 // For matchfuzzypos(), return a list with three items.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004893 // The first item is a list of matched strings. The second item
4894 // is a list of lists where each list item is a list of matched
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004895 // character positions. The third item is a list of matching scores.
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004896 // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
4897 if (retmatchpos)
4898 {
4899 li = list_find(fmatchlist, 0);
4900 if (li == NULL || li->li_tv.vval.v_list == NULL)
4901 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004902 retlist = li->li_tv.vval.v_list;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004903 }
4904 else
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004905 retlist = fmatchlist;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004906
4907 // Copy the matching strings with a valid score to the return list
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004908 for (i = 0; i < match_count; i++)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004909 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004910 if (items[i].score == SCORE_NONE)
Bram Moolenaar635414d2020-09-11 22:25:15 +02004911 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004912 list_append_tv(retlist, &items[i].item->li_tv);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004913 }
4914
4915 // next copy the list of matching positions
4916 if (retmatchpos)
4917 {
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004918 li = list_find(fmatchlist, -2);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004919 if (li == NULL || li->li_tv.vval.v_list == NULL)
4920 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004921 retlist = li->li_tv.vval.v_list;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004922
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004923 for (i = 0; i < match_count; i++)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004924 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004925 if (items[i].score == SCORE_NONE)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004926 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004927 if (items[i].lmatchpos != NULL
Bram Moolenaar9ba61942022-08-31 11:25:06 +01004928 && list_append_list(retlist, items[i].lmatchpos) == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004929 goto done;
4930 }
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004931
4932 // copy the matching scores
4933 li = list_find(fmatchlist, -1);
4934 if (li == NULL || li->li_tv.vval.v_list == NULL)
4935 goto done;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004936 retlist = li->li_tv.vval.v_list;
4937 for (i = 0; i < match_count; i++)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004938 {
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004939 if (items[i].score == SCORE_NONE)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004940 break;
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004941 if (list_append_number(retlist, items[i].score) == FAIL)
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01004942 goto done;
4943 }
Bram Moolenaar635414d2020-09-11 22:25:15 +02004944 }
4945 }
4946
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004947done:
Yegappan Lakshmanan047a7012022-04-16 20:42:40 +01004948 vim_free(items);
Bram Moolenaar635414d2020-09-11 22:25:15 +02004949}
4950
4951/*
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004952 * Do fuzzy matching. Returns the list of matched strings in 'rettv'.
4953 * If 'retmatchpos' is TRUE, also returns the matching character positions.
4954 */
4955 static void
4956do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
4957{
4958 callback_T cb;
4959 char_u *key = NULL;
4960 int ret;
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02004961 int matchseq = FALSE;
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01004962 long max_matches = 0;
glepnir28e40a72025-03-16 21:24:22 +01004963 int camelcase = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004964
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02004965 if (in_vim9script()
4966 && (check_for_list_arg(argvars, 0) == FAIL
4967 || check_for_string_arg(argvars, 1) == FAIL
4968 || check_for_opt_dict_arg(argvars, 2) == FAIL))
4969 return;
4970
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004971 CLEAR_POINTER(&cb);
4972
4973 // validate and get the arguments
4974 if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
4975 {
Bram Moolenaar3a846e62022-01-01 16:21:00 +00004976 semsg(_(e_argument_of_str_must_be_list),
4977 retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004978 return;
4979 }
4980 if (argvars[1].v_type != VAR_STRING
4981 || argvars[1].vval.v_string == NULL)
4982 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004983 semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004984 return;
4985 }
4986
4987 if (argvars[2].v_type != VAR_UNKNOWN)
4988 {
4989 dict_T *d;
4990 dictitem_T *di;
4991
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01004992 if (check_for_nonnull_dict_arg(argvars, 2) == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004993 return;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02004994
4995 // To search a dict, either a callback function or a key can be
4996 // specified.
4997 d = argvars[2].vval.v_dict;
4998 if ((di = dict_find(d, (char_u *)"key", -1)) != NULL)
4999 {
5000 if (di->di_tv.v_type != VAR_STRING
5001 || di->di_tv.vval.v_string == NULL
5002 || *di->di_tv.vval.v_string == NUL)
5003 {
zeertzjqc4815c12025-03-18 20:28:00 +01005004 semsg(_(e_invalid_value_for_argument_str_str), "key",
5005 tv_get_string(&di->di_tv));
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005006 return;
5007 }
5008 key = tv_get_string(&di->di_tv);
5009 }
5010 else if ((di = dict_find(d, (char_u *)"text_cb", -1)) != NULL)
5011 {
5012 cb = get_callback(&di->di_tv);
5013 if (cb.cb_name == NULL)
5014 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005015 semsg(_(e_invalid_value_for_argument_str), "text_cb");
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005016 return;
5017 }
5018 }
Kazuyuki Miyagi47f1a552022-06-17 18:30:03 +01005019
5020 if ((di = dict_find(d, (char_u *)"limit", -1)) != NULL)
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01005021 {
5022 if (di->di_tv.v_type != VAR_NUMBER)
5023 {
zeertzjqc4815c12025-03-18 20:28:00 +01005024 semsg(_(e_invalid_value_for_argument_str), "limit");
Yasuhiro Matsumoto9029a6e2022-04-16 12:35:35 +01005025 return;
5026 }
5027 max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
5028 }
5029
glepnir28e40a72025-03-16 21:24:22 +01005030 if ((di = dict_find(d, (char_u *)"camelcase", -1)) != NULL)
zeertzjqc4815c12025-03-18 20:28:00 +01005031 {
glepnir28e40a72025-03-16 21:24:22 +01005032 if (di->di_tv.v_type != VAR_BOOL)
5033 {
zeertzjqc4815c12025-03-18 20:28:00 +01005034 semsg(_(e_invalid_value_for_argument_str), "camelcase");
glepnir28e40a72025-03-16 21:24:22 +01005035 return;
5036 }
5037 camelcase = tv_get_bool_chk(&di->di_tv, NULL);
zeertzjqc4815c12025-03-18 20:28:00 +01005038 }
glepnir28e40a72025-03-16 21:24:22 +01005039
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01005040 if (dict_has_key(d, "matchseq"))
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02005041 matchseq = TRUE;
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005042 }
5043
5044 // get the fuzzy matches
5045 ret = rettv_list_alloc(rettv);
Bram Moolenaar5ea38d12022-06-16 21:20:48 +01005046 if (ret == FAIL)
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005047 goto done;
5048 if (retmatchpos)
5049 {
5050 list_T *l;
5051
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01005052 // For matchfuzzypos(), a list with three items are returned. First
5053 // item is a list of matching strings, the second item is a list of
5054 // lists with matching positions within each string and the third item
5055 // is the list of scores of the matches.
5056 l = list_alloc();
5057 if (l == NULL)
5058 goto done;
5059 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005060 {
5061 vim_free(l);
Bram Moolenaar9d19e4f2021-01-02 18:31:32 +01005062 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005063 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005064 l = list_alloc();
5065 if (l == NULL)
5066 goto done;
5067 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005068 {
5069 vim_free(l);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005070 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005071 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005072 l = list_alloc();
5073 if (l == NULL)
5074 goto done;
5075 if (list_append_list(rettv->vval.v_list, l) == FAIL)
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005076 {
5077 vim_free(l);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005078 goto done;
Bram Moolenaar9ba61942022-08-31 11:25:06 +01005079 }
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005080 }
5081
Bram Moolenaar8ded5b62020-10-23 16:50:30 +02005082 fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
glepnir28e40a72025-03-16 21:24:22 +01005083 matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches,
5084 camelcase);
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005085
5086done:
5087 free_callback(&cb);
5088}
5089
5090/*
Bram Moolenaar635414d2020-09-11 22:25:15 +02005091 * "matchfuzzy()" function
5092 */
5093 void
5094f_matchfuzzy(typval_T *argvars, typval_T *rettv)
5095{
Bram Moolenaar4f73b8e2020-09-22 20:33:50 +02005096 do_fuzzymatch(argvars, rettv, FALSE);
5097}
5098
5099/*
5100 * "matchfuzzypos()" function
5101 */
5102 void
5103f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
5104{
5105 do_fuzzymatch(argvars, rettv, TRUE);
Bram Moolenaar635414d2020-09-11 22:25:15 +02005106}
Bram Moolenaare8f5ec02020-06-01 17:28:35 +02005107#endif
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005108
5109/*
5110 * Same as fuzzy_match_item_compare() except for use with a string match
5111 */
5112 static int
5113fuzzy_match_str_compare(const void *s1, const void *s2)
5114{
5115 int v1 = ((fuzmatch_str_T *)s1)->score;
5116 int v2 = ((fuzmatch_str_T *)s2)->score;
5117 int idx1 = ((fuzmatch_str_T *)s1)->idx;
5118 int idx2 = ((fuzmatch_str_T *)s2)->idx;
5119
Christian Brabandte06e4372024-02-09 19:39:14 +01005120 if (v1 == v2)
5121 return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
5122 else
5123 return v1 > v2 ? -1 : 1;
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005124}
5125
5126/*
5127 * Sort fuzzy matches by score
5128 */
5129 static void
5130fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz)
5131{
5132 // Sort the list by the descending order of the match score
5133 qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
5134 fuzzy_match_str_compare);
5135}
5136
5137/*
5138 * Same as fuzzy_match_item_compare() except for use with a function name
5139 * string match. <SNR> functions should be sorted to the end.
5140 */
5141 static int
5142fuzzy_match_func_compare(const void *s1, const void *s2)
5143{
5144 int v1 = ((fuzmatch_str_T *)s1)->score;
5145 int v2 = ((fuzmatch_str_T *)s2)->score;
5146 int idx1 = ((fuzmatch_str_T *)s1)->idx;
5147 int idx2 = ((fuzmatch_str_T *)s2)->idx;
5148 char_u *str1 = ((fuzmatch_str_T *)s1)->str;
5149 char_u *str2 = ((fuzmatch_str_T *)s2)->str;
5150
Christian Brabandte06e4372024-02-09 19:39:14 +01005151 if (*str1 != '<' && *str2 == '<')
5152 return -1;
5153 if (*str1 == '<' && *str2 != '<')
5154 return 1;
5155 if (v1 == v2)
5156 return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
5157 else
5158 return v1 > v2 ? -1 : 1;
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005159}
5160
5161/*
5162 * Sort fuzzy matches of function names by score.
5163 * <SNR> functions should be sorted to the end.
5164 */
5165 static void
5166fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz)
5167{
5168 // Sort the list by the descending order of the match score
5169 qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
5170 fuzzy_match_func_compare);
5171}
5172
5173/*
5174 * Fuzzy match 'pat' in 'str'. Returns 0 if there is no match. Otherwise,
5175 * returns the match score.
5176 */
5177 int
5178fuzzy_match_str(char_u *str, char_u *pat)
5179{
5180 int score = 0;
Yegappan Lakshmanan5ec633b2022-02-25 15:24:24 +00005181 int_u matchpos[MAX_FUZZY_MATCHES];
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005182
5183 if (str == NULL || pat == NULL)
5184 return 0;
5185
Yegappan Lakshmanan6caeda22022-02-27 12:07:30 +00005186 fuzzy_match(str, pat, TRUE, &score, matchpos,
glepnir28e40a72025-03-16 21:24:22 +01005187 sizeof(matchpos) / sizeof(matchpos[0]), TRUE);
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005188
5189 return score;
5190}
5191
5192/*
glepnir40c1c332024-06-11 19:37:04 +02005193 * Fuzzy match the position of string 'pat' in string 'str'.
5194 * Returns a dynamic array of matching positions. If there is no match,
5195 * returns NULL.
5196 */
5197 garray_T *
5198fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED)
5199{
5200#ifdef FEAT_SEARCH_EXTRA
5201 int score = 0;
zeertzjq2f95ca92024-06-13 17:14:27 +02005202 garray_T *match_positions = NULL;
5203 int_u matches[MAX_FUZZY_MATCHES];
5204 int j = 0;
glepnir40c1c332024-06-11 19:37:04 +02005205
zeertzjq2f95ca92024-06-13 17:14:27 +02005206 if (str == NULL || pat == NULL)
zeertzjqd9be94c2024-07-14 10:20:20 +02005207 return NULL;
zeertzjq2f95ca92024-06-13 17:14:27 +02005208
5209 match_positions = ALLOC_ONE(garray_T);
glepnir40c1c332024-06-11 19:37:04 +02005210 if (match_positions == NULL)
zeertzjqd9be94c2024-07-14 10:20:20 +02005211 return NULL;
zeertzjq2f95ca92024-06-13 17:14:27 +02005212 ga_init2(match_positions, sizeof(int_u), 10);
5213
glepnir28e40a72025-03-16 21:24:22 +01005214 if (!fuzzy_match(str, pat, FALSE, &score, matches, MAX_FUZZY_MATCHES, TRUE)
zeertzjq2f95ca92024-06-13 17:14:27 +02005215 || score == 0)
glepnir40c1c332024-06-11 19:37:04 +02005216 {
zeertzjq2f95ca92024-06-13 17:14:27 +02005217 ga_clear(match_positions);
5218 vim_free(match_positions);
5219 return NULL;
glepnir40c1c332024-06-11 19:37:04 +02005220 }
5221
zeertzjq2f95ca92024-06-13 17:14:27 +02005222 for (char_u *p = pat; *p != NUL; MB_PTR_ADV(p))
glepnir40c1c332024-06-11 19:37:04 +02005223 {
zeertzjq2f95ca92024-06-13 17:14:27 +02005224 if (!VIM_ISWHITE(PTR2CHAR(p)))
5225 {
5226 ga_grow(match_positions, 1);
5227 ((int_u *)match_positions->ga_data)[match_positions->ga_len] =
5228 matches[j];
5229 match_positions->ga_len++;
5230 j++;
5231 }
glepnir40c1c332024-06-11 19:37:04 +02005232 }
5233
glepnir40c1c332024-06-11 19:37:04 +02005234 return match_positions;
glepnir40c1c332024-06-11 19:37:04 +02005235#else
5236 return NULL;
5237#endif
5238}
5239
5240/*
glepnirf31cfa22025-03-06 21:59:13 +01005241 * This function splits the line pointed to by `*ptr` into words and performs
5242 * a fuzzy match for the pattern `pat` on each word. It iterates through the
5243 * line, moving `*ptr` to the start of each word during the process.
5244 *
5245 * If a match is found:
5246 * - `*ptr` points to the start of the matched word.
5247 * - `*len` is set to the length of the matched word.
5248 * - `*score` contains the match score.
5249 *
glepnir53b14572025-03-12 21:28:39 +01005250 * If no match is found, `*ptr` is updated to the end of the line.
glepnir8159fb12024-07-17 20:32:54 +02005251 */
glepnirf31cfa22025-03-06 21:59:13 +01005252 int
5253fuzzy_match_str_in_line(
5254 char_u **ptr,
5255 char_u *pat,
5256 int *len,
5257 pos_T *current_pos,
5258 int *score)
glepnir8159fb12024-07-17 20:32:54 +02005259{
5260 char_u *str = *ptr;
5261 char_u *strBegin = str;
5262 char_u *end = NULL;
5263 char_u *start = NULL;
5264 int found = FALSE;
glepnir8159fb12024-07-17 20:32:54 +02005265 char save_end;
glepnirdd42b052025-03-08 16:52:55 +01005266 char_u *line_end = NULL;
glepnir8159fb12024-07-17 20:32:54 +02005267
5268 if (str == NULL || pat == NULL)
Naruhiko Nishinoc2a90002025-05-04 20:05:47 +02005269 return found;
glepnirdd42b052025-03-08 16:52:55 +01005270 line_end = find_line_end(str);
glepnir8159fb12024-07-17 20:32:54 +02005271
glepnirdd42b052025-03-08 16:52:55 +01005272 while (str < line_end)
glepnir8159fb12024-07-17 20:32:54 +02005273 {
5274 // Skip non-word characters
5275 start = find_word_start(str);
5276 if (*start == NUL)
5277 break;
5278 end = find_word_end(start);
5279
5280 // Extract the word from start to end
5281 save_end = *end;
5282 *end = NUL;
5283
5284 // Perform fuzzy match
glepnirf31cfa22025-03-06 21:59:13 +01005285 *score = fuzzy_match_str(start, pat);
glepnir8159fb12024-07-17 20:32:54 +02005286 *end = save_end;
5287
glepnirf31cfa22025-03-06 21:59:13 +01005288 if (*score > 0)
glepnir8159fb12024-07-17 20:32:54 +02005289 {
5290 *len = (int)(end - start);
glepnir8159fb12024-07-17 20:32:54 +02005291 found = TRUE;
5292 *ptr = start;
glepnirf31cfa22025-03-06 21:59:13 +01005293 if (current_pos)
5294 current_pos->col += (int)(end - strBegin);
glepnir8159fb12024-07-17 20:32:54 +02005295 break;
5296 }
5297
5298 // Move to the end of the current word for the next iteration
5299 str = end;
5300 // Ensure we continue searching after the current word
5301 while (*str != NUL && !vim_iswordp(str))
5302 MB_PTR_ADV(str);
5303 }
5304
glepnirdd42b052025-03-08 16:52:55 +01005305 if (!found)
5306 *ptr = line_end;
5307
glepnir8159fb12024-07-17 20:32:54 +02005308 return found;
5309}
5310
5311/*
5312 * Search for the next fuzzy match in the specified buffer.
5313 * This function attempts to find the next occurrence of the given pattern
5314 * in the buffer, starting from the current position. It handles line wrapping
5315 * and direction of search.
5316 *
5317 * Return TRUE if a match is found, otherwise FALSE.
5318 */
5319 int
5320search_for_fuzzy_match(
5321 buf_T *buf,
5322 pos_T *pos,
5323 char_u *pattern,
5324 int dir,
5325 pos_T *start_pos,
5326 int *len,
5327 char_u **ptr,
glepnirf31cfa22025-03-06 21:59:13 +01005328 int *score)
glepnir8159fb12024-07-17 20:32:54 +02005329{
5330 pos_T current_pos = *pos;
5331 pos_T circly_end;
zeertzjq58d70522024-08-31 17:05:39 +02005332 int found_new_match = FALSE;
glepnir8159fb12024-07-17 20:32:54 +02005333 int looped_around = FALSE;
glepnir53b14572025-03-12 21:28:39 +01005334 int whole_line = ctrl_x_mode_whole_line();
glepnir8159fb12024-07-17 20:32:54 +02005335
glepnir0be03e12024-07-19 16:45:05 +02005336 if (buf == curbuf)
Naruhiko Nishinoc2a90002025-05-04 20:05:47 +02005337 circly_end = *start_pos;
glepnir0be03e12024-07-19 16:45:05 +02005338 else
5339 {
Naruhiko Nishinoc2a90002025-05-04 20:05:47 +02005340 circly_end.lnum = buf->b_ml.ml_line_count;
5341 circly_end.col = 0;
5342 circly_end.coladd = 0;
glepnir0be03e12024-07-19 16:45:05 +02005343 }
5344
glepnir53b14572025-03-12 21:28:39 +01005345 if (whole_line && start_pos->lnum != pos->lnum)
5346 current_pos.lnum += dir;
5347
Hirohito Higashia4a00a72025-05-08 22:58:31 +02005348 do
5349 {
glepnir8159fb12024-07-17 20:32:54 +02005350
5351 // Check if looped around and back to start position
5352 if (looped_around && EQUAL_POS(current_pos, circly_end))
5353 break;
5354
5355 // Ensure current_pos is valid
5356 if (current_pos.lnum >= 1 && current_pos.lnum <= buf->b_ml.ml_line_count)
5357 {
5358 // Get the current line buffer
5359 *ptr = ml_get_buf(buf, current_pos.lnum, FALSE);
glepnir53b14572025-03-12 21:28:39 +01005360 if (!whole_line)
5361 *ptr += current_pos.col;
5362
glepnir8159fb12024-07-17 20:32:54 +02005363 // If ptr is end of line is reached, move to next line
5364 // or previous line based on direction
glepnirf31cfa22025-03-06 21:59:13 +01005365 if (*ptr != NULL && **ptr != NUL)
glepnir8159fb12024-07-17 20:32:54 +02005366 {
5367 if (!whole_line)
5368 {
glepnirf31cfa22025-03-06 21:59:13 +01005369 // Try to find a fuzzy match in the current line starting
5370 // from current position
5371 found_new_match = fuzzy_match_str_in_line(ptr, pattern,
5372 len, &current_pos, score);
glepnir8159fb12024-07-17 20:32:54 +02005373 if (found_new_match)
5374 {
5375 *pos = current_pos;
5376 break;
5377 }
glepnir0be03e12024-07-19 16:45:05 +02005378 else if (looped_around && current_pos.lnum == circly_end.lnum)
5379 break;
glepnir8159fb12024-07-17 20:32:54 +02005380 }
5381 else
5382 {
5383 if (fuzzy_match_str(*ptr, pattern) > 0)
5384 {
5385 found_new_match = TRUE;
5386 *pos = current_pos;
John Marriottb79fa3d2025-02-21 19:59:56 +01005387 *len = (int)ml_get_buf_len(buf, current_pos.lnum);
glepnir8159fb12024-07-17 20:32:54 +02005388 break;
5389 }
5390 }
5391 }
5392 }
5393
5394 // Move to the next line or previous line based on direction
5395 if (dir == FORWARD)
5396 {
5397 if (++current_pos.lnum > buf->b_ml.ml_line_count)
5398 {
5399 if (p_ws)
5400 {
5401 current_pos.lnum = 1;
5402 looped_around = TRUE;
5403 }
5404 else
5405 break;
5406 }
5407 }
5408 else
5409 {
5410 if (--current_pos.lnum < 1)
5411 {
5412 if (p_ws)
5413 {
5414 current_pos.lnum = buf->b_ml.ml_line_count;
5415 looped_around = TRUE;
5416 }
5417 else
5418 break;
5419
5420 }
5421 }
5422 current_pos.col = 0;
5423 } while (TRUE);
5424
5425 return found_new_match;
5426}
5427
5428/*
Bram Moolenaarc6e0a5e2022-04-10 18:09:06 +01005429 * Free an array of fuzzy string matches "fuzmatch[count]".
5430 */
5431 void
5432fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count)
5433{
5434 int i;
5435
5436 if (fuzmatch == NULL)
5437 return;
5438 for (i = 0; i < count; ++i)
5439 vim_free(fuzmatch[i].str);
5440 vim_free(fuzmatch);
5441}
5442
5443/*
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005444 * Copy a list of fuzzy matches into a string list after sorting the matches by
5445 * the fuzzy score. Frees the memory allocated for 'fuzmatch'.
5446 * Returns OK on success and FAIL on memory allocation failure.
5447 */
5448 int
5449fuzzymatches_to_strmatches(
5450 fuzmatch_str_T *fuzmatch,
5451 char_u ***matches,
5452 int count,
5453 int funcsort)
5454{
5455 int i;
5456
5457 if (count <= 0)
5458 return OK;
5459
5460 *matches = ALLOC_MULT(char_u *, count);
5461 if (*matches == NULL)
5462 {
Bram Moolenaarc6e0a5e2022-04-10 18:09:06 +01005463 fuzmatch_str_free(fuzmatch, count);
Yegappan Lakshmanan38b85cb2022-02-24 13:28:41 +00005464 return FAIL;
5465 }
5466
5467 // Sort the list by the descending order of the match score
5468 if (funcsort)
5469 fuzzy_match_func_sort((void *)fuzmatch, (size_t)count);
5470 else
5471 fuzzy_match_str_sort((void *)fuzmatch, (size_t)count);
5472
5473 for (i = 0; i < count; i++)
5474 (*matches)[i] = fuzmatch[i].str;
5475 vim_free(fuzmatch);
5476
5477 return OK;
5478}