/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/
/*
 * Modified 1988-91 by Tom Hageman.
 * - handle marks correctly in substitute
 * - allow numeric arguments on replace
 * - replace UNDO stuff with more general undo (see also io.c)
 * - improved tag-search (fast tags inspired from 4.14 JOVE)
 * - and more...
 */

#include "jove.h"

RCS("$Id: search.c,v 14.32.0.9 1994/02/02 19:19:55 tom Exp tom $")

#include "io.h"
#include "re.h"
#include "ctype.h"
#include "maps.h"

/* default (literal/regexp) search strings. */
private char	*search_strings[2] ZERO;

/*
 * [TRH] search exp'th occurrence, backward if exp < 0.
 * temporarily enable Wrap search if exp equals zero.
 * If argument flag is non-zero in invoking command,
 * the search string is saved
 */
DEF_CMD( "search-forward-re", Search, ARG(YES)|ARG(8) ); _IF(ndef TINY)
DEF_CMD( "search-forward-re-nd", Search, ARG(NO)|ARG(8) ); _IF(ndef TINY)
DEF_CMD( "search-reverse-re", Search, NEGATE|ARG(YES)|ARG(8) ); _IF(ndef TINY)
DEF_CMD( "search-reverse-re-nd", Search, NEGATE|ARG(NO)|ARG(8) ); _IF(ndef TINY)
DEF_CMD( "search-forward-lit", Search, ARG(YES)|ARG(4) );  _IF(ndef TINY)
DEF_CMD( "search-forward-lit-nd", Search, ARG(NO)|ARG(4) ); _IF(ndef TINY)
DEF_CMD( "search-reverse-lit", Search, NEGATE|ARG(YES)|ARG(4) ); _IF(ndef TINY)
DEF_CMD( "search-reverse-lit-nd", Search, NEGATE|ARG(NO)|ARG(4) ); _IF(ndef TINY)
DEF_CMD( "search-forward", Search, ARG(YES) );
DEF_CMD( "search-forward-nd", Search, ARG(NO) );
DEF_CMD( "search-reverse", Search, NEGATE|ARG(YES) );
DEF_CMD( "search-reverse-nd", Search, NEGATE|ARG(NO) )
{
	const char	*s;
    {
	register char	**p_searchstr = &search_strings[NO];
#ifndef TINY
	register int	re = NO;

	if ((LastCmd->Type & ARG(8)) ||		/* Force regexp */
	    (!(LastCmd->Type & ARG(4)) &&	/* Force literal */
	     True(UseRE))) {
		++re;
		++p_searchstr;
	}
#else
#   define re UseRE

	if (True(UseRE))
		++p_searchstr;
#endif
	s = re_ask((*p_searchstr), re, ProcFmt);
	if (LastCmd->Type & ARG(YES))
		set_str(p_searchstr, s);
    }
#undef re
    {
	register int	num;
	int		dir = FORWARD;
	register int	i;
	Bufpos		prevdot;	/* to remember last match */
	register Bufpos	*newdot = NULL;
	Line		*end_lp = NULL;

	if ((num = exp) < 0) {
		num = -num;
		dir = BACKWARD;
	}
	if ((i = num) == 0 || True(WrapScan))	/* enable wrap search */
		end_lp = curline;
	do {
		if (!(newdot = docompiled(dir, compbuf, newdot, end_lp))) {
			char	*where = (end_lp) ? "in buffer" :
					(dir > 0) ? "to bottom" : "to top";
			if (i == num)
				complain("No \"%s\" found %s.", s, where);

			confirm("No %d \"%s\"%n %s; use last match (%d) instead? ",
				num, s, where, num - i);
			newdot = &prevdot;
			break;
		}
		prevdot = *newdot;	/* well, remember */
	} while (--i > 0);

	PushPntp(newdot->p_line);
	SetDot(newdot);
    }
}

private char QueryHelp[] = "\
Space,Y  Rubout,N  Return,Q !,P(all) .(y+q) ^]D(el) ^]R(ecur) ^]W(d+r) ^]U(ndo)\
";

private	int substitute __(( const char *_(query), Line *_(l1), int _(char1),
			    Line *_(line2), int _(char2) ));
