/*	search.c
 *
 * The functions in this file implement commands that search in the forward
 * and backward directions.  There are no special characters in the search
 * strings.  Probably should have a regular expression search, or something
 * like that.
 *
 * Aug. 1986 John M. Gamble:
 *	Made forward and reverse search use the same scan routine.
 *
 *	Added a limited number of regular expressions - 'any',
 *	'character class', 'closure', 'beginning of line', and
 *	'end of line'.
 *
 *	Replacement metacharacters will have to wait for a re-write of
 *	the replaces function, and a new variation of delete_bytes().
 *
 *	For those curious as to my references, i made use of
 *	Kernighan & Plauger's "Software Tools."
 *	I deliberately did not look at any published grep or editor
 *	source (aside from this one) for inspiration.  I did make use of
 *	Allen Hollub's bitmap routines as published in Doctor Dobb's Journal,
 *	June, 1985 and modified them for the limited needs of character class
 *	matching.  Any inefficiences, bugs, stupid coding examples, etc.,
 *	are therefore my own responsibility.
 *
 * April 1987: John M. Gamble
 *	Deleted the "if (n == 0) n = 1;" statements in front of the
 *	search/hunt routines.  Since we now use a do loop, these
 *	checks are unnecessary.  Consolidated common code into the
 *	function delins().  Renamed global mclen matchlen,
 *	and added the globals matchline, matchoff, patmatch, and
 *	mlenold.
 *	This gave us the ability to unreplace regular expression searches,
 *	and to put the matched string into an evironment variable.
 *	SOON TO COME: Meta-replacement characters!
 *
 *	25-apr-87	DML
 *	- cleaned up an unneccessary if/else in forwsearch() and
 *	  backsearch()
 *	- savematch() failed to malloc room for the terminating byte
 *	  of the match string (stomp...stomp...). It does now. Also
 *	  it now returns gracefully if malloc fails
 *
 *	July 1987: John M. Gamble
 *	Set the variables matchlen and matchoff in the 'unreplace'
 *	section of replaces().  The function savematch() would
 *	get confused if you replaced, unreplaced, then replaced
 *	again (serves you right for being so wishy-washy...)
 *
 *	August 1987: John M. Gamble
 *	Put in new function rmcstr() to create the replacement
 *	meta-character array.  Modified delins() so that it knows
 *	whether or not to make use of the array.  And, put in the
 *	appropriate new structures and variables.
 *
 *	Modified by Petri Kutvonen
 */

#include <stdio.h>

#include "estruct.h"
#include "globals.h"
#include "efunc.h"
#include "line.h"
#include "util.h"

/*
 * The variables magical and rmagical determine if there
 * were actual metacharacters in the search and replace strings -
 * if not, then we don't have to use the slower MAGIC mode
 * search functions.
 */
static short int magical;
static short int rmagical;
static struct magic magic_pattern[NPAT];			/* the pattern, compiled for MAGIC mode */
static struct magic reversed_magic_pattern[NPAT];		/* the same thing backwards, for reverse search */
static struct magic_replacement magic_replace_pattern[NPAT];	/* the replacement, compiled the same way */

static int amatch(struct magic *mcptr, int direct, struct line **pcwline, int *pcwoff);
static int readpattern(char *prompt, char *apat, int srch);
static int replaces(int kind, int f, int n);
static int nextch(struct line **pcurline, int *pcuroff, int dir);
static int mcstr(void);
static int rmcstr(void);
static int mceq(int bc, struct magic *mt);
static int cclmake(char **ppatptr, struct magic *mcptr);
static int biteq(int bc, char *cclmap);
static char *clearbits(void);
static void setbit(int bc, char *cclmap);

/*
 * forwsearch -- Search forward.  Get a search string from the user, and
 *	search for the string.  If found, reset the "." to be just after
 *	the match string, and (perhaps) repaint the display.
 *
 * int f, n;			default flag / numeric argument
 */
int cmd_search_forward(int f, int n)
{
	int status = TRUE;

	/* If n is negative, search backwards.
	 * Otherwise proceed by asking for the search string.
	 */
	if (n < 0)
		return cmd_search_reverse(f, -n);

	/* Ask the user for the text of a pattern.  If the
	 * response is TRUE (responses other than FALSE are
	 * possible), search for the pattern for as long as
	 * n is positive (n == 0 will go through once, which
	 * is just fine).
	 */
	if ((status = readpattern("Search", &search_pattern[0], TRUE)) == TRUE) {
		do {
			if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
				status = mcscanner(&magic_pattern[0], FORWARD, PTEND);
			else
				status = scanner(&search_pattern[0], FORWARD, PTEND);
		} while ((--n > 0) && status);

		/* Save away the match, or complain
		 * if not there.
		 */
		if (status == TRUE)
			savematch();
		else
			msg_printf("Not found");
	}
	return status;
}

/*
 * forwhunt -- Search forward for a previously acquired search string.
 *	If found, reset the "." to be just after the match string,
 *	and (perhaps) repaint the display.
 *
 * int f, n;		default flag / numeric argument
 */
