ftp.nice.ch/pub/next/unix/editor/vim.3.0.s.tar.gz#/vim-3.0/src/search.c

This is search.c in view mode; [Download] [Up]

/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Read the file "credits.txt" for a list of people who contributed.
 * Read the file "uganda.txt" for copying and usage conditions.
 */
/*
 * search.c: code for normal mode searching commands
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "ops.h"		/* for mincl */

/* modified Henry Spencer's regular expression routines */
#include "regexp.h"

static int inmacro __ARGS((char_u *, char_u *));
static int cls __ARGS((void));

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 dosearch() and edit().
 * Highest: dosearch(); 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 char_u 	*subst_pattern = NULL;
static char_u 	*last_pattern = NULL;

static int		want_start;				/* looking for start of line? */

/*
 * translate search pattern for regcomp()
 *
 * sub_cmd == 0: save pat in search_pattern (normal search command)
 * sub_cmd == 1: save pat in subst_pattern (:substitute command)
 * sub_cmd == 2: save pat in both patterns (:global command)
 * which_pat == 0: use previous search pattern if "pat" is NULL
 * which_pat == 1: use previous sustitute pattern if "pat" is NULL
 * which_pat == 2: use last used pattern if "pat" is NULL
 * 
 */
	regexp *
myregcomp(pat, sub_cmd, which_pat)
	char_u	*pat;
	int		sub_cmd;
	int		which_pat;
{
	regexp *retval;

	if (pat == NULL || *pat == NUL)     /* use previous search pattern */
	{
		if (which_pat == 0)
		{
			if (search_pattern == NULL)
			{
				emsg(e_noprevre);
				return (regexp *) NULL;
			}
			pat = search_pattern;
		}
		else if (which_pat == 1)
		{
			if (subst_pattern == NULL)
			{
				emsg(e_nopresub);
				return (regexp *) NULL;
			}
			pat = subst_pattern;
		}
		else	/* which_pat == 2 */
		{
			if (last_pattern == NULL)
			{
				emsg(e_noprevre);
				return (regexp *) NULL;
			}
			pat = last_pattern;
		}
	}

	/*
	 * save the currently used pattern in the appropriate place,
	 * unless the pattern should not be remembered
	 */
	if (!keep_old_search_pattern)
	{
		if (sub_cmd == 0 || sub_cmd == 2)	/* search or global command */
		{
			if (search_pattern != pat)
			{
				free(search_pattern);
				search_pattern = strsave(pat);
				last_pattern = search_pattern;
				reg_magic = p_magic;		/* Magic sticks with the r.e. */
			}
		}
		if (sub_cmd == 1 || sub_cmd == 2)	/* substitute or global command */
		{
			if (subst_pattern != pat)
			{
				free(subst_pattern);
				subst_pattern = strsave(pat);
				last_pattern = subst_pattern;
				reg_magic = p_magic;		/* Magic sticks with the r.e. */
			}
		}
	}

	want_start = (*pat == '^');		/* looking for start of line? */
	reg_ic = p_ic;					/* tell the regexec routine how to search */
	retval = regcomp(pat);
	return retval;
}

/*
 * 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'.
 * Return OK for success, FAIL for failure.
 */
	int