private
substitute(query, l1, char1, l2, char2)
const char	*query;
Line		*l1,
		*l2;
{
	register Mark	*end = MakeMark(l2, char2, FLOATER);
	register Bufpos	*bp = NULL;
	register int	matchlen,
			numdone = 0,
			stop = 0,
			undo_incr;
	/*
	 * delete the match (assuming matchlen is set to the negated length
	 * of the current match (i.e. matchlen = -(REeom - REbom)) and curchar
	 * is at end of match. We use DelNChar with negative argument here
	 * since DelPChar behaves funny in overwrite mode.
	 */
#define	DelMatch()	{ if (exp = matchlen) exp_p = NO, DelNChar(); }

	/*
	 * In query mode "stop" is used to flag a user-break, and
	 * repeat count is ignored (except for its sign -- see replace);
	 * In non-query mode, replace is halted when number
	 * of substitutions equals repeat count (if given)
	 * (negative repeat count is ignored here, but taken care of in
	 *  Replace)
	 */
	if (!query && exp_p == YES)
		stop = exp;

	UndoReset();
	DotTo(l1, char1);
	/*
	 * [TRH 6/89] end scan on line AFTER last line of region to avoid
	 * problems with wraparound.
	 */
	l2 = l2->l_next;
	for (; bp = docompiled(FORWARD, compbuf, bp, l2);
	     DOTsave(bp),
	     (matchlen == 0 && linebuf[bp->p_char]) && (bp->p_char++, 0)
	     /* ...to avoid infinite loops */
	     ) {
		if (bp->p_line == l2)
			break;
		if (bp->p_line == end->m_line && bp->p_char > end->m_char)
			break;		/* nope, leave this alone */
		/*
		 * [TRH 4/89] new routine REsubst() is a replacement for
		 * re_dosub(). It just builds the final substitution string
		 * from the replacement string and the current match.
		 * Do this now since there is a distinct chance that
		 * the disk buffer containing the match is invalidated
		 * later on.
		 */
		REsubst(genbuf, rep_str);
		matchlen = REbom - REeom;	/* (negated) length of match */
		SetDot(bp);
		if (query) {
			register int	c;

			message(query);
reswitch:		redisplay();
			if ((c = getchar()) == AbortChar)
				break;
			switch (toupper(c)) {
			case '.':
				stop++;
				break;

			case ' ':
			case 'Y':
				break;

			case BS:
			case RUBOUT:
			case 'N':
				continue;

			case CTL('D'):
			case 'D':
				genbuf[0] = '\0';
				break;

			case CTL('W'):
			case 'W':
				lsave();	/* so that it can be undone */
				DelMatch();
				numdone++;
				/* Fall into ... */

			case CTL('R'):
			case 'R':
				message("Type C-X C-C to continue with query replace.");
				curchar = REbom;
				RErecur();
				UndoReset();	/* cannot reliably Undo */
				continue;

			case CTL('U'):
			case 'U':

				exp_p++;	/* to get most recent version */
				if (!UndoLine())
					goto reswitch;

				numdone -= undo_incr;
				undo_incr = -undo_incr;	/* UndoLine toggles */
				continue;

			case 'P':
			case '!':
				query = NO;
				break;

			case CR:
			case LF:
			case 'Q':
				goto done;	/* ``break 2;'' */

			case CTL('L'):
				RedrawDisplay();
				goto reswitch;

			default:
				rbell();
				message(QueryHelp);
				goto reswitch;
			}
			lsave();
			/*
			 * so that UNDO leaves any previous substitutions
			 * (in fact: any previous change) in this line alone.
			 * {{ if you want UNDO to restore the entire previous
			 *  line, put this lsave() before the loop or you may
			 *  lose all changes to curline made before
			 *  substitute }}
			 */
			undo_incr = 1;
		}
		/* now do the substitution */
		DelMatch();
		ins_str(genbuf, NO);
		numdone++;
		if (query) {
			if (stop)
				break;

			updmesg();		/* No blinking. */
			redisplay();		/* Show the change. */
		}
		else if (numdone == stop)
			/*
			 * In non-query mode, replace is halted when number
			 * of substitutions equals repeat count (if given)
			 * (negative repeat count is ignored)
			 */
			break;
	}
done:
	DelMark(end);
	return numdone;
}

/* TODO: separate default search/replacement strings for (literal/regexp) */