int cmd_hunt_forward(int f, int n)
{
	int status = TRUE;

	if (n < 0)				/* search backwards */
		return cmd_hunt_backward(f, -n);

	/* Make sure a pattern exists, or that we didn't switch
	 * into MAGIC mode until after we entered the pattern.
	 */
	if (search_pattern[0] == '\0') {
		msg_printf("No pattern set");
		return FALSE;
	}
	if ((curwp->w_bufp->b_mode & MDMAGIC) != 0 && magic_pattern[0].mc_type == MCNIL) {
		if (!mcstr())
			return FALSE;
	}

	/* Search for the pattern for as long as
	 * n is positive (n == 0 will go through once, which
	 * is just fine).
	 */
	do {
		if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
			status = mcscanner(&magic_pattern[0], FORWARD, PTEND);
		else
			status = scanner(&search_pattern[0], FORWARD, PTEND);
	} while ((--n > 0) && status);

	/* Save away the match, or complain
	 * if not there.
	 */
	if (status == TRUE)
		savematch();
	else
		msg_printf("Not found");

	return status;
}

/*
 * backsearch -- Reverse search.  Get a search string from the user, and
 *	search, starting at "." and proceeding toward the front of the buffer.
 *	If found "." is left pointing at the first character of the pattern
 *	(the last character that was matched).
 *
 * int f, n;		default flag / numeric argument
 */
int cmd_search_reverse(int f, int n)
{
	int status = TRUE;

	/* If n is negative, search forwards.
	 * Otherwise proceed by asking for the search string.
	 */
	if (n < 0)
		return cmd_search_forward(f, -n);

	/* Ask the user for the text of a pattern.  If the
	 * response is TRUE (responses other than FALSE are
	 * possible), search for the pattern for as long as
	 * n is positive (n == 0 will go through once, which
	 * is just fine).
	 */
	if ((status = readpattern("Reverse search", &search_pattern[0], TRUE)) == TRUE) {
		do {
			if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
				status = mcscanner(&reversed_magic_pattern[0], REVERSE, PTBEG);
			else
				status = scanner(&reversed_pattern[0], REVERSE, PTBEG);
		} while ((--n > 0) && status);

		/* Save away the match, or complain
		 * if not there.
		 */
		if (status == TRUE)
			savematch();
		else
			msg_printf("Not found");
	}
	return status;
}

/*
 * backhunt -- Reverse search for a previously acquired search string,
 *	starting at "." and proceeding toward the front of the buffer.
 *	If found "." is left pointing at the first character of the pattern
 *	(the last character that was matched).
 *
 * int f, n;		default flag / numeric argument
 */
int cmd_hunt_backward(int f, int n)
{
	int status = TRUE;

	if (n < 0)
		return cmd_hunt_forward(f, -n);

	/* Make sure a pattern exists, or that we didn't switch
	 * into MAGIC mode until after we entered the pattern.
	 */
	if (reversed_pattern[0] == '\0') {
		msg_printf("No pattern set");
		return FALSE;
	}

	if ((curwp->w_bufp->b_mode & MDMAGIC) != 0 && reversed_magic_pattern[0].mc_type == MCNIL) {
		if (!mcstr())
			return FALSE;
	}

	/* Go search for it for as long as
	 * n is positive (n == 0 will go through once, which
	 * is just fine).
	 */
	do {
		if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0)
			status = mcscanner(&reversed_magic_pattern[0], REVERSE, PTBEG);
		else
			status = scanner(&reversed_pattern[0], REVERSE, PTBEG);
	} while ((--n > 0) && status);

	/* Save away the match, or complain
	 * if not there.
	 */
	if (status == TRUE)
		savematch();
	else
		msg_printf("Not found");

	return status;
}

/*
 * mcscanner -- Search for a meta-pattern in either direction.  If found,
 *	reset the "." to be at the start or just after the match string,
 *	and (perhaps) repaint the display.
 *
 * struct magic *mcpatrn;			pointer into pattern
 * int direct;			which way to go.
 * int beg_or_end;		put point at beginning or end of pattern.
 */
int mcscanner(struct magic *mcpatrn, int direct, int beg_or_end)
{
	struct line *curline;			/* current line during scan */
	int curoff;				/* position within current line */

	/* If we are going in reverse, then the 'end' is actually
	 * the beginning of the pattern.  Toggle it.
	 */
	beg_or_end ^= direct;

	/*
	 * Save the old matchlen length, in case it is
	 * very different (closure) from the old length.
	 * This is important for query-replace undo
	 * command.
	 */
	previous_match_bytes = match_bytes;

	/* Setup local scan pointers to global ".".
	 */
	curline = curwp->w_dotp;
	curoff = curwp->w_doto;

	/* Scan each character until we hit the head link record.
	 */
	while (!at_buffer_end(curline, curoff, direct)) {
		/* Save the current position in case we need to
		 * restore it on a match, and initialize matchlen to
		 * zero in case we are doing a search for replacement.
		 */
		match_line = curline;
		match_offset = curoff;
		match_bytes = 0;

		if (amatch(mcpatrn, direct, &curline, &curoff)) {
			/* A SUCCESSFULL MATCH!!!
			 * reset the global "." pointers.
			 */
			if (beg_or_end == PTEND) {	/* at end of string */
				curwp->w_dotp = curline;
				curwp->w_doto = curoff;
			} else {		/* at beginning of string */

				curwp->w_dotp = match_line;
				curwp->w_doto = match_offset;
			}

			curwp->w_flag |= WFMOVE;	/* flag that we have moved */
			return TRUE;
		}

		/* Advance the cursor.
		 */
		nextch(&curline, &curoff, direct);
	}

	return FALSE;				/* We could not find a match. */
}