searchit(pos, dir, str, count, end, message)
	FPOS	*pos;
	int 	dir;
	char_u	*str;
	long	count;
	int		end;
	int		message;
{
	int 				found;
	linenr_t			lnum = 0;			/* init to shut up gcc */
	linenr_t			startlnum;
	regexp				*prog;
	register char_u		*s;
	char_u				*ptr;
	register int		i;
	register char_u		*match, *matchend;
	int 				loop;

	if ((prog = myregcomp(str, 0, 2)) == NULL)
	{
		if (message)
			emsg(e_invstring);
		return FAIL;
	}
/*
 * find the string
 */
	found = 1;
	while (count-- && found)    /* stop after count matches, or no more matches */
	{
		startlnum = pos->lnum;	/* remember start of search for detecting no match */
		found = 0;				/* default: not found */

		i = pos->col + dir; 	/* search starts one postition away */
		lnum = pos->lnum;

		if (dir == BACKWARD && i < 0)
			--lnum;

		for (loop = 0; loop != 2; ++loop)   /* do this twice if 'wrapscan' is set */
		{
			for ( ; lnum > 0 && lnum <= curbuf->b_ml.ml_line_count; lnum += dir, i = -1)
			{
				s = ptr = ml_get(lnum);
				if (dir == FORWARD && i > 0)    /* first line for forward search */
				{
					if (want_start || STRLEN(s) <= (size_t)i)   /* match not possible */
						continue;
					s += i;
				}

				if (regexec(prog, s, dir == BACKWARD || i <= 0))
				{							/* match somewhere on line */
					match = prog->startp[0];
					matchend = prog->endp[0];
					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.
						 */
						while (*match != NUL && regexec(prog, match + 1, (int)FALSE))
						{
							if ((i >= 0) && ((prog->startp[0] - s) > i))
								break;
							match = prog->startp[0];
							matchend = prog->endp[0];
						}

						if ((i >= 0) && ((match - s) > i))
							continue;
					}

					pos->lnum = lnum;
					if (end)
						pos->col = (int) (matchend - ptr - 1);
					else
						pos->col = (int) (match - ptr);
					found = 1;
					break;
				}
				/* breakcheck is slow, do it only once in 16 lines */
				if ((lnum & 15) == 0)
					breakcheck();       /* stop if ctrl-C typed */
				if (got_int)
					break;

				if (loop && lnum == startlnum)  /* if second loop stop where started */
					break;
			}
	/* 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 'terse' is not set, 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 (!p_terse && message)
				{
					msg(top_bot_msg);
					keep_msg = top_bot_msg;
				}
			}
			else
			{
				lnum = 1;
				if (!p_terse && message)
				{
					msg(bot_top_msg);
					keep_msg = bot_top_msg;
				}
			}
		}
		if (got_int)
			break;
	}

	free(prog);

	if (!found)             /* did not find it */
	{
		if (got_int)
			emsg(e_interr);
		else if (message)
		{
			if (p_ws)
				emsg(e_patnotf);
			else if (lnum == 0)
				EMSG("search hit TOP without match");
			else
				EMSG("search hit BOTTOM without match");
		}
		return FAIL;
	}

	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 0 or 'str' is empty: use previous string.
 *			  If 'reverse' is TRUE: go in reverse of previous dir.
 *				 If 'echo' is TRUE: echo the search command and handle options
 *			  If 'message' is TRUE: may give error message
 *
 * return 0 for failure, 1 for found, 2 for found and line offset added
 */
	int
dosearch(dirc, str, reverse, count, echo, message)
	int				dirc;
	char_u		   *str;
	int				reverse;
	long			count;
	int				echo;
	int				message;
{
	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				ret;		/* Return value */
	register char_u	*p;
	register long	c;
	char_u			*dircp = NULL;

	/*
	 * save the values for when keep_old_search_pattern is set
	 * (no if around this because gcc wants them initialized)
	 */
	old_lastsdir = lastsdir;
	old_lastoffline = lastoffline;
	old_lastend = lastend;
	old_lastoff = lastoff;

	if (dirc == 0)
		dirc = lastsdir;
	else
		lastsdir = dirc;
	if (reverse)
	{
		if (dirc == '/')
			dirc = '?';
		else
			dirc = '/';
	}
	searchstr = str;
									/* use previous string */
	if (str == NULL || *str == NUL || *str == dirc)
	{
		if (search_pattern == NULL)
		{
			emsg(e_noprevre);
			ret = 0;
			goto end_dosearch;
		}
		searchstr = (char_u *)"";	/* will use search_pattern in myregcomp() */
	}
	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 (echo && (*p == 'e' || *p == 's' || *p == 'b'))
		{
			if (*p == 'e')			/* end */
				lastend = TRUE;
			++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 lenght of search command
														for get_address() */
	}

	if (echo)
	{
		msg_start();
		msg_outchar(dirc);
		msg_outtrans(*searchstr == NUL ? search_pattern : searchstr, -1);
		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_ceol();
		(void)msg_check();

		gotocmdline(FALSE, NUL);
		flushbuf();
	}

	pos = curwin->w_cursor;

	c = searchit(&pos, dirc == '/' ? FORWARD : BACKWARD, searchstr, count, lastend, message);
	if (dircp)
		*dircp = dirc;			/* put second '/' or '?' back for normal() */
	if (c == FAIL)
	{
		ret = 0;
		goto end_dosearch;
	}
	if (lastend)
		mincl = TRUE;			/* 'e' includes last character */

	if (!lastoffline)           /* add the character offset to the column */
	{
		if (lastoff > 0)        /* offset to the right, check for end of line */
		{
			p = ml_get_pos(&pos) + 1;
			c = lastoff;
			while (c-- && *p++ != NUL)
				++pos.col;
		}
		else					/* offset to the left, check for start of line */
		{
			if ((c = pos.col + lastoff) < 0)
				c = 0;
			pos.col = c;
		}
	}

	if (!tag_busy)
		setpcmark();
	curwin->w_cursor = pos;
	curwin->w_set_curswant = TRUE;

	if (!lastoffline)
	{
		ret = 1;
		goto end_dosearch;
	}

/*
 * add the offset to the line number.
 */
	c = curwin->w_cursor.lnum + lastoff;
	if (c < 1)
		curwin->w_cursor.lnum = 1;
	else if (c > curbuf->b_ml.ml_line_count)
		curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
	else
		curwin->w_cursor.lnum = c;
	curwin->w_cursor.col = 0;

	ret = 2;

end_dosearch:
	if (keep_old_search_pattern)
	{
		lastsdir = old_lastsdir;
		lastoffline = old_lastoffline;
		lastend = old_lastend;
		lastoff = old_lastoff;
	}
	return ret;
}


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

	p = ml_get(curwin->w_cursor.lnum);
	col = curwin->w_cursor.col;
	len = STRLEN(p);

	/*
	 * On 'to' searches, skip one to start with so we can repeat searches in
	 * the same direction and have it work right.
	 * REMOVED to get vi compatibility
	 * if (lastctype)
	 *	col += dir;
	 */

	while (count--)
	{
			for (;;)
			{
				if ((col += dir) < 0 || col >= len)
					return FALSE;
				if (p[col] == lastc)
						break;
			}
	}
	if (lastctype)
		col -= dir;
	curwin->w_cursor.col = col;
	return TRUE;
}

/*
 * "Other" Searches
 */

/*
 * showmatch - move the cursor to the matching paren or brace
 *
 * Improvement over vi: Braces inside quotes are ignored.
 */
	FPOS		   *
showmatch(initc)
	int		initc;
{
	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] = {'(', ')', '[', ']', '{', '}'};
	int 			inquote = 0;		/* non-zero when inside quotes */
	register char_u	*linep;				/* pointer to current line */
	register char_u	*ptr;
	int				do_quotes;			/* check for quotes in current line */
	int				hash_dir = 0;		/* Direction searched for # things */
	int				comment_dir = 0;	/* Direction searched for comments */

	pos = curwin->w_cursor;
	linep = ml_get(pos.lnum); 

	/*
	 * if initc given, look in the table for the matching character
	 */
	if (initc != NUL)
	{
		for (idx = 0; idx < 6; ++idx)
			if (table[idx] == initc)
			{
				initc = table[idx = idx ^ 1];
				break;
			}
		if (idx == 6)			/* invalid initc! */
			return NULL;
	}
	/*
	 * no initc given, look under the cursor
	 */
	else
	{
		if (linep[0] == '#' && pos.col == 0)
			hash_dir = 1;

		/*
		 * Are we on a comment?
		 */
		if (linep[pos.col] == '/')
		{
			if (linep[pos.col + 1] == '*')
			{
				comment_dir = 1;
				idx = 0;
			}
			else if (pos.col > 0 && linep[pos.col - 1] == '*')
			{
				comment_dir = -1;
				idx = 1;
			}
		}
		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); 
			for (;;)
			{
				initc = linep[pos.col];
				if (initc == NUL)
					break;

				for (idx = 0; idx < 6; ++idx)
					if (table[idx] == initc)
						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
			 */
			mtype = MLINE;		/* Linewise for this case only */
			ptr = linep + 1;
			while (*ptr == ' ' || *ptr == TAB)
				ptr++;
			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);
				if ((pos.lnum & 15) == 0)
					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 (STRNCMP(ptr, "endif", (size_t)5) == 0)
						count++;
				}
			}
			return NULL;
		}
	}

	findc = table[idx ^ 1];		/* get matching brace */
	idx &= 1;

	do_quotes = -1;
	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 previous one */
			{
				if (pos.lnum == 1)      /* start of file */
					break;
				--pos.lnum;
				linep = ml_get(pos.lnum);
				pos.col = STRLEN(linep);	/* put pos.col on trailing NUL */
				do_quotes = -1;
					/* we only do a breakcheck() once for every 16 lines */
				if ((pos.lnum & 15) == 0)
					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;
				linep = ml_get(pos.lnum);
				pos.col = 0;
				do_quotes = -1;
					/* we only do a breakcheck() once for every 16 lines */
				if ((pos.lnum & 15) == 0)
					breakcheck();
			}
			else
				++pos.col;
		}

		if (comment_dir)
		{
			/* Note: comments do not nest, and we ignore quotes in them */
			if (linep[pos.col] != '/' ||
							(comment_dir == 1 && pos.col == 0) ||
							linep[pos.col - comment_dir] != '*')
				continue;
			return &pos;
		}

		if (do_quotes == -1)		/* count number of quotes in this line */
		{
			/*
			 * count the number of quotes in the line, skipping \" and '"'
			 */
			for (ptr = linep; *ptr; ++ptr)
				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 (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 (!idx)				/* forward search */
							inquote = TRUE;
					}
				}
			}
		}

		/*
		 * 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:
			inquote = 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;
			break;

		/*
		 * 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 (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:
			if (!inquote)      /* only check for match outside of quotes */
			{
				if (c == initc)
					count++;
				else if (c == findc)
				{
					if (count == 0)
						return &pos;
					count--;
				}
			}
		}
	}
	return (FPOS *) NULL;       /* never found it */
}