/*
 * Prompt for search and replacement strings and do the substitution.  The
 * point is restored when we're done.
 *
 * Rules:
 * 1. if `inreg', constrict replace to Region.
 * 2. replace {from top of buffer to Point, from Point to end of buffer,
 *    entire buffer} if repeat count is {negative,positive,zero} resp.
 * 3. if not query, and repeat count is valid, replace only the first
 *    |repeat-count| occurrences (zero means all). this is handled by
 *    substitute()
 */
DEF_CMD( "query-replace-in-region",	Replace, EDIT|ARG(1|2) );
DEF_CMD( "query-replace-string",	Replace, EDIT|ARG(2) );
DEF_CMD( "replace-in-region",		Replace, EDIT|ARG(1) );
DEF_CMD( "replace-string",		Replace, EDIT|ARG(0) )
{
#	define query	(LastCmd->Type & ARG(2))

	register Buffer	*cb = curbuf;
	register char	*rep_ptr;
	Line		*l1 = cb->b_dot,
			*l2 = cb->b_last;
	int		char1 = cb->b_char,
			char2 = length(l2);
	int		in_region;

	if ((in_region = (LastCmd->Type & ARG(1)))) {
		register Mark	*m = CurMark();

		l2 = m->m_line;
		char2 = m->m_char;
		fixorder(&l1, &char1, &l2, &char2);
	}
	else if (exp <= 0) {
		if (exp < 0) {
			exp = -exp;
			l2 = l1;
			char2 = char1;
		}
		l1 = cb->b_first;
		char1 = 0;
	}
	/* Get search string. */

	if ((rep_ptr = rep_search) == NULL)
		rep_ptr = search_strings[NO];
	set_str(&rep_search, re_ask(rep_ptr, UseRE, ProcFmt));

	/*
	 * Now the replacement string.  Do_ask() so the user can play with
	 * the default (previous) replacement string by typing C-R in ask(),
	 * OR, (s)he can just hit Return to replace with nothing.
	 */

	if (rep_ptr = do_ask("\r\n", (int (*)())0, rep_str,
			     "%N: %f %s with ", rep_search))
		set_str(&rep_str, rep_ptr);
	else
		rep_ptr = rep_str,
		rep_str = (char *) NullStr;

	if (in_region && True(SaveRegion))
		CopyRegion();		/* so you can undo it */
	/*
	 * build a a prompt for query-replace
	 * just copy message left around by ask()
	 */
    {
	char		prompt[MESG_SIZE];
	register Mark	*m = MakeMark(cb->b_dot, cb->b_char, FLOATER);
	register int	n;

	if ((n = substitute(query ? strcpy(prompt, mesgbuf) : (char *)0,
			    l1, char1, l2, char2)) &&
	    !in_region)
		set_mark();
	ToMark(m);
	DelMark(m);

	if (rep_str[0] == '\0')			/* restore default */
		rep_str = rep_ptr;

	s_mess("%d substitution%n.", n);
    }
#	undef query
}

#ifdef CTAGS	/* C tags package. */
/*
 * This search string matches lines (in the tag file) of the form:
 *	"tag<tab>file<tab>/line-containing-tag/"
 * or	"tag<tab>file<tab>?line-containing-tag?"
 * or	"tag<tab>file<tab>lineno"		(for typedefs)
 * After a succesful match, sub-expression
 *  \1 holds the actual tag,
 *  \2 holds "file",
 *  \3 holds "lineno", and iff this is an empty string,
 *  \4 holds the "/" or "?" (only used internally), and
 *  \5 holds "line-containing-tag", which is a search pattern itself,
 *
 * [15-Apr-90, adapt for NeXT ctags: multiple tabs, junk after pattern]
 */
private const char	TagPattern[] =
    "^\\(%s[^\t]*\\)\t+\\([^\t]*\\)\t\\{\\([0-9]+\\),\\([?/]\\)\\(.*\\)\\4\\}";
#define TAG_TAG		1
#define TAG_FILE	2
#define TAG_LINENO	3
#define TAG_PATTERN	5

DEF_STR( "tag-file", TagFile, 128, V_FILENAME ) _IF(def CTAGS) = "tags"; _IF(def PRIVATE)
DEF_INT( "unsorted-tags", SlowTags, V_BOOL ) _IF(def CTAGS)_IF(ndef TINY) ZERO; _IF(def PRIVATE)