/*
 * amatch -- Search for a meta-pattern in either direction.  Based on the
 *	recursive routine amatch() (for "anchored match") in
 *	Kernighan & Plauger's "Software Tools".
 *
 * struct magic *mcptr;		string to scan for
 * int direct;		which way to go.
 * struct line **pcwline;	current line during scan
 * int *pcwoff;		position within current line
 */
static int amatch(struct magic *mcptr, int direct, struct line **pcwline, int *pcwoff)
{
	int c;					/* character at current position */
	struct line *curline;			/* current line during scan */
	int curoff;				/* position within current line */
	int nchars;

	/* Set up local scan pointers to ".", and get
	 * the current character.  Then loop around
	 * the pattern pointer until success or failure.
	 */
	curline = *pcwline;
	curoff = *pcwoff;

	/* The beginning-of-line and end-of-line metacharacters
	 * do not compare against characters, they compare
	 * against positions.
	 * BOL is guaranteed to be at the start of the pattern
	 * for forward searches, and at the end of the pattern
	 * for reverse searches.  The reverse is true for EOL.
	 * So, for a start, we check for them on entry.
	 */
	if (mcptr->mc_type == BOL) {
		if (curoff != 0)
			return FALSE;
		mcptr++;
	}

	if (mcptr->mc_type == EOL) {
		if (curoff != line_length(curline))
			return FALSE;
		mcptr++;
	}

	while (mcptr->mc_type != MCNIL) {
		c = nextch(&curline, &curoff, direct);

		if (mcptr->mc_type & CLOSURE) {
			/* Try to match as many characters as possible
			 * against the current meta-character.  A
			 * newline never matches a closure.
			 */
			nchars = 0;
			while (c != '\n' && mceq(c, mcptr)) {
				c = nextch(&curline, &curoff, direct);
				nchars++;
			}

			/* We are now at the character that made us
			 * fail.  Try to match the rest of the pattern.
			 * Shrink the closure by one for each failure.
			 * Since closure matches *zero* or more occurences
			 * of a pattern, a match may start even if the
			 * previous loop matched no characters.
			 */
			mcptr++;

			for (;;) {
				c = nextch(&curline, &curoff, direct ^ REVERSE);

				if (amatch(mcptr, direct, &curline, &curoff)) {
					match_bytes += nchars;
					goto success;
				}

				if (nchars-- == 0)
					return FALSE;
			}
		} else {			/* Not closure. */

			/* The only way we'd get a BOL metacharacter
			 * at this point is at the end of the reversed pattern.
			 * The only way we'd get an EOL metacharacter
			 * here is at the end of a regular pattern.
			 * So if we match one or the other, and are at
			 * the appropriate position, we are guaranteed success
			 * (since the next pattern character has to be MCNIL).
			 * Before we report success, however, we back up by
			 * one character, so as to leave the cursor in the
			 * correct position.  For example, a search for ")$"
			 * will leave the cursor at the end of the line, while
			 * a search for ")<NL>" will leave the cursor at the
			 * beginning of the next line.  This follows the
			 * notion that the meta-character '$' (and likewise
			 * '^') match positions, not characters.
			 */
			if (mcptr->mc_type == BOL) {
				if (curoff == line_length(curline)) {
					c = nextch(&curline, &curoff, direct ^ REVERSE);
					goto success;
				} else
					return FALSE;
			}

			if (mcptr->mc_type == EOL) {
				if (curoff == 0) {
					c = nextch(&curline, &curoff, direct ^ REVERSE);
					goto success;
				} else
					return FALSE;
			}

			/* Neither BOL nor EOL, so go through
			 * the meta-character equal function.
			 */
			if (!mceq(c, mcptr))
				return FALSE;
		}

		/* Increment the length counter and
		 * advance the pattern pointer.
		 */
		match_bytes++;
		mcptr++;
	}					/* End of mcptr loop. */

	/* A SUCCESSFULL MATCH!!!
	 * Reset the "." pointers.
	 */
 success:
	*pcwline = curline;
	*pcwoff = curoff;

	return TRUE;
}

/*
 * scanner -- Search for a pattern in either direction.  If found,
 *	reset the "." to be at the start or just after the match string,
 *	and (perhaps) repaint the display.
 *
 * unsigned char *patrn;	string to scan for
 * int direct;			which way to go.
 * int beg_or_end;		put point at beginning or end of pattern.
 */
