📄 search.c
字号:
/* vi:set ts=8 sts=4 sw=4:
*
* 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.
*/
/*
* search.c: code for normal mode searching commands
*/
#include "vim.h"
static int inmacro __ARGS((char_u *, char_u *));
static int check_linecomment __ARGS((char_u *line));
static int cls __ARGS((void));
static int skip_chars __ARGS((int, int));
#ifdef TEXT_OBJECTS
static void back_in_line __ARGS((void));
static void find_first_blank __ARGS((FPOS *));
static void findsent_forward __ARGS((long count, int at_start_sent));
#endif
#ifdef FIND_IN_PATH
static void show_pat_in_path __ARGS((char_u *, int,
int, int, FILE *, linenr_t *, long));
#endif
#ifdef VIMINFO
static void wvsp_one __ARGS((FILE *fp, int idx, char *s, char *sc));
#endif
static char_u *top_bot_msg = (char_u *)"search hit TOP, continuing at BOTTOM";
static char_u *bot_top_msg = (char_u *)"search hit BOTTOM, continuing at TOP";
/*
* 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 FPOS 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.
*/
/* The offset for a search command is store in a soff struct */
/* Note: only spats[0].off is really used */
struct soffset
{
int dir; /* search direction */
int line; /* search has line offset */
int end; /* search set cursor at end */
long off; /* line or char offset */
};
/* A search pattern and its attributes are stored in a spat struct */
struct spat
{
char_u *pat; /* the pattern (in allocated memory) or NULL */
int magic; /* magicness of the pattern */
int no_scs; /* no smarcase for this pattern */
struct soffset off;
};
/*
* 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 struct spat spats[2] =
{
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}}, /* last used search pat */
{NULL, TRUE, FALSE, {'/', 0, 0, 0L}} /* last used substitute pat */
};
static int last_idx = 0; /* index in spats[] for RE_LAST */
#if defined(AUTOCMD) || defined(WANT_EVAL) || defined(PROTO)
/* copy of spats[], for keeping the search patterns while executing autocmds */
static struct spat saved_spats[2];
static int saved_last_idx = 0;
#endif
static char_u *mr_pattern = NULL; /* pattern used by search_regcomp() */
#ifdef FIND_IN_PATH
/*
* 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 sustitute 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
*
*/
vim_regexp *
search_regcomp(pat, pat_save, pat_use, options)
char_u *pat;
int pat_save;
int pat_use;
int options;
{
int magic;
int i;
rc_did_emsg = FALSE;
magic = p_magic;
/*
* If no pattern given, use a previously defined pattern.
*/
if (pat == NULL || *pat == NUL)
{
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_nopresub);
else
emsg(e_noprevre);
rc_did_emsg = TRUE;
return (vim_regexp *)NULL;
}
pat = spats[i].pat;
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);
mr_pattern = pat;
/*
* Save the currently used pattern in the appropriate place,
* unless the pattern should not be remembered.
*/
if (!(options & SEARCH_KEEP))
{
/*
* search or global command
*/
if (pat_save == RE_SEARCH || pat_save == RE_BOTH)
{
if (spats[RE_SEARCH].pat != pat)
{
vim_free(spats[RE_SEARCH].pat);
spats[RE_SEARCH].pat = vim_strsave(pat);
spats[RE_SEARCH].magic = magic;
spats[RE_SEARCH].no_scs = no_smartcase;
last_idx = RE_SEARCH;
/* If 'hlsearch' set and search pat changed: need redraw. */
#ifdef EXTRA_SEARCH
if (p_hls)
redraw_all_later(NOT_VALID);
no_hlsearch = FALSE;
#endif
}
}
/*
* substitute or global command
*/
if (pat_save == RE_SUBST || pat_save == RE_BOTH)
{
if (spats[RE_SUBST].pat != pat)
{
vim_free(spats[RE_SUBST].pat);
spats[RE_SUBST].pat = vim_strsave(pat);
spats[RE_SUBST].magic = magic;
spats[RE_SUBST].no_scs = no_smartcase;
last_idx = RE_SUBST;
/* If 'hlsearch' set and search pat changed: need redraw. */
#ifdef EXTRA_SEARCH
if (p_hls)
redraw_all_later(NOT_VALID);
no_hlsearch = FALSE;
#endif
}
}
}
set_reg_ic(pat); /* tell the vim_regexec routine how to search */
return vim_regcomp(pat, magic);
}
#if defined(AUTOCMD) || defined(WANT_EVAL) || defined(PROTO)
/*
* Save the search patterns, so they can be restored later.
* Used before/after executing autocommands.
*/
static int save_level = 0;
void
save_search_patterns()
{
if (save_level++ == 0)
{
saved_spats[0] = spats[0];
if (spats[0].pat != NULL)
saved_spats[0].pat = vim_strsave(spats[0].pat);
saved_spats[1] = spats[1];
if (spats[1].pat != NULL)
saved_spats[1].pat = vim_strsave(spats[1].pat);
saved_last_idx = last_idx;
}
}
void
restore_search_patterns()
{
if (--save_level == 0)
{
vim_free(spats[0].pat);
spats[0] = saved_spats[0];
vim_free(spats[1].pat);
spats[1] = saved_spats[1];
last_idx = saved_last_idx;
}
}
#endif
/*
* Set reg_ic according to p_ic, p_scs and the search pattern.
*/
void
set_reg_ic(pat)
char_u *pat;
{
char_u *p;
reg_ic = p_ic;
if (reg_ic && !no_smartcase && p_scs
#ifdef INSERT_EXPAND
&& !(ctrl_x_mode && curbuf->b_p_inf)
#endif
)
{
/* don't ignore case if pattern has uppercase */
for (p = pat; *p; )
if (isupper(*p++))
reg_ic = FALSE;
}
no_smartcase = FALSE;
}
#ifdef EXTRA_SEARCH
/*
* Get a regexp program for the last used search pattern.
* This is used for highlighting all matches in a window.
*/
vim_regexp *
last_pat_prog()
{
vim_regexp *prog;
if (spats[last_idx].pat == NULL)
return NULL;
emsg_off = TRUE; /* So it doesn't beep if bad expr */
prog = search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP);
emsg_off = FALSE;
return prog;
}
#endif
/*
* lowest level search function.
* Search for 'count'th occurrence of 'str' 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
*
* Return OK for success, FAIL for failure.
*/
int
searchit(buf, pos, dir, str, count, options, pat_use)
BUF *buf;
FPOS *pos;
int dir;
char_u *str;
long count;
int options;
int pat_use;
{
int found;
linenr_t lnum; /* no init to shut up Apollo cc */
vim_regexp *prog;
char_u *ptr;
char_u *match = NULL, *matchend = NULL; /* init for GCC */
int loop;
FPOS start_pos;
int at_first_line;
int extra_col;
int match_ok;
char_u *p;
if ((prog = search_regcomp(str, RE_SEARCH, pat_use,
(options & (SEARCH_HIS + SEARCH_KEEP)))) == NULL)
{
if ((options & SEARCH_MSG) && !rc_did_emsg)
emsg2((char_u *)"Invalid search string: %s", mr_pattern);
return FAIL;
}
if (options & SEARCH_START)
extra_col = 0;
else
extra_col = 1;
/*
* find the string
*/
do /* loop for count */
{
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 (dir == BACKWARD && start_pos.col == 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)
{
/*
* Look for a match somewhere in the line.
*/
ptr = ml_get_buf(buf, lnum, FALSE);
if (vim_regexec(prog, ptr, TRUE))
{
match = prog->startp[0];
matchend = prog->endp[0];
/*
* 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).
*/
if (dir == FORWARD && at_first_line)
{
match_ok = TRUE;
/*
* When *match == NUL the cursor will be put one back
* afterwards, compare with that position, otherwise
* "/$" will get stuck on end of line.
*/
while ((options & SEARCH_END) ?
((int)(matchend - ptr) - 1 <
(int)start_pos.col + extra_col) :
((int)(match - ptr) - (int)(*match == NUL) <
(int)start_pos.col + extra_col))
{
/*
* If vi-compatible searching, continue at the end
* of the match, otherwise continue one position
* forward.
*/
if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
{
p = matchend;
if (match == p && *p != NUL)
++p;
}
else
{
p = match;
if (*p != NUL)
++p;
}
if (*p != NUL && vim_regexec(prog, p, FALSE))
{
match = prog->startp[0];
matchend = prog->endp[0];
}
else
{
match_ok = FALSE;
break;
}
}
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 (;;)
{
if (!at_first_line || ((options & SEARCH_END) ?
((prog->endp[0] - ptr) - 1 + extra_col
<= (int)start_pos.col) :
((prog->startp[0] - ptr) + extra_col
<= (int)start_pos.col)))
{
match_ok = TRUE;
match = prog->startp[0];
matchend = prog->endp[0];
}
else
break;
/*
* If vi-compatible searching, continue at the end
* of the match, otherwise continue one position
* forward.
*/
if (vim_strchr(p_cpo, CPO_SEARCH) != NULL)
{
p = matchend;
if (p == match && *p != NUL)
++p;
}
else
{
p = match;
if (*p != NUL)
++p;
}
if (*p == NUL || !vim_regexec(prog, p, (int)FALSE))
break;
}
/*
* If there is only a match after the cursor, skip
* this match.
*/
if (!match_ok)
continue;
}
pos->lnum = lnum;
if (options & SEARCH_END && !(options & SEARCH_NOOF))
pos->col = (int) (matchend - ptr - 1);
else
pos->col = (int) (match - ptr);
found = 1;
break;
}
line_breakcheck(); /* stop if ctrl-C typed */
if (got_int)
break;
if (loop && lnum == start_pos.lnum)
break; /* if second loop, stop where started */
}
at_first_line = FALSE;
/*
* stop the search if wrapscan isn't set, after an interrupt and
* after a match
*/
if (!p_ws || got_int || found)
break;
/*
* If 'wrapscan' is set we continue at the other end of the file.
* If 'shortmess' does not contain 's', we give a message.
* 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;
if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
give_warning(top_bot_msg, TRUE);
}
else
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -