[BACK]Return to search.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / vim

File: [local] / src / usr.bin / vim / Attic / search.c (download)

Revision 1.2, Sat Sep 21 06:23:19 1996 UTC (27 years, 8 months ago) by downsj
Branch: MAIN
CVS Tags: OPENBSD_2_2_BASE, OPENBSD_2_2, OPENBSD_2_1_BASE, OPENBSD_2_1, OPENBSD_2_0_BASE, OPENBSD_2_0
Changes since 1.1: +8 -16 lines

update to vim 4.4beta

/*	$OpenBSD: search.c,v 1.2 1996/09/21 06:23:19 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() */

/*
 * 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;
			}
		}
	}

	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)
			{
				/*
				 * Look for a match somewhere in the line.
				 */
				ptr = ml_get(lnum);
				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 = 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
 *    If 'options & SEARCH_START': accept match at curpos itself
 *
 * 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 + SEARCH_START +
						   ((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, FALSE) > 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;
}