int scanner(const char *patrn, int direct, int beg_or_end)
{
	int c;					/* character at current position */
	const char *patptr;			/* pointer into pattern */
	struct line *curline;			/* current line during scan */
	int curoff;				/* position within current line */
	struct line *scanline;			/* current line during scanning */
	int scanoff;				/* position in scanned line */

	/* If we are going in reverse, then the 'end' is actually
	 * the beginning of the pattern.  Toggle it.
	 */
	beg_or_end ^= direct;

	/* Set up local pointers to global ".".
	 */
	curline = curwp->w_dotp;
	curoff = curwp->w_doto;

	/* Scan each character until we hit the head link record.
	 */
	while (!at_buffer_end(curline, curoff, direct)) {
		/* Save the current position in case we match
		 * the search string at this point.
		 */
		match_line = curline;
		match_offset = curoff;

		/* Get the character resolving newlines, and
		 * test it against first char in pattern.
		 */
		c = nextch(&curline, &curoff, direct);

		if (eq(c, patrn[0])) {		/* if we find it.. */
			/* Setup scanning pointers.
			 */
			scanline = curline;
			scanoff = curoff;
			patptr = &patrn[0];

			/* Scan through the pattern for a match.
			 */
			while (*++patptr != '\0') {
				c = nextch(&scanline, &scanoff, direct);

				if (!eq(c, *patptr))
					goto fail;
			}

			/* A SUCCESSFULL MATCH!!!
			 * reset the global "." pointers
			 */
			if (beg_or_end == PTEND) {	/* at end of string */
				curwp->w_dotp = scanline;
				curwp->w_doto = scanoff;
			} else {		/* at beginning of string */

				curwp->w_dotp = match_line;
				curwp->w_doto = match_offset;
			}

			curwp->w_flag |= WFMOVE;	/* Flag that we have moved. */
			return TRUE;

		}
 fail:		;				/* continue to search */
	}

	return FALSE;				/* We could not find a match */
}

/*
 * eq -- Compare two characters.  The "bc" comes from the buffer, "pc"
 *	from the pattern.  If we are not in EXACT mode, fold out the case.
 */
int eq(unsigned char bc, unsigned char pc)
{
	if ((curwp->w_bufp->b_mode & MDEXACT) == 0) {
		if (islower(bc))
			bc ^= DIFCASE;

		if (islower(pc))
			pc ^= DIFCASE;
	}

	return bc == pc;
}

/*
 * readpattern -- Read a pattern.  Stash it in apat.  If it is the
 *	search string, create the reverse pattern and the magic
 *	pattern, assuming we are in MAGIC mode (and defined that way).
 *	Apat is not updated if the user types in an empty line.  If
 *	the user typed an empty line, and there is no old pattern, it is
 *	an error.  Display the old pattern, in the style of Jeff Lomicka.
 *	There is some do-it-yourself control expansion.  Change to using
 *	<META> to delimit the end-of-pattern to allow <NL>s in the search
 *	string. 
 */
static int readpattern(char *prompt, char *apat, int srch)
{
	int status;
	char tpat[NPAT + 20];

	mystrscpy(tpat, prompt, sizeof(tpat));	/* copy prompt to output string */
	mystrscat(tpat, " (", sizeof(tpat));	/* build new prompt string */
	expandp(&apat[0], tpat + strlen(tpat), NPAT / 2);	/* add old pattern */
	mystrscat(tpat, ")<Meta>: ", sizeof(tpat));

	/* Read a pattern.  Either we get one,
	 * or we just get the META charater, and use the previous pattern.
	 * Then, if it's the search string, make a reversed pattern.
	 * *Then*, make the meta-pattern, if we are defined that way.
	 */
	if ((status = ask_string_until(tpat, tpat, NPAT, meta_char)) == TRUE) {
		strcpy(apat, tpat);
		if (srch) {			/* If we are doing the search string. */
			/* Reverse string copy, and remember
			 * the length for substitution purposes.
			 */
			rvstrcpy(reversed_pattern, apat);
			previous_match_bytes = match_bytes = strlen(apat);
		}
		/* Only make the meta-pattern if in magic mode,
		 * since the pattern in question might have an
		 * invalid meta combination.
		 */
		if ((curwp->w_bufp->b_mode & MDMAGIC) == 0) {
			mcclear();
			rmcclear();
		} else
			status = srch ? mcstr() : rmcstr();
	} else if (status == FALSE && apat[0] != 0)	/* Old one */
		status = TRUE;

	return status;
}

/*
 * savematch -- We found the pattern?  Let's save it away.
 */
void savematch(void)
{
	char *ptr;				/* pointer to last match string */
	int j;
	struct line *curline;			/* line of last match */
	int curoff;				/* offset "      "    */

	/* Free any existing match string, then
	 * attempt to allocate a new one.
	 */
	if (matched_text != NULL)
		free(matched_text);

	ptr = matched_text = malloc(match_bytes + 1);

	if (ptr != NULL) {
		curoff = match_offset;
		curline = match_line;

		for (j = 0; j < match_bytes; j++)
			*ptr++ = nextch(&curline, &curoff, FORWARD);

		*ptr = '\0';
	}
}

/*
 * rvstrcpy -- Reverse string copy.
 */
void rvstrcpy(char *rvstr, char *str)
{
	int i;

	str += (i = strlen(str));

	while (i-- > 0)
		*rvstr++ = *--str;

	*rvstr = '\0';
}

/*
 * sreplace -- Search and replace.
 *
 * int f;		default flag
 * int n;		# of repetitions wanted
 */
int cmd_replace_string(int f, int n)
{
	return replaces(FALSE, f, n);
}

/*
 * qreplace -- search and replace with query.
 *
 * int f;		default flag
 * int n;		# of repetitions wanted
 */
int cmd_query_replace_string(int f, int n)
{
	return replaces(TRUE, f, n);
}

/*
 * replaces -- Search for a string and replace it with another
 *	string.  Query might be enabled (according to kind).
 *
 * int kind;		Query enabled flag
 * int f;		default flag
 * int n;		# of repetitions wanted
 */
static int replaces(int kind, int f, int n)
{
	int status;				/* success flag on pattern inputs */
	int rlength;				/* length of replacement string */
	int numsub;				/* number of substitutions */
	int nummatch;				/* number of found matches */
	int nlflag;				/* last char of search string a <NL>? */
	int nlrepl;				/* was a replace done on the last line? */
	char c;					/* input char for query */
	char tpat[NPAT];			/* temporary to hold search pattern */
	struct line *origline;			/* original "." position */
	int origoff;				/* and offset (for . query option) */
	struct line *lastline;			/* position of last replace and */
	int lastoff;				/* offset (for 'u' query option) */

	if (curbp->b_mode & MDVIEW)		/* don't allow this command if      */
		return readonly_error();		/* we are in read only mode     */

	/* Check for negative repetitions.
	 */
	if (f && n < 0)
		return FALSE;

	/* Ask the user for the text of a pattern.
	 */
	if ((status = readpattern((kind == FALSE ? "Replace" : "Query replace"), &search_pattern[0], TRUE))
	    != TRUE)
		return status;

	/* Ask for the replacement string.
	 */
	if ((status = readpattern("with", &replace_pattern[0], FALSE)) == ABORT)
		return status;

	/* Find the length of the replacement string.
	 */
	rlength = strlen(&replace_pattern[0]);

	/* Set up flags so we can make sure not to do a recursive
	 * replace on the last line.
	 */
	nlflag = (search_pattern[match_bytes - 1] == '\n');
	nlrepl = FALSE;

	if (kind) {
		/* Build query replace question string.
		 */
		mystrscpy(tpat, "Replace '", sizeof(tpat));
		expandp(&search_pattern[0], tpat + strlen(tpat), NPAT / 3);
		mystrscat(tpat, "' with '", sizeof(tpat));
		expandp(&replace_pattern[0], tpat + strlen(tpat), NPAT / 3);
		mystrscat(tpat, "'? ", sizeof(tpat));

		/* Initialize last replaced pointers.
		 */
		lastline = NULL;
		lastoff = 0;
	}

	/* Save original . position, init the number of matches and
	 * substitutions, and scan through the file.
	 */
	origline = curwp->w_dotp;
	origoff = curwp->w_doto;
	numsub = 0;
	nummatch = 0;

	while ((f == FALSE || n > nummatch) && (nlflag == FALSE || nlrepl == FALSE)) {
		/* Search for the pattern.
		 * If we search with a regular expression,
		 * matchlen is reset to the true length of
		 * the matched string.
		 */
		if ((magical && curwp->w_bufp->b_mode & MDMAGIC) != 0) {
			if (!mcscanner(&magic_pattern[0], FORWARD, PTBEG))
				break;
		} else if (!scanner(&search_pattern[0], FORWARD, PTBEG))
			break;			/* all done */

		++nummatch;			/* Increment # of matches */

		/* Check if we are on the last line.
		 */
		nlrepl = (line_next(curwp->w_dotp) == curwp->w_bufp->b_linep);

		/* Check for query.
		 */
		if (kind) {
			/* Get the query.
			 */
 pprompt:		msg_puts(tpat);
 qprompt:
			update_now();		/* show the proposed place to change */
			c = tgetc();		/* and input */
			msg_printf("");		/* and clear it */

			/* And respond appropriately.
			 */
			switch (c) {
			case 'Y':
			case 'y':		/* yes, substitute */
			case ' ':
				savematch();
				break;

			case 'N':
			case 'n':		/* no, onword */
				cmd_forward_character(FALSE, 1);
				continue;

			case '!':		/* yes/stop asking */
				kind = FALSE;
				break;

			case 'U':
			case 'u':		/* undo last and re-prompt */

				/* Restore old position.
				 */
				if (lastline == NULL) {
					/* There is nothing to undo.
					 */
					tcapbeep();
					goto pprompt;
				}
				curwp->w_dotp = lastline;
				curwp->w_doto = lastoff;
				lastline = NULL;
				lastoff = 0;

				/* Delete the new string.
				 */
				cmd_backward_character(FALSE, rlength);
				match_line = curwp->w_dotp;
				match_offset = curwp->w_doto;
				status = delins(rlength, matched_text, FALSE);
				if (status != TRUE)
					return status;

				/* Record one less substitution,
				 * backup, save our place, and
				 * reprompt.
				 */
				--numsub;
				cmd_backward_character(FALSE, previous_match_bytes);
				match_line = curwp->w_dotp;
				match_offset = curwp->w_doto;
				goto pprompt;

			case '.':		/* abort! and return */
				/* restore old position */
				curwp->w_dotp = origline;
				curwp->w_doto = origoff;
				curwp->w_flag |= WFMOVE;

			case BELL:		/* abort! and stay */
				msg_printf("Aborted!");
				return FALSE;

			default:		/* bitch and beep */
				tcapbeep();

			case '?':		/* help me */
				msg_printf
				    ("(Y)es, (N)o, (!)Do rest, (U)ndo last, (^G)Abort, (.)Abort back, (?)Help: ");
				goto qprompt;

			}			/* end of switch */
		}

		/* end of "if kind" */
		/*
		 * Delete the sucker, and insert its
		 * replacement.
		 */
		status = delins(match_bytes, &replace_pattern[0], TRUE);
		if (status != TRUE)
			return status;

		/* Save our position, since we may
		 * undo this.
		 */
		if (kind) {
			lastline = curwp->w_dotp;
			lastoff = curwp->w_doto;
		}

		numsub++;			/* increment # of substitutions */
	}

	/* And report the results.
	 */
	msg_printf("%d substitutions", numsub);
	return TRUE;
}