void
find_tag(tag, localp)
const char	*tag;
{
	char	filebuf[FILESIZE*2],
		tag_lineno[10],
		tag_pattern[100],
		actual_tag[100];
    {
	register File	*fp;
      {
	register char		*tagfile = filebuf;
	register const char	*default_tagfile = TagFile;

	strcpy(tagfile, default_tagfile);	/* need it later on */

	if (localp)
		ask_file(sprint("With tag file (default \"%s\"): ",
				default_tagfile), default_tagfile, tagfile);

	if (!(fp = open_file(tagfile, iobuff, F_READ|F_TEXT|F_QUIET)))
		return;
      }
      {
	register const char	*orgtag = tag;
	register int		try = 2;

	for (;;) {	/* until we have a match, or we failed completely */
		if (try)
			f_bsearch(fp, tag, 0, strlen(orgtag));

		if (re_fsearch(fp, sprint(TagPattern, orgtag)))
			break;
		/*
		 * The search for the tag failed. If we are case-insensitive
		 * retry binary search with an all-uppercase tag.  Else if
		 * we allow unsorted tag files and haven't already done
		 * this, rewind the file and retry without binary search.
		 */
		if (--try > 0) {
			if (True(CaseIgnore) && islower(*orgtag)) {
				tag = strupr(strcpy(actual_tag, orgtag));
				continue;
			}
			--try;
		}
		if (try == 0) {
#ifndef TINY
			if (True(SlowTags)) {
				f_rewind(fp);
				continue;
			}
#endif
			--try;
		}
		s_mess("Can't find tag \"%s\".", orgtag);
		break;
	}
	f_close(fp);

	if (try < 0)
		return;
      }
    }
    {
	register Buffer	*b;
	register char	*tag_file = filebuf,
			*tagf_base = basename(tag_file);
			/* to search file relative to tag file directory */
	/*
	 * we need to save all matches BEFORE do_find, since that may
	 * clobber the match registers when reading a file (auto-execute).
	 */
	putmatch(TAG_FILE, tagf_base, (int)(sizeof filebuf - (tagf_base - tag_file)));
	putmatch(TAG_TAG, actual_tag, sizeof actual_tag);
	putmatch(TAG_LINENO, tag_lineno, sizeof tag_lineno);
	putmatch(TAG_PATTERN, tag_pattern, sizeof tag_pattern);

	if (ISABSPATH(tagf_base))	/* handle absolute tag-file name */
		tag_file = tagf_base;

	b = do_find(curwind, tag_file, NO);
	if (curbuf != b) {
		SetABuf(curbuf);
		SetBuf(b);
	}
	/* if we can't seem to find the file, delete this buffer and give up. */
	if (b_nofile(b) && b_empty(b)) {
		kill_buf(b);
		s_mess("I can't find the file \"%s\" for this tag.", tag_file);
		return;
	}
    }
    {
	register Bufpos *bp;
	Bufpos		old,
			new;
	/*
	 * Try to locate the tag. Use line number if non-empty, else use
	 * search pattern.  We want exact match, so temporarily disable
	 * CaseIgnore here.
	 */
	CaseIgnore--;

	if (tag_lineno[0]) {
		register int	dir = FORWARD;

		DOTsave(&old);

		/* Try to locate the actual tag in a limited region around
		   where it's supposed to be. */

		to_line(chr_to_int(tag_lineno, 10, NO));

		while ((bp = dosearch(actual_tag, dir, NO)) == NULL ||
		       LineDist(bp->p_line, curline) >= MarkThresh) {
			if ((dir = -dir) > 0) {		/* tried both ways */
				bp = &new;
				DOTsave(bp);
				break;
			}
		}
		SetDot(&old);
	}
	else {
		register char	*s = tag_pattern,
				*d = genbuf;
		/*
		 * Unfortunately CTAGS only escapes '/' (and '\' I presume) but
		 * not any other RE-special characters in the search pattern,
		 * so we have to do it ourselves. (we cannot use non-RE search
		 * anymore since that is truly literal now...)
		 */
		for (;;) {
			if (*s == '\\')
				s++;
			if ((*d = *s++) == '\0')
				break;
			if (index(".*?+[\\", *d++))
				*d++ = s[-1], d[-2] = '\\';
		}
		/* search through the whole file */

		okay_wrap++;	WrapScan++;
		if (bp = dosearch(genbuf, FORWARD, YES)) {
			/*
			 * display tag near top of window
			 * (to display more of the routine's body)
			 */
			SetTop(curwind, bp->p_line->l_prev);

		}
		okay_wrap = NO;	WrapScan--;
	}
	CaseIgnore++;

	if (bp) {
#if (HIGHLIGHT)
		WHighLight(curwind, bp->p_line);
#endif
		PushPntp(bp->p_line);
		SetDot(bp);
	}
	else
		message("Well, I found the file, but the tag is missing.");
    }
}

DEF_CMD( "find-tag-at-point",	NonExisting, ARG(YES) );_IF(ndef CTAGS)
DEF_CMD( "find-tag",		NonExisting, ARG(NO) ); _IF(ndef CTAGS)
DEF_CMD( "find-tag-at-point",	FindTag, ARG(YES) );	_IF(def CTAGS)
DEF_CMD( "find-tag",		FindTag, ARG(NO) )	_IF(def CTAGS)
{
	char		tagbuf[100];
	register char	*tag = tagbuf;

	WordAtDot(tag, sizeof tagbuf);

	if (LastCmd->Type & ARG(YES)) {		/* Find Tag at Dot. */
		if (tag[0] == '\0')
			complain("not a tag.");
	} else {
		register const char	*prompt = ProcDefFmt;

		if (tag[0] == '\0')
			tag = NULL, prompt = ProcFmt;
		tag = strcpy(tagbuf, ask(tag, prompt, tag));
	}
	find_tag(tag, (exp_p & YES));
}
#else
#   ifndef CLIOPTIONS
	void find_tag() { /* dummy, gets called by main() */ }
#   endif
#endif /* CTAGS */

#ifdef ISEARCH

/* I-search returns a code saying what to do:
   STOP:	We found the match, so unwind the stack and leave
		where it is.
   DELETE:	Rubout the last command.
   BACKUP:	Back up to where the isearch was last NOT failing.

   When a character is typed it is appended to the search string, and
   then, isearch is called recursively.  When C-S or C-R is typed, isearch
   is again called recursively. */

#define TOSTART	0
#define STOP	1
#define DELETE	2
#define BACKUP	3

private char	*ISdef ZERO;

#define ISbuf		genbuf		/* well, it's there... */
#define ISBUFSIZE	LBSIZE

DEF_INT( "search-exit-char", SExitChar, V_CHAR ) _IF(def ISEARCH) = CR; _IF(def PRIVATE)

#define DIR_CMD	0	/* value of c on one of CTL('R') or CTL('S') */

#ifdef OLD_CASEIGNORE
#define cmp_char(a, b)	((a)==(b) || (True(CaseIgnore) && CEquiv(a)==CEquiv(b)))
#else
/* This assumes `b' is the pattern character to match against. */
#define cmp_char(a, b)	((a)==(b) || (True(CaseIgnore) && CEquiv(a)==(b)))
#endif

private Bufpos *doisearch __(( int _(dir), int _(ch), int _(failing) ));
private Bufpos *
doisearch(dir, ch, failing)
{
	static Bufpos	buf;
	register Bufpos	*bp = &buf;
	register int	c;
	extern int	okay_wrap;

	if ((c = ch) != DIR_CMD) {

		if (failing)
			return NULL;
		DOTsave(bp);
		if (dir > 0) {				/* FORWARD */
			register int	curc = linebuf[bp->p_char];
			if (cmp_char(curc, c)) {
				bp->p_char++;	/* i.e. curchar + 1 */
				return bp;
			}
		} else {				/* BACKWARD */
			if (look_at(ISbuf))
				return bp;
		}
	}
	okay_wrap++;
	if ((bp = dosearch(ISbuf, dir, NO)) == NULL)
		rbell();	/* ring the first time there's no match */
	okay_wrap = NO;
	return bp;
}

DEF_REF( "quoted-insert", QuoteCmd );

/* Nicely recursive. */

