/*	ISEARCH.C
 *
 * The functions in this file implement commands that perform incremental
 * searches in the forward and backward directions.  This "ISearch" command
 * is intended to emulate the same command from the original EMACS 
 * implementation (ITS).  Contains references to routines internal to
 * SEARCH.C.
 *
 * REVISION HISTORY:
 *
 *	D. R. Banks 9-May-86
 *	- added ITS EMACSlike ISearch
 *
 *	John M. Gamble 5-Oct-86
 *	- Made iterative search use search.c's scanner() routine.
 *	  This allowed the elimination of bakscan().
 *	- Put isearch constants into estruct.h
 *	- Eliminated the passing of 'status' to scanmore() and
 *	  checknext(), since there were no circumstances where
 *	  it ever equalled FALSE.
 *
 *	modified by Petri Kutvonen	
 */

#include        <stdio.h>
#include	"estruct.h"
#include        "edef.h"

#if	ISRCH

extern int scanner();			/* Handy search routine */
extern int eq();			/* Compare chars, match case */

/* A couple of "own" variables for re-eat */

int	(*saved_get_char)();		/* Get character routine */
int	eaten_char = -1;		/* Re-eaten char */

/* A couple more "own" variables for the command string */

int	cmd_buff[CMDBUFLEN];		/* Save the command args here */
int	cmd_offset;			/* Current offset into command buff */
int	cmd_reexecute = -1;		/* > 0 if re-executing command */


/*
 * Subroutine to do incremental reverse search.  It actually uses the
 * same code as the normal incremental search, as both can go both ways.
 */
 
int risearch(f, n)
{
    LINE *curline;			/* Current line on entry	      */
    int  curoff;			/* Current offset on entry	      */

    /* remember the initial . on entry: */

    curline = curwp->w_dotp;		/* Save the current line pointer      */
    curoff  = curwp->w_doto;		/* Save the current offset	      */

    /* Make sure the search doesn't match where we already are:		      */

    backchar(TRUE, 1);			/* Back up a character		      */

    if (!(isearch(f, -n)))		/* Call ISearch backwards	      */
    {					/* If error in search:		      */
	curwp->w_dotp = curline;	/* Reset the line pointer	      */
	curwp->w_doto = curoff;		/*  and the offset to original value  */
	curwp->w_flag |= WFMOVE;	/* Say we've moved		      */
	update(FALSE);			/* And force an update		      */
	mlwrite ("(search failed)");	/* Say we died			      */
#if	PKCODE
	matchlen = strlen(pat);
#endif
    } else mlerase ();			/* If happy, just erase the cmd line  */
#if	PKCODE
	matchlen = strlen(pat);
#endif
}

/* Again, but for the forward direction */

int fisearch(f, n)
{
    LINE *curline;			/* Current line on entry	      */
    int  curoff;			/* Current offset on entry	      */

    /* remember the initial . on entry: */

    curline = curwp->w_dotp;		/* Save the current line pointer      */
    curoff  = curwp->w_doto;		/* Save the current offset	      */

    /* do the search */

    if (!(isearch(f, n)))		/* Call ISearch forwards	      */
    {					/* If error in search:		      */
	curwp->w_dotp = curline;	/* Reset the line pointer	      */
	curwp->w_doto = curoff;		/*  and the offset to original value  */
	curwp->w_flag |= WFMOVE;	/* Say we've moved		      */
	update(FALSE);			/* And force an update		      */
	mlwrite ("(search failed)");	/* Say we died			      */
#if	PKCODE
	matchlen = strlen(pat);
#endif
   } else mlerase ();			/* If happy, just erase the cmd line  */
#if	PKCODE
	matchlen = strlen(pat);
#endif
}

/*
 * Subroutine to do an incremental search.  In general, this works similarly
 * to the older micro-emacs search function, except that the search happens
 * as each character is typed, with the screen and cursor updated with each
 * new search character.
 *
 * While searching forward, each successive character will leave the cursor
 * at the end of the entire matched string.  Typing a Control-S or Control-X
 * will cause the next occurrence of the string to be searched for (where the
 * next occurrence does NOT overlap the current occurrence).  A Control-R will
 * change to a backwards search, META will terminate the search and Control-G
 * will abort the search.  Rubout will back up to the previous match of the
 * string, or if the starting point is reached first, it will delete the
 * last character from the search string.
 *
 * While searching backward, each successive character will leave the cursor
 * at the beginning of the matched string.  Typing a Control-R will search
 * backward for the next occurrence of the string.  Control-S or Control-X
 * will revert the search to the forward direction.  In general, the reverse
 * incremental search is just like the forward incremental search inverted.
 *
 * In all cases, if the search fails, the user will be feeped, and the search
 * will stall until the pattern string is edited back into something that
 * exists (or until the search is aborted).
 */
 
isearch(f, n)
{
    int			status;		/* Search status */
    int			col;		/* prompt column */
    register int	cpos;		/* character number in search string  */
    register int	c;		/* current input character */
    register int	expc;		/* function expanded input char	      */
    char		pat_save[NPAT];	/* Saved copy of the old pattern str  */
    LINE		*curline;	/* Current line on entry	      */
    int			curoff;		/* Current offset on entry	      */
    int			init_direction;	/* The initial search direction	      */

    /* Initialize starting conditions */

    cmd_reexecute = -1;		/* We're not re-executing (yet?)      */
    cmd_offset = 0;			/* Start at the beginning of the buff */
    cmd_buff[0] = '\0';		/* Init the command buffer	      */
    strncpy (pat_save, pat, NPAT);	/* Save the old pattern string	      */
    curline = curwp->w_dotp;		/* Save the current line pointer      */
    curoff  = curwp->w_doto;		/* Save the current offset	      */
    init_direction = n;			/* Save the initial search direction  */

    /* This is a good place to start a re-execution: */

start_over:

    /* ask the user for the text of a pattern */
    col = promptpattern("ISearch: ");		/* Prompt, remember the col   */

    cpos = 0;					/* Start afresh		      */
    status = TRUE;				/* Assume everything's cool   */

    /*
       Get the first character in the pattern.  If we get an initial Control-S
       or Control-R, re-use the old search string and find the first occurrence
     */

    c = ectoc(expc = get_char());		/* Get the first character    */
    if ((c == IS_FORWARD) ||
        (c == IS_REVERSE) ||
        (c == IS_VMSFORW))			/* Reuse old search string?   */
    {
    	for (cpos = 0; pat[cpos] != 0; cpos++)	/* Yup, find the length	      */
    	    col = echochar(pat[cpos],col);	/*  and re-echo the string    */
	if (c == IS_REVERSE) {			/* forward search?	      */
	    n = -1;				/* No, search in reverse      */
	    backchar (TRUE, 1);			/* Be defensive about EOB     */
	} else
	    n = 1;				/* Yes, search forward	      */
	status = scanmore(pat, n);		/* Do the search	      */
	c = ectoc(expc = get_char());		/* Get another character      */
    }

    /* Top of the per character loop */
        	
    for (;;)					/* ISearch per character loop */
    {
	/* Check for special characters first: */
	/* Most cases here change the search */

	if (expc == metac)			/* Want to quit searching?    */
	    return (TRUE);			/* Quit searching now	      */

	switch (c)				/* dispatch on the input char */
	{
	  case IS_ABORT:			/* If abort search request    */
	    return(FALSE);			/* Quit searching again	      */

	  case IS_REVERSE:			/* If backward search	      */
	  case IS_FORWARD:			/* If forward search	      */
	  case IS_VMSFORW:			/*  of either flavor	      */
	    if (c == IS_REVERSE)		/* If reverse search	      */
		n = -1;				/* Set the reverse direction  */
	    else				/* Otherwise, 		      */
		n = 1;				/*  go forward		      */
	    status = scanmore(pat, n);		/* Start the search again     */
	    c = ectoc(expc = get_char());	/* Get the next char	      */
	    continue;				/* Go continue with the search*/

	  case IS_NEWLINE:			/* Carriage return	      */
	    c = '\n';				/* Make it a new line	      */
	    break;				/* Make sure we use it	      */

	  case IS_QUOTE:			/* Quote character	      */
	  case IS_VMSQUOTE:			/*  of either variety	      */
	    c = ectoc(expc = get_char());	/* Get the next char	      */

	  case IS_TAB:				/* Generically allowed	      */
	  case '\n':				/*  controlled characters     */
	    break;				/* Make sure we use it	      */

	  case IS_BACKSP:			/* If a backspace:            */
	  case IS_RUBOUT:			/*  or if a Rubout:	      */
	    if (cmd_offset <= 1)		/* Anything to delete?	      */
		return (TRUE);			/* No, just exit	      */
	    --cmd_offset;			/* Back up over the Rubout    */
	    cmd_buff[--cmd_offset] = '\0';	/* Yes, delete last char   */
	    curwp->w_dotp = curline;		/* Reset the line pointer     */
	    curwp->w_doto = curoff;		/*  and the offset	      */
	    n = init_direction;			/* Reset the search direction */
	    strncpy (pat, pat_save, NPAT);	/* Restore the old search str */
	    cmd_reexecute = 0;			/* Start the whole mess over  */
	    goto start_over;			/* Let it take care of itself */

	  /* Presumably a quasi-normal character comes here */

	  default:				/* All other chars    	      */
	    if (c < ' ')			/* Is it printable?	      */
	    {					/* Nope.		      */
		reeat (c);			/* Re-eat the char	      */
		return (TRUE);			/* And return the last status */
	    }
	}  /* Switch */

	/* I guess we got something to search for, so search for it	      */

	pat[cpos++] = c;			/* put the char in the buffer */
	if (cpos >= NPAT)			/* too many chars in string?  */
	{					/* Yup.  Complain about it    */
	    mlwrite("? Search string too long");
	    return(TRUE);			/* Return an error	      */
	}
	pat[cpos] = 0;				/* null terminate the buffer  */
	col = echochar(c,col);			/* Echo the character	      */
	if (!status) {				/* If we lost last time	      */
	    TTputc(BELL);		/* Feep again		      */
	    TTflush();			/* see that the feep feeps    */
	} else					/* Otherwise, we must have won*/
	    if (!(status = checknext(c, pat, n))) /* See if match	      */
		status = scanmore(pat, n);	/*  or find the next match    */
	c = ectoc(expc = get_char());		/* Get the next char	      */
    } /* for {;;} */
}

/*
 * Trivial routine to insure that the next character in the search string is
 * still true to whatever we're pointing to in the buffer.  This routine will
 * not attempt to move the "point" if the match fails, although it will 
 * implicitly move the "point" if we're forward searching, and find a match,
 * since that's the way forward isearch works.
 *
 * If the compare fails, we return FALSE and assume the caller will call
 * scanmore or something.
 */

int checknext (chr, patrn, dir)	/* Check next character in search string */
char	chr;			/* Next char to look for		 */
char	*patrn;			/* The entire search string (incl chr)   */
int	dir;			/* Search direction			 */
{
    register LINE *curline;		/* current line during scan	      */
    register int curoff;		/* position within current line	      */
    register int buffchar;		/* character at current position      */
    int status;				/* how well things go		      */


    /* setup the local scan pointer to current "." */

    curline = curwp->w_dotp;		/* Get the current line structure     */
    curoff  = curwp->w_doto;		/* Get the offset within that line    */

    if (dir > 0)			/* If searching forward		      */
    {
    	if (curoff == llength(curline)) /* If at end of line		      */
    	{
	    curline = lforw(curline);	/* Skip to the next line	      */
	    if (curline == curbp->b_linep)
		return (FALSE);		/* Abort if at end of buffer	      */
	    curoff = 0;			/* Start at the beginning of the line */
	    buffchar = '\n';		/* And say the next char is NL	      */
	} else
	    buffchar = lgetc(curline, curoff++); /* Get the next char	      */
	if (status = eq(buffchar, chr))	/* Is it what we're looking for?      */
	{
	    curwp->w_dotp = curline;	/* Yes, set the buffer's point	      */
	    curwp->w_doto = curoff;	/*  to the matched character	      */
	    curwp->w_flag |= WFMOVE;	/* Say that we've moved		      */
	}
	return (status);		/* And return the status	      */
    } else				/* Else, if reverse search:	      */
	return (match_pat (patrn));	/* See if we're in the right place    */
}

/*
 * This hack will search for the next occurrence of <pat> in the buffer, either
 * forward or backward.  It is called with the status of the prior search
 * attempt, so that it knows not to bother if it didn't work last time.  If
 * we can't find any more matches, "point" is left where it was before.  If
 * we do find a match, "point" will be at the end of the matched string for
 * forward searches and at the beginning of the matched string for reverse
 * searches.
 */
 
int scanmore(patrn, dir)	/* search forward or back for a pattern	      */
char	*patrn;			/* string to scan for			      */
int	dir;			/* direction to search			      */
{
	int	sts;			/* search status		      */

    	if (dir < 0)				/* reverse search?	      */
    	{
		rvstrcpy(tap, patrn);		/* Put reversed string in tap */
		sts = scanner(tap, REVERSE, PTBEG);
	}
	else
		sts = scanner(patrn, FORWARD, PTEND);	/* Nope. Go forward   */

	if (!sts)
	{
		TTputc(BELL);	/* Feep if search fails       */
		TTflush();		/* see that the feep feeps    */
	}

	return(sts);				/* else, don't even try	      */
}

/*
 * The following is a worker subroutine used by the reverse search.  It
 * compares the pattern string with the characters at "." for equality. If
 * any characters mismatch, it will return FALSE.
 *
 * This isn't used for forward searches, because forward searches leave "."
 * at the end of the search string (instead of in front), so all that needs to
 * be done is match the last char input.
 */