/*
 * delins -- Delete a specified length from the current point
 *	then either insert the string directly, or make use of
 *	replacement meta-array.
 */
int delins(int dlength, char *instr, int use_meta)
{
	int status;
	struct magic_replacement *rmcptr;

	/* Zap what we gotta,
	 * and insert its replacement.
	 */
	if ((status = delete_bytes((long)dlength, FALSE)) != TRUE)
		msg_printf("%%ERROR while deleting");
	else if ((rmagical && use_meta) && (curwp->w_bufp->b_mode & MDMAGIC) != 0) {
		rmcptr = &magic_replace_pattern[0];
		while (rmcptr->mc_type != MCNIL && status == TRUE) {
			if (rmcptr->mc_type == LITCHAR)
				status = insert_string(rmcptr->rstr);
			else
				status = insert_string(matched_text);
			rmcptr++;
		}
	} else
		status = insert_string(instr);

	return status;
}

/*
 * expandp -- Expand control key sequences for output.
 *
 * char *srcstr;		string to expand
 * char *deststr;		destination of expanded string
 * int maxlength;		maximum chars in destination
 */
int expandp(char *srcstr, char *deststr, int size)
{
	unsigned char c;			/* current char to translate */

	if (size < 2) {				/* no room even for the '$' */
		if (size > 0)
			*deststr = '\0';
		return FALSE;
	}

	/*
	 * Keep a byte back, so that the '$' that says "there was more"
	 * always has somewhere to go.  Everything below can then treat
	 * 'size' as the room for the text and its terminator.
	 */
	size--;

	/* Scan through the string.
	 */
	while ((c = *srcstr++) != 0) {
		char piece[4];
		int len;

		if (c == '\n') {		/* it's a newline */
			memcpy(piece, "<NL>", 4);
			len = 4;
		} else if ((c > 0 && c < 0x20) || c == 0x7f) {	/* control character */
			piece[0] = '^';
			piece[1] = c ^ 0x40;
			len = 2;
		} else {			/* any other character */
			piece[0] = c;
			len = 1;
		}

		/* room for it and the terminator, or say we ran out */
		if (len + 1 > size) {
			*deststr++ = '$';
			*deststr = '\0';
			return FALSE;
		}
		memcpy(deststr, piece, len);
		deststr += len;
		size -= len;
	}
	*deststr = '\0';
	return TRUE;
}

/*
 * at_buffer_end -- Is there anywhere further to search in this
 *	direction?  The beginning and the end of the buffer are the only
 *	things that stop a search now; a region to search within, the way
 *	VMS EDT had, would also stop one here.
 */
int at_buffer_end(struct line *curline, int curoff, int dir)
{
	int border;

	if (dir == FORWARD) {
		border = (curoff == line_length(curline)) && (line_next(curline) == curbp->b_linep);
	} else {
		border = (curoff == 0) && (line_prev(curline) == curbp->b_linep);
	}
	return border;
}

/*
 * nextch -- retrieve the next/previous character in the buffer,
 *	and advance/retreat the point.
 *	The order in which this is done is significant, and depends
 *	upon the direction of the search.  Forward searches look at
 *	the current character and move, reverse searches move and
 *	look at the character.
 */
static int nextch(struct line **pcurline, int *pcuroff, int dir)
{
	struct line *curline;
	int curoff;
	int c;

	curline = *pcurline;
	curoff = *pcuroff;

	if (dir == FORWARD) {
		if (curoff == line_length(curline)) {	/* if at EOL */
			curline = line_next(curline);	/* skip to next line */
			curoff = 0;
			c = '\n';		/* and return a <NL> */
		} else
			c = lgetc(curline, curoff++);	/* get the char */
	} else {				/* Reverse. */

		if (curoff == 0) {
			curline = line_prev(curline);
			curoff = line_length(curline);
			c = '\n';
		} else
			c = lgetc(curline, --curoff);

	}
	*pcurline = curline;
	*pcuroff = curoff;

	return c;
}

/*
 * mcstr -- Set up the 'magic' array.  The closure symbol is taken as
 *	a literal character when (1) it is the first character in the
 *	pattern, and (2) when preceded by a symbol that does not allow
 *	closure, such as a newline, beginning of line symbol, or another
 *	closure symbol.
 *
 *	Coding comment (jmg):  yes, i know i have gotos that are, strictly
 *	speaking, unnecessary.  But right now we are so cramped for
 *	code space that i will grab what i can in order to remain
 *	within the 64K limit.  C compilers actually do very little
 *	in the way of optimizing - they expect you to do that.
 */