private int isearch __(( char *_(push_incp), int _(push_dir), Bufpos *_(bp) ));
private int
isearch(push_incp, push_dir, bp)
char	*push_incp;
int	push_dir;
Bufpos	*bp;
{
	Bufpos		push_bp;
	register char	*incp = push_incp;
	register int	dir = push_dir,
			c,
			failing = NO;

	if (bp) {		/* Move to the new position. */
		push_bp = *bp;
	} else {
		DOTsave(&push_bp);
		failing++;
	}

	for (;;) {
		SetDot(&push_bp);

		f_mess("%s%sI-search: %s",
			(failing) ? "Failing " : NullStr,
			(dir < 0) ? "reverse-" : NullStr,
			ISbuf);

		c = getch();

		if (Inputp) {
			/* Regurgitated search string, to be taken literally. */
#if (BPC > 7)
			/* Oh dear!  8-bit hack strikes back! */
			if (c == QuoteChar) {
				if ((c = getch()) < 0200) {
					Ungetc(c);
					c = QuoteChar;
				}
			}
#endif
			goto append_char;
		}
		if (c == SExitChar)
			return STOP;
		if (c == AbortChar)
			/* If we're failing, we backup until we're no longer
			   failing or we've reached the beginning; else, we
			   just about the search and go back to the start. */
			return (failing) ? BACKUP : TOSTART;

		switch (c) {
		case RUBOUT:
		case BS:
			return DELETE;

		case CTL('\\'):
		case CTL('S'):
			dir = FORWARD;
			c = DIR_CMD;
			break;

		case CTL('R'):
			dir = BACKWARD;
			c = DIR_CMD;
			break;
#ifndef TINY
		case CTL('W'):			/* Yank next word. */
		case CTL('Y'):			/* Yank rest of line. */
			Inputp = strcpy(Minibuf,
					&linebuf[curchar +
						 (dir > 0 ? 0 : incp - ISbuf)]);
			if (c == CTL('W')) {	/* Truncate at end of word. */
				incp = Inputp;
				while (isword(*incp++)) ;
				*--incp = '\0';
				incp = push_incp;
			}
			continue;
#endif
#if NO
		/* [TRH] I don't see the point in this, now that non-RE
		   search is  truly literal... */
		case '\\':
			if (incp >= &ISbuf[ISBUFSIZE - 1]) {
				rbell();
				continue;
			}
			*incp++ = '\\';
			add_mess("\\");
			/* Fall into ... */
#endif
		case_QuoteChar:
			updmesg();	/* a fast add_mess(NullStr); */
			c = getch();
#ifdef NULLCHARS
			if (c == '\0')
				c = '\n';
#endif
			break;

		default:
			if (printable(c))
				break;
#ifndef TINY
			/* Lookahead to handle quoted characters, macros,
			   non-standard bindings to incremental-search
			   and accented characters transparently. */
		    {
			register data_obj *dp;
			register char *end_prompt = mesgbuf + strlen(mesgbuf);

			/* Use stroke-buffer to remember keys,
			   so we can backtrack if necessary. */
			init_strokes();
			Ungetc(c);
			add_stroke(c);	/* Ungetc()'ed chars are not
					   automatically added. */
			add_mess(" ");

			if ((dp = *MapKey(active_map(),
					  MAP_FOLLOW_ACTIVE_LIST))) {
#   define cp ((Command *) dp)
				extern void IncSearch __(( void ));
				extern void ClAndRedraw __(( void ));
				extern void RedrawDisplay __(( void ));
#   if (BPC > 7)
				extern void Accent __(( void ));

				if (cp->c_proc == Accent) {
					(cp->c_proc)();
					/* "accent" leaves its result in the
					   Ungetc() buffer, so read them. */
					continue;
				}
#   endif
				if (dp == QuoteCmd) {
					goto case_QuoteChar;
				}
				if (cp->c_proc == IncSearch) {
					*end_prompt = '\0';
					dir = FORWARD;
					if (cp->Type & NEGATE)
						dir = BACKWARD;
					c = DIR_CMD;
					break;
				}
				if ((dp->Type & TYPEMASK) == MACRO ||
				    (cp->c_proc == ClAndRedraw) ||
				    (cp->c_proc == RedrawDisplay)) {
					ExecCmd(dp);
					continue;
				}
#   undef cp
			}
		    }
			/* Backtrack if no special case. */
			while ((c = pop_stroke()) >= 0)
				Ungetc(c);
#else /* TINY */
			if (get_bind(active_map(), c) == QuoteCmd) {
				goto case_QuoteChar;
			}
			Ungetc(c);
#endif /* TINY */
			return STOP;
		}
		if (c == DIR_CMD) {		/* CTL('S') or CTL('R') */
			/* If this is the first time through and we have a
			   search string left over from last time, use that
			   one now. Setup Inputp so that chars are read
			   through getch() and the recursion gets right. */
			if (incp == ISbuf) {
				if (push_dir == dir)
					Inputp = ISdef;
				else
					push_dir = dir;
					/* so next time will work. */
				continue;
			}
			/* If we're failing and we're not changing our
			  direction, don't recur since there's no way
			the search can work. */
			if (failing && push_dir == dir) {
				rbell();
				continue;
			}
		} else {			/* ordinary character */
append_char:
			if (incp >= &ISbuf[ISBUFSIZE - 1]) {
				rbell();
				continue;
			}
			*incp++ = c;
			*incp = '\0';
		}
		add_mess("%s ...", push_incp);	/* so we know what's going on */
		DrawMesg(NO);			/* do it now */
		switch (c = isearch(incp, dir, doisearch(dir, c, failing))) {
		case BACKUP:
			/* If we're not failing, we just continue to to the
			   for loop; otherwise we keep returning to the
			   previous levels until we find one that isn't
			   failing OR we reach the beginning. */
			if (failing)
				return BACKUP;
			/* Fall into ... */

		case DELETE:
			incp = push_incp;
			if (*incp == '\0')
				dir = push_dir;	/* "rubout" direction */
			else
				*incp = '\0';
			continue;

		default: /* TOSTART or STOP */
			return c;
		}
	}
}