int match_pat (patrn)	/* See if the pattern string matches string at "."   */
char	*patrn;		/* String to match to buffer			     */
{
    register int  i;			/* Generic loop index/offset	      */
    register int buffchar;		/* character at current position      */
    register LINE *curline;		/* current line during scan	      */
    register int curoff;		/* position within current line	      */

    /* setup the local scan pointer to current "." */

    curline = curwp->w_dotp;		/* Get the current line structure     */
    curoff  = curwp->w_doto;		/* Get the offset within that line    */

    /* top of per character compare loop: */

    for (i = 0; i < strlen(patrn); i++)	/* Loop for all characters in patrn   */
    {
    	if (curoff == llength(curline)) /* If at end of line		      */
    	{
	    curline = lforw(curline);	/* Skip to the next line	      */
	    curoff = 0;			/* Start at the beginning of the line */
	    if (curline == curbp->b_linep)
		return (FALSE);		/* Abort if at end of buffer	      */
	    buffchar = '\n';		/* And say the next char is NL	      */
	} else
	    buffchar = lgetc(curline, curoff++); /* Get the next char	      */
	if (!eq(buffchar, patrn[i]))	/* Is it what we're looking for?      */
	    return (FALSE);		/* Nope, just punt it then	      */
    }
    return (TRUE);			/* Everything matched? Let's celebrate*/
}

/* Routine to prompt for I-Search string. */

int promptpattern(prompt)
char *prompt;
{
    char tpat[NPAT+20];

    strcpy(tpat, prompt);		/* copy prompt to output string */
    strcat(tpat, " (");			/* build new prompt string */
    expandp(pat, &tpat[strlen(tpat)], NPAT/2);	/* add old pattern */
    strcat(tpat, ")<Meta>: ");

    /* check to see if we are executing a command line */
    if (!clexec) {
	mlwrite(tpat);
    }
    return(strlen(tpat));
}

/* routine to echo i-search characters */

int echochar(c,col)
int	c;	/* character to be echoed */
int	col;	/* column to be echoed in */
{
    movecursor(term.t_nrow,col);		/* Position the cursor	      */
    if ((c < ' ') || (c == 0x7F))		/* Control character?	      */
    {
	switch (c)				/* Yes, dispatch special cases*/
	{
	  case '\n':				/* Newline		      */
	    TTputc('<');
	    TTputc('N');
	    TTputc('L');
	    TTputc('>');
	    col += 3;
	    break;

	  case '\t':				/* Tab			      */
	    TTputc('<');
	    TTputc('T');
	    TTputc('A');
	    TTputc('B');
	    TTputc('>');
	    col += 4;
	    break;

	  case 0x7F:				/* Rubout:		      */
	    TTputc('^');		/* Output a funny looking     */
	    TTputc('?');		/*  indication of Rubout      */
	    col++;				/* Count the extra char       */
	    break;

	  default:				/* Vanilla control char       */
	    TTputc('^');		/* Yes, output prefix	      */
    	    TTputc(c+0x40);		/* Make it "^X"		      */
	    col++;				/* Count this char	      */
	}
    } else
	TTputc(c);			/* Otherwise, output raw char */
    TTflush();				/* Flush the output	      */
    return(++col);				/* return the new column no   */
}

/*
 * Routine to get the next character from the input stream.  If we're reading
 * from the real terminal, force a screen update before we get the char. 
 * Otherwise, we must be re-executing the command string, so just return the
 * next character.
 */

int get_char ()
{
    int	c;				/* A place to get a character	      */

    /* See if we're re-executing: */

    if (cmd_reexecute >= 0)		/* Is there an offset?		      */
	if ((c = cmd_buff[cmd_reexecute++]) != 0)
	    return (c);			/* Yes, return any character	      */

    /* We're not re-executing (or aren't any more).  Try for a real char      */

    cmd_reexecute = -1;		/* Say we're in real mode again	      */
    update(FALSE);			/* Pretty up the screen		      */
    if (cmd_offset >= CMDBUFLEN-1)	/* If we're getting too big ...	      */
    {
	mlwrite ("? command too long");	/* Complain loudly and bitterly	      */
	return (metac);			/* And force a quit		      */
    }
    c = get1key();		/* Get the next character	      */
    cmd_buff[cmd_offset++] = c; /* Save the char for next time        */
    cmd_buff[cmd_offset] = '\0';/* And terminate the buffer	      */
    return (c);				/* Return the character		      */
}

/*
 * Hacky routine to re-eat a character.  This will save the character to be
 * re-eaten by redirecting the input call to a routine here.  Hack, etc.
 */

/* Come here on the next term.t_getchar call: */

int uneat()
{
    int c;

    term.t_getchar = saved_get_char;	/* restore the routine address	      */
    c = eaten_char;			/* Get the re-eaten char	      */
    eaten_char = -1;			/* Clear the old char		      */
    return(c);				/* and return the last char	      */
}

int reeat(c)
int	c;
{
    if (eaten_char != -1)		/* If we've already been here	      */
	return/*(NULL)*/;		/* Don't do it again		      */
    eaten_char = c;			/* Else, save the char for later      */
    saved_get_char = term.t_getchar;	/* Save the char get routine	      */
    term.t_getchar = uneat;		/* Replace it with ours		      */
}
#else
isearch()
{
}
#endif
