summaryrefslogtreecommitdiff
path: root/usr.bin/vim/search.c
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
committerJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
commitc224fc199c25dd257673c273eb344786b9bf532c (patch)
tree8f8ed1297120c537480d9e5d46bfe7452bd8505b /usr.bin/vim/search.c
parentd0d91e2d3d6569e4defdd5178241f28fa678d753 (diff)
Initial import of vim 4.2.
This is meant to replace nvi in the tree. Vim, in general, works better, provides more features, and does not suffer from the license problems being imposed upon nvi. On the other hand, vim lacks a non-visual ex mode, in addition to open mode. This includes the GUI (X11) code, but doesn't try to compile it.
Diffstat (limited to 'usr.bin/vim/search.c')
-rw-r--r--usr.bin/vim/search.c3138
1 files changed, 3138 insertions, 0 deletions
diff --git a/usr.bin/vim/search.c b/usr.bin/vim/search.c
new file mode 100644
index 00000000000..acbec11b596
--- /dev/null
+++ b/usr.bin/vim/search.c
@@ -0,0 +1,3138 @@
+/* $OpenBSD: search.c,v 1.1 1996/09/07 21:40:24 downsj Exp $ */
+/* vi:set ts=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"
+#include "globals.h"
+#include "proto.h"
+#include "option.h"
+#include "ops.h" /* for op_inclusive */
+
+/* modified Henry Spencer's regular expression routines */
+#include "regexp.h"
+
+static int inmacro __ARGS((char_u *, char_u *));
+static int cls __ARGS((void));
+static int skip_chars __ARGS((int, int));
+static void back_in_line __ARGS((void));
+static void find_first_blank __ARGS((FPOS *));
+static void show_pat_in_path __ARGS((char_u *, int,
+ int, int, FILE *, linenr_t *, long));
+static void give_warning __ARGS((char_u *));
+
+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(); called by do_search() and edit().
+ * Highest: do_search(); changes curwin->w_cursor, called by normal().
+ *
+ * The last search pattern is remembered for repeating the same search.
+ * This pattern is shared between the :g, :s, ? and / commands.
+ * This is in myregcomp().
+ *
+ * The actual string matching is done using a heavily modified version of
+ * Henry Spencer's regular expression library.
+ */
+
+/*
+ * Two search patterns are remembered: One for the :substitute command and
+ * one for other searches. last_pattern points to the one that was
+ * used the last time.
+ */
+static char_u *search_pattern = NULL;
+static int search_magic = TRUE;
+static int search_no_scs = FALSE;
+static char_u *subst_pattern = NULL;
+static int subst_magic = TRUE;
+static int subst_no_scs = FALSE;
+static char_u *last_pattern = NULL;
+static int last_magic = TRUE;
+static int last_no_scs = FALSE;
+static char_u *mr_pattern = NULL; /* pattern used by myregcomp() */
+
+static int want_start; /* looking for start of line? */
+
+/*
+ * 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;
+
+/*
+ * translate search pattern for vim_regcomp()
+ *
+ * sub_cmd == RE_SEARCH: save pat in search_pattern (normal search command)
+ * sub_cmd == RE_SUBST: save pat in subst_pattern (:substitute command)
+ * sub_cmd == RE_BOTH: save pat in both patterns (:global command)
+ * which_pat == RE_SEARCH: use previous search pattern if "pat" is NULL
+ * which_pat == RE_SUBST: use previous sustitute pattern if "pat" is NULL
+ * which_pat == 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
+ *
+ */
+ regexp *
+myregcomp(pat, sub_cmd, which_pat, options)
+ char_u *pat;
+ int sub_cmd;
+ int which_pat;
+ int options;
+{
+ rc_did_emsg = FALSE;
+ reg_magic = p_magic;
+ if (pat == NULL || *pat == NUL) /* use previous search pattern */
+ {
+ if (which_pat == RE_SEARCH)
+ {
+ if (search_pattern == NULL)
+ {
+ emsg(e_noprevre);
+ rc_did_emsg = TRUE;
+ return (regexp *) NULL;
+ }
+ pat = search_pattern;
+ reg_magic = search_magic;
+ no_smartcase = search_no_scs;
+ }
+ else if (which_pat == RE_SUBST)
+ {
+ if (subst_pattern == NULL)
+ {
+ emsg(e_nopresub);
+ rc_did_emsg = TRUE;
+ return (regexp *) NULL;
+ }
+ pat = subst_pattern;
+ reg_magic = subst_magic;
+ no_smartcase = subst_no_scs;
+ }
+ else /* which_pat == RE_LAST */
+ {
+ if (last_pattern == NULL)
+ {
+ emsg(e_noprevre);
+ rc_did_emsg = TRUE;
+ return (regexp *) NULL;
+ }
+ pat = last_pattern;
+ reg_magic = last_magic;
+ no_smartcase = last_no_scs;
+ }
+ }
+ else if (options & SEARCH_HIS)
+ add_to_history(1, pat); /* put new pattern in history */
+
+ 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 (sub_cmd == RE_SEARCH || sub_cmd == RE_BOTH)
+ {
+ if (search_pattern != pat)
+ {
+ vim_free(search_pattern);
+ search_pattern = strsave(pat);
+ last_pattern = search_pattern;
+ search_magic = reg_magic;
+ last_magic = reg_magic; /* Magic sticks with the r.e. */
+ search_no_scs = no_smartcase;
+ last_no_scs = no_smartcase;
+ }
+ }
+ /*
+ * substitute or global command
+ */
+ if (sub_cmd == RE_SUBST || sub_cmd == RE_BOTH)
+ {
+ if (subst_pattern != pat)
+ {
+ vim_free(subst_pattern);
+ subst_pattern = strsave(pat);
+ last_pattern = subst_pattern;
+ subst_magic = reg_magic;
+ last_magic = reg_magic; /* Magic sticks with the r.e. */
+ subst_no_scs = no_smartcase;
+ last_no_scs = no_smartcase;
+ }
+ }
+ }
+
+ want_start = (*pat == '^'); /* looking for start of line? */
+ set_reg_ic(pat); /* tell the vim_regexec routine how to search */
+ return vim_regcomp(pat);
+}
+
+/*
+ * 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 (!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;
+}
+
+/*
+ * 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(pos, dir, str, count, options, which_pat)
+ FPOS *pos;
+ int dir;
+ char_u *str;
+ long count;
+ int options;
+ int which_pat;
+{
+ int found;
+ linenr_t lnum; /* no init to shut up Apollo cc */
+ regexp *prog;
+ char_u *ptr;
+ register 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 = myregcomp(str, RE_SEARCH, which_pat,
+ (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 */
+
+ /*
+ * Start searching in current line, unless searching backwards and
+ * we're in column 0 or searching forward and we're past the end of
+ * the line
+ */
+ if (dir == BACKWARD && start_pos.col == 0)
+ {
+ lnum = pos->lnum - 1;
+ at_first_line = FALSE;
+ }
+ else
+ {
+ lnum = pos->lnum;
+ at_first_line = TRUE;
+ }
+
+ for (loop = 0; loop <= 1; ++loop) /* loop twice if 'wrapscan' set */
+ {
+ for ( ; lnum > 0 && lnum <= curbuf->b_ml.ml_line_count;
+ lnum += dir, at_first_line = FALSE)
+ {
+ ptr = ml_get(lnum);
+ /* forward search, first line */
+ if (dir == FORWARD && at_first_line && start_pos.col > 0 &&
+ want_start)
+ continue; /* match not possible */
+
+ /*
+ * Look for a match somewhere in the line.
+ */
+ 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. Same for
+ * matchend.
+ */
+ while ((options & SEARCH_END) ?
+ ((int)(matchend - ptr) - 1 - (int)(*matchend == NUL) <
+ (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 && !want_start)
+ {
+ /*
+ * 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 = curbuf->b_ml.ml_line_count;
+ if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
+ give_warning(top_bot_msg);
+ }
+ else
+ {
+ lnum = 1;
+ if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
+ give_warning(bot_top_msg);
+ }
+ }
+ if (got_int)
+ break;
+ }
+ while (--count > 0 && found); /* stop after count matches or no match */
+
+ vim_free(prog);
+
+ if (!found) /* did not find it */
+ {
+ if (got_int)
+ emsg(e_interr);
+ else if ((options & SEARCH_MSG) == SEARCH_MSG)
+ {
+ if (p_ws)
+ EMSG2("Pattern not found: %s", mr_pattern);
+ else if (lnum == 0)
+ EMSG2("search hit TOP without match for: %s", mr_pattern);
+ else
+ EMSG2("search hit BOTTOM without match for: %s", mr_pattern);
+ }
+ return FAIL;
+ }
+ search_match_len = matchend - match;
+
+ return OK;
+}
+
+/*
+ * Highest level string search function.
+ * Search for the 'count'th occurence of string 'str' in direction 'dirc'
+ * If 'dirc' is 0: use previous dir.
+ * If 'str' 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
+ *
+ * Careful: If lastoffline == TRUE and lastoff == 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(dirc, str, count, options)
+ int dirc;
+ char_u *str;
+ long count;
+ int options;
+{
+ FPOS pos; /* position of the last match */
+ char_u *searchstr;
+ static int lastsdir = '/'; /* previous search direction */
+ static int lastoffline;/* previous/current search has line offset */
+ static int lastend; /* previous/current search set cursor at end */
+ static long lastoff; /* previous/current line or char offset */
+ int old_lastsdir;
+ int old_lastoffline;
+ int old_lastend;
+ long old_lastoff;
+ int retval; /* Return value */
+ register char_u *p;
+ register long c;
+ char_u *dircp;
+ int i = 0; /* init for GCC */
+
+ /*
+ * A line offset is not remembered, this is vi compatible.
+ */
+ if (lastoffline && vim_strchr(p_cpo, CPO_LINEOFF) != NULL)
+ {
+ lastoffline = FALSE;
+ lastoff = 0;
+ }
+
+ /*
+ * Save the values for when (options & SEARCH_KEEP) is used.
+ * (there is no "if ()" around this because gcc wants them initialized)
+ */
+ old_lastsdir = lastsdir;
+ old_lastoffline = lastoffline;
+ old_lastend = lastend;
+ old_lastoff = lastoff;
+
+ pos = curwin->w_cursor; /* start searching at the cursor position */
+
+ /*
+ * Find out the direction of the search.
+ */
+ if (dirc == 0)
+ dirc = lastsdir;
+ else
+ lastsdir = dirc;
+ if (options & SEARCH_REV)
+ {
+#ifdef WIN32
+ /* 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
+ }
+
+ /*
+ * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
+ */
+ for (;;)
+ {
+ searchstr = str;
+ dircp = NULL;
+ /* use previous pattern */
+ if (str == NULL || *str == NUL || *str == dirc)
+ {
+ if (search_pattern == NULL) /* no previous pattern */
+ {
+ emsg(e_noprevre);
+ retval = 0;
+ goto end_do_search;
+ }
+ searchstr = (char_u *)""; /* make myregcomp() use search_pattern */
+ }
+
+ if (str != NULL && *str != NUL) /* look for (new) offset */
+ {
+ /*
+ * Find end of regular expression.
+ * If there is a matching '/' or '?', toss it.
+ */
+ p = skip_regexp(str, dirc);
+ if (*p == dirc)
+ {
+ dircp = p; /* remember where we put the NUL */
+ *p++ = NUL;
+ }
+ lastoffline = FALSE;
+ lastend = FALSE;
+ lastoff = 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 == '-' || isdigit(*p))
+ lastoffline = TRUE;
+ else if ((options & SEARCH_OPT) &&
+ (*p == 'e' || *p == 's' || *p == 'b'))
+ {
+ if (*p == 'e') /* end */
+ lastend = SEARCH_END;
+ ++p;
+ }
+ if (isdigit(*p) || *p == '+' || *p == '-') /* got an offset */
+ {
+ if (isdigit(*p) || isdigit(*(p + 1)))
+ lastoff = atol((char *)p); /* 'nr' or '+nr' or '-nr' */
+ else if (*p == '-') /* single '-' */
+ lastoff = -1;
+ else /* single '+' */
+ lastoff = 1;
+ ++p;
+ while (isdigit(*p)) /* skip number */
+ ++p;
+ }
+ searchcmdlen = p - str; /* compute length of search command
+ for get_address() */
+ str = p; /* put str after search command */
+ }
+
+ if (options & SEARCH_ECHO)
+ {
+ msg_start();
+ msg_outchar(dirc);
+ msg_outtrans(*searchstr == NUL ? search_pattern : searchstr);
+ if (lastoffline || lastend || lastoff)
+ {
+ msg_outchar(dirc);
+ if (lastend)
+ msg_outchar('e');
+ else if (!lastoffline)
+ msg_outchar('s');
+ if (lastoff < 0)
+ {
+ msg_outchar('-');
+ msg_outnum((long)-lastoff);
+ }
+ else if (lastoff > 0 || lastoffline)
+ {
+ msg_outchar('+');
+ msg_outnum((long)lastoff);
+ }
+ }
+ msg_clr_eos();
+ (void)msg_check();
+
+ gotocmdline(FALSE);
+ flushbuf();
+ }
+
+ /*
+ * 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".
+ * This is not done for a line offset, because then we would not be vi
+ * compatible.
+ */
+ if (!lastoffline && lastoff)
+ {
+ if (lastoff > 0)
+ {
+ c = lastoff;
+ while (c--)
+ if ((i = dec(&pos)) != 0)
+ break;
+ if (i == -1) /* at start of buffer */
+ goto_endofbuf(&pos);
+ }
+ else
+ {
+ c = -lastoff;
+ while (c--)
+ if ((i = inc(&pos)) != 0)
+ break;
+ if (i == -1) /* at end of buffer */
+ {
+ pos.lnum = 1;
+ pos.col = 0;
+ }
+ }
+ }
+
+ c = searchit(&pos, dirc == '/' ? FORWARD : BACKWARD, searchstr, count,
+ lastend + (options &
+ (SEARCH_KEEP + SEARCH_HIS + SEARCH_MSG +
+ ((str != NULL && *str == ';') ? 0 : SEARCH_NOOF))),
+ 2);
+ if (dircp != NULL)
+ *dircp = dirc; /* put second '/' or '?' back for normal() */
+ if (c == FAIL)
+ {
+ retval = 0;
+ goto end_do_search;
+ }
+ if (lastend)
+ op_inclusive = TRUE; /* 'e' includes last character */
+
+ retval = 1; /* pattern found */
+
+ /*
+ * Add character and/or line offset
+ */
+ if (!(options & SEARCH_NOOF) || *str == ';')
+ {
+ if (lastoffline) /* Add the offset to the line number. */
+ {
+ c = pos.lnum + lastoff;
+ 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 (lastoff > 0) /* to the right, check for end of line */
+ {
+ p = ml_get_pos(&pos) + 1;
+ c = lastoff;
+ while (c-- && *p++ != NUL)
+ ++pos.col;
+ }
+ else /* to the left, check for start of line */
+ {
+ if ((c = pos.col + lastoff) < 0)
+ c = 0;
+ pos.col = c;
+ }
+ }
+ }
+
+ /*
+ * 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) || str == NULL || *str != ';')
+ break;
+
+ dirc = *++str;
+ if (dirc != '?' && dirc != '/')
+ {
+ retval = 0;
+ EMSG("Expected '?' or '/' after ';'");
+ goto end_do_search;
+ }
+ ++str;
+ }
+
+ if (options & SEARCH_MARK)
+ setpcmark();
+ curwin->w_cursor = pos;
+ curwin->w_set_curswant = TRUE;
+
+end_do_search:
+ if (options & SEARCH_KEEP)
+ {
+ lastsdir = old_lastsdir;
+ lastoffline = old_lastoffline;
+ lastend = old_lastend;
+ lastoff = old_lastoff;
+ }
+ return retval;
+}
+
+/*
+ * search_for_exact_line(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 will never match.
+ * Return OK for success, or FAIL if no line found.
+ */
+ int
+search_for_exact_line(pos, dir, pat)
+ FPOS *pos;
+ int dir;
+ char_u *pat;
+{
+ linenr_t start = 0;
+ char_u *ptr;
+ char_u *p;
+
+ if (curbuf->b_ml.ml_line_count == 0)
+ return FAIL;
+ for (;;)
+ {
+ pos->lnum += dir;
+ if (pos->lnum < 1)
+ {
+ if (p_ws)
+ {
+ pos->lnum = curbuf->b_ml.ml_line_count;
+ if (!shortmess(SHM_SEARCH))
+ give_warning(top_bot_msg);
+ }
+ else
+ {
+ pos->lnum = 1;
+ break;
+ }
+ }
+ else if (pos->lnum > curbuf->b_ml.ml_line_count)
+ {
+ if (p_ws)
+ {
+ pos->lnum = 1;
+ if (!shortmess(SHM_SEARCH))
+ give_warning(bot_top_msg);
+ }
+ else
+ {
+ pos->lnum = 1;
+ break;
+ }
+ }
+ if (pos->lnum == start)
+ break;
+ if (start == 0)
+ start = pos->lnum;
+ ptr = ml_get(pos->lnum);
+ p = skipwhite(ptr);
+ pos->col = p - ptr;
+ if (*p != NUL && STRNCMP(p, pat, STRLEN(pat)) == 0)
+ return OK;
+ else if (*p != NUL && p_ic)
+ {
+ ptr = pat;
+ while (*p && TO_LOWER(*p) == TO_LOWER(*ptr))
+ {
+ ++p;
+ ++ptr;
+ }
+ if (*ptr == NUL)
+ return OK;
+ }
+ }
+ return FAIL;
+}
+
+/*
+ * Character Searches
+ */
+
+/*
+ * searchc(c, dir, type, count)
+ *
+ * Search for character 'c', in direction 'dir'. If 'type' is 0, move to the
+ * position of the character, otherwise move to just before the char.
+ * Repeat this 'count' times.
+ */
+ int
+searchc(c, dir, type, count)
+ int c;
+ register int dir;
+ int type;
+ long count;
+{
+ static int lastc = NUL; /* last character searched for */
+ static int lastcdir; /* last direction of character search */
+ static int lastctype; /* last type of search ("find" or "to") */
+ register int col;
+ char_u *p;
+ int len;
+
+ if (c != NUL) /* normal search: remember args for repeat */
+ {
+ if (!KeyStuffed) /* don't remember when redoing */
+ {
+ lastc = c;
+ lastcdir = dir;
+ lastctype = type;
+ }
+ }
+ else /* repeat previous search */
+ {
+ if (lastc == NUL)
+ return FALSE;
+ if (dir) /* repeat in opposite direction */
+ dir = -lastcdir;
+ else
+ dir = lastcdir;
+ type = lastctype;
+ c = lastc;
+ }
+
+ p = ml_get_curline();
+ col = curwin->w_cursor.col;
+ len = STRLEN(p);
+
+ while (count--)
+ {
+ for (;;)
+ {
+ if ((col += dir) < 0 || col >= len)
+ return FALSE;
+ if (p[col] == c)
+ break;
+ }
+ }
+ if (type)
+ col -= dir;
+ curwin->w_cursor.col = col;
+ return TRUE;
+}
+
+/*
+ * "Other" Searches
+ */
+
+/*
+ * findmatch - find the matching paren or brace
+ *
+ * Improvement over vi: Braces inside quotes are ignored.
+ */
+ FPOS *
+findmatch(initc)
+ int initc;
+{
+ return findmatchlimit(initc, 0, 0);
+}
+
+/*
+ * findmatchlimit -- find the matching paren or brace, if it exists within
+ * maxtravel lines of here. A maxtravel of 0 means search until you fall off
+ * the edge of the file.
+ *
+ * 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!)
+ */
+
+ FPOS *
+findmatchlimit(initc, flags, maxtravel)
+ int initc;
+ int flags;
+ int maxtravel;
+{
+ static FPOS pos; /* current search position */
+ int findc; /* matching brace */
+ int c;
+ int count = 0; /* cumulative number of braces */
+ int idx = 0; /* init for gcc */
+ static char_u table[6] = {'(', ')', '[', ']', '{', '}'};
+#ifdef RIGHTLEFT
+ static char_u table_rl[6] = {')', '(', ']', '[', '}', '{'};
+#endif
+ int inquote = FALSE; /* TRUE when inside quotes */
+ register 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 */
+ FPOS 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 dir; /* Direction to search */
+
+ pos = curwin->w_cursor;
+ linep = ml_get(pos.lnum);
+
+ cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != 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 == '*')
+ {
+ comment_dir = dir;
+ if (initc == '/')
+ ignore_cend = TRUE;
+ idx = (dir == FORWARD) ? 0 : 1;
+ initc = NUL;
+ }
+ else if (initc != '#' && initc != NUL)
+ {
+ for (idx = 0; idx < 6; ++idx)
+#ifdef RIGHTLEFT
+ if ((curwin->w_p_rl ? table_rl : table)[idx] == initc)
+ {
+ initc = (curwin->w_p_rl ? table_rl : table)[idx = idx ^ 1];
+
+#else
+ if (table[idx] == initc)
+ {
+ initc = table[idx = idx ^ 1];
+#endif
+ break;
+ }
+ if (idx == 6) /* invalid initc! */
+ return NULL;
+ }
+ /*
+ * Either initc is '#', or no initc was given and we need to look under the
+ * cursor.
+ */
+ else
+ {
+ if (initc == '#')
+ {
+ hash_dir = dir;
+ }
+ else
+ {
+ /*
+ * initc was not given, must look for something to match under
+ * or near the cursor.
+ */
+ if (linep[0] == '#' && pos.col == 0)
+ {
+ /* If it's not #if, #else etc, we should look for a brace
+ * instead */
+ for (c = 1; vim_iswhite(linep[c]); c++)
+ ;
+ if (STRNCMP(linep + c, "if", (size_t)2) == 0 ||
+ STRNCMP(linep + c, "endif", (size_t)5) == 0 ||
+ STRNCMP(linep + c, "el", (size_t)2) == 0)
+ hash_dir = 1;
+ }
+
+ /*
+ * Are we on a comment?
+ */
+ else if (linep[pos.col] == '/')
+ {
+ if (linep[pos.col + 1] == '*')
+ {
+ comment_dir = 1;
+ idx = 0;
+ pos.col++;
+ }
+ else if (pos.col > 0 && linep[pos.col - 1] == '*')
+ {
+ comment_dir = -1;
+ idx = 1;
+ pos.col--;
+ }
+ }
+ else if (linep[pos.col] == '*')
+ {
+ if (linep[pos.col + 1] == '/')
+ {
+ comment_dir = -1;
+ idx = 1;
+ }
+ else if (pos.col > 0 && linep[pos.col - 1] == '/')
+ {
+ comment_dir = 1;
+ idx = 0;
+ }
+ }
+
+ /*
+ * 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
+ */
+ linep = ml_get(pos.lnum);
+ idx = 6; /* error if this line is empty */
+ for (;;)
+ {
+ initc = linep[pos.col];
+ if (initc == NUL)
+ break;
+
+ for (idx = 0; idx < 6; ++idx)
+#ifdef RIGHTLEFT
+ if ((curwin->w_p_rl ? table_rl : table)[idx] == initc)
+#else
+ if (table[idx] == initc)
+#endif
+ break;
+ if (idx != 6)
+ break;
+ ++pos.col;
+ }
+ if (idx == 6)
+ {
+ if (linep[0] == '#')
+ hash_dir = 1;
+ else
+ return NULL;
+ }
+ }
+ }
+ if (hash_dir)
+ {
+ /*
+ * Look for matching #if, #else, #elif, or #endif
+ */
+ op_motion_type = MLINE; /* Linewise for this case only */
+ if (initc != '#')
+ {
+ ptr = skipwhite(linep + 1);
+ if (STRNCMP(ptr, "if", (size_t)2) == 0 ||
+ STRNCMP(ptr, "el", (size_t)2) == 0)
+ hash_dir = 1;
+ else if (STRNCMP(ptr, "endif", (size_t)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();
+ if (linep[0] != '#')
+ continue;
+ ptr = linep + 1;
+ while (*ptr == ' ' || *ptr == TAB)
+ ptr++;
+ if (hash_dir > 0)
+ {
+ if (STRNCMP(ptr, "if", (size_t)2) == 0)
+ count++;
+ else if (STRNCMP(ptr, "el", (size_t)2) == 0)
+ {
+ if (count == 0)
+ return &pos;
+ }
+ else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
+ {
+ if (count == 0)
+ return &pos;
+ count--;
+ }
+ }
+ else
+ {
+ if (STRNCMP(ptr, "if", (size_t)2) == 0)
+ {
+ if (count == 0)
+ return &pos;
+ count--;
+ }
+ else if (initc == '#' && STRNCMP(ptr, "el", (size_t)2) == 0)
+ {
+ if (count == 0)
+ return &pos;
+ }
+ else if (STRNCMP(ptr, "endif", (size_t)5) == 0)
+ count++;
+ }
+ }
+ return NULL;
+ }
+ }
+
+#ifdef RIGHTLEFT
+ findc = (curwin->w_p_rl ? table_rl : table)[idx ^ 1];
+#else
+ findc = table[idx ^ 1]; /* get matching brace */
+#endif
+ idx &= 1;
+
+ do_quotes = -1;
+ start_in_quotes = MAYBE;
+ while (!got_int)
+ {
+ /*
+ * Go to the next position, forward or backward. We could use
+ * inc() and dec() here, but that is much slower
+ */
+ if (idx) /* backward search */
+ {
+ if (pos.col == 0) /* at start of line, go to prev. one */
+ {
+ if (pos.lnum == 1) /* start of file */
+ break;
+ --pos.lnum;
+
+ if (maxtravel && traveled++ > maxtravel)
+ break;
+
+ linep = ml_get(pos.lnum);
+ pos.col = STRLEN(linep); /* put pos.col on trailing NUL */
+ do_quotes = -1;
+ line_breakcheck();
+ }
+ else
+ --pos.col;
+ }
+ else /* forward search */
+ {
+ if (linep[pos.col] == NUL) /* at end of line, go to next one */
+ {
+ if (pos.lnum == curbuf->b_ml.ml_line_count) /* end of file */
+ break;
+ ++pos.lnum;
+
+ if (maxtravel && traveled++ > maxtravel)
+ break;
+
+ linep = ml_get(pos.lnum);
+ pos.col = 0;
+ do_quotes = -1;
+ line_breakcheck();
+ }
+ 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 */
+ if (comment_dir == 1)
+ {
+ if (linep[pos.col] == '*' && linep[pos.col + 1] == '/')
+ {
+ pos.col++;
+ return &pos;
+ }
+ }
+ else /* Searching backwards */
+ {
+ /*
+ * A comment may contain slash-star, it may also start or end
+ * with slash-star-slash. I'm not using real examples though
+ * because "gcc -Wall" would complain -- webb
+ */
+ if (pos.col == 0)
+ continue;
+ else if (linep[pos.col - 1] == '/' && linep[pos.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] == '/')
+ 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 '"'
+ */
+ at_start = do_quotes;
+ for (ptr = linep; *ptr; ++ptr)
+ {
+ if (ptr == linep + curwin->w_cursor.col)
+ at_start = (do_quotes & 1);
+ if (*ptr == '"' && (ptr == linep || ptr[-1] != '\\') &&
+ (ptr == linep || ptr[-1] != '\'' || ptr[1] != '\''))
+ ++do_quotes;
+ }
+ 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)
+ {
+ inquote = !at_start;
+ if (inquote)
+ start_in_quotes = TRUE;
+ }
+ else if (idx) /* backward search */
+ inquote = TRUE;
+ }
+ if (pos.lnum > 1)
+ {
+ ptr = ml_get(pos.lnum - 1);
+ if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\')
+ {
+ do_quotes = 1;
+ if (start_in_quotes == MAYBE)
+ {
+ inquote = at_start;
+ if (inquote)
+ start_in_quotes = TRUE;
+ }
+ else if (!idx) /* forward search */
+ inquote = TRUE;
+ }
+ }
+ }
+ }
+ 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?
+ */
+ switch (c = linep[pos.col])
+ {
+ 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 a backslash is ignored */
+ if (do_quotes && (pos.col == 0 || linep[pos.col - 1] != '\\'))
+ {
+ 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.
+ */
+ case '\'':
+ if (vim_strchr(p_cpo, CPO_MATCH) == NULL)
+ {
+ if (idx) /* backward search */
+ {
+ if (pos.col > 1)
+ {
+ if (linep[pos.col - 2] == '\'')
+ pos.col -= 2;
+ else if (linep[pos.col - 2] == '\\' &&
+ pos.col > 2 && linep[pos.col - 3] == '\'')
+ pos.col -= 3;
+ }
+ }
+ else if (linep[pos.col + 1]) /* forward search */
+ {
+ if (linep[pos.col + 1] == '\\' &&
+ linep[pos.col + 2] && linep[pos.col + 3] == '\'')
+ pos.col += 3;
+ else if (linep[pos.col + 2] == '\'')
+ pos.col += 2;
+ }
+ }
+ break;
+
+ default:
+ /* Check for match outside of quotes, and inside of
+ * quotes when the start is also inside of quotes */
+ if (!inquote || start_in_quotes == TRUE)
+ {
+ if (c == initc)
+ count++;
+ else if (c == findc)
+ {
+ if (count == 0)
+ return &pos;
+ count--;
+ }
+ }
+ }
+ }
+
+ if (comment_dir == -1 && count > 0)
+ {
+ pos = match_pos;
+ return &pos;
+ }
+ return (FPOS *) NULL; /* never found it */
+}
+
+/*
+ * Move cursor briefly to character matching the one under the cursor.
+ * Show the match only if it is visible on the screen.
+ */
+ void
+showmatch()
+{
+ FPOS *lpos, csave;
+ colnr_t vcol;
+
+ if ((lpos = findmatch(NUL)) == NULL) /* no match, so beep */
+ beep_flush();
+ else if (lpos->lnum >= curwin->w_topline)
+ {
+ if (!curwin->w_p_wrap)
+ getvcol(curwin, lpos, NULL, &vcol, NULL);
+ if (curwin->w_p_wrap || (vcol >= curwin->w_leftcol &&
+ vcol < curwin->w_leftcol + Columns))
+ {
+ updateScreen(VALID_TO_CURSCHAR); /* show the new char first */
+ csave = curwin->w_cursor;
+ curwin->w_cursor = *lpos; /* move to matching char */
+ cursupdate();
+ showruler(0);
+ setcursor();
+ cursor_on(); /* make sure that the cursor is shown */
+ flushbuf();
+
+ /*
+ * brief pause, unless 'm' is present in 'cpo' and a character is
+ * available.
+ */
+ if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL)
+ mch_delay(500L, TRUE);
+ else if (!char_avail())
+ mch_delay(500L, FALSE);
+ curwin->w_cursor = csave; /* restore cursor position */
+ cursupdate();
+ }
+ }
+}
+
+/*
+ * findsent(dir, count) - Find the start of the next sentence in direction
+ * 'dir' Sentences are supposed to end in ".", "!" or "?" followed by white
+ * space or a line break. Also stop at an empty line.
+ * Return OK if the next sentence was found.
+ */
+ int
+findsent(dir, count)
+ int dir;
+ long count;
+{
+ FPOS pos, tpos;
+ register int c;
+ int (*func) __PARMS((FPOS *));
+ int startlnum;
+ int noskip = FALSE; /* do not skip blanks */
+
+ pos = curwin->w_cursor;
+ if (dir == FORWARD)
+ func = incl;
+ else
+ func = decl;
+
+ while (count--)
+ {
+ /*
+ * if on an empty line, skip upto a non-empty line
+ */
+ if (gchar(&pos) == NUL)
+ {
+ do
+ if ((*func)(&pos) == -1)
+ break;
+ while (gchar(&pos) == NUL);
+ if (dir == FORWARD)
+ goto found;
+ }
+ /*
+ * if on the start of a paragraph or a section and searching forward,
+ * go to the next line
+ */
+ else if (dir == FORWARD && pos.col == 0 &&
+ startPS(pos.lnum, NUL, FALSE))
+ {
+ if (pos.lnum == curbuf->b_ml.ml_line_count)
+ return FAIL;
+ ++pos.lnum;
+ goto found;
+ }
+ else if (dir == BACKWARD)
+ decl(&pos);
+
+ /* go back to the previous non-blank char */
+ while ((c = gchar(&pos)) == ' ' || c == '\t' ||
+ (dir == BACKWARD && vim_strchr((char_u *)".!?)]\"'", c) != NULL))
+ {
+ if (decl(&pos) == -1)
+ break;
+ /* when going forward: Stop in front of empty line */
+ if (lineempty(pos.lnum) && dir == FORWARD)
+ {
+ incl(&pos);
+ goto found;
+ }
+ }
+
+ /* remember the line where the search started */
+ startlnum = pos.lnum;
+
+ for (;;) /* find end of sentence */
+ {
+ c = gchar(&pos);
+ if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE)))
+ {
+ if (dir == BACKWARD && pos.lnum != startlnum)
+ ++pos.lnum;
+ break;
+ }
+ if (c == '.' || c == '!' || c == '?')
+ {
+ tpos = pos;
+ do
+ if ((c = inc(&tpos)) == -1)
+ break;
+ while (vim_strchr((char_u *)")]\"'", c = gchar(&tpos)) != NULL);
+ if (c == -1 || c == ' ' || c == '\t' || c == NUL)
+ {
+ pos = tpos;
+ if (gchar(&pos) == NUL) /* skip NUL at EOL */
+ inc(&pos);
+ break;
+ }
+ }
+ if ((*func)(&pos) == -1)
+ {
+ if (count)
+ return FAIL;
+ noskip = TRUE;
+ break;
+ }
+ }
+found:
+ /* skip white space */
+ while (!noskip && ((c = gchar(&pos)) == ' ' || c == '\t'))
+ if (incl(&pos) == -1)
+ break;
+ }
+
+ setpcmark();
+ curwin->w_cursor = pos;
+ return OK;
+}
+
+/*
+ * findpar(dir, count, what) - Find the next paragraph in direction 'dir'
+ * Paragraphs are currently supposed to be separated by empty lines.
+ * Return TRUE if the next paragraph was found.
+ * If 'what' is '{' or '}' we go to the next section.
+ * If 'both' is TRUE also stop at '}'.
+ */
+ int
+findpar(dir, count, what, both)
+ register int dir;
+ long count;
+ int what;
+ int both;
+{
+ register linenr_t curr;
+ int did_skip; /* TRUE after separating lines have
+ been skipped */
+ int first; /* TRUE on first line */
+
+ curr = curwin->w_cursor.lnum;
+
+ while (count--)
+ {
+ did_skip = FALSE;
+ for (first = TRUE; ; first = FALSE)
+ {
+ if (*ml_get(curr) != NUL)
+ did_skip = TRUE;
+
+ if (!first && did_skip && startPS(curr, what, both))
+ break;
+
+ if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count)
+ {
+ if (count)
+ return FALSE;
+ curr -= dir;
+ break;
+ }
+ }
+ }
+ setpcmark();
+ if (both && *ml_get(curr) == '}') /* include line with '}' */
+ ++curr;
+ curwin->w_cursor.lnum = curr;
+ if (curr == curbuf->b_ml.ml_line_count)
+ {
+ if ((curwin->w_cursor.col = STRLEN(ml_get(curr))) != 0)
+ {
+ --curwin->w_cursor.col;
+ op_inclusive = TRUE;
+ }
+ }
+ else
+ curwin->w_cursor.col = 0;
+ return TRUE;
+}
+
+/*
+ * check if the string 's' is a nroff macro that is in option 'opt'
+ */
+ static int
+inmacro(opt, s)
+ char_u *opt;
+ register char_u *s;
+{
+ register char_u *macro;
+
+ for (macro = opt; macro[0]; ++macro)
+ {
+ if (macro[0] == s[0] && (((s[1] == NUL || s[1] == ' ') &&
+ (macro[1] == NUL || macro[1] == ' ')) || macro[1] == s[1]))
+ break;
+ ++macro;
+ if (macro[0] == NUL)
+ break;
+ }
+ return (macro[0] != NUL);
+}
+
+/*
+ * startPS: return TRUE if line 'lnum' is the start of a section or paragraph.
+ * If 'para' is '{' or '}' only check for sections.
+ * If 'both' is TRUE also stop at '}'
+ */
+ int
+startPS(lnum, para, both)
+ linenr_t lnum;
+ int para;
+ int both;
+{
+ register char_u *s;
+
+ s = ml_get(lnum);
+ if (*s == para || *s == '\f' || (both && *s == '}'))
+ return TRUE;
+ if (*s == '.' && (inmacro(p_sections, s + 1) ||
+ (!para && inmacro(p_para, s + 1))))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * The following routines do the word searches performed by the 'w', 'W',
+ * 'b', 'B', 'e', and 'E' commands.
+ */
+
+/*
+ * To perform these searches, characters are placed into one of three
+ * classes, and transitions between classes determine word boundaries.
+ *
+ * The classes are:
+ *
+ * 0 - white space
+ * 1 - keyword charactes (letters, digits and underscore)
+ * 2 - everything else
+ */
+
+static int stype; /* type of the word motion being performed */
+
+/*
+ * cls() - returns the class of character at curwin->w_cursor
+ *
+ * The 'type' of the current search modifies the classes of characters if a
+ * 'W', 'B', or 'E' motion is being done. In this case, chars. from class 2
+ * are reported as class 1 since only white space boundaries are of interest.
+ */
+ static int
+cls()
+{
+ register int c;
+
+ c = gchar_cursor();
+ if (c == ' ' || c == '\t' || c == NUL)
+ return 0;
+
+ if (iswordchar(c))
+ return 1;
+
+ /*
+ * If stype is non-zero, report these as class 1.
+ */
+ return (stype == 0) ? 2 : 1;
+}
+
+
+/*
+ * fwd_word(count, type, eol) - move forward one word
+ *
+ * Returns FAIL if the cursor was already at the end of the file.
+ * If eol is TRUE, last word stops at end of line (for operators).
+ */
+ int
+fwd_word(count, type, eol)
+ long count;
+ int type;
+ int eol;
+{
+ int sclass; /* starting class */
+ int i;
+ int last_line;
+
+ stype = type;
+ while (--count >= 0)
+ {
+ sclass = cls();
+
+ /*
+ * We always move at least one character, unless on the last character
+ * in the buffer.
+ */
+ last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
+ i = inc_cursor();
+ if (i == -1 || (i == 1 && last_line))
+ /* started at last char in file */
+ return FAIL;
+ if (i == 1 && eol && count == 0) /* started at last char in line */
+ return OK;
+
+ /*
+ * Go one char past end of current word (if any)
+ */
+ if (sclass != 0)
+ while (cls() == sclass)
+ {
+ i = inc_cursor();
+ if (i == -1 || (i == 1 && eol && count == 0))
+ return OK;
+ }
+
+ /*
+ * go to next non-white
+ */
+ while (cls() == 0)
+ {
+ /*
+ * We'll stop if we land on a blank line
+ */
+ if (curwin->w_cursor.col == 0 && *ml_get_curline() == NUL)
+ break;
+
+ i = inc_cursor();
+ if (i == -1 || (i == 1 && eol && count == 0))
+ return OK;
+ }
+ }
+ return OK;
+}
+
+/*
+ * bck_word() - move backward 'count' words
+ *
+ * If stop is TRUE and we are already on the start of a word, move one less.
+ *
+ * Returns FAIL if top of the file was reached.
+ */
+ int
+bck_word(count, type, stop)
+ long count;
+ int type;
+ int stop;
+{
+ int sclass; /* starting class */
+
+ stype = type;
+ while (--count >= 0)
+ {
+ sclass = cls();
+ if (dec_cursor() == -1) /* started at start of file */
+ return FAIL;
+
+ if (!stop || sclass == cls() || sclass == 0)
+ {
+ /*
+ * Skip white space before the word.
+ * Stop on an empty line.
+ */
+ while (cls() == 0)
+ {
+ if (curwin->w_cursor.col == 0 &&
+ lineempty(curwin->w_cursor.lnum))
+ goto finished;
+
+ if (dec_cursor() == -1) /* hit start of file, stop here */
+ return OK;
+ }
+
+ /*
+ * Move backward to start of this word.
+ */
+ if (skip_chars(cls(), BACKWARD))
+ return OK;
+ }
+
+ inc_cursor(); /* overshot - forward one */
+finished:
+ stop = FALSE;
+ }
+ return OK;
+}
+
+/*
+ * end_word() - move to the end of the word
+ *
+ * There is an apparent bug in the 'e' motion of the real vi. At least on the
+ * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e'
+ * motion crosses blank lines. When the real vi crosses a blank line in an
+ * 'e' motion, the cursor is placed on the FIRST character of the next
+ * non-blank line. The 'E' command, however, works correctly. Since this
+ * appears to be a bug, I have not duplicated it here.
+ *
+ * Returns FAIL if end of the file was reached.
+ *
+ * If stop is TRUE and we are already on the end of a word, move one less.
+ * If empty is TRUE stop on an empty line.
+ */
+ int
+end_word(count, type, stop, empty)
+ long count;
+ int type;
+ int stop;
+ int empty;
+{
+ int sclass; /* starting class */
+
+ stype = type;
+ while (--count >= 0)
+ {
+ sclass = cls();
+ if (inc_cursor() == -1)
+ return FAIL;
+
+ /*
+ * If we're in the middle of a word, we just have to move to the end
+ * of it.
+ */
+ if (cls() == sclass && sclass != 0)
+ {
+ /*
+ * Move forward to end of the current word
+ */
+ if (skip_chars(sclass, FORWARD))
+ return FAIL;
+ }
+ else if (!stop || sclass == 0)
+ {
+ /*
+ * We were at the end of a word. Go to the end of the next word.
+ * First skip white space, if 'empty' is TRUE, stop at empty line.
+ */
+ while (cls() == 0)
+ {
+ if (empty && curwin->w_cursor.col == 0 &&
+ lineempty(curwin->w_cursor.lnum))
+ goto finished;
+ if (inc_cursor() == -1) /* hit end of file, stop here */
+ return FAIL;
+ }
+
+ /*
+ * Move forward to the end of this word.
+ */
+ if (skip_chars(cls(), FORWARD))
+ return FAIL;
+ }
+ dec_cursor(); /* overshot - one char backward */
+finished:
+ stop = FALSE; /* we move only one word less */
+ }
+ return OK;
+}
+
+/*
+ * bckend_word(count, type) - move back to the end of the word
+ *
+ * If 'eol' is TRUE, stop at end of line.
+ *
+ * Returns FAIL if start of the file was reached.
+ */
+ int
+bckend_word(count, type, eol)
+ long count;
+ int type;
+ int eol;
+{
+ int sclass; /* starting class */
+ int i;
+
+ stype = type;
+ while (--count >= 0)
+ {
+ sclass = cls();
+ if ((i = dec_cursor()) == -1)
+ return FAIL;
+ if (eol && i == 1)
+ return OK;
+
+ /*
+ * Move backward to before the start of this word.
+ */
+ if (sclass != 0)
+ {
+ while (cls() == sclass)
+ if ((i = dec_cursor()) == -1 || (eol && i == 1))
+ return OK;
+ }
+
+ /*
+ * Move backward to end of the previous word
+ */
+ while (cls() == 0)
+ {
+ if (curwin->w_cursor.col == 0 && lineempty(curwin->w_cursor.lnum))
+ break;
+ if ((i = dec_cursor()) == -1 || (eol && i == 1))
+ return OK;
+ }
+ }
+ return OK;
+}
+
+ static int
+skip_chars(cclass, dir)
+ int cclass;
+ int dir;
+{
+ while (cls() == cclass)
+ if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1)
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * Go back to the start of the word or the start of white space
+ */
+ static void
+back_in_line()
+{
+ int sclass; /* starting class */
+
+ sclass = cls();
+ for (;;)
+ {
+ if (curwin->w_cursor.col == 0) /* stop at start of line */
+ break;
+ --curwin->w_cursor.col;
+ if (cls() != sclass) /* stop at start of word */
+ {
+ ++curwin->w_cursor.col;
+ break;
+ }
+ }
+}
+
+/*
+ * Find word under cursor, cursor at end
+ */
+ int
+current_word(count, type)
+ long count;
+ int type;
+{
+ FPOS start;
+ FPOS pos;
+ int inclusive = TRUE;
+
+ stype = type;
+
+ /*
+ * When visual area is bigger than one character: Extend it.
+ */
+ if (VIsual_active && !equal(curwin->w_cursor, VIsual))
+ {
+ if (lt(curwin->w_cursor, VIsual))
+ {
+ if (decl(&curwin->w_cursor) == -1)
+ return FAIL;
+ if (cls() == 0)
+ {
+ if (bckend_word(count, type, TRUE) == FAIL)
+ return FAIL;
+ (void)incl(&curwin->w_cursor);
+ }
+ else
+ {
+ if (bck_word(count, type, TRUE) == FAIL)
+ return FAIL;
+ }
+ }
+ else
+ {
+ if (incl(&curwin->w_cursor) == -1)
+ return FAIL;
+ if (cls() == 0)
+ {
+ if (fwd_word(count, type, TRUE) == FAIL)
+ return FAIL;
+ (void)oneleft();
+ }
+ else
+ {
+ if (end_word(count, type, TRUE, TRUE) == FAIL)
+ return FAIL;
+ }
+ }
+ return OK;
+ }
+
+ /*
+ * Go to start of current word or white space.
+ */
+ back_in_line();
+ start = curwin->w_cursor;
+
+ /*
+ * If the start is on white space, find end of word.
+ * Otherwise find start of next word.
+ */
+ if (cls() == 0)
+ {
+ if (end_word(count, type, TRUE, TRUE) == FAIL)
+ return FAIL;
+ }
+ else
+ {
+ if (fwd_word(count, type, TRUE) == FAIL)
+ return FAIL;
+ /*
+ * If end is just past a new-line, we don't want to include the first
+ * character on the line
+ */
+ if (oneleft() == FAIL) /* put cursor on last char of area */
+ inclusive = FALSE;
+ else
+ {
+ pos = curwin->w_cursor; /* save cursor position */
+ /*
+ * If we don't include white space at the end, move the start to
+ * include some white space there. This makes "d." work better on
+ * the last word in a sentence. Don't delete white space at start
+ * of line (indent).
+ */
+ if (cls() != 0)
+ {
+ curwin->w_cursor = start;
+ if (oneleft() == OK)
+ {
+ back_in_line();
+ if (cls() == 0 && curwin->w_cursor.col > 0)
+ start = curwin->w_cursor;
+ }
+ }
+ curwin->w_cursor = pos; /* put cursor back at end */
+ }
+ }
+ if (VIsual_active)
+ {
+ /* should do something when inclusive == FALSE ! */
+ VIsual = start;
+ VIsual_mode = 'v';
+ update_curbuf(NOT_VALID); /* update the inversion */
+ }
+ else
+ {
+ curbuf->b_op_start = start;
+ op_motion_type = MCHAR;
+ op_inclusive = inclusive;
+ }
+ return OK;
+}
+
+/*
+ * Find sentence under the cursor, cursor at end.
+ */
+ int
+current_sent(count)
+ long count;
+{
+ FPOS start;
+ FPOS pos;
+ int start_blank;
+ int c;
+
+ pos = curwin->w_cursor;
+ start = pos;
+
+ /*
+ * When visual area is bigger than one character: Extend it.
+ */
+ if (VIsual_active && !equal(curwin->w_cursor, VIsual))
+ {
+ if (lt(pos, VIsual))
+ {
+ /*
+ * Do a "sentence backward" on the next character.
+ * If we end up on the same position, we were already at the start
+ * of a sentence
+ */
+ if (incl(&curwin->w_cursor) == -1)
+ return FAIL;
+ findsent(BACKWARD, 1L);
+ start = curwin->w_cursor;
+ if (count > 1)
+ findsent(BACKWARD, count - 1);
+ /*
+ * When at start of sentence: Include blanks in front of sentence.
+ * Use current_word() to cross line boundaries.
+ * If we don't end up on a blank or on an empty line, go back to
+ * the start of the previous sentence.
+ */
+ if (equal(pos, start))
+ {
+ current_word(1L, 0);
+ c = gchar_cursor();
+ if (c != NUL && !vim_iswhite(c))
+ findsent(BACKWARD, 1L);
+ }
+
+ }
+ else
+ {
+ /*
+ * When one char before start of sentence: Don't include blanks in
+ * front of next sentence.
+ * Else go count sentences forward.
+ */
+ findsent(FORWARD, 1L);
+ incl(&pos);
+ if (equal(pos, curwin->w_cursor))
+ {
+ findsent(FORWARD, count);
+ find_first_blank(&curwin->w_cursor);
+ }
+ else if (count > 1)
+ findsent(FORWARD, count - 1);
+ decl(&curwin->w_cursor);
+ }
+ return OK;
+ }
+
+ /*
+ * Find start of next sentence.
+ */
+ findsent(FORWARD, 1L);
+
+ /*
+ * If cursor started on blank, check if it is just before the start of the
+ * next sentence.
+ */
+ while (vim_iswhite(gchar(&pos)))
+ incl(&pos);
+ if (equal(pos, curwin->w_cursor))
+ {
+ start_blank = TRUE;
+ /*
+ * go back to first blank
+ */
+ while (decl(&start) != -1)
+ {
+ if (!vim_iswhite(gchar(&start)))
+ {
+ incl(&start);
+ break;
+ }
+ }
+ }
+ else
+ {
+ start_blank = FALSE;
+ findsent(BACKWARD, 1L);
+ start = curwin->w_cursor;
+ }
+ findsent(FORWARD, count);
+
+ /*
+ * If the blank in front of the sentence is included, exclude the blanks
+ * at the end of the sentence, go back to the first blank.
+ */
+ if (start_blank)
+ find_first_blank(&curwin->w_cursor);
+ else
+ {
+ /*
+ * If there are no trailing blanks, try to include leading blanks
+ */
+ pos = curwin->w_cursor;
+ decl(&pos);
+ if (!vim_iswhite(gchar(&pos)))
+ find_first_blank(&start);
+ }
+
+ if (VIsual_active)
+ {
+ VIsual = start;
+ VIsual_mode = 'v';
+ decl(&curwin->w_cursor); /* don't include the cursor char */
+ update_curbuf(NOT_VALID); /* update the inversion */
+ }
+ else
+ {
+ curbuf->b_op_start = start;
+ op_motion_type = MCHAR;
+ op_inclusive = FALSE;
+ }
+ return OK;
+}
+
+ int
+current_block(what, count)
+ int what; /* '(' or '{' */
+ long count;
+{
+ FPOS old_pos;
+ FPOS *pos = NULL;
+ FPOS start_pos;
+ FPOS *end_pos;
+ FPOS old_start, old_end;
+ int inclusive = FALSE;
+ int other;
+
+ old_pos = curwin->w_cursor;
+ if (what == '{')
+ other = '}';
+ else
+ other = ')';
+
+ old_end = curwin->w_cursor; /* remember where we started */
+ old_start = old_end;
+
+ /*
+ * If we start on '(', '{', ')' or '}', use the whole block inclusive.
+ */
+ if (!VIsual_active || (VIsual.lnum == curwin->w_cursor.lnum &&
+ VIsual.col == curwin->w_cursor.col))
+ {
+ if (what == '{') /* ignore indent */
+ while (inindent(1))
+ if (inc_cursor() != 0)
+ break;
+ if (gchar_cursor() == what) /* cursor on '(' or '{' */
+ {
+ ++curwin->w_cursor.col;
+ inclusive = TRUE;
+ }
+ else if (gchar_cursor() == other) /* cursor on ')' or '}' */
+ inclusive = TRUE;
+ }
+ else if (lt(VIsual, curwin->w_cursor))
+ {
+ old_start = VIsual;
+ curwin->w_cursor = VIsual; /* cursor at low end of Visual */
+ }
+ else
+ old_end = VIsual;
+
+ /*
+ * Search backwards for unclosed '(' or '{'.
+ * put this position in start_pos.
+ */
+ while (count--)
+ {
+ if ((pos = findmatch(what)) == NULL)
+ break;
+ curwin->w_cursor = *pos;
+ start_pos = *pos; /* the findmatch for end_pos will overwrite *pos */
+ }
+
+ /*
+ * Search for matching ')' or '}'.
+ * Put this position in curwin->w_cursor.
+ */
+ if (pos == NULL || (end_pos = findmatch(other)) == NULL)
+ {
+ curwin->w_cursor = old_pos;
+ return FAIL;
+ }
+ curwin->w_cursor = *end_pos;
+
+ /*
+ * Try to exclude the '(', '{', ')' and '}'.
+ * If the ending '}' is only preceded by indent, skip that indent.
+ * But only if the resulting area is not smaller than what we started with.
+ */
+ if (!inclusive)
+ {
+ incl(&start_pos);
+ old_pos = curwin->w_cursor;
+ decl(&curwin->w_cursor);
+ if (what == '{')
+ while (inindent(0))
+ if (decl(&curwin->w_cursor) != 0)
+ break;
+ if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor))
+ {
+ decl(&start_pos);
+ curwin->w_cursor = old_pos;
+ }
+ }
+
+ if (VIsual_active)
+ {
+ VIsual = start_pos;
+ VIsual_mode = 'v';
+ update_curbuf(NOT_VALID); /* update the inversion */
+ showmode();
+ }
+ else
+ {
+ curbuf->b_op_start = start_pos;
+ op_motion_type = MCHAR;
+ op_inclusive = TRUE;
+ }
+
+ return OK;
+}
+
+ int
+current_par(type, count)
+ int type; /* 'p' for paragraph, 'S' for section */
+ long count;
+{
+ linenr_t start;
+ linenr_t end;
+ int white_in_front;
+ int dir;
+ int start_is_white;
+ int retval = OK;
+
+ if (type == 'S') /* not implemented yet */
+ return FAIL;
+
+ start = curwin->w_cursor.lnum;
+
+ /*
+ * When visual area is more than one line: extend it.
+ */
+ if (VIsual_active && start != VIsual.lnum)
+ {
+ if (start < VIsual.lnum)
+ dir = BACKWARD;
+ else
+ dir = FORWARD;
+ while (count--)
+ {
+ if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
+ retval = FAIL;
+
+ start_is_white = -1;
+ for (;;)
+ {
+ if (start == (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count))
+ break;
+ if (start_is_white >= 0 &&
+ (start_is_white != linewhite(start + dir) ||
+ startPS(start + (dir > 0 ? 1 : 0), 0, 0)))
+ break;
+ start += dir;
+ if (start_is_white < 0)
+ start_is_white = linewhite(start);
+ }
+ }
+ curwin->w_cursor.lnum = start;
+ curwin->w_cursor.col = 0;
+ return retval;
+ }
+
+ /*
+ * First move back to the start of the paragraph or white lines
+ */
+ white_in_front = linewhite(start);
+ while (start > 1)
+ {
+ if (white_in_front) /* stop at first white line */
+ {
+ if (!linewhite(start - 1))
+ break;
+ }
+ else /* stop at first non-white line of start of paragraph */
+ {
+ if (linewhite(start - 1) || startPS(start, 0, 0))
+ break;
+ }
+ --start;
+ }
+
+ /*
+ * Move past the end of the white lines.
+ */
+ end = start;
+ while (linewhite(end) && end < curbuf->b_ml.ml_line_count)
+ ++end;
+
+ --end;
+ while (count--)
+ {
+ if (end == curbuf->b_ml.ml_line_count)
+ return FAIL;
+
+ ++end;
+ /*
+ * skip to end of paragraph
+ */
+ while (end < curbuf->b_ml.ml_line_count &&
+ !linewhite(end + 1) && !startPS(end + 1, 0, 0))
+ ++end;
+
+ if (count == 0 && white_in_front)
+ break;
+
+ /*
+ * skip to end of white lines after paragraph
+ */
+ while (end < curbuf->b_ml.ml_line_count && linewhite(end + 1))
+ ++end;
+ }
+
+ /*
+ * If there are no empty lines at the end, try to find some empty lines at
+ * the start (unless that has been done already).
+ */
+ if (!white_in_front && !linewhite(end))
+ while (start > 1 && linewhite(start - 1))
+ --start;
+
+ if (VIsual_active)
+ {
+ VIsual.lnum = start;
+ VIsual_mode = 'V';
+ update_curbuf(NOT_VALID); /* update the inversion */
+ showmode();
+ }
+ else
+ {
+ curbuf->b_op_start.lnum = start;
+ op_motion_type = MLINE;
+ }
+ curwin->w_cursor.lnum = end;
+ curwin->w_cursor.col = 0;
+
+ return OK;
+}
+
+/*
+ * linewhite -- return TRUE if line 'lnum' is empty or has white chars only.
+ */
+ int
+linewhite(lnum)
+ linenr_t lnum;
+{
+ char_u *p;
+
+ p = skipwhite(ml_get(lnum));
+ return (*p == NUL);
+}
+
+ static void
+find_first_blank(posp)
+ FPOS *posp;
+{
+ while (decl(posp) != -1)
+ {
+ if (!vim_iswhite(gchar(posp)))
+ {
+ incl(posp);
+ break;
+ }
+ }
+}
+
+ void
+find_pattern_in_path(ptr, len, whole, skip_comments,
+ type, count, action, start_lnum, end_lnum)
+ char_u *ptr; /* pointer to search pattern */
+ 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 */
+{
+ 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_xfilename;
+ 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 *p2 = NUL; /* Init for gcc */
+ char_u save_char = NUL;
+ int define_matched;
+ struct regexp *prog = NULL;
+ struct regexp *include_prog = NULL;
+ struct regexp *define_prog = NULL;
+ int matched = FALSE;
+ int did_show = FALSE;
+ int found = FALSE;
+ int i;
+
+ file_line = alloc(LSIZE);
+ if (file_line == NULL)
+ return;
+
+ reg_magic = p_magic;
+ if (type != CHECK_PATH)
+ {
+ pat = alloc(len + 5);
+ if (pat == NULL)
+ goto fpip_end;
+ sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", len, ptr);
+ set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */
+ prog = vim_regcomp(pat);
+ vim_free(pat);
+ if (prog == NULL)
+ goto fpip_end;
+ }
+ reg_ic = FALSE; /* don't ignore case in include and define patterns */
+ if (*p_inc != NUL)
+ {
+ include_prog = vim_regcomp(p_inc);
+ if (include_prog == NULL)
+ goto fpip_end;
+ }
+ if (type == FIND_DEFINE && *p_def != NUL)
+ {
+ define_prog = vim_regcomp(p_def);
+ if (define_prog == NULL)
+ goto fpip_end;
+ }
+ files = (SearchedFile *)lalloc(max_path_depth * sizeof(SearchedFile), TRUE);
+ if (files == NULL)
+ goto fpip_end;
+ for (i = 0; i < max_path_depth; i++)
+ {
+ files[i].fp = NULL;
+ files[i].name = NULL;
+ files[i].lnum = 0;
+ files[i].matched = FALSE;
+ }
+ 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 = ml_get(lnum);
+
+ for (;;)
+ {
+ if (include_prog != NULL && vim_regexec(include_prog, line, TRUE))
+ {
+ new_fname = get_file_name_in_path(include_prog->endp[0] + 1,
+ 0, FNAME_EXP);
+ 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 (STRCMP(new_fname, files[i].name) == 0)
+ {
+ if (type != CHECK_PATH &&
+ action == ACTION_SHOW_ALL && files[i].matched)
+ {
+ msg_outchar('\n'); /* cursor below last one */
+ if (!got_int) /* don't display if 'q'
+ typed at "--more--"
+ mesage */
+ {
+ set_highlight('d'); /* Same as for dirs */
+ start_highlight();
+ msg_home_replace(new_fname);
+ stop_highlight();
+ MSG_OUTSTR(" (includes previously listed match)");
+ prev_fname = NULL;
+ }
+ }
+ vim_free(new_fname);
+ new_fname = NULL;
+ already_searched = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (type == CHECK_PATH && (action == ACTION_SHOW_ALL ||
+ (new_fname == NULL && !already_searched)))
+ {
+ if (did_show)
+ msg_outchar('\n'); /* cursor below last one */
+ else
+ {
+ gotocmdline(TRUE); /* cursor at status line */
+ set_highlight('t'); /* Highlight title */
+ start_highlight();
+ MSG_OUTSTR("--- Included files ");
+ if (action != ACTION_SHOW_ALL)
+ MSG_OUTSTR("not found ");
+ MSG_OUTSTR("in path ---\n");
+ stop_highlight();
+ }
+ did_show = TRUE;
+ while (depth_displayed < depth && !got_int)
+ {
+ ++depth_displayed;
+ for (i = 0; i < depth_displayed; i++)
+ MSG_OUTSTR(" ");
+ msg_home_replace(files[depth_displayed].name);
+ MSG_OUTSTR(" -->\n");
+ }
+ if (!got_int) /* don't display if 'q' typed
+ for "--more--" message */
+ {
+ for (i = 0; i <= depth_displayed; i++)
+ MSG_OUTSTR(" ");
+ set_highlight('d'); /* Same as for directories */
+ start_highlight();
+ /*
+ * Isolate the file name.
+ * Include the surrounding "" or <> if present.
+ */
+ for (p = include_prog->endp[0] + 1; !isfilechar(*p); p++)
+ ;
+ for (i = 0; isfilechar(p[i]); i++)
+ ;
+ if (p[-1] == '"' || p[-1] == '<')
+ {
+ --p;
+ ++i;
+ }
+ if (p[i] == '"' || p[i] == '>')
+ ++i;
+ save_char = p[i];
+ p[i] = NUL;
+ msg_outstr(p);
+ p[i] = save_char;
+ stop_highlight();
+ if (new_fname == NULL && action == ACTION_SHOW_ALL)
+ {
+ if (already_searched)
+ MSG_OUTSTR(" (Already listed)");
+ else
+ MSG_OUTSTR(" NOT FOUND");
+ }
+ }
+ flushbuf(); /* output each line directly */
+ }
+
+ if (new_fname != NULL)
+ {
+ /* Push the new file onto the file stack */
+ if (depth + 1 == old_files)
+ {
+ bigger = (SearchedFile *)lalloc(max_path_depth * 2
+ * sizeof(SearchedFile), TRUE);
+ 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 = 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;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Check if the line is a define (type == FIND_DEFINE)
+ */
+ p = line;
+ define_matched = FALSE;
+ if (define_prog != NULL && vim_regexec(define_prog, line, TRUE))
+ {
+ /*
+ * 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 = define_prog->endp[0] + 1;
+ while (*p && !isidchar(*p))
+ p++;
+ p2 = p;
+ while (*p2 && isidchar(*p2))
+ p2++;
+ save_char = *p2;
+ *p2 = NUL;
+ 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 ((define_prog == NULL || define_matched) &&
+ prog != NULL && vim_regexec(prog, p, p == line))
+ {
+ matched = TRUE;
+ /*
+ * 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)
+ {
+ fo_do_comments = TRUE;
+ if ((*line != '#' ||
+ STRNCMP(skipwhite(line + 1), "define", 6) != 0) &&
+ get_leader_len(line, NULL))
+ matched = FALSE;
+
+ /*
+ * Also check for a "/ *" or "/ /" before the match.
+ * Skips lines like "int idx; / * normal index * /" when
+ * looking for "normal".
+ */
+ else
+ for (p = line; *p && p < prog->startp[0]; ++p)
+ if (p[0] == '/' && (p[1] == '*' || p[1] == '/'))
+ {
+ matched = FALSE;
+ break;
+ }
+ fo_do_comments = FALSE;
+ }
+ }
+ if (define_matched)
+ *p2 = save_char;
+ }
+ if (matched)
+ {
+#ifdef INSERT_EXPAND
+ if (action == ACTION_EXPAND)
+ {
+ if (depth == -1 && lnum == curwin->w_cursor.lnum)
+ break;
+ found = TRUE;
+ p = prog->startp[0];
+ while (iswordchar(*p))
+ ++p;
+ if (add_completion_and_infercase(prog->startp[0],
+ (int)(p - prog->startp[0]),
+ curr_fname == curbuf->b_xfilename ? NULL : curr_fname,
+ FORWARD) == RET_ERROR)
+ break;
+ }
+ else
+#endif
+ 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_outchar('\n'); /* cursor below last one */
+ if (!got_int) /* don't display if 'q' typed
+ at "--more--" mesage */
+ {
+ set_highlight('d'); /* Same as for directories */
+ start_highlight();
+ msg_home_replace(curr_fname);
+ stop_highlight();
+ }
+ 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)
+ EMSG("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
+ {
+ if (action == ACTION_SPLIT)
+ {
+ if (win_split(0, FALSE) == FAIL)
+ break;
+ }
+ if (depth == -1)
+ {
+ setpcmark();
+ curwin->w_cursor.lnum = lnum;
+ }
+ else
+ if (getfile(0, files[depth].name, NULL, TRUE,
+ files[depth].lnum) > 0)
+ break; /* failed to jump to file */
+ }
+ if (action != ACTION_SHOW)
+ {
+ curwin->w_cursor.col = prog->startp[0] - line;
+ curwin->w_set_curswant = TRUE;
+ }
+ break;
+ }
+ matched = FALSE;
+ }
+ line_breakcheck();
+ if (got_int)
+ break;
+ while (depth >= 0)
+ {
+ if (!vim_fgets(line = file_line, LSIZE, files[depth].fp))
+ {
+ ++files[depth].lnum;
+ break;
+ }
+ 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_xfilename
+ : files[depth].name;
+ if (depth < depth_displayed)
+ depth_displayed = depth;
+ }
+ if (depth < 0)
+ {
+ if (++lnum > end_lnum)
+ break;
+ line = ml_get(lnum);
+ }
+ }
+ 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
+#ifdef INSERT_EXPAND
+ && action != ACTION_EXPAND
+#endif
+ )
+ {
+ if (got_int)
+ emsg(e_interr);
+ else if (type == FIND_DEFINE)
+ EMSG("Couldn't find definition");
+ else
+ EMSG("Couldn't find pattern");
+ }
+ if (action == ACTION_SHOW || action == ACTION_SHOW_ALL)
+ msg_end();
+
+fpip_end:
+ vim_free(file_line);
+ vim_free(prog);
+ vim_free(include_prog);
+ vim_free(define_prog);
+}
+
+ static void
+show_pat_in_path(line, type, did_show, action, fp, lnum, count)
+ char_u *line;
+ int type;
+ int did_show;
+ int action;
+ FILE *fp;
+ linenr_t *lnum;
+ long count;
+{
+ char_u *p;
+
+ if (did_show)
+ msg_outchar('\n'); /* cursor below last one */
+ else
+ gotocmdline(TRUE); /* cursor at status line */
+ if (got_int) /* 'q' typed at "--more--" message */
+ return;
+ for (;;)
+ {
+ p = line + STRLEN(line) - 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_outstr(IObuff);
+ set_highlight('n'); /* Highlight line numbers */
+ start_highlight();
+ sprintf((char *)IObuff, "%4ld", *lnum); /* show line nr */
+ msg_outstr(IObuff);
+ stop_highlight();
+ MSG_OUTSTR(" ");
+ }
+ msg_prt_line(line);
+ flushbuf(); /* 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;
+ ++*lnum;
+ }
+ else
+ {
+ if (++*lnum > curbuf->b_ml.ml_line_count)
+ break;
+ line = ml_get(*lnum);
+ }
+ msg_outchar('\n');
+ }
+}
+
+#ifdef VIMINFO
+ int
+read_viminfo_search_pattern(line, fp, force)
+ char_u *line;
+ FILE *fp;
+ int force;
+{
+ char_u *lp;
+ char_u **pattern;
+
+ lp = line;
+ if (lp[0] == '~')
+ lp++;
+ if (lp[0] == '/')
+ pattern = &search_pattern;
+ else
+ pattern = &subst_pattern;
+ if (*pattern != NULL && force)
+ vim_free(*pattern);
+ if (force || *pattern == NULL)
+ {
+ viminfo_readstring(lp);
+ *pattern = strsave(lp + 1);
+ if (line[0] == '~')
+ last_pattern = *pattern;
+ }
+ return vim_fgets(line, LSIZE, fp);
+}
+
+ void
+write_viminfo_search_pattern(fp)
+ FILE *fp;
+{
+ if (get_viminfo_parameter('/') != 0)
+ {
+ if (search_pattern != NULL)
+ {
+ fprintf(fp, "\n# Last Search Pattern:\n");
+ fprintf(fp, "%s/", (last_pattern == search_pattern) ? "~" : "");
+ viminfo_writestring(fp, search_pattern);
+ }
+ if (subst_pattern != NULL)
+ {
+ fprintf(fp, "\n# Last Substitute Search Pattern:\n");
+ fprintf(fp, "%s&", (last_pattern == subst_pattern) ? "~" : "");
+ viminfo_writestring(fp, subst_pattern);
+ }
+ }
+}
+#endif /* VIMINFO */
+
+/*
+ * Give a warning message.
+ * Use 'w' highlighting and may repeat the message after redrawing
+ */
+ static void
+give_warning(message)
+ char_u *message;
+{
+ (void)set_highlight('w');
+ msg_highlight = TRUE;
+ if (msg(message) && !msg_scroll)
+ {
+ keep_msg = message;
+ keep_msg_highlight = 'w';
+ }
+ msg_didout = FALSE; /* overwrite this message */
+ msg_col = 0;
+}