DEF_CMD( "i-search-reverse", NonExisting, NEGATE );	_IF(ndef ISEARCH)
DEF_CMD( "i-search-forward", NonExisting, NO );		_IF(ndef ISEARCH)
DEF_CMD( "i-search-reverse", IncSearch, NEGATE );	_IF(def ISEARCH)
DEF_CMD( "i-search-forward", IncSearch, NO )		_IF(def ISEARCH)
{
	Bufpos		save;
	register Bufpos	*sp = &save;
	register char	*str = ISbuf;

#ifndef TINY
	/* Interactive search from .joverc? You gotta be kidding!! */
	if (InJoverc)
		complain((char *) 0);
#endif
#if 0	/* Ugh! this creates more trouble than it solves! */
	Interactive++;			/* In case we're in a macro. */
#endif
	DOTsave(sp);
	str[0] = '\0';
	if (isearch(str, (exp > 0) ? FORWARD : BACKWARD, sp) == TOSTART)
		SetDot(sp);
	else if (LineDist(curline, sp->p_line) >= MarkThresh)
		DoSetMark(sp->p_line, sp->p_char);

	if (str[0] != '\0')		/* save default */
		set_str(&ISdef, str);
	s_mess(NullStr);		/* remove prompt */
#if 0
	Interactive--;
#endif
}
#endif /* ISEARCH */

/*======================================================================
 * $Log: search.c,v $
 * Revision 14.32.0.9  1994/02/02  19:19:55  tom
 * (ISearch): disable Interactive because of unwanted side-effects in Macros.
 *
 * Revision 14.32.0.8  1993/11/16  14:11:00  tom
 * (Search): use separate default strings for regexp and literal search;
 * (search-{forward,backward}-{lit,re}{,-nd}): new user-commands;
 * (Replace): reshuffles in preparation of separate re,lit replace strings;
 * (isearch): handle "accent" and macro input transparently, fix bug in
 *  regurgitated input handling, add C-W (yank word) and C-Y (yank line),
 *  allow "clear-and-redraw" and "redraw-display" without exiting i-search,
 *  disallow i-search from .joverc.
 *
 * Revision 14.32.0.1  1993/07/07  12:12:57  tom
 * (F_TEXT): new option for f_open et al.
 *
 * Revision 14.32  1993/04/09  02:36:02  tom
 * (cmp_char): change to new case-ignore scheme.
 *
 * Revision 14.31  1993/02/17  23:36:12  tom
 * lotsa random optimizations.
 *
 * Revision 14.30  1993/01/27  01:33:21  tom
 * cleanup whitespace; parenthize || && expr to avoid compiler warnings.
 *
 * Revision 14.27  1992/09/22  15:59:20  tom
 * rename "re1.c" to "search.c"; replace CTL('Q') with `QuoteChar'.
 *
 * Revision 14.26  1992/08/26  23:56:58  tom
 * NULLCHARS fix in i-search; PRIVATE-ized some Variable defs;
 *  add RCS directives.
 *
 */
