vim/src/search.c
John Marriott b79fa3d9c8
patch 9.1.1131: potential out-of-memory issue in search.c
Problem:  potential out-of-memory issue in search.c
Solution: improve situation and refactor search.c slightly
          (John Marriott)

- In function update_search_stat():
  add a check for a theoretical null pointer reference, set and remember
  the length of lastpat, remove the three calls to STRLEN() and use the
  various string's associated lengths instead, add a check for an
  out-of-memory condition.

- In function search_for_fuzz_match():
  remove a call to strnsave() and thus avoid having to add a check for
  an out-of-memory condition, also replace the call to STRLEN() by
  ml_get_buf_len().

closes: #16689

Signed-off-by: John Marriott <basilisk@internode.on.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
2025-02-21 19:59:56 +01:00

5466 lines
135 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* search.c: code for normal mode searching commands
*/
#include "vim.h"
#ifdef FEAT_EVAL
static void set_vv_searchforward(void);
static int first_submatch(regmmatch_T *rp);
#endif
#ifdef FEAT_FIND_ID
static char_u *get_line_and_copy(linenr_T lnum, char_u *buf);
static void show_pat_in_path(char_u *, int, int, int, FILE *, linenr_T *, long);
#endif
typedef struct searchstat
{
int cur; // current position of found words
int cnt; // total count of found words
int exact_match; // TRUE if matched exactly on specified position
int incomplete; // 0: search was fully completed
// 1: recomputing was timed out
// 2: max count exceeded
int last_maxcount; // the max count of the last search
} searchstat_T;
#ifdef FEAT_SEARCH_EXTRA
static void save_incsearch_state(void);
static void restore_incsearch_state(void);
#endif
static int check_prevcol(char_u *linep, int col, int ch, int *prevcol);
static int find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos);
static void find_mps_values(int *initc, int *findc, int *backwards, int switchit);
static int is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction);
static 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);
static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat, int recompute, int maxcount, long timeout);
static int fuzzy_match_compute_score(char_u *fuzpat, char_u *str, int strSz, int_u *matches, int numMatches);
static int fuzzy_match_recursive(char_u *fuzpat, char_u *str, int_u strIdx, int *outScore, char_u *strBegin, int strLen, int_u *srcMatches, int_u *matches, int maxMatches, int nextMatch, int *recursionCount);
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
static int fuzzy_match_item_compare(const void *s1, const void *s2);
static 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);
static void do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos);
#endif
static int fuzzy_match_str_compare(const void *s1, const void *s2);
static void fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz);
static int fuzzy_match_func_compare(const void *s1, const void *s2);
static void fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz);
static int fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos);
#define SEARCH_STAT_DEF_TIMEOUT 40L
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
/*
* This file contains various searching-related routines. These fall into
* three groups:
* 1. string searches (for /, ?, n, and N)
* 2. character searches within a single line (for f, F, t, T, etc)
* 3. "other" kinds of searches like the '%' command, and 'word' searches.
*/
/*
* String searches
*
* The string search functions are divided into two levels:
* lowest: searchit(); uses an pos_T for starting position and found match.
* Highest: do_search(); uses curwin->w_cursor; calls searchit().
*
* The last search pattern is remembered for repeating the same search.
* This pattern is shared between the :g, :s, ? and / commands.
* This is in search_regcomp().
*
* The actual string matching is done using a heavily modified version of
* Henry Spencer's regular expression library. See regexp.c.
*/
/*
* Two search patterns are remembered: One for the :substitute command and
* one for other searches. last_idx points to the one that was used the last
* time.
*/
static spat_T spats[2] =
{
{NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}}, // last used search pat
{NULL, 0, TRUE, FALSE, {'/', 0, 0, 0L}} // last used substitute pat
};
static int last_idx = 0; // index in spats[] for RE_LAST
static char_u lastc[2] = {NUL, NUL}; // last character searched for
static int lastcdir = FORWARD; // last direction of character search
static int last_t_cmd = TRUE; // last search t_cmd
static char_u lastc_bytes[MB_MAXBYTES + 1];
static int lastc_bytelen = 1; // >1 for multi-byte char
// copy of spats[], for keeping the search patterns while executing autocmds
static spat_T saved_spats[ARRAY_LENGTH(spats)];
static char_u *saved_mr_pattern = NULL;
static size_t saved_mr_patternlen = 0;
# ifdef FEAT_SEARCH_EXTRA
static int saved_spats_last_idx = 0;
static int saved_spats_no_hlsearch = 0;
# endif
// allocated copy of pattern used by search_regcomp()
static char_u *mr_pattern = NULL;
static size_t mr_patternlen = 0;
#ifdef FEAT_FIND_ID
/*
* Type used by find_pattern_in_path() to remember which included files have
* been searched already.
*/
typedef struct SearchedFile
{
FILE *fp; // File pointer
char_u *name; // Full name of file
linenr_T lnum; // Line we were up to in file
int matched; // Found a match in this file
} SearchedFile;
#endif
/*
* translate search pattern for vim_regcomp()
*
* pat_save == RE_SEARCH: save pat in spats[RE_SEARCH].pat (normal search cmd)
* pat_save == RE_SUBST: save pat in spats[RE_SUBST].pat (:substitute command)
* pat_save == RE_BOTH: save pat in both patterns (:global command)
* pat_use == RE_SEARCH: use previous search pattern if "pat" is NULL
* pat_use == RE_SUBST: use previous substitute pattern if "pat" is NULL
* pat_use == RE_LAST: use last used pattern if "pat" is NULL
* options & SEARCH_HIS: put search string in history
* options & SEARCH_KEEP: keep previous search pattern
*
* returns FAIL if failed, OK otherwise.
*/
int
search_regcomp(
char_u *pat,
size_t patlen,
char_u **used_pat,
int pat_save,
int pat_use,
int options,
regmmatch_T *regmatch) // return: pattern and ignore-case flag
{
int magic;
rc_did_emsg = FALSE;
magic = magic_isset();
/*
* If no pattern given, use a previously defined pattern.
*/
if (pat == NULL || *pat == NUL)
{
int i;
if (pat_use == RE_LAST)
i = last_idx;
else
i = pat_use;
if (spats[i].pat == NULL) // pattern was never defined
{
if (pat_use == RE_SUBST)
emsg(_(e_no_previous_substitute_regular_expression));
else
emsg(_(e_no_previous_regular_expression));
rc_did_emsg = TRUE;
return FAIL;
}
pat = spats[i].pat;
patlen = spats[i].patlen;
magic = spats[i].magic;
no_smartcase = spats[i].no_scs;
}
else if (options & SEARCH_HIS) // put new pattern in history
add_to_history(HIST_SEARCH, pat, patlen, TRUE, NUL);
if (used_pat)
*used_pat = pat;
vim_free(mr_pattern);
#ifdef FEAT_RIGHTLEFT
if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
mr_pattern = reverse_text(pat);
else
#endif
mr_pattern = vim_strnsave(pat, patlen);
if (mr_pattern == NULL)
mr_patternlen = 0;
else
mr_patternlen = patlen;
/*
* Save the currently used pattern in the appropriate place,
* unless the pattern should not be remembered.
*/
if (!(options & SEARCH_KEEP)
&& (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
{
// search or global command
if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
save_re_pat(RE_SEARCH, pat, patlen, magic);
// substitute or global command
if (pat_save == RE_SUBST || pat_save == RE_BOTH)
save_re_pat(RE_SUBST, pat, patlen, magic);
}
regmatch->rmm_ic = ignorecase(pat);
regmatch->rmm_maxcol = 0;
regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0);
if (regmatch->regprog == NULL)
return FAIL;
return OK;
}
/*
* Get search pattern used by search_regcomp().
*/
char_u *
get_search_pat(void)
{
return mr_pattern;
}
void
save_re_pat(int idx, char_u *pat, size_t patlen, int magic)
{
if (spats[idx].pat == pat)
return;
vim_free(spats[idx].pat);
spats[idx].pat = vim_strnsave(pat, patlen);
if (spats[idx].pat == NULL)
spats[idx].patlen = 0;
else
spats[idx].patlen = patlen;
spats[idx].magic = magic;
spats[idx].no_scs = no_smartcase;
last_idx = idx;
#ifdef FEAT_SEARCH_EXTRA
// If 'hlsearch' set and search pat changed: need redraw.
if (p_hls)
redraw_all_later(UPD_SOME_VALID);
set_no_hlsearch(FALSE);
#endif
}
/*
* Save the search patterns, so they can be restored later.
* Used before/after executing autocommands and user functions.
*/
static int save_level = 0;
void
save_search_patterns(void)
{
int i;
if (save_level++ != 0)
return;
for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
{
saved_spats[i] = spats[i];
if (spats[i].pat != NULL)
{
saved_spats[i].pat = vim_strnsave(spats[i].pat, spats[i].patlen);
if (saved_spats[i].pat == NULL)
saved_spats[i].patlen = 0;
else
saved_spats[i].patlen = spats[i].patlen;
}
}
if (mr_pattern == NULL)
saved_mr_pattern = NULL;
else
saved_mr_pattern = vim_strnsave(mr_pattern, mr_patternlen);
if (saved_mr_pattern == NULL)
saved_mr_patternlen = 0;
else
saved_mr_patternlen = mr_patternlen;
#ifdef FEAT_SEARCH_EXTRA
saved_spats_last_idx = last_idx;
saved_spats_no_hlsearch = no_hlsearch;
#endif
}
void
restore_search_patterns(void)
{
int i;
if (--save_level != 0)
return;
for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
{
vim_free(spats[i].pat);
spats[i] = saved_spats[i];
}
#if defined(FEAT_EVAL)
set_vv_searchforward();
#endif
vim_free(mr_pattern);
mr_pattern = saved_mr_pattern;
mr_patternlen = saved_mr_patternlen;
#ifdef FEAT_SEARCH_EXTRA
last_idx = saved_spats_last_idx;
set_no_hlsearch(saved_spats_no_hlsearch);
#endif
}
#if defined(EXITFREE) || defined(PROTO)
void
free_search_patterns(void)
{
int i;
for (i = 0; i < (int)ARRAY_LENGTH(spats); ++i)
{
VIM_CLEAR(spats[i].pat);
spats[i].patlen = 0;
}
VIM_CLEAR(mr_pattern);
mr_patternlen = 0;
}
#endif
#ifdef FEAT_SEARCH_EXTRA
// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
// searching
static spat_T saved_last_search_spat;
static int did_save_last_search_spat = 0;
static int saved_last_idx = 0;
static int saved_no_hlsearch = 0;
static int saved_search_match_endcol;
static int saved_search_match_lines;
/*
* Save and restore the search pattern for incremental highlight search
* feature.
*
* It's similar to but different from save_search_patterns() and
* restore_search_patterns(), because the search pattern must be restored when
* canceling incremental searching even if it's called inside user functions.
*/
void
save_last_search_pattern(void)
{
if (++did_save_last_search_spat != 1)
// nested call, nothing to do
return;
saved_last_search_spat = spats[RE_SEARCH];
if (spats[RE_SEARCH].pat != NULL)
{
saved_last_search_spat.pat = vim_strnsave(spats[RE_SEARCH].pat, spats[RE_SEARCH].patlen);
if (saved_last_search_spat.pat == NULL)
saved_last_search_spat.patlen = 0;
else
saved_last_search_spat.patlen = spats[RE_SEARCH].patlen;
}
saved_last_idx = last_idx;
saved_no_hlsearch = no_hlsearch;
}
void
restore_last_search_pattern(void)
{
if (--did_save_last_search_spat > 0)
// nested call, nothing to do
return;
if (did_save_last_search_spat != 0)
{
iemsg("restore_last_search_pattern() called more often than save_last_search_pattern()");
return;
}
vim_free(spats[RE_SEARCH].pat);
spats[RE_SEARCH] = saved_last_search_spat;
saved_last_search_spat.pat = NULL;
saved_last_search_spat.patlen = 0;
# if defined(FEAT_EVAL)
set_vv_searchforward();
# endif
last_idx = saved_last_idx;
set_no_hlsearch(saved_no_hlsearch);
}
/*
* Save and restore the incsearch highlighting variables.
* This is required so that calling searchcount() at does not invalidate the
* incsearch highlighting.
*/
static void
save_incsearch_state(void)
{
saved_search_match_endcol = search_match_endcol;
saved_search_match_lines = search_match_lines;
}
static void
restore_incsearch_state(void)
{
search_match_endcol = saved_search_match_endcol;
search_match_lines = saved_search_match_lines;
}
char_u *
last_search_pattern(void)
{
return spats[RE_SEARCH].pat;
}
size_t
last_search_pattern_len(void)
{
return spats[RE_SEARCH].patlen;
}
#endif
/*
* Return TRUE when case should be ignored for search pattern "pat".
* Uses the 'ignorecase' and 'smartcase' options.
*/
int
ignorecase(char_u *pat)
{
return ignorecase_opt(pat, p_ic, p_scs);
}
/*
* As ignorecase() put pass the "ic" and "scs" flags.
*/
int
ignorecase_opt(char_u *pat, int ic_in, int scs)
{
int ic = ic_in;
if (ic && !no_smartcase && scs
&& !(ctrl_x_mode_not_default() && curbuf->b_p_inf))
ic = !pat_has_uppercase(pat);
no_smartcase = FALSE;
return ic;
}
/*
* Return TRUE if pattern "pat" has an uppercase character.
*/
int
pat_has_uppercase(char_u *pat)
{
char_u *p = pat;
magic_T magic_val = MAGIC_ON;
// get the magicness of the pattern
(void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val);
while (*p != NUL)
{
int l;
if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1)
{
if (enc_utf8 && utf_isupper(utf_ptr2char(p)))
return TRUE;
p += l;
}
else if (*p == '\\' && magic_val <= MAGIC_ON)
{
if (p[1] == '_' && p[2] != NUL) // skip "\_X"
p += 3;
else if (p[1] == '%' && p[2] != NUL) // skip "\%X"
p += 3;
else if (p[1] != NUL) // skip "\X"
p += 2;
else
p += 1;
}
else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL)
{
if (p[1] != NUL) // skip "_X" and %X
p += 2;
else
p++;
}
else if (MB_ISUPPER(*p))
return TRUE;
else
++p;
}
return FALSE;
}
#if defined(FEAT_EVAL) || defined(PROTO)
char_u *
last_csearch(void)
{
return lastc_bytes;
}
int
last_csearch_forward(void)
{
return lastcdir == FORWARD;
}
int
last_csearch_until(void)
{
return last_t_cmd == TRUE;
}
void
set_last_csearch(int c, char_u *s, int len)
{
*lastc = c;
lastc_bytelen = len;
if (len)
memcpy(lastc_bytes, s, len);
else
CLEAR_FIELD(lastc_bytes);
}
#endif
void
set_csearch_direction(int cdir)
{
lastcdir = cdir;
}
void
set_csearch_until(int t_cmd)
{
last_t_cmd = t_cmd;
}
char_u *
last_search_pat(void)
{
return spats[last_idx].pat;
}
/*
* Reset search direction to forward. For "gd" and "gD" commands.
*/
void
reset_search_dir(void)
{
spats[0].off.dir = '/';
#if defined(FEAT_EVAL)
set_vv_searchforward();
#endif
}
#if defined(FEAT_EVAL) || defined(FEAT_VIMINFO)
/*
* Set the last search pattern. For ":let @/ =" and viminfo.
* Also set the saved search pattern, so that this works in an autocommand.
*/
void
set_last_search_pat(
char_u *s,
int idx,
int magic,
int setlast)
{
vim_free(spats[idx].pat);
// An empty string means that nothing should be matched.
if (*s == NUL)
spats[idx].pat = NULL;
else
{
spats[idx].patlen = STRLEN(s);
spats[idx].pat = vim_strnsave(s, spats[idx].patlen);
}
if (spats[idx].pat == NULL)
spats[idx].patlen = 0;
spats[idx].magic = magic;
spats[idx].no_scs = FALSE;
spats[idx].off.dir = '/';
#if defined(FEAT_EVAL)
set_vv_searchforward();
#endif
spats[idx].off.line = FALSE;
spats[idx].off.end = FALSE;
spats[idx].off.off = 0;
if (setlast)
last_idx = idx;
if (save_level)
{
vim_free(saved_spats[idx].pat);
saved_spats[idx] = spats[0];
if (spats[idx].pat == NULL)
saved_spats[idx].pat = NULL;
else
saved_spats[idx].pat = vim_strnsave(spats[idx].pat, spats[idx].patlen);
if (saved_spats[idx].pat == NULL)
saved_spats[idx].patlen = 0;
else
saved_spats[idx].patlen = spats[idx].patlen;
# ifdef FEAT_SEARCH_EXTRA
saved_spats_last_idx = last_idx;
# endif
}
# ifdef FEAT_SEARCH_EXTRA
// If 'hlsearch' set and search pat changed: need redraw.
if (p_hls && idx == last_idx && !no_hlsearch)
redraw_all_later(UPD_SOME_VALID);
# endif
}
#endif
#ifdef FEAT_SEARCH_EXTRA
/*
* Get a regexp program for the last used search pattern.
* This is used for highlighting all matches in a window.
* Values returned in regmatch->regprog and regmatch->rmm_ic.
*/
void
last_pat_prog(regmmatch_T *regmatch)
{
if (spats[last_idx].pat == NULL)
{
regmatch->regprog = NULL;
return;
}
++emsg_off; // So it doesn't beep if bad expr
(void)search_regcomp((char_u *)"", 0, NULL, 0, last_idx, SEARCH_KEEP, regmatch);
--emsg_off;
}
#endif
/*
* Lowest level search function.
* Search for 'count'th occurrence of pattern "pat" in direction "dir".
* Start at position "pos" and return the found position in "pos".
*
* if (options & SEARCH_MSG) == 0 don't give any messages
* if (options & SEARCH_MSG) == SEARCH_NFMSG don't give 'notfound' messages
* if (options & SEARCH_MSG) == SEARCH_MSG give all messages
* if (options & SEARCH_HIS) put search pattern in history
* if (options & SEARCH_END) return position at end of match
* if (options & SEARCH_START) accept match at pos itself
* if (options & SEARCH_KEEP) keep previous search pattern
* if (options & SEARCH_FOLD) match only once in a closed fold
* if (options & SEARCH_PEEK) check for typed char, cancel search
* if (options & SEARCH_COL) start at pos->col instead of zero
*
* Return FAIL (zero) for failure, non-zero for success.
* When FEAT_EVAL is defined, returns the index of the first matching
* subpattern plus one; one if there was none.
*/
int
searchit(
win_T *win, // window to search in; can be NULL for a
// buffer without a window!
buf_T *buf,
pos_T *pos,
pos_T *end_pos, // set to end of the match, unless NULL
int dir,
char_u *pat,
size_t patlen,
long count,
int options,
int pat_use, // which pattern to use when "pat" is empty
searchit_arg_T *extra_arg) // optional extra arguments, can be NULL
{
int found;
linenr_T lnum; // no init to shut up Apollo cc
colnr_T col;
regmmatch_T regmatch;
char_u *ptr;
colnr_T matchcol;
lpos_T endpos;
lpos_T matchpos;
int loop;
pos_T start_pos;
int at_first_line;
int extra_col;
int start_char_len;
int match_ok;
long nmatched;
int submatch = 0;
int first_match = TRUE;
int called_emsg_before = called_emsg;
#ifdef FEAT_SEARCH_EXTRA
int break_loop = FALSE;
#endif
linenr_T stop_lnum = 0; // stop after this line number when != 0
int unused_timeout_flag = FALSE;
int *timed_out = &unused_timeout_flag; // set when timed out.
int search_from_match_end; // vi-compatible search?
if (search_regcomp(pat, patlen, NULL, RE_SEARCH, pat_use,
(options & (SEARCH_HIS + SEARCH_KEEP)), &regmatch) == FAIL)
{
if ((options & SEARCH_MSG) && !rc_did_emsg)
semsg(_(e_invalid_search_string_str), mr_pattern);
return FAIL;
}
search_from_match_end = vim_strchr(p_cpo, CPO_SEARCH) != NULL;
if (extra_arg != NULL)
{
stop_lnum = extra_arg->sa_stop_lnum;
#ifdef FEAT_RELTIME
if (extra_arg->sa_tm > 0)
init_regexp_timeout(extra_arg->sa_tm);
// Also set the pointer when sa_tm is zero, the caller may have set the
// timeout.
timed_out = &extra_arg->sa_timed_out;
#endif
}
/*
* find the string
*/
do // loop for count
{
// When not accepting a match at the start position set "extra_col" to
// a non-zero value. Don't do that when starting at MAXCOL, since
// MAXCOL + 1 is zero.
if (pos->col == MAXCOL)
start_char_len = 0;
// Watch out for the "col" being MAXCOL - 2, used in a closed fold.
else if (has_mbyte
&& pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count
&& pos->col < MAXCOL - 2)
{
ptr = ml_get_buf(buf, pos->lnum, FALSE);
if (ml_get_buf_len(buf, pos->lnum) <= pos->col)
start_char_len = 1;
else
start_char_len = (*mb_ptr2len)(ptr + pos->col);
}
else
start_char_len = 1;
if (dir == FORWARD)
{
if (options & SEARCH_START)
extra_col = 0;
else
extra_col = start_char_len;
}
else
{
if (options & SEARCH_START)
extra_col = start_char_len;
else
extra_col = 0;
}
start_pos = *pos; // remember start pos for detecting no match
found = 0; // default: not found
at_first_line = TRUE; // default: start in first line
if (pos->lnum == 0) // correct lnum for when starting in line 0
{
pos->lnum = 1;
pos->col = 0;
at_first_line = FALSE; // not in first line now
}
/*
* Start searching in current line, unless searching backwards and
* we're in column 0.
* If we are searching backwards, in column 0, and not including the
* current position, gain some efficiency by skipping back a line.
* Otherwise begin the search in the current line.
*/
if (dir == BACKWARD && start_pos.col == 0
&& (options & SEARCH_START) == 0)
{
lnum = pos->lnum - 1;
at_first_line = FALSE;
}
else
lnum = pos->lnum;
for (loop = 0; loop <= 1; ++loop) // loop twice if 'wrapscan' set
{
for ( ; lnum > 0 && lnum <= buf->b_ml.ml_line_count;
lnum += dir, at_first_line = FALSE)
{
// Stop after checking "stop_lnum", if it's set.
if (stop_lnum != 0 && (dir == FORWARD
? lnum > stop_lnum : lnum < stop_lnum))
break;
// Stop after passing the time limit.
if (*timed_out)
break;
/*
* Look for a match somewhere in line "lnum".
*/
col = at_first_line && (options & SEARCH_COL) ? pos->col
: (colnr_T)0;
nmatched = vim_regexec_multi(&regmatch, win, buf,
lnum, col, timed_out);
// vim_regexec_multi() may clear "regprog"
if (regmatch.regprog == NULL)
break;
// Abort searching on an error (e.g., out of stack).
if (called_emsg > called_emsg_before || *timed_out)
break;
if (nmatched > 0)
{
// match may actually be in another line when using \zs
matchpos = regmatch.startpos[0];
endpos = regmatch.endpos[0];
#ifdef FEAT_EVAL
submatch = first_submatch(&regmatch);
#endif
// "lnum" may be past end of buffer for "\n\zs".
if (lnum + matchpos.lnum > buf->b_ml.ml_line_count)
ptr = (char_u *)"";
else
ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
/*
* Forward search in the first line: match should be after
* the start position. If not, continue at the end of the
* match (this is vi compatible) or on the next char.
*/
if (dir == FORWARD && at_first_line)
{
match_ok = TRUE;
/*
* When the match starts in a next line it's certainly
* past the start position.
* When match lands on a NUL the cursor will be put
* one back afterwards, compare with that position,
* otherwise "/$" will get stuck on end of line.
*/
while (matchpos.lnum == 0
&& ((options & SEARCH_END) && first_match
? (nmatched == 1
&& (int)endpos.col - 1
< (int)start_pos.col + extra_col)
: ((int)matchpos.col
- (ptr[matchpos.col] == NUL)
< (int)start_pos.col + extra_col)))
{
/*
* If vi-compatible searching, continue at the end
* of the match, otherwise continue one position
* forward.
*/
if (search_from_match_end)
{
if (nmatched > 1)
{
// end is in next line, thus no match in
// this line
match_ok = FALSE;
break;
}
matchcol = endpos.col;
// for empty match: advance one char
if (matchcol == matchpos.col
&& ptr[matchcol] != NUL)
{
if (has_mbyte)
matchcol +=
(*mb_ptr2len)(ptr + matchcol);
else
++matchcol;
}
}
else
{
// Advance "matchcol" to the next character.
// This uses rmm_matchcol, the actual start of
// the match, ignoring "\zs".
matchcol = regmatch.rmm_matchcol;
if (ptr[matchcol] != NUL)
{
if (has_mbyte)
matchcol += (*mb_ptr2len)(ptr
+ matchcol);
else
++matchcol;
}
}
if (matchcol == 0 && (options & SEARCH_START))
break;
if (ptr[matchcol] == NUL
|| (nmatched = vim_regexec_multi(&regmatch,
win, buf, lnum + matchpos.lnum,
matchcol, timed_out)) == 0)
{
match_ok = FALSE;
break;
}
// vim_regexec_multi() may clear "regprog"
if (regmatch.regprog == NULL)
break;
matchpos = regmatch.startpos[0];
endpos = regmatch.endpos[0];
# ifdef FEAT_EVAL
submatch = first_submatch(&regmatch);
# endif
// Need to get the line pointer again, a
// multi-line search may have made it invalid.
ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
}
if (!match_ok)
continue;
}
if (dir == BACKWARD)
{
/*
* Now, if there are multiple matches on this line,
* we have to get the last one. Or the last one before
* the cursor, if we're on that line.
* When putting the new cursor at the end, compare
* relative to the end of the match.
*/
match_ok = FALSE;
for (;;)
{
// Remember a position that is before the start
// position, we use it if it's the last match in
// the line. Always accept a position after
// wrapping around.
if (loop
|| ((options & SEARCH_END)
? (lnum + regmatch.endpos[0].lnum
< start_pos.lnum
|| (lnum + regmatch.endpos[0].lnum
== start_pos.lnum
&& (int)regmatch.endpos[0].col - 1
< (int)start_pos.col
+ extra_col))
: (lnum + regmatch.startpos[0].lnum
< start_pos.lnum
|| (lnum + regmatch.startpos[0].lnum
== start_pos.lnum
&& (int)regmatch.startpos[0].col
< (int)start_pos.col
+ extra_col))))
{
match_ok = TRUE;
matchpos = regmatch.startpos[0];
endpos = regmatch.endpos[0];
# ifdef FEAT_EVAL
submatch = first_submatch(&regmatch);
# endif
}
else
break;
/*
* We found a valid match, now check if there is
* another one after it.
* If vi-compatible searching, continue at the end
* of the match, otherwise continue one position
* forward.
*/
if (search_from_match_end)
{
if (nmatched > 1)
break;
matchcol = endpos.col;
// for empty match: advance one char
if (matchcol == matchpos.col
&& ptr[matchcol] != NUL)
{
if (has_mbyte)
matchcol +=
(*mb_ptr2len)(ptr + matchcol);
else
++matchcol;
}
}
else
{
// Stop when the match is in a next line.
if (matchpos.lnum > 0)
break;
matchcol = matchpos.col;
if (ptr[matchcol] != NUL)
{
if (has_mbyte)
matchcol +=
(*mb_ptr2len)(ptr + matchcol);
else
++matchcol;
}
}
if (ptr[matchcol] == NUL
|| (nmatched = vim_regexec_multi(&regmatch,
win, buf, lnum + matchpos.lnum,
matchcol, timed_out)) == 0)
{
// If the search timed out, we did find a match
// but it might be the wrong one, so that's not
// OK.
if (*timed_out)
match_ok = FALSE;
break;
}
// vim_regexec_multi() may clear "regprog"
if (regmatch.regprog == NULL)
break;
// Need to get the line pointer again, a
// multi-line search may have made it invalid.
ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
}
/*
* If there is only a match after the cursor, skip
* this match.
*/
if (!match_ok)
continue;
}
// With the SEARCH_END option move to the last character
// of the match. Don't do it for an empty match, end
// should be same as start then.
if ((options & SEARCH_END) && !(options & SEARCH_NOOF)
&& !(matchpos.lnum == endpos.lnum
&& matchpos.col == endpos.col))
{
// For a match in the first column, set the position
// on the NUL in the previous line.
pos->lnum = lnum + endpos.lnum;
pos->col = endpos.col;
if (endpos.col == 0)
{
if (pos->lnum > 1) // just in case
{
--pos->lnum;
pos->col = ml_get_buf_len(buf, pos->lnum);
}
}
else
{
--pos->col;
if (has_mbyte
&& pos->lnum <= buf->b_ml.ml_line_count)
{
ptr = ml_get_buf(buf, pos->lnum, FALSE);
pos->col -= (*mb_head_off)(ptr, ptr + pos->col);
}
}
if (end_pos != NULL)
{
end_pos->lnum = lnum + matchpos.lnum;
end_pos->col = matchpos.col;
}
}
else
{
pos->lnum = lnum + matchpos.lnum;
pos->col = matchpos.col;
if (end_pos != NULL)
{
end_pos->lnum = lnum + endpos.lnum;
end_pos->col = endpos.col;
}
}
pos->coladd = 0;
if (end_pos != NULL)
end_pos->coladd = 0;
found = 1;
first_match = FALSE;
// Set variables used for 'incsearch' highlighting.
search_match_lines = endpos.lnum - matchpos.lnum;
search_match_endcol = endpos.col;
break;
}
line_breakcheck(); // stop if ctrl-C typed
if (got_int)
break;
#ifdef FEAT_SEARCH_EXTRA
// Cancel searching if a character was typed. Used for
// 'incsearch'. Don't check too often, that would slowdown
// searching too much.
if ((options & SEARCH_PEEK)
&& ((lnum - pos->lnum) & 0x3f) == 0
&& char_avail())
{
break_loop = TRUE;
break;
}
#endif
if (loop && lnum == start_pos.lnum)
break; // if second loop, stop where started
}
at_first_line = FALSE;
// vim_regexec_multi() may clear "regprog"
if (regmatch.regprog == NULL)
break;
/*
* Stop the search if wrapscan isn't set, "stop_lnum" is
* specified, after an interrupt, after a match and after looping
* twice.
*/
if (!p_ws || stop_lnum != 0 || got_int
|| called_emsg > called_emsg_before || *timed_out
#ifdef FEAT_SEARCH_EXTRA
|| break_loop
#endif
|| found || loop)
break;
/*
* If 'wrapscan' is set we continue at the other end of the file.
* If 'shortmess' does not contain 's', we give a message, but
* only, if we won't show the search stat later anyhow,
* (so SEARCH_COUNT must be absent).
* This message is also remembered in keep_msg for when the screen
* is redrawn. The keep_msg is cleared whenever another message is
* written.
*/
if (dir == BACKWARD) // start second loop at the other end
lnum = buf->b_ml.ml_line_count;
else
lnum = 1;
if (!shortmess(SHM_SEARCH)
&& shortmess(SHM_SEARCHCOUNT)
&& (options & SEARCH_MSG))
give_warning((char_u *)_(dir == BACKWARD
? top_bot_msg : bot_top_msg), TRUE);
if (extra_arg != NULL)
extra_arg->sa_wrapped = TRUE;
}
if (got_int || called_emsg > called_emsg_before || *timed_out
#ifdef FEAT_SEARCH_EXTRA
|| break_loop
#endif
)
break;
}
while (--count > 0 && found); // stop after count matches or no match
#ifdef FEAT_RELTIME
if (extra_arg != NULL && extra_arg->sa_tm > 0)
disable_regexp_timeout();
#endif
vim_regfree(regmatch.regprog);
if (!found) // did not find it
{
if (got_int)
emsg(_(e_interrupted));
else if ((options & SEARCH_MSG) == SEARCH_MSG)
{
if (p_ws)
semsg(_(e_pattern_not_found_str), mr_pattern);
else if (lnum == 0)
semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
else
semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
}
return FAIL;
}
// A pattern like "\n\zs" may go past the last line.
if (pos->lnum > buf->b_ml.ml_line_count)
{
pos->lnum = buf->b_ml.ml_line_count;
pos->col = ml_get_buf_len(buf, pos->lnum);
if (pos->col > 0)
--pos->col;
}
return submatch + 1;
}
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
void
set_search_direction(int cdir)
{
spats[0].off.dir = cdir;
}
static void
set_vv_searchforward(void)
{
set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
}
/*
* Return the number of the first subpat that matched.
* Return zero if none of them matched.
*/
static int
first_submatch(regmmatch_T *rp)
{
int submatch;
for (submatch = 1; ; ++submatch)
{
if (rp->startpos[submatch].lnum >= 0)
break;
if (submatch == 9)
{
submatch = 0;
break;
}
}
return submatch;
}
#endif
/*
* Highest level string search function.
* Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
* If 'dirc' is 0: use previous dir.
* If 'pat' is NULL or empty : use previous string.
* If 'options & SEARCH_REV' : go in reverse of previous dir.
* If 'options & SEARCH_ECHO': echo the search command and handle options
* If 'options & SEARCH_MSG' : may give error message
* If 'options & SEARCH_OPT' : interpret optional flags
* If 'options & SEARCH_HIS' : put search pattern in history
* If 'options & SEARCH_NOOF': don't add offset to position
* If 'options & SEARCH_MARK': set previous context mark
* If 'options & SEARCH_KEEP': keep previous search pattern
* If 'options & SEARCH_START': accept match at curpos itself
* If 'options & SEARCH_PEEK': check for typed char, cancel search
*
* Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this
* makes the movement linewise without moving the match position.
*
* Return 0 for failure, 1 for found, 2 for found and line offset added.
*/
int
do_search(
oparg_T *oap, // can be NULL
int dirc, // '/' or '?'
int search_delim, // the delimiter for the search, e.g. '%' in
// s%regex%replacement%
char_u *pat,
size_t patlen,
long count,
int options,
searchit_arg_T *sia) // optional arguments or NULL
{
pos_T pos; // position of the last match
char_u *searchstr;
size_t searchstrlen;
soffset_T old_off;
int retval; // Return value
char_u *p;
long c;
char_u *dircp;
char_u *strcopy = NULL;
char_u *ps;
int show_search_stats;
char_u *msgbuf = NULL;
size_t msgbuflen = 0;
int has_offset = FALSE;
searchcmdlen = 0;
/*
* A line offset is not remembered, this is vi compatible.
*/
if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
{
spats[0].off.line = FALSE;
spats[0].off.off = 0;
}
/*
* Save the values for when (options & SEARCH_KEEP) is used.
* (there is no "if ()" around this because gcc wants them initialized)
*/
old_off = spats[0].off;
pos = curwin->w_cursor; // start searching at the cursor position
/*
* Find out the direction of the search.
*/
if (dirc == 0)
dirc = spats[0].off.dir;
else
{
spats[0].off.dir = dirc;
#if defined(FEAT_EVAL)
set_vv_searchforward();
#endif
}
if (options & SEARCH_REV)
{
#ifdef MSWIN
// There is a bug in the Visual C++ 2.2 compiler which means that
// dirc always ends up being '/'
dirc = (dirc == '/') ? '?' : '/';
#else
if (dirc == '/')
dirc = '?';
else
dirc = '/';
#endif
}
#ifdef FEAT_FOLDING
// If the cursor is in a closed fold, don't find another match in the same
// fold.
if (dirc == '/')
{
if (hasFolding(pos.lnum, NULL, &pos.lnum))
pos.col = MAXCOL - 2; // avoid overflow when adding 1
}
else
{
if (hasFolding(pos.lnum, &pos.lnum, NULL))
pos.col = 0;
}
#endif
#ifdef FEAT_SEARCH_EXTRA
/*
* Turn 'hlsearch' highlighting back on.
*/
if (no_hlsearch && !(options & SEARCH_KEEP))
{
redraw_all_later(UPD_SOME_VALID);
set_no_hlsearch(FALSE);
}
#endif
/*
* Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
*/
for (;;)
{
int show_top_bot_msg = FALSE;
searchstr = pat;
searchstrlen = patlen;
dircp = NULL;
// use previous pattern
if (pat == NULL || *pat == NUL || *pat == search_delim)
{
if (spats[RE_SEARCH].pat == NULL) // no previous pattern
{
if (spats[RE_SUBST].pat == NULL)
{
emsg(_(e_no_previous_regular_expression));
retval = 0;
goto end_do_search;
}
searchstr = spats[RE_SUBST].pat;
searchstrlen = spats[RE_SUBST].patlen;
}
else
{
// make search_regcomp() use spats[RE_SEARCH].pat
searchstr = (char_u *)"";
searchstrlen = 0;
}
}
if (pat != NULL && *pat != NUL) // look for (new) offset
{
/*
* Find end of regular expression.
* If there is a matching '/' or '?', toss it.
*/
ps = strcopy;
p = skip_regexp_ex(pat, search_delim, magic_isset(),
&strcopy, NULL, NULL);
if (strcopy != ps)
{
size_t len = STRLEN(strcopy);
// made a copy of "pat" to change "\?" to "?"
searchcmdlen += (int)(patlen - len);
pat = strcopy;
patlen = len;
searchstr = strcopy;
searchstrlen = len;
}
if (*p == search_delim)
{
searchstrlen = p - pat;
dircp = p; // remember where we put the NUL
*p++ = NUL;
}
spats[0].off.line = FALSE;
spats[0].off.end = FALSE;
spats[0].off.off = 0;
/*
* Check for a line offset or a character offset.
* For get_address (echo off) we don't check for a character
* offset, because it is meaningless and the 's' could be a
* substitute command.
*/
if (*p == '+' || *p == '-' || VIM_ISDIGIT(*p))
spats[0].off.line = TRUE;
else if ((options & SEARCH_OPT)
&& (*p == 'e' || *p == 's' || *p == 'b'))
{
if (*p == 'e') // end
spats[0].off.end = SEARCH_END;
++p;
}
if (VIM_ISDIGIT(*p) || *p == '+' || *p == '-') // got an offset
{
// 'nr' or '+nr' or '-nr'
if (VIM_ISDIGIT(*p) || VIM_ISDIGIT(*(p + 1)))
spats[0].off.off = atol((char *)p);
else if (*p == '-') // single '-'
spats[0].off.off = -1;
else // single '+'
spats[0].off.off = 1;
++p;
while (VIM_ISDIGIT(*p)) // skip number
++p;
}
// compute length of search command for get_address()
searchcmdlen += (int)(p - pat);
patlen -= p - pat;
pat = p; // put pat after search command
}
show_search_stats = FALSE;
if ((options & SEARCH_ECHO) && messaging()
&& !msg_silent
&& (!cmd_silent || !shortmess(SHM_SEARCHCOUNT)))
{
char_u off_buf[40];
size_t off_len = 0;
size_t plen;
size_t msgbufsize;
// Compute msg_row early.
msg_start();
// Get the offset, so we know how long it is.
if (!cmd_silent &&
(spats[0].off.line || spats[0].off.end || spats[0].off.off))
{
off_buf[off_len++] = dirc;
if (spats[0].off.end)
off_buf[off_len++] = 'e';
else if (!spats[0].off.line)
off_buf[off_len++] = 's';
if (spats[0].off.off > 0 || spats[0].off.line)
off_buf[off_len++] = '+';
off_buf[off_len] = NUL;
if (spats[0].off.off != 0 || spats[0].off.line)
off_len += vim_snprintf((char *)off_buf + off_len, sizeof(off_buf) - off_len, "%ld", spats[0].off.off);
}
if (*searchstr == NUL)
{
p = spats[0].pat;
plen = spats[0].patlen;
}
else
{
p = searchstr;
plen = searchstrlen;
}
if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent)
{
// Reserve enough space for the search pattern + offset +
// search stat. Use all the space available, so that the
// search state is right aligned. If there is not enough space
// msg_strtrunc() will shorten in the middle.
if (msg_scrolled != 0 && !cmd_silent)
// Use all the columns.
msgbufsize = (int)(Rows - msg_row) * Columns - 1;
else
// Use up to 'showcmd' column.
msgbufsize = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
if (msgbufsize < plen + off_len + SEARCH_STAT_BUF_LEN + 3)
msgbufsize = plen + off_len + SEARCH_STAT_BUF_LEN + 3;
}
else
// Reserve enough space for the search pattern + offset.
msgbufsize = plen + off_len + 3;
vim_free(msgbuf);
msgbuf = alloc(msgbufsize);
if (msgbuf == NULL)
{
msgbuflen = 0;
}
else
{
vim_memset(msgbuf, ' ', msgbufsize);
msgbuflen = msgbufsize - 1;
msgbuf[msgbuflen] = NUL;
// do not fill the msgbuf buffer, if cmd_silent is set, leave it
// empty for the search_stat feature.
if (!cmd_silent)
{
char_u *trunc;
msgbuf[0] = dirc;
if (enc_utf8 && utf_iscomposing(utf_ptr2char(p)))
{
// Use a space to draw the composing char on.
msgbuf[1] = ' ';
mch_memmove(msgbuf + 2, p, plen);
}
else
mch_memmove(msgbuf + 1, p, plen);
if (off_len > 0)
mch_memmove(msgbuf + plen + 1, off_buf, off_len);
trunc = msg_strtrunc(msgbuf, TRUE);
if (trunc != NULL)
{
vim_free(msgbuf);
msgbuf = trunc;
msgbuflen = STRLEN(msgbuf);
}
#ifdef FEAT_RIGHTLEFT
// The search pattern could be shown on the right in
// rightleft mode, but the 'ruler' and 'showcmd' area use
// it too, thus it would be blanked out again very soon.
// Show it on the left, but do reverse the text.
if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
{
char_u *r;
size_t pat_len;
r = reverse_text(msgbuf);
if (r != NULL)
{
vim_free(msgbuf);
msgbuf = r;
msgbuflen = STRLEN(msgbuf);
// move reversed text to beginning of buffer
while (*r != NUL && *r == ' ')
r++;
pat_len = msgbuf + msgbuflen - r;
mch_memmove(msgbuf, r, pat_len);
// overwrite old text
if ((size_t)(r - msgbuf) >= pat_len)
vim_memset(r, ' ', pat_len);
else
vim_memset(msgbuf + pat_len, ' ', r - msgbuf);
}
}
#endif
msg_outtrans(msgbuf);
msg_clr_eos();
msg_check();
gotocmdline(FALSE);
out_flush();
msg_nowait = TRUE; // don't wait for this message
}
if (!shortmess(SHM_SEARCHCOUNT))
show_search_stats = TRUE;
} // msgbuf != NULL
}
/*
* If there is a character offset, subtract it from the current
* position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
* Skip this if pos.col is near MAXCOL (closed fold).
* This is not done for a line offset, because then we would not be vi
* compatible.
*/
if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2)
{
if (spats[0].off.off > 0)
{
for (c = spats[0].off.off; c; --c)
if (decl(&pos) == -1)
break;
if (c) // at start of buffer
{
pos.lnum = 0; // allow lnum == 0 here
pos.col = MAXCOL;
}
}
else
{
for (c = spats[0].off.off; c; ++c)
if (incl(&pos) == -1)
break;
if (c) // at end of buffer
{
pos.lnum = curbuf->b_ml.ml_line_count + 1;
pos.col = 0;
}
}
}
/*
* The actual search.
*/
c = searchit(curwin, curbuf, &pos, NULL,
dirc == '/' ? FORWARD : BACKWARD,
searchstr, searchstrlen, count, spats[0].off.end + (options &
(SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS
+ SEARCH_MSG + SEARCH_START
+ ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF))),
RE_LAST, sia);
if (dircp != NULL)
*dircp = search_delim; // restore second '/' or '?' for normal_cmd()
if (!shortmess(SHM_SEARCH)
&& ((dirc == '/' && LT_POS(pos, curwin->w_cursor))
|| (dirc == '?' && LT_POS(curwin->w_cursor, pos))))
show_top_bot_msg = TRUE;
if (c == FAIL)
{
retval = 0;
goto end_do_search;
}
if (spats[0].off.end && oap != NULL)
oap->inclusive = TRUE; // 'e' includes last character
retval = 1; // pattern found
/*
* Add character and/or line offset
*/
if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';'))
{
pos_T org_pos = pos;
if (spats[0].off.line) // Add the offset to the line number.
{
c = pos.lnum + spats[0].off.off;
if (c < 1)
pos.lnum = 1;
else if (c > curbuf->b_ml.ml_line_count)
pos.lnum = curbuf->b_ml.ml_line_count;
else
pos.lnum = c;
pos.col = 0;
retval = 2; // pattern found, line offset added
}
else if (pos.col < MAXCOL - 2) // just in case
{
// to the right, check for end of file
c = spats[0].off.off;
if (c > 0)
{
while (c-- > 0)
if (incl(&pos) == -1)
break;
}
// to the left, check for start of file
else
{
while (c++ < 0)
if (decl(&pos) == -1)
break;
}
}
if (!EQUAL_POS(pos, org_pos))
has_offset = TRUE;
}
// Show [1/15] if 'S' is not in 'shortmess'.
if (show_search_stats)
cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
show_top_bot_msg, msgbuf, msgbuflen,
(count != 1 || has_offset
#ifdef FEAT_FOLDING
|| (!(fdo_flags & FDO_SEARCH)
&& hasFolding(curwin->w_cursor.lnum,
NULL, NULL))
#endif
),
SEARCH_STAT_DEF_MAX_COUNT,
SEARCH_STAT_DEF_TIMEOUT);
/*
* The search command can be followed by a ';' to do another search.
* For example: "/pat/;/foo/+3;?bar"
* This is like doing another search command, except:
* - The remembered direction '/' or '?' is from the first search.
* - When an error happens the cursor isn't moved at all.
* Don't do this when called by get_address() (it handles ';' itself).
*/
if (!(options & SEARCH_OPT) || pat == NULL || *pat != ';')
break;
dirc = *++pat;
search_delim = dirc;
if (dirc != '?' && dirc != '/')
{
retval = 0;
emsg(_(e_expected_question_or_slash_after_semicolon));
goto end_do_search;
}
++pat;
--patlen;
}
if (options & SEARCH_MARK)
setpcmark();
curwin->w_cursor = pos;
curwin->w_set_curswant = TRUE;
end_do_search:
if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS))
spats[0].off = old_off;
vim_free(strcopy);
vim_free(msgbuf);
return retval;
}
/*
* search_for_exact_line(buf, pos, dir, pat)
*
* Search for a line starting with the given pattern (ignoring leading
* white-space), starting from pos and going in direction "dir". "pos" will
* contain the position of the match found. Blank lines match only if
* ADDING is set. If p_ic is set then the pattern must be in lowercase.
* Return OK for success, or FAIL if no line found.
*/
int
search_for_exact_line(
buf_T *buf,
pos_T *pos,
int dir,
char_u *pat)
{
linenr_T start = 0;
char_u *ptr;
char_u *p;
if (buf->b_ml.ml_line_count == 0)
return FAIL;
for (;;)
{
pos->lnum += dir;
if (pos->lnum < 1)
{
if (p_ws)
{
pos->lnum = buf->b_ml.ml_line_count;
if (!shortmess(SHM_SEARCH))
give_warning((char_u *)_(top_bot_msg), TRUE);
}
else
{
pos->lnum = 1;
break;
}
}
else if (pos->lnum > buf->b_ml.ml_line_count)
{
if (p_ws)
{
pos->lnum = 1;
if (!shortmess(SHM_SEARCH))
give_warning((char_u *)_(bot_top_msg), TRUE);
}
else
{
pos->lnum = 1;
break;
}
}
if (pos->lnum == start)
break;
if (start == 0)
start = pos->lnum;
ptr = ml_get_buf(buf, pos->lnum, FALSE);
p = skipwhite(ptr);
pos->col = (colnr_T) (p - ptr);
// when adding lines the matching line may be empty but it is not
// ignored because we are interested in the next line -- Acevedo
if (compl_status_adding() && !compl_status_sol())
{
if ((p_ic ? MB_STRICMP(p, pat) : STRCMP(p, pat)) == 0)
return OK;
}
else if (*p != NUL) // ignore empty lines
{ // expanding lines or words
if ((p_ic ? MB_STRNICMP(p, pat, ins_compl_len())
: STRNCMP(p, pat, ins_compl_len())) == 0)
return OK;
}
}
return FAIL;
}
/*
* Character Searches
*/
/*
* Search for a character in a line. If "t_cmd" is FALSE, move to the
* position of the character, otherwise move to just before the char.
* Do this "cap->count1" times.
* Return FAIL or OK.
*/
int
searchc(cmdarg_T *cap, int t_cmd)
{
int c = cap->nchar; // char to search for
int dir = cap->arg; // TRUE for searching forward
long count = cap->count1; // repeat count
int col;
char_u *p;
int len;
int stop = TRUE;
if (c != NUL) // normal search: remember args for repeat
{
if (!KeyStuffed) // don't remember when redoing
{
*lastc = c;
set_csearch_direction(dir);
set_csearch_until(t_cmd);
lastc_bytelen = (*mb_char2bytes)(c, lastc_bytes);
if (cap->ncharC1 != 0)
{
lastc_bytelen += (*mb_char2bytes)(cap->ncharC1,
lastc_bytes + lastc_bytelen);
if (cap->ncharC2 != 0)
lastc_bytelen += (*mb_char2bytes)(cap->ncharC2,
lastc_bytes + lastc_bytelen);
}
}
}
else // repeat previous search
{
if (*lastc == NUL && lastc_bytelen <= 1)
return FAIL;
if (dir) // repeat in opposite direction
dir = -lastcdir;
else
dir = lastcdir;
t_cmd = last_t_cmd;
c = *lastc;
// For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
// Force a move of at least one char, so ";" and "," will move the
// cursor, even if the cursor is right in front of char we are looking
// at.
if (vim_strchr(p_cpo, CPO_SCOLON) == NULL && count == 1 && t_cmd)
stop = FALSE;
}
if (dir == BACKWARD)
cap->oap->inclusive = FALSE;
else
cap->oap->inclusive = TRUE;
p = ml_get_curline();
col = curwin->w_cursor.col;
len = ml_get_curline_len();
while (count--)
{
if (has_mbyte)
{
for (;;)
{
if (dir > 0)
{
col += (*mb_ptr2len)(p + col);
if (col >= len)
return FAIL;
}
else
{
if (col == 0)
return FAIL;
col -= (*mb_head_off)(p, p + col - 1) + 1;
}
if (lastc_bytelen <= 1)
{
if (p[col] == c && stop)
break;
}
else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0
&& stop)
break;
stop = TRUE;
}
}
else
{
for (;;)
{
if ((col += dir) < 0 || col >= len)
return FAIL;
if (p[col] == c && stop)
break;
stop = TRUE;
}
}
}
if (t_cmd)
{
// backup to before the character (possibly double-byte)
col -= dir;
if (has_mbyte)
{
if (dir < 0)
// Landed on the search char which is lastc_bytelen long
col += lastc_bytelen - 1;
else
// To previous char, which may be multi-byte.
col -= (*mb_head_off)(p, p + col);
}
}
curwin->w_cursor.col = col;
return OK;
}
/*
* "Other" Searches
*/
/*
* findmatch - find the matching paren or brace
*
* Improvement over vi: Braces inside quotes are ignored.
*/
pos_T *
findmatch(oparg_T *oap, int initc)
{
return findmatchlimit(oap, initc, 0, 0);
}
/*
* Return TRUE if the character before "linep[col]" equals "ch".
* Return FALSE if "col" is zero.
* Update "*prevcol" to the column of the previous character, unless "prevcol"
* is NULL.
* Handles multibyte string correctly.
*/
static int
check_prevcol(
char_u *linep,
int col,
int ch,
int *prevcol)
{
--col;
if (col > 0 && has_mbyte)
col -= (*mb_head_off)(linep, linep + col);
if (prevcol)
*prevcol = col;
return (col >= 0 && linep[col] == ch) ? TRUE : FALSE;
}
/*
* Raw string start is found at linep[startpos.col - 1].
* Return TRUE if the matching end can be found between startpos and endpos.
*/
static int
find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
{
char_u *p;
char_u *delim_copy;
size_t delim_len;
linenr_T lnum;
int found = FALSE;
for (p = linep + startpos->col + 1; *p && *p != '('; ++p)
;
delim_len = (p - linep) - startpos->col - 1;
delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len);
if (delim_copy == NULL)
return FALSE;
for (lnum = startpos->lnum; lnum <= endpos->lnum; ++lnum)
{
char_u *line = ml_get(lnum);
for (p = line + (lnum == startpos->lnum
? startpos->col + 1 : 0); *p; ++p)
{
if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col)
break;
if (*p == ')' && STRNCMP(delim_copy, p + 1, delim_len) == 0
&& p[delim_len + 1] == '"')
{
found = TRUE;
break;
}
}
if (found)
break;
}
vim_free(delim_copy);
return found;
}
/*
* Check matchpairs option for "*initc".
* If there is a match set "*initc" to the matching character and "*findc" to
* the opposite character. Set "*backwards" to the direction.
* When "switchit" is TRUE swap the direction.
*/
static void
find_mps_values(
int *initc,
int *findc,
int *backwards,
int switchit)
{
char_u *ptr;
ptr = curbuf->b_p_mps;
while (*ptr != NUL)
{
if (has_mbyte)
{
char_u *prev;
if (mb_ptr2char(ptr) == *initc)
{
if (switchit)
{
*findc = *initc;
*initc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
*backwards = TRUE;
}
else
{
*findc = mb_ptr2char(ptr + mb_ptr2len(ptr) + 1);
*backwards = FALSE;
}
return;
}
prev = ptr;
ptr += mb_ptr2len(ptr) + 1;
if (mb_ptr2char(ptr) == *initc)
{
if (switchit)
{
*findc = *initc;
*initc = mb_ptr2char(prev);
*backwards = FALSE;
}
else
{
*findc = mb_ptr2char(prev);
*backwards = TRUE;
}
return;
}
ptr += mb_ptr2len(ptr);
}
else
{
if (*ptr == *initc)
{
if (switchit)
{
*backwards = TRUE;
*findc = *initc;
*initc = ptr[2];
}
else
{
*backwards = FALSE;
*findc = ptr[2];
}
return;
}
ptr += 2;
if (*ptr == *initc)
{
if (switchit)
{
*backwards = FALSE;
*findc = *initc;
*initc = ptr[-2];
}
else
{
*backwards = TRUE;
*findc = ptr[-2];
}
return;
}
++ptr;
}
if (*ptr == ',')
++ptr;
}
}
/*
* findmatchlimit -- find the matching paren or brace, if it exists within
* maxtravel lines of the cursor. A maxtravel of 0 means search until falling
* off the edge of the file.
*
* "initc" is the character to find a match for. NUL means to find the
* character at or after the cursor. Special values:
* '*' look for C-style comment / *
* '/' look for C-style comment / *, ignoring comment-end
* '#' look for preprocessor directives
* 'R' look for raw string start: R"delim(text)delim" (only backwards)
*
* flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
* FM_FORWARD search forwards (when initc is '/', '*' or '#')
* FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
* FM_SKIPCOMM skip comments (not implemented yet!)
*
* "oap" is only used to set oap->motion_type for a linewise motion, it can be
* NULL
*/
pos_T *
findmatchlimit(
oparg_T *oap,
int initc,
int flags,
int maxtravel)
{
static pos_T pos; // current search position
int findc = 0; // matching brace
int c;
int count = 0; // cumulative number of braces
int backwards = FALSE; // init for gcc
int raw_string = FALSE; // search for raw string
int inquote = FALSE; // TRUE when inside quotes
char_u *linep; // pointer to current line
char_u *ptr;
int do_quotes; // check for quotes in current line
int at_start; // do_quotes value at start position
int hash_dir = 0; // Direction searched for # things
int comment_dir = 0; // Direction searched for comments
pos_T match_pos; // Where last slash-star was found
int start_in_quotes; // start position is in quotes
int traveled = 0; // how far we've searched so far
int ignore_cend = FALSE; // ignore comment end
int cpo_match; // vi compatible matching
int cpo_bsl; // don't recognize backslashes
int match_escaped = 0; // search for escaped match
int dir; // Direction to search
int comment_col = MAXCOL; // start of / / comment
int lispcomm = FALSE; // inside of Lisp-style comment
int lisp = curbuf->b_p_lisp; // engage Lisp-specific hacks ;)
pos = curwin->w_cursor;
pos.coladd = 0;
linep = ml_get(pos.lnum);
cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL);
cpo_bsl = (vim_strchr(p_cpo, CPO_MATCHBSL) != NULL);
// Direction to search when initc is '/', '*' or '#'
if (flags & FM_BACKWARD)
dir = BACKWARD;
else if (flags & FM_FORWARD)
dir = FORWARD;
else
dir = 0;
/*
* if initc given, look in the table for the matching character
* '/' and '*' are special cases: look for start or end of comment.
* When '/' is used, we ignore running backwards into an star-slash, for
* "[*" command, we just want to find any comment.
*/
if (initc == '/' || initc == '*' || initc == 'R')
{
comment_dir = dir;
if (initc == '/')
ignore_cend = TRUE;
backwards = (dir == FORWARD) ? FALSE : TRUE;
raw_string = (initc == 'R');
initc = NUL;
}
else if (initc != '#' && initc != NUL)
{
find_mps_values(&initc, &findc, &backwards, TRUE);
if (dir)
backwards = (dir == FORWARD) ? FALSE : TRUE;
if (findc == NUL)
return NULL;
}
else
{
/*
* Either initc is '#', or no initc was given and we need to look
* under the cursor.
*/
if (initc == '#')
{
hash_dir = dir;
}
else
{
/*
* initc was not given, must look for something to match under
* or near the cursor.
* Only check for special things when 'cpo' doesn't have '%'.
*/
if (!cpo_match)
{
// Are we before or at #if, #else etc.?
ptr = skipwhite(linep);
if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep))
{
ptr = skipwhite(ptr + 1);
if ( STRNCMP(ptr, "if", 2) == 0
|| STRNCMP(ptr, "endif", 5) == 0
|| STRNCMP(ptr, "el", 2) == 0)
hash_dir = 1;
}
// Are we on a comment?
else if (linep[pos.col] == '/')
{
if (linep[pos.col + 1] == '*')
{
comment_dir = FORWARD;
backwards = FALSE;
pos.col++;
}
else if (pos.col > 0 && linep[pos.col - 1] == '*')
{
comment_dir = BACKWARD;
backwards = TRUE;
pos.col--;
}
}
else if (linep[pos.col] == '*')
{
if (linep[pos.col + 1] == '/')
{
comment_dir = BACKWARD;
backwards = TRUE;
}
else if (pos.col > 0 && linep[pos.col - 1] == '/')
{
comment_dir = FORWARD;
backwards = FALSE;
}
}
}
/*
* If we are not on a comment or the # at the start of a line, then
* look for brace anywhere on this line after the cursor.
*/
if (!hash_dir && !comment_dir)
{
/*
* Find the brace under or after the cursor.
* If beyond the end of the line, use the last character in
* the line.
*/
if (linep[pos.col] == NUL && pos.col)
--pos.col;
for (;;)
{
initc = PTR2CHAR(linep + pos.col);
if (initc == NUL)
break;
find_mps_values(&initc, &findc, &backwards, FALSE);
if (findc)
break;
pos.col += mb_ptr2len(linep + pos.col);
}
if (!findc)
{
// no brace in the line, maybe use " #if" then
if (!cpo_match && *skipwhite(linep) == '#')
hash_dir = 1;
else
return NULL;
}
else if (!cpo_bsl)
{
int col, bslcnt = 0;
// Set "match_escaped" if there are an odd number of
// backslashes.
for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
bslcnt++;
match_escaped = (bslcnt & 1);
}
}
}
if (hash_dir)
{
/*
* Look for matching #if, #else, #elif, or #endif
*/
if (oap != NULL)
oap->motion_type = MLINE; // Linewise for this case only
if (initc != '#')
{
ptr = skipwhite(skipwhite(linep) + 1);
if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0)
hash_dir = 1;
else if (STRNCMP(ptr, "endif", 5) == 0)
hash_dir = -1;
else
return NULL;
}
pos.col = 0;
while (!got_int)
{
if (hash_dir > 0)
{
if (pos.lnum == curbuf->b_ml.ml_line_count)
break;
}
else if (pos.lnum == 1)
break;
pos.lnum += hash_dir;
linep = ml_get(pos.lnum);
line_breakcheck(); // check for CTRL-C typed
ptr = skipwhite(linep);
if (*ptr != '#')
continue;
pos.col = (colnr_T) (ptr - linep);
ptr = skipwhite(ptr + 1);
if (hash_dir > 0)
{
if (STRNCMP(ptr, "if", 2) == 0)
count++;
else if (STRNCMP(ptr, "el", 2) == 0)
{
if (count == 0)
return &pos;
}
else if (STRNCMP(ptr, "endif", 5) == 0)
{
if (count == 0)
return &pos;
count--;
}
}
else
{
if (STRNCMP(ptr, "if", 2) == 0)
{
if (count == 0)
return &pos;
count--;
}
else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0)
{
if (count == 0)
return &pos;
}
else if (STRNCMP(ptr, "endif", 5) == 0)
count++;
}
}
return NULL;
}
}
#ifdef FEAT_RIGHTLEFT
// This is just guessing: when 'rightleft' is set, search for a matching
// paren/brace in the other direction.
if (curwin->w_p_rl && vim_strchr((char_u *)"()[]{}<>", initc) != NULL)
backwards = !backwards;
#endif
do_quotes = -1;
start_in_quotes = MAYBE;
CLEAR_POS(&match_pos);
// backward search: Check if this line contains a single-line comment
if ((backwards && comment_dir) || lisp)
comment_col = check_linecomment(linep);
if (lisp && comment_col != MAXCOL && pos.col > (colnr_T)comment_col)
lispcomm = TRUE; // find match inside this comment
while (!got_int)
{
/*
* Go to the next position, forward or backward. We could use
* inc() and dec() here, but that is much slower
*/
if (backwards)
{
// char to match is inside of comment, don't search outside
if (lispcomm && pos.col < (colnr_T)comment_col)
break;
if (pos.col == 0) // at start of line, go to prev. one
{
if (pos.lnum == 1) // start of file
break;
--pos.lnum;
if (maxtravel > 0 && ++traveled > maxtravel)
break;
linep = ml_get(pos.lnum);
pos.col = ml_get_len(pos.lnum); // pos.col on trailing NUL
do_quotes = -1;
line_breakcheck();
// Check if this line contains a single-line comment
if (comment_dir || lisp)
comment_col = check_linecomment(linep);
// skip comment
if (lisp && comment_col != MAXCOL)
pos.col = comment_col;
}
else
{
--pos.col;
if (has_mbyte)
pos.col -= (*mb_head_off)(linep, linep + pos.col);
}
}
else // forward search
{
if (linep[pos.col] == NUL
// at end of line, go to next one
// For lisp don't search for match in comment
|| (lisp && comment_col != MAXCOL
&& pos.col == (colnr_T)comment_col))
{
if (pos.lnum == curbuf->b_ml.ml_line_count // end of file
// line is exhausted and comment with it,
// don't search for match in code
|| lispcomm)
break;
++pos.lnum;
if (maxtravel && traveled++ > maxtravel)
break;
linep = ml_get(pos.lnum);
pos.col = 0;
do_quotes = -1;
line_breakcheck();
if (lisp) // find comment pos in new line
comment_col = check_linecomment(linep);
}
else
{
if (has_mbyte)
pos.col += (*mb_ptr2len)(linep + pos.col);
else
++pos.col;
}
}
/*
* If FM_BLOCKSTOP given, stop at a '{' or '}' in column 0.
*/
if (pos.col == 0 && (flags & FM_BLOCKSTOP)
&& (linep[0] == '{' || linep[0] == '}'))
{
if (linep[0] == findc && count == 0) // match!
return &pos;
break; // out of scope
}
if (comment_dir)
{
// Note: comments do not nest, and we ignore quotes in them
// TODO: ignore comment brackets inside strings
if (comment_dir == FORWARD)
{
if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
{
pos.col++;
return &pos;
}
}
else // Searching backwards
{
/*
* A comment may contain / * or / /, it may also start or end
* with / * /. Ignore a / * after / / and after *.
*/
if (pos.col == 0)
continue;
else if (raw_string)
{
if (linep[pos.col - 1] == 'R'
&& linep[pos.col] == '"'
&& vim_strchr(linep + pos.col + 1, '(') != NULL)
{
// Possible start of raw string. Now that we have the
// delimiter we can check if it ends before where we
// started searching, or before the previously found
// raw string start.
if (!find_rawstring_end(linep, &pos,
count > 0 ? &match_pos : &curwin->w_cursor))
{
count++;
match_pos = pos;
match_pos.col--;
}
linep = ml_get(pos.lnum); // may have been released
}
}
else if ( linep[pos.col - 1] == '/'
&& linep[pos.col] == '*'
&& (pos.col == 1 || linep[pos.col - 2] != '*')
&& (int)pos.col < comment_col)
{
count++;
match_pos = pos;
match_pos.col--;
}
else if (linep[pos.col - 1] == '*' && linep[pos.col] == '/')
{
if (count > 0)
pos = match_pos;
else if (pos.col > 1 && linep[pos.col - 2] == '/'
&& (int)pos.col <= comment_col)
pos.col -= 2;
else if (ignore_cend)
continue;
else
return NULL;
return &pos;
}
}
continue;
}
/*
* If smart matching ('cpoptions' does not contain '%'), braces inside
* of quotes are ignored, but only if there is an even number of
* quotes in the line.
*/
if (cpo_match)
do_quotes = 0;
else if (do_quotes == -1)
{
/*
* Count the number of quotes in the line, skipping \" and '"'.
* Watch out for "\\".
*/
at_start = do_quotes;
for (ptr = linep; *ptr; ++ptr)
{
if (ptr == linep + pos.col + backwards)
at_start = (do_quotes & 1);
if (*ptr == '"'
&& (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
++do_quotes;
if (*ptr == '\\' && ptr[1] != NUL)
++ptr;
}
do_quotes &= 1; // result is 1 with even number of quotes
/*
* If we find an uneven count, check current line and previous
* one for a '\' at the end.
*/
if (!do_quotes)
{
inquote = FALSE;
if (ptr[-1] == '\\')
{
do_quotes = 1;
if (start_in_quotes == MAYBE)
{
// Do we need to use at_start here?
inquote = TRUE;
start_in_quotes = TRUE;
}
else if (backwards)
inquote = TRUE;
}
if (pos.lnum > 1)
{
ptr = ml_get(pos.lnum - 1);
if (*ptr && *(ptr + ml_get_len(pos.lnum - 1) - 1) == '\\')
{
do_quotes = 1;
if (start_in_quotes == MAYBE)
{
inquote = at_start;
if (inquote)
start_in_quotes = TRUE;
}
else if (!backwards)
inquote = TRUE;
}
// ml_get() only keeps one line, need to get linep again
linep = ml_get(pos.lnum);
}
}
}
if (start_in_quotes == MAYBE)
start_in_quotes = FALSE;
/*
* If 'smartmatch' is set:
* Things inside quotes are ignored by setting 'inquote'. If we
* find a quote without a preceding '\' invert 'inquote'. At the
* end of a line not ending in '\' we reset 'inquote'.
*
* In lines with an uneven number of quotes (without preceding '\')
* we do not know which part to ignore. Therefore we only set
* inquote if the number of quotes in a line is even, unless this
* line or the previous one ends in a '\'. Complicated, isn't it?
*/
c = PTR2CHAR(linep + pos.col);
switch (c)
{
case NUL:
// at end of line without trailing backslash, reset inquote
if (pos.col == 0 || linep[pos.col - 1] != '\\')
{
inquote = FALSE;
start_in_quotes = FALSE;
}
break;
case '"':
// a quote that is preceded with an odd number of backslashes is
// ignored
if (do_quotes)
{
int col;
for (col = pos.col - 1; col >= 0; --col)
if (linep[col] != '\\')
break;
if ((((int)pos.col - 1 - col) & 1) == 0)
{
inquote = !inquote;
start_in_quotes = FALSE;
}
}
break;
/*
* If smart matching ('cpoptions' does not contain '%'):
* Skip things in single quotes: 'x' or '\x'. Be careful for single
* single quotes, eg jon's. Things like '\233' or '\x3f' are not
* skipped, there is never a brace in them.
* Ignore this when finding matches for `'.
*/
case '\'':
if (!cpo_match && initc != '\'' && findc != '\'')
{
if (backwards)
{
if (pos.col > 1)
{
if (linep[pos.col - 2] == '\'')
{
pos.col -= 2;
break;
}
else if (linep[pos.col - 2] == '\\'
&& pos.col > 2 && linep[pos.col - 3] == '\'')
{
pos.col -= 3;
break;
}
}
}
else if (linep[pos.col + 1]) // forward search
{
if (linep[pos.col + 1] == '\\'
&& linep[pos.col + 2] && linep[pos.col + 3] == '\'')
{
pos.col += 3;
break;
}
else if (linep[pos.col + 2] == '\'')
{
pos.col += 2;
break;
}
}
}
// FALLTHROUGH
default:
/*
* For Lisp skip over backslashed (), {} and [].
* (actually, we skip #\( et al)
*/
if (curbuf->b_p_lisp
&& vim_strchr((char_u *)"{}()[]", c) != NULL
&& pos.col > 1
&& check_prevcol(linep, pos.col, '\\', NULL)
&& check_prevcol(linep, pos.col - 1, '#', NULL))
break;
// Check for match outside of quotes, and inside of
// quotes when the start is also inside of quotes.
if ((!inquote || start_in_quotes == TRUE)
&& (c == initc || c == findc))
{
int col, bslcnt = 0;
if (!cpo_bsl)
{
for (col = pos.col; check_prevcol(linep, col, '\\', &col);)
bslcnt++;
}
// Only accept a match when 'M' is in 'cpo' or when escaping
// is what we expect.
if (cpo_bsl || (bslcnt & 1) == match_escaped)
{
if (c == initc)
count++;
else
{
if (count == 0)
return &pos;
count--;
}
}
}
}
}
if (comment_dir == BACKWARD && count > 0)
{
pos = match_pos;
return &pos;
}
return (pos_T *)NULL; // never found it
}
/*
* Check if line[] contains a / / comment.
* Return MAXCOL if not, otherwise return the column.
*/
int
check_linecomment(char_u *line)
{
char_u *p;
p = line;
// skip Lispish one-line comments
if (curbuf->b_p_lisp)
{
if (vim_strchr(p, ';') != NULL) // there may be comments
{
int in_str = FALSE; // inside of string
p = line; // scan from start
while ((p = vim_strpbrk(p, (char_u *)"\";")) != NULL)
{
if (*p == '"')
{
if (in_str)
{
if (*(p - 1) != '\\') // skip escaped quote
in_str = FALSE;
}
else if (p == line || ((p - line) >= 2
// skip #\" form
&& *(p - 1) != '\\' && *(p - 2) != '#'))
in_str = TRUE;
}
else if (!in_str && ((p - line) < 2
|| (*(p - 1) != '\\' && *(p - 2) != '#'))
&& !is_pos_in_string(line, (colnr_T)(p - line)))
break; // found!
++p;
}
}
else
p = NULL;
}
else
while ((p = vim_strchr(p, '/')) != NULL)
{
// Accept a double /, unless it's preceded with * and followed by
// *, because * / / * is an end and start of a C comment. Only
// accept the position if it is not inside a string.
if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')
&& !is_pos_in_string(line, (colnr_T)(p - line)))
break;
++p;
}
if (p == NULL)
return MAXCOL;
return (int)(p - line);
}
/*
* Move cursor briefly to character matching the one under the cursor.
* Used for Insert mode and "r" command.
* Show the match only if it is visible on the screen.
* If there isn't a match, then beep.
*/
void
showmatch(
int c) // char to show match for
{
pos_T *lpos, save_cursor;
pos_T mpos;
colnr_T vcol;
long save_so;
long save_siso;
#ifdef CURSOR_SHAPE
int save_state;
#endif
colnr_T save_dollar_vcol;
char_u *p;
long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
/*
* Only show match for chars in the 'matchpairs' option.
*/
// 'matchpairs' is "x:y,x:y"
for (p = curbuf->b_p_mps; *p != NUL; ++p)
{
#ifdef FEAT_RIGHTLEFT
if (PTR2CHAR(p) == c && (curwin->w_p_rl ^ p_ri))
break;
#endif
p += mb_ptr2len(p) + 1;
if (PTR2CHAR(p) == c
#ifdef FEAT_RIGHTLEFT
&& !(curwin->w_p_rl ^ p_ri)
#endif
)
break;
p += mb_ptr2len(p);
if (*p == NUL)
return;
}
if (*p == NUL)
return;
if ((lpos = findmatch(NULL, NUL)) == NULL) // no match, so beep
{
vim_beep(BO_MATCH);
return;
}
if (lpos->lnum < curwin->w_topline || lpos->lnum >= curwin->w_botline)
return;
if (!curwin->w_p_wrap)
getvcol(curwin, lpos, NULL, &vcol, NULL);
int col_visible = (curwin->w_p_wrap
|| (vcol >= curwin->w_leftcol
&& vcol < curwin->w_leftcol + curwin->w_width));
if (!col_visible)
return;
mpos = *lpos; // save the pos, update_screen() may change it
save_cursor = curwin->w_cursor;
save_so = *so;
save_siso = *siso;
// Handle "$" in 'cpo': If the ')' is typed on top of the "$",
// stop displaying the "$".
if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
dollar_vcol = -1;
++curwin->w_virtcol; // do display ')' just before "$"
update_screen(UPD_VALID); // show the new char first
save_dollar_vcol = dollar_vcol;
#ifdef CURSOR_SHAPE
save_state = State;
State = MODE_SHOWMATCH;
ui_cursor_shape(); // may show different cursor shape
#endif
curwin->w_cursor = mpos; // move to matching char
*so = 0; // don't use 'scrolloff' here
*siso = 0; // don't use 'sidescrolloff' here
showruler(FALSE);
setcursor();
cursor_on(); // make sure that the cursor is shown
out_flush_cursor(TRUE, FALSE);
// Restore dollar_vcol(), because setcursor() may call curs_rows()
// which resets it if the matching position is in a previous line
// and has a higher column number.
dollar_vcol = save_dollar_vcol;
/*
* brief pause, unless 'm' is present in 'cpo' and a character is
* available.
*/
if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
ui_delay(p_mat * 100L + 8, TRUE);
else if (!char_avail())
ui_delay(p_mat * 100L + 9, FALSE);
curwin->w_cursor = save_cursor; // restore cursor position
*so = save_so;
*siso = save_siso;
#ifdef CURSOR_SHAPE
State = save_state;
ui_cursor_shape(); // may show different cursor shape
#endif
}
/*
* Check if the pattern is zero-width.
* If move is TRUE, check from the beginning of the buffer, else from position
* "cur".
* "direction" is FORWARD or BACKWARD.
* Returns TRUE, FALSE or -1 for failure.
*/
static int
is_zero_width(char_u *pattern, size_t patternlen, int move, pos_T *cur, int direction)
{
regmmatch_T regmatch;
int nmatched = 0;
int result = -1;
pos_T pos;
int called_emsg_before = called_emsg;
int flag = 0;
if (pattern == NULL)
{
pattern = spats[last_idx].pat;
patternlen = spats[last_idx].patlen;
}
if (search_regcomp(pattern, patternlen, NULL, RE_SEARCH, RE_SEARCH,
SEARCH_KEEP, &regmatch) == FAIL)
return -1;
// init startcol correctly
regmatch.startpos[0].col = -1;
// move to match
if (move)
{
CLEAR_POS(&pos);
}
else
{
pos = *cur;
// accept a match at the cursor position
flag = SEARCH_START;
}
if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, patternlen, 1,
SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL)
{
// Zero-width pattern should match somewhere, then we can check if
// start and end are in the same position.
do
{
regmatch.startpos[0].col++;
nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
pos.lnum, regmatch.startpos[0].col, NULL);
if (nmatched != 0)
break;
} while (regmatch.regprog != NULL
&& direction == FORWARD ? regmatch.startpos[0].col < pos.col
: regmatch.startpos[0].col > pos.col);
if (called_emsg == called_emsg_before)
{
result = (nmatched != 0
&& regmatch.startpos[0].lnum == regmatch.endpos[0].lnum
&& regmatch.startpos[0].col == regmatch.endpos[0].col);
}
}
vim_regfree(regmatch.regprog);
return result;
}
/*
* Find next search match under cursor, cursor at end.
* Used while an operator is pending, and in Visual mode.
*/
int
current_search(
long count,
int forward) // TRUE for forward, FALSE for backward
{
pos_T start_pos; // start position of the pattern match
pos_T end_pos; // end position of the pattern match
pos_T orig_pos; // position of the cursor at beginning
pos_T pos; // position after the pattern
int i;
int dir;
int result; // result of various function calls
char_u old_p_ws = p_ws;
int flags = 0;
pos_T save_VIsual = VIsual;
int zero_width;
int skip_first_backward;
// Correct cursor when 'selection' is exclusive
if (VIsual_active && *p_sel == 'e' && LT_POS(VIsual, curwin->w_cursor))
dec_cursor();
// When searching forward and the cursor is at the start of the Visual
// area, skip the first search backward, otherwise it doesn't move.
skip_first_backward = forward && VIsual_active
&& LT_POS(curwin->w_cursor, VIsual);
orig_pos = pos = curwin->w_cursor;
if (VIsual_active)
{
if (forward)
incl(&pos);
else
decl(&pos);
}
// Is the pattern is zero-width?, this time, don't care about the direction
zero_width = is_zero_width(spats[last_idx].pat, spats[last_idx].patlen,
TRUE, &curwin->w_cursor, FORWARD);
if (zero_width == -1)
return FAIL; // pattern not found
/*
* The trick is to first search backwards and then search forward again,
* so that a match at the current cursor position will be correctly
* captured. When "forward" is false do it the other way around.
*/
for (i = 0; i < 2; i++)
{
if (forward)
{
if (i == 0 && skip_first_backward)
continue;
dir = i;
}
else
dir = !i;
flags = 0;
if (!dir && !zero_width)
flags = SEARCH_END;
end_pos = pos;
// wrapping should not occur in the first round
if (i == 0)
p_ws = FALSE;
result = searchit(curwin, curbuf, &pos, &end_pos,
(dir ? FORWARD : BACKWARD),
spats[last_idx].pat, spats[last_idx].patlen, (long) (i ? count : 1),
SEARCH_KEEP | flags, RE_SEARCH, NULL);
p_ws = old_p_ws;
// First search may fail, but then start searching from the
// beginning of the file (cursor might be on the search match)
// except when Visual mode is active, so that extending the visual
// selection works.
if (i == 1 && !result) // not found, abort
{
curwin->w_cursor = orig_pos;
if (VIsual_active)
VIsual = save_VIsual;
return FAIL;
}
else if (i == 0 && !result)
{
if (forward)
{
// try again from start of buffer
CLEAR_POS(&pos);
}
else
{
// try again from end of buffer
// searching backwards, so set pos to last line and col
pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
pos.col = ml_get_len(curwin->w_buffer->b_ml.ml_line_count);
}
}
}
start_pos = pos;
if (!VIsual_active)
VIsual = start_pos;
// put the cursor after the match
curwin->w_cursor = end_pos;
if (LT_POS(VIsual, end_pos) && forward)
{
if (skip_first_backward)
// put the cursor on the start of the match
curwin->w_cursor = pos;
else
// put the cursor on last character of match
dec_cursor();
}
else if (VIsual_active && LT_POS(curwin->w_cursor, VIsual) && forward)
curwin->w_cursor = pos; // put the cursor on the start of the match
VIsual_active = TRUE;
VIsual_mode = 'v';
if (*p_sel == 'e')
{
// Correction for exclusive selection depends on the direction.
if (forward && LTOREQ_POS(VIsual, curwin->w_cursor))
inc_cursor();
else if (!forward && LTOREQ_POS(curwin->w_cursor, VIsual))
inc(&VIsual);
}
#ifdef FEAT_FOLDING
if (fdo_flags & FDO_SEARCH && KeyTyped)
foldOpenCursor();
#endif
may_start_select('c');
setmouse();
#ifdef FEAT_CLIPBOARD
// Make sure the clipboard gets updated. Needed because start and
// end are still the same, and the selection needs to be owned
clip_star.vmode = NUL;
#endif
redraw_curbuf_later(UPD_INVERTED);
showmode();
return OK;
}
/*
* return TRUE if line 'lnum' is empty or has white chars only.
*/
int
linewhite(linenr_T lnum)
{
char_u *p;
p = skipwhite(ml_get(lnum));
return (*p == NUL);
}
/*
* Add the search count "[3/19]" to "msgbuf".
* See update_search_stat() for other arguments.
*/
static 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)
{
searchstat_T stat;
update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
timeout);
if (stat.cur <= 0)
return;
char t[SEARCH_STAT_BUF_LEN];
size_t len;
#ifdef FEAT_RIGHTLEFT
if (curwin->w_p_rl && *curwin->w_p_rlc == 's')
{
if (stat.incomplete == 1)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
else if (stat.cnt > maxcount && stat.cur > maxcount)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
maxcount, maxcount);
else if (stat.cnt > maxcount)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
maxcount, stat.cur);
else
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
stat.cnt, stat.cur);
}
else
#endif
{
if (stat.incomplete == 1)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
else if (stat.cnt > maxcount && stat.cur > maxcount)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
maxcount, maxcount);
else if (stat.cnt > maxcount)
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
stat.cur, maxcount);
else
len = vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
stat.cur, stat.cnt);
}
if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN)
{
mch_memmove(t + 2, t, len);
t[0] = 'W';
t[1] = ' ';
len += 2;
}
if (len > msgbuflen)
len = msgbuflen;
mch_memmove(msgbuf + msgbuflen - len, t, len);
if (dirc == '?' && stat.cur == maxcount + 1)
stat.cur = -1;
// keep the message even after redraw, but don't put in history
msg_hist_off = TRUE;
give_warning(msgbuf, FALSE);
msg_hist_off = FALSE;
}
/*
* Add the search count information to "stat".
* "stat" must not be NULL.
* When "recompute" is TRUE always recompute the numbers.
* dirc == 0: don't find the next/previous match (only set the result to "stat")
* dirc == '/': find the next match
* dirc == '?': find the previous match
*/
static void
update_search_stat(
int dirc,
pos_T *pos,
pos_T *cursor_pos,
searchstat_T *stat,
int recompute,
int maxcount,
long timeout UNUSED)
{
int save_ws = p_ws;
int wraparound = FALSE;
pos_T p = (*pos);
static pos_T lastpos = {0, 0, 0};
static int cur = 0;
static int cnt = 0;
static int exact_match = FALSE;
static int incomplete = 0;
static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
static int chgtick = 0;
static char_u *lastpat = NULL;
static size_t lastpatlen = 0;
static buf_T *lbuf = NULL;
#ifdef FEAT_RELTIME
proftime_T start;
#endif
CLEAR_POINTER(stat);
if (dirc == 0 && !recompute && !EMPTY_POS(lastpos))
{
stat->cur = cur;
stat->cnt = cnt;
stat->exact_match = exact_match;
stat->incomplete = incomplete;
stat->last_maxcount = last_maxcount;
return;
}
last_maxcount = maxcount;
wraparound = ((dirc == '?' && LT_POS(lastpos, p))
|| (dirc == '/' && LT_POS(p, lastpos)));
// If anything relevant changed the count has to be recomputed.
// MB_STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no MB_STRNICMP function.
// XXX: above comment should be "no MB_STRCMP function" ?
if (!(chgtick == CHANGEDTICK(curbuf)
&& (lastpat != NULL
&& MB_STRNICMP(lastpat, spats[last_idx].pat, lastpatlen) == 0
&& lastpatlen == spats[last_idx].patlen
)
&& EQUAL_POS(lastpos, *cursor_pos)
&& lbuf == curbuf) || wraparound || cur < 0
|| (maxcount > 0 && cur > maxcount) || recompute)
{
cur = 0;
cnt = 0;
exact_match = FALSE;
incomplete = 0;
CLEAR_POS(&lastpos);
lbuf = curbuf;
}
// when searching backwards and having jumped to the first occurrence,
// cur must remain greater than 1
if (EQUAL_POS(lastpos, *cursor_pos) && !wraparound
&& (dirc == 0 || dirc == '/' ? cur < cnt : cur > 1))
cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
else
{
int done_search = FALSE;
pos_T endpos = {0, 0, 0};
p_ws = FALSE;
#ifdef FEAT_RELTIME
if (timeout > 0)
profile_setlimit(timeout, &start);
#endif
while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 0, 1, SEARCH_KEEP, RE_LAST, NULL) != FAIL)
{
done_search = TRUE;
#ifdef FEAT_RELTIME
// Stop after passing the time limit.
if (timeout > 0 && profile_passed_limit(&start))
{
incomplete = 1;
break;
}
#endif
cnt++;
if (LTOREQ_POS(lastpos, p))
{
cur = cnt;
if (LT_POS(p, endpos))
exact_match = TRUE;
}
fast_breakcheck();
if (maxcount > 0 && cnt > maxcount)
{
incomplete = 2; // max count exceeded
break;
}
}
if (got_int)
cur = -1; // abort
if (done_search)
{
vim_free(lastpat);
lastpat = vim_strnsave(spats[last_idx].pat, spats[last_idx].patlen);
if (lastpat == NULL)
lastpatlen = 0;
else
lastpatlen = spats[last_idx].patlen;
chgtick = CHANGEDTICK(curbuf);
lbuf = curbuf;
lastpos = p;
}
}
stat->cur = cur;
stat->cnt = cnt;
stat->exact_match = exact_match;
stat->incomplete = incomplete;
stat->last_maxcount = last_maxcount;
p_ws = save_ws;
}
#if defined(FEAT_FIND_ID) || defined(PROTO)
/*
* Get line "lnum" and copy it into "buf[LSIZE]".
* The copy is made because the regexp may make the line invalid when using a
* mark.
*/
static char_u *
get_line_and_copy(linenr_T lnum, char_u *buf)
{
char_u *line = ml_get(lnum);
vim_strncpy(buf, line, LSIZE - 1);
return buf;
}
/*
* Find identifiers or defines in included files.
* If p_ic && compl_status_sol() then ptr must be in lowercase.
*/
void
find_pattern_in_path(
char_u *ptr, // pointer to search pattern
int dir UNUSED, // direction of expansion
int len, // length of search pattern
int whole, // match whole words only
int skip_comments, // don't match inside comments
int type, // Type of search; are we looking for a type?
// a macro?
long count,
int action, // What to do when we find it
linenr_T start_lnum, // first line to start searching
linenr_T end_lnum, // last line for searching
int forceit) // If true, always switch to the found path
{
SearchedFile *files; // Stack of included files
SearchedFile *bigger; // When we need more space
int max_path_depth = 50;
long match_count = 1;
char_u *pat;
char_u *new_fname;
char_u *curr_fname = curbuf->b_fname;
char_u *prev_fname = NULL;
linenr_T lnum;
int depth;
int depth_displayed; // For type==CHECK_PATH
int old_files;
int already_searched;
char_u *file_line;
char_u *line;
char_u *p;
char_u save_char;
int define_matched;
regmatch_T regmatch;
regmatch_T incl_regmatch;
regmatch_T def_regmatch;
int matched = FALSE;
int did_show = FALSE;
int found = FALSE;
int i;
char_u *already = NULL;
char_u *startp = NULL;
char_u *inc_opt = NULL;
#if defined(FEAT_QUICKFIX)
win_T *curwin_save = NULL;
#endif
regmatch.regprog = NULL;
incl_regmatch.regprog = NULL;
def_regmatch.regprog = NULL;
file_line = alloc(LSIZE);
if (file_line == NULL)
return;
if (type != CHECK_PATH && type != FIND_DEFINE
// when CONT_SOL is set compare "ptr" with the beginning of the
// line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
&& !compl_status_sol())
{
pat = alloc(len + 5);
if (pat == NULL)
goto fpip_end;
vim_snprintf((char *)pat, len + 5, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
// ignore case according to p_ic, p_scs and pat
regmatch.rm_ic = ignorecase(pat);
regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0);
vim_free(pat);
if (regmatch.regprog == NULL)
goto fpip_end;
}
inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc;
if (*inc_opt != NUL)
{
incl_regmatch.regprog = vim_regcomp(inc_opt,
magic_isset() ? RE_MAGIC : 0);
if (incl_regmatch.regprog == NULL)
goto fpip_end;
incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat.
}
if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL))
{
def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL ? p_def : curbuf->b_p_def,
magic_isset() ? RE_MAGIC : 0);
if (def_regmatch.regprog == NULL)
goto fpip_end;
def_regmatch.rm_ic = FALSE; // don't ignore case in define pat.
}
files = lalloc_clear(max_path_depth * sizeof(SearchedFile), TRUE);
if (files == NULL)
goto fpip_end;
old_files = max_path_depth;
depth = depth_displayed = -1;
lnum = start_lnum;
if (end_lnum > curbuf->b_ml.ml_line_count)
end_lnum = curbuf->b_ml.ml_line_count;
if (lnum > end_lnum) // do at least one line
lnum = end_lnum;
line = get_line_and_copy(lnum, file_line);
for (;;)
{
if (incl_regmatch.regprog != NULL
&& vim_regexec(&incl_regmatch, line, (colnr_T)0))
{
char_u *p_fname = (curr_fname == curbuf->b_fname)
? curbuf->b_ffname : curr_fname;
if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL)
// Use text from '\zs' to '\ze' (or end) of 'include'.
new_fname = find_file_name_in_path(incl_regmatch.startp[0],
(int)(incl_regmatch.endp[0] - incl_regmatch.startp[0]),
FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname);
else
// Use text after match with 'include'.
new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL);
already_searched = FALSE;
if (new_fname != NULL)
{
// Check whether we have already searched in this file
for (i = 0;; i++)
{
if (i == depth + 1)
i = old_files;
if (i == max_path_depth)
break;
if (fullpathcmp(new_fname, files[i].name, TRUE, TRUE)
& FPC_SAME)
{
if (type != CHECK_PATH
&& action == ACTION_SHOW_ALL
&& files[i].matched)
{
msg_putchar('\n'); // cursor below last one
if (!got_int) // don't display if 'q'
// typed at "--more--"
// message
{
msg_home_replace_hl(new_fname);
msg_puts(_(" (includes previously listed match)"));
prev_fname = NULL;
}
}
VIM_CLEAR(new_fname);
already_searched = TRUE;
break;
}
}
}
if (type == CHECK_PATH && (action == ACTION_SHOW_ALL
|| (new_fname == NULL && !already_searched)))
{
if (did_show)
msg_putchar('\n'); // cursor below last one
else
{
gotocmdline(TRUE); // cursor at status line
msg_puts_title(_("--- Included files "));
if (action != ACTION_SHOW_ALL)
msg_puts_title(_("not found "));
msg_puts_title(_("in path ---\n"));
}
did_show = TRUE;
while (depth_displayed < depth && !got_int)
{
++depth_displayed;
for (i = 0; i < depth_displayed; i++)
msg_puts(" ");
msg_home_replace(files[depth_displayed].name);
msg_puts(" -->\n");
}
if (!got_int) // don't display if 'q' typed
// for "--more--" message
{
for (i = 0; i <= depth_displayed; i++)
msg_puts(" ");
if (new_fname != NULL)
{
// using "new_fname" is more reliable, e.g., when
// 'includeexpr' is set.
msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
}
else
{
/*
* Isolate the file name.
* Include the surrounding "" or <> if present.
*/
if (inc_opt != NULL
&& strstr((char *)inc_opt, "\\zs") != NULL)
{
// pattern contains \zs, use the match
p = incl_regmatch.startp[0];
i = (int)(incl_regmatch.endp[0]
- incl_regmatch.startp[0]);
}
else
{
// find the file name after the end of the match
for (p = incl_regmatch.endp[0];
*p && !vim_isfilec(*p); p++)
;
for (i = 0; vim_isfilec(p[i]); i++)
;
}
if (i == 0)
{
// Nothing found, use the rest of the line.
p = incl_regmatch.endp[0];
i = (int)STRLEN(p);
}
// Avoid checking before the start of the line, can
// happen if \zs appears in the regexp.
else if (p > line)
{
if (p[-1] == '"' || p[-1] == '<')
{
--p;
++i;
}
if (p[i] == '"' || p[i] == '>')
++i;
}
save_char = p[i];
p[i] = NUL;
msg_outtrans_attr(p, HL_ATTR(HLF_D));
p[i] = save_char;
}
if (new_fname == NULL && action == ACTION_SHOW_ALL)
{
if (already_searched)
msg_puts(_(" (Already listed)"));
else
msg_puts(_(" NOT FOUND"));
}
}
out_flush(); // output each line directly
}
if (new_fname != NULL)
{
// Push the new file onto the file stack
if (depth + 1 == old_files)
{
bigger = ALLOC_MULT(SearchedFile, max_path_depth * 2);
if (bigger != NULL)
{
for (i = 0; i <= depth; i++)
bigger[i] = files[i];
for (i = depth + 1; i < old_files + max_path_depth; i++)
{
bigger[i].fp = NULL;
bigger[i].name = NULL;
bigger[i].lnum = 0;
bigger[i].matched = FALSE;
}
for (i = old_files; i < max_path_depth; i++)
bigger[i + max_path_depth] = files[i];
old_files += max_path_depth;
max_path_depth *= 2;
vim_free(files);
files = bigger;
}
}
if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
== NULL)
vim_free(new_fname);
else
{
if (++depth == old_files)
{
/*
* lalloc() for 'bigger' must have failed above. We
* will forget one of our already visited files now.
*/
vim_free(files[old_files].name);
++old_files;
}
files[depth].name = curr_fname = new_fname;
files[depth].lnum = 0;
files[depth].matched = FALSE;
if (action == ACTION_EXPAND)
{
msg_hist_off = TRUE; // reset in msg_trunc_attr()
vim_snprintf((char*)IObuff, IOSIZE,
_("Scanning included file: %s"),
(char *)new_fname);
msg_trunc_attr((char *)IObuff, TRUE, HL_ATTR(HLF_R));
}
else if (p_verbose >= 5)
{
verbose_enter();
smsg(_("Searching included file %s"),
(char *)new_fname);
verbose_leave();
}
}
}
}
else
{
/*
* Check if the line is a define (type == FIND_DEFINE)
*/
p = line;
search_line:
define_matched = FALSE;
if (def_regmatch.regprog != NULL
&& vim_regexec(&def_regmatch, line, (colnr_T)0))
{
/*
* Pattern must be first identifier after 'define', so skip
* to that position before checking for match of pattern. Also
* don't let it match beyond the end of this identifier.
*/
p = def_regmatch.endp[0];
while (*p && !vim_iswordc(*p))
p++;
define_matched = TRUE;
}
/*
* Look for a match. Don't do this if we are looking for a
* define and this line didn't match define_prog above.
*/
if (def_regmatch.regprog == NULL || define_matched)
{
if (define_matched || compl_status_sol())
{
// compare the first "len" chars from "ptr"
startp = skipwhite(p);
if (p_ic)
matched = !MB_STRNICMP(startp, ptr, len);
else
matched = !STRNCMP(startp, ptr, len);
if (matched && define_matched && whole
&& vim_iswordc(startp[len]))
matched = FALSE;
}
else if (regmatch.regprog != NULL
&& vim_regexec(&regmatch, line, (colnr_T)(p - line)))
{
matched = TRUE;
startp = regmatch.startp[0];
/*
* Check if the line is not a comment line (unless we are
* looking for a define). A line starting with "# define"
* is not considered to be a comment line.
*/
if (!define_matched && skip_comments)
{
if ((*line != '#' ||
STRNCMP(skipwhite(line + 1), "define", 6) != 0)
&& get_leader_len(line, NULL, FALSE, TRUE))
matched = FALSE;
/*
* Also check for a "/ *" or "/ /" before the match.
* Skips lines like "int backwards; / * normal index
* * /" when looking for "normal".
* Note: Doesn't skip "/ *" in comments.
*/
p = skipwhite(line);
if (matched
|| (p[0] == '/' && p[1] == '*') || p[0] == '*')
for (p = line; *p && p < startp; ++p)
{
if (matched
&& p[0] == '/'
&& (p[1] == '*' || p[1] == '/'))
{
matched = FALSE;
// After "//" all text is comment
if (p[1] == '/')
break;
++p;
}
else if (!matched && p[0] == '*' && p[1] == '/')
{
// Can find match after "* /".
matched = TRUE;
++p;
}
}
}
}
}
}
if (matched)
{
if (action == ACTION_EXPAND)
{
int cont_s_ipos = FALSE;
int add_r;
char_u *aux;
if (depth == -1 && lnum == curwin->w_cursor.lnum)
break;
found = TRUE;
aux = p = startp;
if (compl_status_adding())
{
p += ins_compl_len();
if (vim_iswordp(p))
goto exit_matched;
p = find_word_start(p);
}
p = find_word_end(p);
i = (int)(p - aux);
if (compl_status_adding() && i == ins_compl_len())
{
// IOSIZE > compl_length, so the STRNCPY works
STRNCPY(IObuff, aux, i);
// Get the next line: when "depth" < 0 from the current
// buffer, otherwise from the included file. Jump to
// exit_matched when past the last line.
if (depth < 0)
{
if (lnum >= end_lnum)
goto exit_matched;
line = get_line_and_copy(++lnum, file_line);
}
else if (vim_fgets(line = file_line,
LSIZE, files[depth].fp))
goto exit_matched;
// we read a line, set "already" to check this "line" later
// if depth >= 0 we'll increase files[depth].lnum far
// below -- Acevedo
already = aux = p = skipwhite(line);
p = find_word_start(p);
p = find_word_end(p);
if (p > aux)
{
if (*aux != ')' && IObuff[i-1] != TAB)
{
if (IObuff[i-1] != ' ')
IObuff[i++] = ' ';
// IObuf =~ "\(\k\|\i\).* ", thus i >= 2
if (p_js
&& (IObuff[i-2] == '.'
|| (vim_strchr(p_cpo, CPO_JOINSP) == NULL
&& (IObuff[i-2] == '?'
|| IObuff[i-2] == '!'))))
IObuff[i++] = ' ';
}
// copy as much as possible of the new word
if (p - aux >= IOSIZE - i)
p = aux + IOSIZE - i - 1;
STRNCPY(IObuff + i, aux, p - aux);
i += (int)(p - aux);
cont_s_ipos = TRUE;
}
IObuff[i] = NUL;
aux = IObuff;
if (i == ins_compl_len())
goto exit_matched;
}
add_r = ins_compl_add_infercase(aux, i, p_ic,
curr_fname == curbuf->b_fname ? NULL : curr_fname,
dir, cont_s_ipos);
if (add_r == OK)
// if dir was BACKWARD then honor it just once
dir = FORWARD;
else if (add_r == FAIL)
break;
}
else if (action == ACTION_SHOW_ALL)
{
found = TRUE;
if (!did_show)
gotocmdline(TRUE); // cursor at status line
if (curr_fname != prev_fname)
{
if (did_show)
msg_putchar('\n'); // cursor below last one
if (!got_int) // don't display if 'q' typed
// at "--more--" message
msg_home_replace_hl(curr_fname);
prev_fname = curr_fname;
}
did_show = TRUE;
if (!got_int)
show_pat_in_path(line, type, TRUE, action,
(depth == -1) ? NULL : files[depth].fp,
(depth == -1) ? &lnum : &files[depth].lnum,
match_count++);
// Set matched flag for this file and all the ones that
// include it
for (i = 0; i <= depth; ++i)
files[i].matched = TRUE;
}
else if (--count <= 0)
{
found = TRUE;
if (depth == -1 && lnum == curwin->w_cursor.lnum
#if defined(FEAT_QUICKFIX)
&& g_do_tagpreview == 0
#endif
)
emsg(_(e_match_is_on_current_line));
else if (action == ACTION_SHOW)
{
show_pat_in_path(line, type, did_show, action,
(depth == -1) ? NULL : files[depth].fp,
(depth == -1) ? &lnum : &files[depth].lnum, 1L);
did_show = TRUE;
}
else
{
#ifdef FEAT_GUI
need_mouse_correct = TRUE;
#endif
#if defined(FEAT_QUICKFIX)
// ":psearch" uses the preview window
if (g_do_tagpreview != 0)
{
curwin_save = curwin;
prepare_tagpreview(TRUE, TRUE, FALSE);
}
#endif
if (action == ACTION_SPLIT)
{
if (win_split(0, 0) == FAIL)
break;
RESET_BINDING(curwin);
}
if (depth == -1)
{
// match in current file
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0)
{
if (!win_valid(curwin_save))
break;
if (!GETFILE_SUCCESS(getfile(
curwin_save->w_buffer->b_fnum, NULL,
NULL, TRUE, lnum, forceit)))
break; // failed to jump to file
}
else
#endif
setpcmark();
curwin->w_cursor.lnum = lnum;
check_cursor();
}
else
{
if (!GETFILE_SUCCESS(getfile(
0, files[depth].name, NULL, TRUE,
files[depth].lnum, forceit)))
break; // failed to jump to file
// autocommands may have changed the lnum, we don't
// want that here
curwin->w_cursor.lnum = files[depth].lnum;
}
}
if (action != ACTION_SHOW)
{
curwin->w_cursor.col = (colnr_T)(startp - line);
curwin->w_set_curswant = TRUE;
}
#if defined(FEAT_QUICKFIX)
if (g_do_tagpreview != 0
&& curwin != curwin_save && win_valid(curwin_save))
{
// Return cursor to where we were
validate_cursor();
redraw_later(UPD_VALID);
win_enter(curwin_save, TRUE);
}
# ifdef FEAT_PROP_POPUP
else if (WIN_IS_POPUP(curwin))
// can't keep focus in popup window
win_enter(firstwin, TRUE);
# endif
#endif
break;
}
exit_matched:
matched = FALSE;
// look for other matches in the rest of the line if we
// are not at the end of it already
if (def_regmatch.regprog == NULL
&& action == ACTION_EXPAND
&& !compl_status_sol()
&& *startp != NUL
&& *(startp + mb_ptr2len(startp)) != NUL)
goto search_line;
}
line_breakcheck();
if (action == ACTION_EXPAND)
ins_compl_check_keys(30, FALSE);
if (got_int || ins_compl_interrupted())
break;
/*
* Read the next line. When reading an included file and encountering
* end-of-file, close the file and continue in the file that included
* it.
*/
while (depth >= 0 && !already
&& vim_fgets(line = file_line, LSIZE, files[depth].fp))
{
fclose(files[depth].fp);
--old_files;
files[old_files].name = files[depth].name;
files[old_files].matched = files[depth].matched;
--depth;
curr_fname = (depth == -1) ? curbuf->b_fname
: files[depth].name;
if (depth < depth_displayed)
depth_displayed = depth;
}
if (depth >= 0) // we could read the line
{
files[depth].lnum++;
// Remove any CR and LF from the line.
i = (int)STRLEN(line);
if (i > 0 && line[i - 1] == '\n')
line[--i] = NUL;
if (i > 0 && line[i - 1] == '\r')
line[--i] = NUL;
}
else if (!already)
{
if (++lnum > end_lnum)
break;
line = get_line_and_copy(lnum, file_line);
}
already = NULL;
}
// End of big for (;;) loop.
// Close any files that are still open.
for (i = 0; i <= depth; i++)
{
fclose(files[i].fp);
vim_free(files[i].name);
}
for (i = old_files; i < max_path_depth; i++)
vim_free(files[i].name);
vim_free(files);
if (type == CHECK_PATH)
{
if (!did_show)
{
if (action != ACTION_SHOW_ALL)
msg(_("All included files were found"));
else
msg(_("No included files"));
}
}
else if (!found && action != ACTION_EXPAND)
{
if (got_int || ins_compl_interrupted())
emsg(_(e_interrupted));
else if (type == FIND_DEFINE)
emsg(_(e_couldnt_find_definition));
else
emsg(_(e_couldnt_find_pattern));
}
if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
msg_end();
fpip_end:
vim_free(file_line);
vim_regfree(regmatch.regprog);
vim_regfree(incl_regmatch.regprog);
vim_regfree(def_regmatch.regprog);
}
static void
show_pat_in_path(
char_u *line,
int type,
int did_show,
int action,
FILE *fp,
linenr_T *lnum,
long count)
{
char_u *p;
size_t linelen;
if (did_show)
msg_putchar('\n'); // cursor below last one
else if (!msg_silent)
gotocmdline(TRUE); // cursor at status line
if (got_int) // 'q' typed at "--more--" message
return;
linelen = STRLEN(line);
for (;;)
{
p = line + linelen - 1;
if (fp != NULL)
{
// We used fgets(), so get rid of newline at end
if (p >= line && *p == '\n')
--p;
if (p >= line && *p == '\r')
--p;
*(p + 1) = NUL;
}
if (action == ACTION_SHOW_ALL)
{
sprintf((char *)IObuff, "%3ld: ", count); // show match nr
msg_puts((char *)IObuff);
sprintf((char *)IObuff, "%4ld", *lnum); // show line nr
// Highlight line numbers
msg_puts_attr((char *)IObuff, HL_ATTR(HLF_N));
msg_puts(" ");
}
msg_prt_line(line, FALSE);
out_flush(); // show one line at a time
// Definition continues until line that doesn't end with '\'
if (got_int || type != FIND_DEFINE || p < line || *p != '\\')
break;
if (fp != NULL)
{
if (vim_fgets(line, LSIZE, fp)) // end of file
break;
linelen = STRLEN(line);
++*lnum;
}
else
{
if (++*lnum > curbuf->b_ml.ml_line_count)
break;
line = ml_get(*lnum);
linelen = ml_get_len(*lnum);
}
msg_putchar('\n');
}
}
#endif
#ifdef FEAT_VIMINFO
/*
* Return the last used search pattern at "idx".
*/
spat_T *
get_spat(int idx)
{
return &spats[idx];
}
/*
* Return the last used search pattern index.
*/
int
get_spat_last_idx(void)
{
return last_idx;
}
#endif
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
/*
* "searchcount()" function
*/
void
f_searchcount(typval_T *argvars, typval_T *rettv)
{
pos_T pos = curwin->w_cursor;
char_u *pattern = NULL;
int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
long timeout = SEARCH_STAT_DEF_TIMEOUT;
int recompute = TRUE;
searchstat_T stat;
if (rettv_dict_alloc(rettv) == FAIL)
return;
if (in_vim9script() && check_for_opt_dict_arg(argvars, 0) == FAIL)
return;
if (shortmess(SHM_SEARCHCOUNT)) // 'shortmess' contains 'S' flag
recompute = TRUE;
if (argvars[0].v_type != VAR_UNKNOWN)
{
dict_T *dict;
dictitem_T *di;
listitem_T *li;
int error = FALSE;
if (check_for_nonnull_dict_arg(argvars, 0) == FAIL)
return;
dict = argvars[0].vval.v_dict;
di = dict_find(dict, (char_u *)"timeout", -1);
if (di != NULL)
{
timeout = (long)tv_get_number_chk(&di->di_tv, &error);
if (error)
return;
}
di = dict_find(dict, (char_u *)"maxcount", -1);
if (di != NULL)
{
maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
if (error)
return;
}
recompute = dict_get_bool(dict, "recompute", recompute);
di = dict_find(dict, (char_u *)"pattern", -1);
if (di != NULL)
{
pattern = tv_get_string_chk(&di->di_tv);
if (pattern == NULL)
return;
}
di = dict_find(dict, (char_u *)"pos", -1);
if (di != NULL)
{
if (di->di_tv.v_type != VAR_LIST)
{
semsg(_(e_invalid_argument_str), "pos");
return;
}
if (list_len(di->di_tv.vval.v_list) != 3)
{
semsg(_(e_invalid_argument_str), "List format should be [lnum, col, off]");
return;
}
li = list_find(di->di_tv.vval.v_list, 0L);
if (li != NULL)
{
pos.lnum = tv_get_number_chk(&li->li_tv, &error);
if (error)
return;
}
li = list_find(di->di_tv.vval.v_list, 1L);
if (li != NULL)
{
pos.col = tv_get_number_chk(&li->li_tv, &error) - 1;
if (error)
return;
}
li = list_find(di->di_tv.vval.v_list, 2L);
if (li != NULL)
{
pos.coladd = tv_get_number_chk(&li->li_tv, &error);
if (error)
return;
}
}
}
save_last_search_pattern();
#ifdef FEAT_SEARCH_EXTRA
save_incsearch_state();
#endif
if (pattern != NULL)
{
if (*pattern == NUL)
goto the_end;
vim_free(spats[last_idx].pat);
spats[last_idx].patlen = STRLEN(pattern);
spats[last_idx].pat = vim_strnsave(pattern, spats[last_idx].patlen);
if (spats[last_idx].pat == NULL)
spats[last_idx].patlen = 0;
}
if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL)
goto the_end; // the previous pattern was never defined
update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
dict_add_number(rettv->vval.v_dict, "current", stat.cur);
dict_add_number(rettv->vval.v_dict, "total", stat.cnt);
dict_add_number(rettv->vval.v_dict, "exact_match", stat.exact_match);
dict_add_number(rettv->vval.v_dict, "incomplete", stat.incomplete);
dict_add_number(rettv->vval.v_dict, "maxcount", stat.last_maxcount);
the_end:
restore_last_search_pattern();
#ifdef FEAT_SEARCH_EXTRA
restore_incsearch_state();
#endif
}
#endif
/*
* Fuzzy string matching
*
* Ported from the lib_fts library authored by Forrest Smith.
* https://github.com/forrestthewoods/lib_fts/tree/master/code
*
* The following blog describes the fuzzy matching algorithm:
* https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/
*
* Each matching string is assigned a score. The following factors are checked:
* - Matched letter
* - Unmatched letter
* - Consecutively matched letters
* - Proximity to start
* - Letter following a separator (space, underscore)
* - Uppercase letter following lowercase (aka CamelCase)
*
* Matched letters are good. Unmatched letters are bad. Matching near the start
* is good. Matching the first letter in the middle of a phrase is good.
* Matching the uppercase letters in camel case entries is good.
*
* The score assigned for each factor is explained below.
* File paths are different from file names. File extensions may be ignorable.
* Single words care about consecutive matches but not separators or camel
* case.
* Score starts at 100
* Matched letter: +0 points
* Unmatched letter: -1 point
* Consecutive match bonus: +15 points
* First letter bonus: +15 points
* Separator bonus: +30 points
* Camel case bonus: +30 points
* Unmatched leading letter: -5 points (max: -15)
*
* There is some nuance to this. Scores dont have an intrinsic meaning. The
* score range isnt 0 to 100. Its roughly [50, 150]. Longer words have a
* lower minimum score due to unmatched letter penalty. Longer search patterns
* have a higher maximum score due to match bonuses.
*
* Separator and camel case bonus is worth a LOT. Consecutive matches are worth
* quite a bit.
*
* There is a penalty if you DONT match the first three letters. Which
* effectively rewards matching near the start. However theres no difference
* in matching between the middle and end.
*
* There is not an explicit bonus for an exact match. Unmatched letters receive
* a penalty. So shorter strings and closer matches are worth more.
*/
typedef struct
{
int idx; // used for stable sort
listitem_T *item;
int score;
list_T *lmatchpos;
} fuzzyItem_T;
// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that
// matching a whole word is preferred.
#define SEQUENTIAL_BONUS 40
// bonus if match occurs after a path separator
#define PATH_SEPARATOR_BONUS 30
// bonus if match occurs after a word separator
#define WORD_SEPARATOR_BONUS 25
// bonus if match is uppercase and prev is lower
#define CAMEL_BONUS 30
// bonus if the first letter is matched
#define FIRST_LETTER_BONUS 15
// bonus if exact match
#define EXACT_MATCH_BONUS 100
// bonus if case match when no ignorecase
#define CASE_MATCH_BONUS 25
// penalty applied for every letter in str before the first match
#define LEADING_LETTER_PENALTY (-5)
// maximum penalty for leading letters
#define MAX_LEADING_LETTER_PENALTY (-15)
// penalty for every letter that doesn't match
#define UNMATCHED_LETTER_PENALTY (-1)
// penalty for gap in matching positions (-2 * k)
#define GAP_PENALTY (-2)
// Score for a string that doesn't fuzzy match the pattern
#define SCORE_NONE (-9999)
#define FUZZY_MATCH_RECURSION_LIMIT 10
/*
* Compute a score for a fuzzy matched string. The matching character locations
* are in 'matches'.
*/
static int
fuzzy_match_compute_score(
char_u *fuzpat,
char_u *str,
int strSz,
int_u *matches,
int numMatches)
{
int score;
int penalty;
int unmatched;
int i;
char_u *p = str;
int_u sidx = 0;
int is_exact_match = TRUE;
char_u *orig_fuzpat = fuzpat - numMatches;
char_u *curpat = orig_fuzpat;
int pat_idx = 0;
// Track consecutive camel case matches
int consecutive_camel = 0;
// Initialize score
score = 100;
// Apply leading letter penalty
penalty = LEADING_LETTER_PENALTY * matches[0];
if (penalty < MAX_LEADING_LETTER_PENALTY)
penalty = MAX_LEADING_LETTER_PENALTY;
score += penalty;
// Apply unmatched penalty
unmatched = strSz - numMatches;
score += UNMATCHED_LETTER_PENALTY * unmatched;
// Apply ordering bonuses
for (i = 0; i < numMatches; ++i)
{
int_u currIdx = matches[i];
int curr;
int is_camel = FALSE;
if (i > 0)
{
int_u prevIdx = matches[i - 1];
// Sequential
if (currIdx == (prevIdx + 1))
score += SEQUENTIAL_BONUS;
else
{
score += GAP_PENALTY * (currIdx - prevIdx);
// Reset consecutive camel count on gap
consecutive_camel = 0;
}
}
// Check for bonuses based on neighbor character value
if (currIdx > 0)
{
// Camel case
int neighbor = ' ';
if (has_mbyte)
{
while (sidx < currIdx)
{
neighbor = (*mb_ptr2char)(p);
MB_PTR_ADV(p);
sidx++;
}
curr = (*mb_ptr2char)(p);
}
else
{
neighbor = str[currIdx - 1];
curr = str[currIdx];
}
// Enhanced camel case scoring
if (vim_islower(neighbor) && vim_isupper(curr))
{
score += CAMEL_BONUS * 2; // Double the camel case bonus
is_camel = TRUE;
consecutive_camel++;
// Additional bonus for consecutive camel
if (consecutive_camel > 1)
score += CAMEL_BONUS;
}
else
consecutive_camel = 0;
// Bonus if the match follows a separator character
if (neighbor == '/' || neighbor == '\\')
score += PATH_SEPARATOR_BONUS;
else if (neighbor == ' ' || neighbor == '_')
score += WORD_SEPARATOR_BONUS;
}
else
{
// First letter
score += FIRST_LETTER_BONUS;
curr = has_mbyte ? (*mb_ptr2char)(p) : str[currIdx];
}
// Case matching bonus
if (vim_isalpha(curr))
{
while (pat_idx < i && *curpat)
{
if (has_mbyte)
MB_PTR_ADV(curpat);
else
curpat++;
pat_idx++;
}
if (has_mbyte)
{
if (curr == (*mb_ptr2char)(curpat))
{
score += CASE_MATCH_BONUS;
// Extra bonus for exact case match in camel
if (is_camel)
score += CASE_MATCH_BONUS / 2;
}
}
else if (curr == *curpat)
{
score += CASE_MATCH_BONUS;
if (is_camel)
score += CASE_MATCH_BONUS / 2;
}
}
// Check exact match condition
if (currIdx != (int_u)i)
is_exact_match = FALSE;
}
// Boost score for exact matches
if (is_exact_match && numMatches == strSz)
score += EXACT_MATCH_BONUS;
return score;
}
/*
* Perform a recursive search for fuzzy matching 'fuzpat' in 'str'.
* Return the number of matching characters.
*/
static int
fuzzy_match_recursive(
char_u *fuzpat,
char_u *str,
int_u strIdx,
int *outScore,
char_u *strBegin,
int strLen,
int_u *srcMatches,
int_u *matches,
int maxMatches,
int nextMatch,
int *recursionCount)
{
// Recursion params
int recursiveMatch = FALSE;
int_u bestRecursiveMatches[MAX_FUZZY_MATCHES];
int bestRecursiveScore = 0;
int first_match;
int matched;
// Count recursions
++*recursionCount;
if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT)
return 0;
// Detect end of strings
if (*fuzpat == NUL || *str == NUL)
return 0;
// Loop through fuzpat and str looking for a match
first_match = TRUE;
while (*fuzpat != NUL && *str != NUL)
{
int c1;
int c2;
c1 = PTR2CHAR(fuzpat);
c2 = PTR2CHAR(str);
// Found match
if (vim_tolower(c1) == vim_tolower(c2))
{
// Supplied matches buffer was too short
if (nextMatch >= maxMatches)
return 0;
int recursiveScore = 0;
int_u recursiveMatches[MAX_FUZZY_MATCHES];
CLEAR_FIELD(recursiveMatches);
// "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches)
{
memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
first_match = FALSE;
}
// Recursive call that "skips" this match
char_u *next_char = str + (has_mbyte ? (*mb_ptr2len)(str) : 1);
if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
&recursiveScore, strBegin, strLen, matches,
recursiveMatches,
ARRAY_LENGTH(recursiveMatches),
nextMatch, recursionCount))
{
// Pick best recursive score
if (!recursiveMatch || recursiveScore > bestRecursiveScore)
{
memcpy(bestRecursiveMatches, recursiveMatches,
MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0]));
bestRecursiveScore = recursiveScore;
}
recursiveMatch = TRUE;
}
// Advance
matches[nextMatch++] = strIdx;
if (has_mbyte)
MB_PTR_ADV(fuzpat);
else
++fuzpat;
}
if (has_mbyte)
MB_PTR_ADV(str);
else
++str;
strIdx++;
}
// Determine if full fuzpat was matched
matched = *fuzpat == NUL ? TRUE : FALSE;
// Calculate score
if (matched)
*outScore = fuzzy_match_compute_score(fuzpat, strBegin, strLen, matches,
nextMatch);
// Return best result
if (recursiveMatch && (!matched || bestRecursiveScore > *outScore))
{
// Recursive score is better than "this"
memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0]));
*outScore = bestRecursiveScore;
return nextMatch;
}
else if (matched)
return nextMatch; // "this" score is better than recursive
return 0; // no match
}
/*
* fuzzy_match()
*
* Performs exhaustive search via recursion to find all possible matches and
* match with highest score.
* Scores values have no intrinsic meaning. Possible score range is not
* normalized and varies with pattern.
* Recursion is limited internally (default=10) to prevent degenerate cases
* (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
* Uses char_u for match indices. Therefore patterns are limited to
* MAX_FUZZY_MATCHES characters.
*
* Returns TRUE if "pat_arg" matches "str". Also returns the match score in
* "outScore" and the matching character positions in "matches".
*/
int
fuzzy_match(
char_u *str,
char_u *pat_arg,
int matchseq,
int *outScore,
int_u *matches,
int maxMatches)
{
int recursionCount = 0;
int len = MB_CHARLEN(str);
char_u *save_pat;
char_u *pat;
char_u *p;
int complete = FALSE;
int score = 0;
int numMatches = 0;
int matchCount;
*outScore = 0;
save_pat = vim_strsave(pat_arg);
if (save_pat == NULL)
return FALSE;
pat = save_pat;
p = pat;
// Try matching each word in 'pat_arg' in 'str'
while (TRUE)
{
if (matchseq)
complete = TRUE;
else
{
// Extract one word from the pattern (separated by space)
p = skipwhite(p);
if (*p == NUL)
break;
pat = p;
while (*p != NUL && !VIM_ISWHITE(PTR2CHAR(p)))
{
if (has_mbyte)
MB_PTR_ADV(p);
else
++p;
}
if (*p == NUL) // processed all the words
complete = TRUE;
*p = NUL;
}
score = 0;
recursionCount = 0;
matchCount = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL,
matches + numMatches, maxMatches - numMatches,
0, &recursionCount);
if (matchCount == 0)
{
numMatches = 0;
break;
}
// Accumulate the match score and the number of matches
*outScore += score;
numMatches += matchCount;
if (complete)
break;
// try matching the next word
++p;
}
vim_free(save_pat);
return numMatches != 0;
}
#if defined(FEAT_EVAL) || defined(FEAT_PROTO)
/*
* Sort the fuzzy matches in the descending order of the match score.
* For items with same score, retain the order using the index (stable sort)
*/
static int
fuzzy_match_item_compare(const void *s1, const void *s2)
{
int v1 = ((fuzzyItem_T *)s1)->score;
int v2 = ((fuzzyItem_T *)s2)->score;
int idx1 = ((fuzzyItem_T *)s1)->idx;
int idx2 = ((fuzzyItem_T *)s2)->idx;
if (v1 == v2)
return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
else
return v1 > v2 ? -1 : 1;
}
/*
* Fuzzy search the string 'str' in a list of 'items' and return the matching
* strings in 'fmatchlist'.
* If 'matchseq' is TRUE, then for multi-word search strings, match all the
* words in sequence.
* If 'items' is a list of strings, then search for 'str' in the list.
* If 'items' is a list of dicts, then either use 'key' to lookup the string
* for each item or use 'item_cb' Funcref function to get the string.
* If 'retmatchpos' is TRUE, then return a list of positions where 'str'
* matches for each item.
*/
static 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)
{
long len;
fuzzyItem_T *items;
listitem_T *li;
long i = 0;
long match_count = 0;
int_u matches[MAX_FUZZY_MATCHES];
len = list_len(l);
if (len == 0)
return;
if (max_matches > 0 && len > max_matches)
len = max_matches;
items = ALLOC_CLEAR_MULT(fuzzyItem_T, len);
if (items == NULL)
return;
// For all the string items in items, get the fuzzy matching score
FOR_ALL_LIST_ITEMS(l, li)
{
int score;
char_u *itemstr;
typval_T rettv;
if (max_matches > 0 && match_count >= max_matches)
break;
itemstr = NULL;
rettv.v_type = VAR_UNKNOWN;
if (li->li_tv.v_type == VAR_STRING) // list of strings
itemstr = li->li_tv.vval.v_string;
else if (li->li_tv.v_type == VAR_DICT
&& (key != NULL || item_cb->cb_name != NULL))
{
// For a dict, either use the specified key to lookup the string or
// use the specified callback function to get the string.
if (key != NULL)
itemstr = dict_get_string(li->li_tv.vval.v_dict,
(char *)key, FALSE);
else
{
typval_T argv[2];
// Invoke the supplied callback (if any) to get the dict item
li->li_tv.vval.v_dict->dv_refcount++;
argv[0].v_type = VAR_DICT;
argv[0].vval.v_dict = li->li_tv.vval.v_dict;
argv[1].v_type = VAR_UNKNOWN;
if (call_callback(item_cb, -1, &rettv, 1, argv) != FAIL)
{
if (rettv.v_type == VAR_STRING)
itemstr = rettv.vval.v_string;
}
dict_unref(li->li_tv.vval.v_dict);
}
}
if (itemstr != NULL
&& fuzzy_match(itemstr, str, matchseq, &score, matches,
MAX_FUZZY_MATCHES))
{
items[match_count].idx = match_count;
items[match_count].item = li;
items[match_count].score = score;
// Copy the list of matching positions in itemstr to a list, if
// 'retmatchpos' is set.
if (retmatchpos)
{
int j = 0;
char_u *p;
items[match_count].lmatchpos = list_alloc();
if (items[match_count].lmatchpos == NULL)
goto done;
p = str;
while (*p != NUL)
{
if (!VIM_ISWHITE(PTR2CHAR(p)) || matchseq)
{
if (list_append_number(items[match_count].lmatchpos,
matches[j]) == FAIL)
goto done;
j++;
}
if (has_mbyte)
MB_PTR_ADV(p);
else
++p;
}
}
++match_count;
}
clear_tv(&rettv);
}
if (match_count > 0)
{
list_T *retlist;
// Sort the list by the descending order of the match score
qsort((void *)items, (size_t)match_count, sizeof(fuzzyItem_T),
fuzzy_match_item_compare);
// For matchfuzzy(), return a list of matched strings.
// ['str1', 'str2', 'str3']
// For matchfuzzypos(), return a list with three items.
// The first item is a list of matched strings. The second item
// is a list of lists where each list item is a list of matched
// character positions. The third item is a list of matching scores.
// [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]]
if (retmatchpos)
{
li = list_find(fmatchlist, 0);
if (li == NULL || li->li_tv.vval.v_list == NULL)
goto done;
retlist = li->li_tv.vval.v_list;
}
else
retlist = fmatchlist;
// Copy the matching strings with a valid score to the return list
for (i = 0; i < match_count; i++)
{
if (items[i].score == SCORE_NONE)
break;
list_append_tv(retlist, &items[i].item->li_tv);
}
// next copy the list of matching positions
if (retmatchpos)
{
li = list_find(fmatchlist, -2);
if (li == NULL || li->li_tv.vval.v_list == NULL)
goto done;
retlist = li->li_tv.vval.v_list;
for (i = 0; i < match_count; i++)
{
if (items[i].score == SCORE_NONE)
break;
if (items[i].lmatchpos != NULL
&& list_append_list(retlist, items[i].lmatchpos) == FAIL)
goto done;
}
// copy the matching scores
li = list_find(fmatchlist, -1);
if (li == NULL || li->li_tv.vval.v_list == NULL)
goto done;
retlist = li->li_tv.vval.v_list;
for (i = 0; i < match_count; i++)
{
if (items[i].score == SCORE_NONE)
break;
if (list_append_number(retlist, items[i].score) == FAIL)
goto done;
}
}
}
done:
vim_free(items);
}
/*
* Do fuzzy matching. Returns the list of matched strings in 'rettv'.
* If 'retmatchpos' is TRUE, also returns the matching character positions.
*/
static void
do_fuzzymatch(typval_T *argvars, typval_T *rettv, int retmatchpos)
{
callback_T cb;
char_u *key = NULL;
int ret;
int matchseq = FALSE;
long max_matches = 0;
if (in_vim9script()
&& (check_for_list_arg(argvars, 0) == FAIL
|| check_for_string_arg(argvars, 1) == FAIL
|| check_for_opt_dict_arg(argvars, 2) == FAIL))
return;
CLEAR_POINTER(&cb);
// validate and get the arguments
if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL)
{
semsg(_(e_argument_of_str_must_be_list),
retmatchpos ? "matchfuzzypos()" : "matchfuzzy()");
return;
}
if (argvars[1].v_type != VAR_STRING
|| argvars[1].vval.v_string == NULL)
{
semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
return;
}
if (argvars[2].v_type != VAR_UNKNOWN)
{
dict_T *d;
dictitem_T *di;
if (check_for_nonnull_dict_arg(argvars, 2) == FAIL)
return;
// To search a dict, either a callback function or a key can be
// specified.
d = argvars[2].vval.v_dict;
if ((di = dict_find(d, (char_u *)"key", -1)) != NULL)
{
if (di->di_tv.v_type != VAR_STRING
|| di->di_tv.vval.v_string == NULL
|| *di->di_tv.vval.v_string == NUL)
{
semsg(_(e_invalid_argument_str), tv_get_string(&di->di_tv));
return;
}
key = tv_get_string(&di->di_tv);
}
else if ((di = dict_find(d, (char_u *)"text_cb", -1)) != NULL)
{
cb = get_callback(&di->di_tv);
if (cb.cb_name == NULL)
{
semsg(_(e_invalid_value_for_argument_str), "text_cb");
return;
}
}
if ((di = dict_find(d, (char_u *)"limit", -1)) != NULL)
{
if (di->di_tv.v_type != VAR_NUMBER)
{
semsg(_(e_invalid_argument_str), tv_get_string(&di->di_tv));
return;
}
max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
}
if (dict_has_key(d, "matchseq"))
matchseq = TRUE;
}
// get the fuzzy matches
ret = rettv_list_alloc(rettv);
if (ret == FAIL)
goto done;
if (retmatchpos)
{
list_T *l;
// For matchfuzzypos(), a list with three items are returned. First
// item is a list of matching strings, the second item is a list of
// lists with matching positions within each string and the third item
// is the list of scores of the matches.
l = list_alloc();
if (l == NULL)
goto done;
if (list_append_list(rettv->vval.v_list, l) == FAIL)
{
vim_free(l);
goto done;
}
l = list_alloc();
if (l == NULL)
goto done;
if (list_append_list(rettv->vval.v_list, l) == FAIL)
{
vim_free(l);
goto done;
}
l = list_alloc();
if (l == NULL)
goto done;
if (list_append_list(rettv->vval.v_list, l) == FAIL)
{
vim_free(l);
goto done;
}
}
fuzzy_match_in_list(argvars[0].vval.v_list, tv_get_string(&argvars[1]),
matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches);
done:
free_callback(&cb);
}
/*
* "matchfuzzy()" function
*/
void
f_matchfuzzy(typval_T *argvars, typval_T *rettv)
{
do_fuzzymatch(argvars, rettv, FALSE);
}
/*
* "matchfuzzypos()" function
*/
void
f_matchfuzzypos(typval_T *argvars, typval_T *rettv)
{
do_fuzzymatch(argvars, rettv, TRUE);
}
#endif
/*
* Same as fuzzy_match_item_compare() except for use with a string match
*/
static int
fuzzy_match_str_compare(const void *s1, const void *s2)
{
int v1 = ((fuzmatch_str_T *)s1)->score;
int v2 = ((fuzmatch_str_T *)s2)->score;
int idx1 = ((fuzmatch_str_T *)s1)->idx;
int idx2 = ((fuzmatch_str_T *)s2)->idx;
if (v1 == v2)
return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
else
return v1 > v2 ? -1 : 1;
}
/*
* Sort fuzzy matches by score
*/
static void
fuzzy_match_str_sort(fuzmatch_str_T *fm, int sz)
{
// Sort the list by the descending order of the match score
qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
fuzzy_match_str_compare);
}
/*
* Same as fuzzy_match_item_compare() except for use with a function name
* string match. <SNR> functions should be sorted to the end.
*/
static int
fuzzy_match_func_compare(const void *s1, const void *s2)
{
int v1 = ((fuzmatch_str_T *)s1)->score;
int v2 = ((fuzmatch_str_T *)s2)->score;
int idx1 = ((fuzmatch_str_T *)s1)->idx;
int idx2 = ((fuzmatch_str_T *)s2)->idx;
char_u *str1 = ((fuzmatch_str_T *)s1)->str;
char_u *str2 = ((fuzmatch_str_T *)s2)->str;
if (*str1 != '<' && *str2 == '<')
return -1;
if (*str1 == '<' && *str2 != '<')
return 1;
if (v1 == v2)
return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
else
return v1 > v2 ? -1 : 1;
}
/*
* Sort fuzzy matches of function names by score.
* <SNR> functions should be sorted to the end.
*/
static void
fuzzy_match_func_sort(fuzmatch_str_T *fm, int sz)
{
// Sort the list by the descending order of the match score
qsort((void *)fm, (size_t)sz, sizeof(fuzmatch_str_T),
fuzzy_match_func_compare);
}
/*
* Fuzzy match 'pat' in 'str'. Returns 0 if there is no match. Otherwise,
* returns the match score.
*/
int
fuzzy_match_str(char_u *str, char_u *pat)
{
int score = 0;
int_u matchpos[MAX_FUZZY_MATCHES];
if (str == NULL || pat == NULL)
return 0;
fuzzy_match(str, pat, TRUE, &score, matchpos,
sizeof(matchpos) / sizeof(matchpos[0]));
return score;
}
/*
* Fuzzy match the position of string 'pat' in string 'str'.
* Returns a dynamic array of matching positions. If there is no match,
* returns NULL.
*/
garray_T *
fuzzy_match_str_with_pos(char_u *str UNUSED, char_u *pat UNUSED)
{
#ifdef FEAT_SEARCH_EXTRA
int score = 0;
garray_T *match_positions = NULL;
int_u matches[MAX_FUZZY_MATCHES];
int j = 0;
if (str == NULL || pat == NULL)
return NULL;
match_positions = ALLOC_ONE(garray_T);
if (match_positions == NULL)
return NULL;
ga_init2(match_positions, sizeof(int_u), 10);
if (!fuzzy_match(str, pat, FALSE, &score, matches, MAX_FUZZY_MATCHES)
|| score == 0)
{
ga_clear(match_positions);
vim_free(match_positions);
return NULL;
}
for (char_u *p = pat; *p != NUL; MB_PTR_ADV(p))
{
if (!VIM_ISWHITE(PTR2CHAR(p)))
{
ga_grow(match_positions, 1);
((int_u *)match_positions->ga_data)[match_positions->ga_len] =
matches[j];
match_positions->ga_len++;
j++;
}
}
return match_positions;
#else
return NULL;
#endif
}
/*
* This function searches for a fuzzy match of the pattern `pat` within the
* line pointed to by `*ptr`. It splits the line into words, performs fuzzy
* matching on each word, and returns the length and position of the first
* matched word.
*/
static int
fuzzy_match_str_in_line(char_u **ptr, char_u *pat, int *len, pos_T *current_pos)
{
char_u *str = *ptr;
char_u *strBegin = str;
char_u *end = NULL;
char_u *start = NULL;
int found = FALSE;
int result;
char save_end;
if (str == NULL || pat == NULL)
return found;
while (*str != NUL)
{
// Skip non-word characters
start = find_word_start(str);
if (*start == NUL)
break;
end = find_word_end(start);
// Extract the word from start to end
save_end = *end;
*end = NUL;
// Perform fuzzy match
result = fuzzy_match_str(start, pat);
*end = save_end;
if (result > 0)
{
*len = (int)(end - start);
current_pos->col += (int)(end - strBegin);
found = TRUE;
*ptr = start;
break;
}
// Move to the end of the current word for the next iteration
str = end;
// Ensure we continue searching after the current word
while (*str != NUL && !vim_iswordp(str))
MB_PTR_ADV(str);
}
return found;
}
/*
* Search for the next fuzzy match in the specified buffer.
* This function attempts to find the next occurrence of the given pattern
* in the buffer, starting from the current position. It handles line wrapping
* and direction of search.
*
* Return TRUE if a match is found, otherwise FALSE.
*/
int
search_for_fuzzy_match(
buf_T *buf,
pos_T *pos,
char_u *pattern,
int dir,
pos_T *start_pos,
int *len,
char_u **ptr,
int whole_line)
{
pos_T current_pos = *pos;
pos_T circly_end;
int found_new_match = FALSE;
int looped_around = FALSE;
if (whole_line)
current_pos.lnum += dir;
if (buf == curbuf)
circly_end = *start_pos;
else
{
circly_end.lnum = buf->b_ml.ml_line_count;
circly_end.col = 0;
circly_end.coladd = 0;
}
do {
// Check if looped around and back to start position
if (looped_around && EQUAL_POS(current_pos, circly_end))
break;
// Ensure current_pos is valid
if (current_pos.lnum >= 1 && current_pos.lnum <= buf->b_ml.ml_line_count)
{
// Get the current line buffer
*ptr = ml_get_buf(buf, current_pos.lnum, FALSE);
// If ptr is end of line is reached, move to next line
// or previous line based on direction
if (**ptr != NUL)
{
if (!whole_line)
{
*ptr += current_pos.col;
// Try to find a fuzzy match in the current line starting from current position
found_new_match = fuzzy_match_str_in_line(ptr, pattern, len, &current_pos);
if (found_new_match)
{
if (ctrl_x_mode_normal())
{
if (STRNCMP(*ptr, pattern, *len) == 0 && pattern[*len] == NUL)
{
char_u *next_word_end = find_word_start(*ptr + *len);
if (*next_word_end != NUL && *next_word_end != NL)
{
// Find end of the word.
if (has_mbyte)
while (*next_word_end != NUL)
{
int l = (*mb_ptr2len)(next_word_end);
if (l < 2 && !vim_iswordc(*next_word_end))
break;
next_word_end += l;
}
else
next_word_end = find_word_end(next_word_end);
}
else if (looped_around)
found_new_match = FALSE;
*len = next_word_end - *ptr;
current_pos.col = *len;
}
}
*pos = current_pos;
break;
}
else if (looped_around && current_pos.lnum == circly_end.lnum)
break;
}
else
{
if (fuzzy_match_str(*ptr, pattern) > 0)
{
found_new_match = TRUE;
*pos = current_pos;
*len = (int)ml_get_buf_len(buf, current_pos.lnum);
break;
}
}
}
}
// Move to the next line or previous line based on direction
if (dir == FORWARD)
{
if (++current_pos.lnum > buf->b_ml.ml_line_count)
{
if (p_ws)
{
current_pos.lnum = 1;
looped_around = TRUE;
}
else
break;
}
}
else
{
if (--current_pos.lnum < 1)
{
if (p_ws)
{
current_pos.lnum = buf->b_ml.ml_line_count;
looped_around = TRUE;
}
else
break;
}
}
current_pos.col = 0;
} while (TRUE);
return found_new_match;
}
/*
* Free an array of fuzzy string matches "fuzmatch[count]".
*/
void
fuzmatch_str_free(fuzmatch_str_T *fuzmatch, int count)
{
int i;
if (fuzmatch == NULL)
return;
for (i = 0; i < count; ++i)
vim_free(fuzmatch[i].str);
vim_free(fuzmatch);
}
/*
* Copy a list of fuzzy matches into a string list after sorting the matches by
* the fuzzy score. Frees the memory allocated for 'fuzmatch'.
* Returns OK on success and FAIL on memory allocation failure.
*/
int
fuzzymatches_to_strmatches(
fuzmatch_str_T *fuzmatch,
char_u ***matches,
int count,
int funcsort)
{
int i;
if (count <= 0)
return OK;
*matches = ALLOC_MULT(char_u *, count);
if (*matches == NULL)
{
fuzmatch_str_free(fuzmatch, count);
return FAIL;
}
// Sort the list by the descending order of the match score
if (funcsort)
fuzzy_match_func_sort((void *)fuzmatch, (size_t)count);
else
fuzzy_match_str_sort((void *)fuzmatch, (size_t)count);
for (i = 0; i < count; i++)
(*matches)[i] = fuzmatch[i].str;
vim_free(fuzmatch);
return OK;
}