/*
 * findfunc(dir, what) - Find the next line starting with 'what' in direction 'dir'
 *
 * Return TRUE if a line was found.
 */
	int
findfunc(dir, what, count)
	int 		dir;
	int			what;
	long		count;
{
	linenr_t	curr;

	curr = curwin->w_cursor.lnum;

	for (;;)
	{
		if (dir == FORWARD)
		{
				if (curr++ == curbuf->b_ml.ml_line_count)
						break;
		}
		else
		{
				if (curr-- == 1)
						break;
		}

		if (*ml_get(curr) == what)
		{
			if (--count > 0)
				continue;
			setpcmark();
			curwin->w_cursor.lnum = curr;
			curwin->w_cursor.col = 0;
			return TRUE;
		}
	}

	return FALSE;
}

/*
 * 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 TRUE 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 FALSE;
			++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 && strchr(".!?)]\"'", c) != NULL && c != NUL))
			if (decl(&pos) == -1)
				break;

		/* remember the line where the search started */
		startlnum = pos.lnum;

		for (;;)                /* find end of sentence */
		{
			if ((c = gchar(&pos)) == 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 (strchr(")}\"'", c = gchar(&tpos)) != NULL && c != NUL);
				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 FALSE;
				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 TRUE;
}

/*
 * 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;
		mincl = 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 - 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 (isidchar(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 TRUE 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;

	stype = type;
	while (--count >= 0)
	{
		sclass = cls();

		/*
		 * We always move at least one character.
		 */
		i = inc_cursor();
		if (i == -1)
			return TRUE;
		if (i == 1 && eol && count == 0)	/* started at last char in line */
			return FALSE;

		if (sclass != 0)
			while (cls() == sclass)
			{
				i = inc_cursor();
				if (i == -1 || (i == 1 && eol && count == 0))
					return FALSE;
			}

		/*
		 * 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(curwin->w_cursor.lnum) == NUL)
				break;

			i = inc_cursor();
			if (i == -1 || (i == 1 && eol && count == 0))
				return FALSE;
		}
	}
	return FALSE;
}

/*
 * bck_word(count, type) - move backward 'count' words
 *
 * Returns TRUE if top of the file was reached.
 */
	int
bck_word(count, type)
	long		count;
	int 		type;
{
	int 		sclass; 	/* starting class */

	stype = type;
	while (--count >= 0)
	{
		sclass = cls();

		if (dec_cursor() == -1)		/* started at start of file */
			return TRUE;

		if (cls() != sclass || sclass == 0)
		{
			/*
			 * We were at the start of a word. Go back to the end of the prior
			 * word.
			 */
			while (cls() == 0)  /* skip any white space */
			{
				/*
				 * We'll stop if we land on a blank line
				 */
				if (curwin->w_cursor.col == 0 && *ml_get(curwin->w_cursor.lnum) == NUL)
					goto finished;

				if (dec_cursor() == -1)		/* hit start of file, stop here */
					return FALSE;
			}
			sclass = cls();
		}

		/*
		 * Move backward to start of this word.
		 */
		if (skip_chars(sclass, BACKWARD))
				return FALSE;

		inc_cursor();                    /* overshot - forward one */
finished:
		;
	}
	return FALSE;
}

/*
 * end_word(count, type, stop) - 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 TRUE if end of the file was reached.
 *
 * If stop is TRUE and we are already on the end of a word, move one less.
 */
	int
end_word(count, type, stop)
	long		count;
	int 		type;
	int			stop;
{
	int 		sclass; 	/* starting class */

	stype = type;
	while (--count >= 0)
	{
		sclass = cls();
		if (inc_cursor() == -1)
			return TRUE;

		/*
		 * 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 TRUE;
		}
		else if (!stop || sclass == 0)
		{
			/*
			 * We were at the end of a word. Go to the end of the next word.
			 */

			if (skip_chars(0, FORWARD))     /* skip any white space */
				return TRUE;

			/*
			 * Move forward to the end of this word.
			 */
			if (skip_chars(cls(), FORWARD))
				return TRUE;
		}
		dec_cursor();                    /* overshot - backward one */
		stop = FALSE;					/* we move only one word less */
	}
	return FALSE;
}

	int
skip_chars(class, dir)
	int class;
	int dir;
{
		while (cls() == class)
			if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1)
				return TRUE;
		return FALSE;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.