static int mcstr(void)
{
	struct magic *mcptr, *rtpcm;
	char *patptr;
	int mj;
	int pchr;
	int status = TRUE;
	int does_closure = FALSE;

	/* If we had metacharacters in the struct magic array previously,
	 * free up any bitmaps that may have been allocated.
	 */
	if (magical)
		mcclear();

	magical = FALSE;
	mj = 0;
	mcptr = &magic_pattern[0];
	patptr = &search_pattern[0];

	while ((pchr = *patptr) && status) {
		switch (pchr) {
		case MC_CCL:
			status = cclmake(&patptr, mcptr);
			magical = TRUE;
			does_closure = TRUE;
			break;
		case MC_BOL:
			if (mj != 0)
				goto litcase;

			mcptr->mc_type = BOL;
			magical = TRUE;
			does_closure = FALSE;
			break;
		case MC_EOL:
			if (*(patptr + 1) != '\0')
				goto litcase;

			mcptr->mc_type = EOL;
			magical = TRUE;
			does_closure = FALSE;
			break;
		case MC_ANY:
			mcptr->mc_type = ANY;
			magical = TRUE;
			does_closure = TRUE;
			break;
		case MC_CLOSURE:
			/* Does the closure symbol mean closure here?
			 * If so, back up to the previous element
			 * and indicate it is enclosed.
			 */
			if (!does_closure)
				goto litcase;
			mj--;
			mcptr--;
			mcptr->mc_type |= CLOSURE;
			magical = TRUE;
			does_closure = FALSE;
			break;

			/* Note: no break between MC_ESC case and the default.
			 */
		case MC_ESC:
			if (*(patptr + 1) != '\0') {
				pchr = *++patptr;
				magical = TRUE;
			}
		default:
 litcase:		mcptr->mc_type = LITCHAR;
			mcptr->u.lchar = pchr;
			does_closure = (pchr != '\n');
			break;
		}				/* End of switch. */
		mcptr++;
		patptr++;
		mj++;
	}					/* End of while. */

	/* Close off the meta-string.
	 */
	mcptr->mc_type = MCNIL;

	/* Set up the reverse array, if the status is good.  Please note the
	 * structure assignment - your compiler may not like that.
	 * If the status is not good, nil out the meta-pattern.
	 * The only way the status would be bad is from the cclmake()
	 * routine, and the bitmap for that member is guarenteed to be
	 * freed.  So we stomp a MCNIL value there, and call mcclear()
	 * to free any other bitmaps.
	 */
	if (status) {
		rtpcm = &reversed_magic_pattern[0];
		while (--mj >= 0) {
			*rtpcm++ = *--mcptr;
		}
		rtpcm->mc_type = MCNIL;
	} else {
		(--mcptr)->mc_type = MCNIL;
		mcclear();
	}

	return status;
}

/*
 * rmcstr -- Set up the replacement 'magic' array.  Note that if there
 *	are no meta-characters encountered in the replacement string,
 *	the array is never actually created - we will just use the
 *	character array rpat[] as the replacement string.
 */
static int rmcstr(void)
{
	struct magic_replacement *rmcptr;
	char *patptr;
	int status = TRUE;
	int mj;

	patptr = &replace_pattern[0];
	rmcptr = &magic_replace_pattern[0];
	mj = 0;
	rmagical = FALSE;

	while (*patptr && status == TRUE) {
		switch (*patptr) {
		case MC_DITTO:

			/* If there were non-magical characters
			 * in the string before reaching this
			 * character, plunk it in the replacement
			 * array before processing the current
			 * meta-character.
			 */
			if (mj != 0) {
				rmcptr->mc_type = LITCHAR;
				if ((rmcptr->rstr = malloc(mj + 1)) == NULL) {
					msg_printf("%%Out of memory");
					status = FALSE;
					break;
				}
				strncpy(rmcptr->rstr, patptr - mj, mj);
				rmcptr++;
				mj = 0;
			}
			rmcptr->mc_type = DITTO;
			rmcptr++;
			rmagical = TRUE;
			break;

		case MC_ESC:
			rmcptr->mc_type = LITCHAR;

			/* We malloc mj plus two here, instead
			 * of one, because we have to count the
			 * current character.
			 */
			if ((rmcptr->rstr = malloc(mj + 2)) == NULL) {
				msg_printf("%%Out of memory");
				status = FALSE;
				break;
			}

			strncpy(rmcptr->rstr, patptr - mj, mj + 1);

			/* If MC_ESC is not the last character
			 * in the string, find out what it is
			 * escaping, and overwrite the last
			 * character with it.
			 */
			if (*(patptr + 1) != '\0')
				*((rmcptr->rstr) + mj) = *++patptr;

			rmcptr++;
			mj = 0;
			rmagical = TRUE;
			break;

		default:
			mj++;
		}
		patptr++;
	}

	if (rmagical && mj > 0) {
		rmcptr->mc_type = LITCHAR;
		if ((rmcptr->rstr = malloc(mj + 1)) == NULL) {
			msg_printf("%%Out of memory.");
			status = FALSE;
		}
		strncpy(rmcptr->rstr, patptr - mj, mj);
		rmcptr++;
	}

	rmcptr->mc_type = MCNIL;
	return status;
}

/*
 * mcclear -- Free up any CCL bitmaps, and MCNIL the struct magic search arrays.
 */
void mcclear(void)
{
	struct magic *mcptr;

	mcptr = &magic_pattern[0];

	while (mcptr->mc_type != MCNIL) {
		if ((mcptr->mc_type & MASKCL) == CCL || (mcptr->mc_type & MASKCL) == NCCL)
			free(mcptr->u.cclmap);
		mcptr++;
	}
	magic_pattern[0].mc_type = reversed_magic_pattern[0].mc_type = MCNIL;
}

/*
 * rmcclear -- Free up any strings, and MCNIL the struct magic_replacement array.
 */
void rmcclear(void)
{
	struct magic_replacement *rmcptr;

	rmcptr = &magic_replace_pattern[0];

	while (rmcptr->mc_type != MCNIL) {
		if (rmcptr->mc_type == LITCHAR)
			free(rmcptr->rstr);
		rmcptr++;
	}

	magic_replace_pattern[0].mc_type = MCNIL;
}

/*
 * mceq -- meta-character equality with a character.  In Kernighan & Plauger's
 *	Software Tools, this is the function omatch(), but i felt there
 *	were too many functions with the 'match' name already.
 */
static int mceq(int bc, struct magic *mt)
{
	int result;

	bc = bc & 0xFF;
	switch (mt->mc_type & MASKCL) {
	case LITCHAR:
		result = eq(bc, mt->u.lchar);
		break;

	case ANY:
		result = (bc != '\n');
		break;

	case CCL:
		if (!(result = biteq(bc, mt->u.cclmap))) {
			if ((curwp->w_bufp->b_mode & MDEXACT) == 0 && (isletter(bc))) {
				result = biteq(CHCASE(bc), mt->u.cclmap);
			}
		}
		break;

	case NCCL:
		result = !biteq(bc, mt->u.cclmap);

		if ((curwp->w_bufp->b_mode & MDEXACT) == 0 && (isletter(bc))) {
			result &= !biteq(CHCASE(bc), mt->u.cclmap);
		}
		break;

	default:
		msg_printf("mceq: what is %d?", mt->mc_type);
		result = FALSE;
		break;

	}					/* End of switch. */

	return result;
}

extern char *clearbits(void);

/*
 * cclmake -- create the bitmap for the character class.
 *	ppatptr is left pointing to the end-of-character-class character,
 *	so that a loop may automatically increment with safety.
 */
static int cclmake(char **ppatptr, struct magic *mcptr)
{
	char *bmap;
	char *patptr;
	int pchr, ochr;

	if ((bmap = clearbits()) == NULL) {
		msg_printf("%%Out of memory");
		return FALSE;
	}

	mcptr->u.cclmap = bmap;
	patptr = *ppatptr;

	/*
	 * Test the initial character(s) in ccl for
	 * special cases - negate ccl, or an end ccl
	 * character as a first character.  Anything
	 * else gets set in the bitmap.
	 */
	if (*++patptr == MC_NCCL) {
		patptr++;
		mcptr->mc_type = NCCL;
	} else
		mcptr->mc_type = CCL;

	if ((ochr = *patptr) == MC_ECCL) {
		msg_printf("%%No characters in character class");
		return FALSE;
	} else {
		if (ochr == MC_ESC)
			ochr = *++patptr;

		setbit(ochr, bmap);
		patptr++;
	}

	while (ochr != '\0' && (pchr = *patptr) != MC_ECCL) {
		switch (pchr) {
			/* Range character loses its meaning
			 * if it is the last character in
			 * the class.
			 */
		case MC_RCCL:
			if (*(patptr + 1) == MC_ECCL)
				setbit(pchr, bmap);
			else {
				pchr = *++patptr;
				while (++ochr <= pchr)
					setbit(ochr, bmap);
			}
			break;

			/* Note: no break between case MC_ESC and the default.
			 */
		case MC_ESC:
			pchr = *++patptr;
		default:
			setbit(pchr, bmap);
			break;
		}
		patptr++;
		ochr = pchr;
	}

	*ppatptr = patptr;

	if (ochr == '\0') {
		msg_printf("%%Character class not ended");
		free(bmap);
		return FALSE;
	}
	return TRUE;
}

/*
 * biteq -- is the character in the bitmap?
 */
static int biteq(int bc, char *cclmap)
{
	bc = bc & 0xFF;
	if (bc >= HICHAR)
		return FALSE;

	return (*(cclmap + (bc >> 3)) & BIT(bc & 7)) ? TRUE : FALSE;
}

/*
 * clearbits -- Allocate and zero out a CCL bitmap.
 */
static char *clearbits(void)
{
	char *cclstart;
	char *cclmap;
	int i;

	if ((cclmap = cclstart = (char *)malloc(HIBYTE)) != NULL) {
		for (i = 0; i < HIBYTE; i++)
			*cclmap++ = 0;
	}
	return cclstart;
}

/*
 * setbit -- Set a bit (ON only) in the bitmap.
 */
static void setbit(int bc, char *cclmap)
{
	bc = bc & 0xFF;
	if (bc < HICHAR)
		*(cclmap + (bc >> 3)) |= BIT(bc & 7);
}
