richk@pogo.UUCP (Richard G. Knowles) (08/19/86)
----------------------------------------------------------------
The following is an extensive set of changes to the user interface of
isearch. I made them to the 3.6 version and re-applied them to the 3.7
version. The list of changes/bug fixes is as follows:
1-Aug-86
- Removed command buffer reexecution; backup instead. Buffer re-
execution took too long when deep into multiple re-searches.
- Changed prompts to indicate direction
- Added search failure prompt
- Won't save quote command - just quoted char. Saving the quote
command made it impossible to backup over it, since the ^H/^?
ended up being quoted themselves (with the reexecution method).
- Keep track of last successful search string so that additional
search requests (same direction only) can be ignored until the
the bad characters are remove from the search string.
- Won't save additional search commands if search failure since
nothing there to backup to.
- Changed BELL writes to use beep routine instead
12-Aug-86 -- to much memory usage for functionality gained so:
- Removed command buffer altogether, ^H/^? removes last char
and restores cursor to position it was at when that char
was entered. User must switch search direction to backup to
prior occurance.
- Commented out the VMSxx character checks (I use ^V too much)
The only major short coming I know about is that the search command
characters are hard coded to ^S/^R (and/or ^V) as well as the quote/
rubout/backspace to ^Q(or ^X)/^H/^? . It really ought to use the
ones the user has bound instead of using hardcoded values (albeit the common
defaults)
------------- shell archive of isearch.c below here ----------------
# This is a shar archive.
# Remove everything above this line.
# Run the file through sh, not csh.
# (type `sh isearch.shar.1')
# If you do not see the message
# `isearch.shar.1 completed!'
# then the file was incomplete.
echo extracting - isearch.c
sed 's/^X//' > isearch.c << 'FRIDAY_NIGHT'
X/*
X * The functions in this file implement commands that perform incremental
X * searches in the forward and backward directions. This "ISearch" command
X * is intended to emulate the same command from the original EMACS
X * implementation (ITS). Contains references to routines internal to
X * SEARCH.C.
X *
X * REVISION HISTORY:
X *
X * D. R. Banks 9-May-86
X * - added ITS EMACSlike ISearch
X *
X * R. G. Knowles 1-Aug-86
X * - Removed command buffer reexecution; backup instead. Buffer re-
X * execution took too long when deep into multiple re-searches.
X * - Changed prompts to indicate direction
X * - Added search failure prompt
X * - Won't save quote command - just quoted char. Saving the quote
X * command made it impossible to backup over it, since the ^H/^?
X * ended up being quoted themselves (with the reexecution method).
X * - Keep track of last successful search string so that additional
X * search requests (same direction only) can be ignored until the
X * the bad characters are remove from the search string.
X * - Won't save additional search commands if search failure since
X * nothing there to backup to.
X * - Changed BELL writes to use beep routine instead
X *
X * 12-Aug-86 -- to much memory usage for functionality gained so:
X * - Removed command buffer altogether, ^H/^? removes last char
X * and restores cursor to position it was at when that char
X * was entered. User must switch search direction to backup to
X * prior occurance.
X * - Commented out the VMSxx character checks
X *
X */
X
X#include <stdio.h>
X#include "estruct.h"
X#include "edef.h"
X
X/* string search input parameters */
X
X#define PTBEG 1 /* leave the point at the beginning on search */
X#define PTEND 2 /* leave the point at the end on search */
X
Xextern int forscan(); /* Handy search routine */
Xextern int eq(); /* Compare chars, match case */
X
X/* A couple of "own" variables for re-eat */
X
Xint (*saved_get_char)(); /* Get character routine */
Xint eaten_char = -1; /* Re-eaten char */
X
X/* A couple more "own" variables for the command string */
X
XLINE * curline_after_char[NPAT]; /* Current line after command */
Xint curoff_after_char[NPAT]; /* Current offset after command */
Xint first_bad_char; /* save position of char failed on */
Xint oldstatus; /* status of previous search */
X
X/* Some character constants within ISearch */
X
X#define IS_ABORT 0x07 /* Abort the isearch */
X#define IS_BACKSP 0x08 /* Delete previous char */
X#define IS_TAB 0x09 /* Tab character (allowed search char) */
X#define IS_NEWLINE 0x0D /* New line from keyboard (Carriage return) */
X#define IS_QUOTE 0x11 /* Quote next character */
X#define IS_REVERSE 0x12 /* Search backward */
X#define IS_FORWARD 0x13 /* Search forward */
X#define IS_VMSQUOTE 0x16 /* VMS quote character */
X#define IS_VMSFORW 0x18 /* Search forward for VMS */
X#define IS_QUIT 0x1B /* Exit the search */
X#define IS_RUBOUT 0x7F /* Delete previous character */
X
X/*
X * Subroutine to do incremental reverse search. It actually uses the
X * same code as the normal incremental search, as both can go both ways.
X */
X
Xint risearch(f, n)
X{
X LINE *curline; /* Current line on entry */
X int curoff; /* Current offset on entry */
X
X /* remember the initial . on entry: */
X
X curline = curwp->w_dotp; /* Save the current line pointer */
X curoff = curwp->w_doto; /* Save the current offset */
X
X /* Make sure the search doesn't match where we already are: */
X
X backchar(TRUE, 1); /* Back up a character */
X
X if (!(isearch(f, -n))) /* Call ISearch backwards */
X { /* If error in search: */
X curwp->w_dotp = curline; /* Reset the line pointer */
X curwp->w_doto = curoff; /* and the offset to original value */
X curwp->w_flag |= WFMOVE; /* Say we've moved */
X update(); /* And force an update */
X mlwrite ("[search failed]"); /* Say we died */
X } else mlerase (); /* If happy, just erase the cmd line */
X}
X
X/* Again, but for the forward direction */
X
Xint fisearch(f, n)
X{
X LINE *curline; /* Current line on entry */
X int curoff; /* Current offset on entry */
X
X /* remember the initial . on entry: */
X
X curline = curwp->w_dotp; /* Save the current line pointer */
X curoff = curwp->w_doto; /* Save the current offset */
X
X /* do the search */
X
X if (!(isearch(f, n))) /* Call ISearch forwards */
X { /* If error in search: */
X curwp->w_dotp = curline; /* Reset the line pointer */
X curwp->w_doto = curoff; /* and the offset to original value */
X curwp->w_flag |= WFMOVE; /* Say we've moved */
X update(); /* And force an update */
X mlwrite ("[search failed]"); /* Say we died */
X } else mlerase (); /* If happy, just erase the cmd line */
X}
X
X/*
X * Subroutine to do an incremental search. In general, this works similarly
X * to the older micro-emacs search function, except that the search happens
X * as each character is typed, with the screen and cursor updated with each
X * new search character.
X *
X * While searching forward, each successive character will leave the cursor
X * at the end of the entire matched string. Typing a Control-S or Control-X
X * will cause the next occurrence of the string to be searched for (where the
X * next occurrence does NOT overlap the current occurrence). A Control-R will
X * change to a backwards search, ESC will terminate the search and Control-G
X * will abort the search. Rubout will back up to the previous match of the
X * string, or if the starting point is reached first, it will delete the
X * last character from the search string.
X *
X * While searching backward, each successive character will leave the cursor
X * at the beginning of the matched string. Typing a Control-R will search
X * backward for the next occurrence of the string. Control-S or Control-X
X * will revert the search to the forward direction. In general, the reverse
X * incremental search is just like the forward incremental search inverted.
X *
X * In all cases, if the search fails, the user will be feeped, and the search
X * will stall until the pattern string is edited back into something that
X * exists (or until the search is aborted).
X */
X
Xisearch(f, n)
X{
X int status; /* Search status */
X int col; /* curr prompt column */
X int scol; /* initial prompt column */
X int tcol, t2col; /* temporary prompt column */
X register int cpos; /* character number in search string */
X register int c; /* current input character */
X char pat_save[NPAT]; /* Saved copy of the old pattern str */
X LINE *curline; /* Current line on entry */
X int curoff; /* Current offset on entry */
X int init_direction; /* The initial search direction */
X
X /* Initialize starting conditions */
X
X strncpy (pat_save, pat, NPAT); /* Save the old pattern string */
X curline = curwp->w_dotp; /* Save the current line pointer */
X curoff = curwp->w_doto; /* Save the current offset */
X init_direction = n; /* Save the initial search direction */
X
X /* ask the user for the text of a pattern */
X if (n >= 0)
X {
X scol = promptpattern("I-Search: "); /* Prompt, remember the col */
X }
X else
X {
X scol = promptpattern("R-Search: "); /* Prompt, remember the col */
X }
X
X col = scol; /* keyboard echo starts here */
X cpos = 0; /* Start afresh */
X status = TRUE; /* Assume everything's cool */
X oldstatus = TRUE; /* Assume everything's cool */
X
X /*
X Get the first character in the pattern. If we get an initial Control-S
X or Control-R, re-use the old search string and find the first occurrence
X */
X
X c = get_char(); /* Get the first character */
X if ((c == IS_FORWARD) ||
X (c == IS_REVERSE) ||
X (c == IS_VMSFORW)) /* Reuse old search string? */
X {
X for (cpos = 0; pat[cpos] != 0; cpos++) /* Yup, find the length */
X col = echochar(pat[cpos],col); /* and re-echo the string */
X if (c == IS_REVERSE) { /* forward search? */
X n = -1; /* No, search in reverse */
X backchar (TRUE, 1); /* Be defensive about EOB */
X } else
X n = 1; /* Yes, search forward */
X status = scanmore(pat,n,status); /* Do the search */
X display_status(status); /* Indicate results */
X if (!status)
X (*term.t_beep)(); /* It failed */
X c = get_char (); /* Get another character */
X }
X
X /* Top of the per character loop */
X
X for (;;) /* ISearch per character loop */
X {
X /* Check for magic characters first: */
X /* Most cases here change the search */
X
X switch (c) /* dispatch on the input char */
X {
X case IS_ABORT: /* If abort search request */
X return(FALSE); /* Quit searching again */
X
X case IS_REVERSE: /* If backward search */
X case IS_FORWARD: /* If forward search */
X/* case IS_VMSFORW: */ /* of either flavor */
X if (c == IS_REVERSE) /* If reverse search */
X {
X if (n != -1) /* and wasn't before */
X {
X status = TRUE; /* Switched so must look again*/
X n = -1; /* Set the reverse direction */
X movecursor(term.t_nrow,0); /* Position the cursor */
X (*term.t_putchar)('R'); /* Change prompt */
X }
X }
X else /* Otherwise, forward search */
X if (n != 1) /* and wasn't before. */
X {
X status = TRUE; /* Switched so must look again*/
X n = 1; /* Set forward direction */
X movecursor(term.t_nrow,0); /* Position the cursor */
X (*term.t_putchar)('I'); /* Change prompt */
X }
X if (status)
X {
X status = scanmore(pat,n,status);/* Start the search again */
X display_status (status);
X }
X if (!status)
X (*term.t_beep)(); /* It failed (again?)! */
X c = get_char (); /* Get the next char */
X continue; /* Go continue with the search*/
X
X case IS_QUIT: /* Want to quit searching? */
X return (TRUE); /* Quit searching now */
X
X case IS_NEWLINE: /* Carriage return */
X c = '\n'; /* Make it a new line */
X break; /* Make sure we use it */
X
X case IS_QUOTE: /* Quote character */
X/* case IS_VMSQUOTE: */ /* of either variety */
X c = get_char (); /* Get the next char */
X break;
X
X case IS_TAB: /* Generically allowed */
X case '\n': /* controlled characters */
X break; /* Make sure we use it */
X
X case IS_BACKSP: /* If a backspace: */
X case IS_RUBOUT: /* or if a Rubout: */
X if (cpos <= 0) /* Anything to delete? */
X return (TRUE); /* No, just exit */
X
X if (--cpos < first_bad_char)
X {
X first_bad_char = -1;
X status = TRUE;
X display_status (status);
X }
X pat[cpos] = '\0';
X tcol = col;
X col = scol;
X for (cpos = 0; pat[cpos] != 0; cpos++) /* re-echo search */
X col = echochar(pat[cpos],col); /* string. */
X for (t2col=col; t2col < tcol ; ) /* delete any extra */
X t2col = echochar(' ',t2col); /* string. */
X /* Reset the line pointer and */
X curwp->w_dotp = curline_after_char[cpos];
X /* the offset to prev pos */
X curwp->w_doto = curoff_after_char[cpos];
X
X curwp->w_flag |= WFMOVE; /* Say we've moved */
X c = get_char (); /* Get the next char */
X continue; /* and try again */
X
X
X /* Presumably a quasi-normal character comes here */
X
X default: /* All other chars */
X if (c < ' ') /* Is it printable? */
X { /* Nope. */
X re_eat (c); /* Re-eat the char */
X return (TRUE); /* And return the last status */
X }
X } /* Switch */
X
X /* I guess we got something to search for, so search for it */
X
X /* save cursor pos of last result so */
X curline_after_char[cpos] = curwp->w_dotp;
X /* we can quickly return there when */
X curoff_after_char[cpos] = curwp->w_doto;
X /* backing up with BS or RUB */
X pat[cpos++] = c; /* put the char in the buffer */
X if (cpos >= NPAT) /* too many chars in string? */
X { /* Yup. Complain about it */
X mlwrite("? Search string too long");
X return(TRUE); /* Return an error */
X }
X pat[cpos] = 0; /* null terminate the buffer */
X col = echochar(c,col); /* Echo the character */
X if (!status) { /* If we lost last time */
X (*term.t_beep)(); /* Feep again */
X } else /* Otherwise, we must have won*/
X if (!(status = checknext(c,pat,n,status))) /* See if match or */
X if (!(status = scanmore(pat,n,TRUE))) /* find the next match */
X {
X first_bad_char = cpos; /* remember where we went bad */
X display_status (status);
X (*term.t_beep)();
X }
X
X c = get_char (); /* Get the next char */
X } /* for {;;} */
X}
X
X/*
X * Routine to visually indicate the state of the search by overwritting
X * the prompt with FAIL when in a failure mode.
X */
X
Xdisplay_status (status)
Xint status;
X{
X if (status != oldstatus)
X {
X oldstatus = status;
X movecursor(term.t_nrow,2);
X if (status)
X {
X (*term.t_putchar)('S');
X (*term.t_putchar)('e');
X (*term.t_putchar)('a');
X (*term.t_putchar)('r');
X (*term.t_putchar)('c');
X (*term.t_putchar)('h');
X }
X else
X {
X (*term.t_putchar)('S');
X (*term.t_putchar)(' ');
X (*term.t_putchar)('F');
X (*term.t_putchar)('A');
X (*term.t_putchar)('I');
X (*term.t_putchar)('L');
X }
X }
X}
X
X/*
X * Trivial routine to insure that the next character in the search string is
X * still true to whatever we're pointing to in the buffer. This routine will
X * not attempt to move the "point" if the match fails, although it will
X * implicitly move the "point" if we're forward searching, and find a match,
X * since that's the way forward isearch works.
X *
X * If the compare fails, we return FALSE and assume the caller will call
X * scanmore or something.
X */
X
Xint checknext (chr, patrn, dir, sts)/* Check next character in search string */
Xchar chr; /* Next char to look for */
Xchar *patrn; /* The entire search string (incl chr) */
Xint dir; /* Search direction */
Xint sts; /* Search status */
X{
X register LINE *curline; /* current line during scan */
X register int curoff; /* position within current line */
X register int buffchar; /* character at current position */
X int status; /* how well things go */
X
X if (!sts) return(FALSE); /* Don't try unless ok so far */
X
X /* setup the local scan pointer to current "." */
X
X curline = curwp->w_dotp; /* Get the current line structure */
X curoff = curwp->w_doto; /* Get the offset within that line */
X
X if (dir > 0) /* If searching forward */
X {
X if (curoff == llength(curline)) /* If at end of line */
X {
X curline = lforw(curline); /* Skip to the next line */
X if (curline == curbp->b_linep)
X return (FALSE); /* Abort if at end of buffer */
X curoff = 0; /* Start at the beginning of the line */
X buffchar = '\n'; /* And say the next char is NL */
X } else
X buffchar = lgetc(curline, curoff++); /* Get the next char */
X if (status = eq(buffchar, chr)) /* Is it what we're looking for? */
X {
X curwp->w_dotp = curline; /* Yes, set the buffer's point */
X curwp->w_doto = curoff; /* to the matched character */
X curwp->w_flag |= WFMOVE; /* Say that we've moved */
X }
X return (status); /* And return the status */
X } else /* Else, if reverse search: */
X return (match_pat (patrn)); /* See if we're in the right place */
X}
X
X/*
X * This hack will search for the next occurrence of <pat> in the buffer, either
X * forward or backward. It is called with the status of the prior search
X * attempt, so that it knows not to bother if it didn't work last time. If
X * we can't find any more matches, "point" is left where it was before. If
X * we do find a match, "point" will be at the end of the matched string for
X * forward searches and at the beginning of the matched string for reverse
X * searches.
X */
X
Xint scanmore(patrn,dir,sts) /* search forward or back for a pattern */
Xchar *patrn; /* string to scan for */
Xint dir; /* direction to search */
Xint sts; /* previous search status */
X{
X if ((sts) && /* don't try unless successful last time */
X (patrn[0] != '\0')) /* and there is something to search for */
X {
X if (dir < 0) /* reverse search? */
X sts = bakscan(patrn); /* Yes, call our hacky routine*/
X else
X sts = forscan(patrn,PTEND); /* Nope. Go forward */
X }
X else
X (*term.t_beep)(); /* Feep if search fails */
X
X return(sts); /* else, don't even try */
X}
X
X/*
X * The following is a minimal implementation of the reverse of "forscan".
X * We aren't using the routine in SEARCH.C because it likes to type stuff,
X * but the real solution is probably to fix that instead of duplicate the
X * code here like we're doing. On the other hand, we don't want to touch
X * more modules than we have to for this first round ...
X *
X * This always leaves "." at the beginning of the matched pattern string
X */
X
Xint bakscan (patrn) /* Scan backwards for a match */
Xchar *patrn; /* Search string to be matched */
X{
X LINE *initline; /* initial line pointer before scan */
X int initoff; /* position within initial line */
X
X /* Remember "point" on entry: */
X
X initline = curwp->w_dotp; /* Get the current line structure */
X initoff = curwp->w_doto; /* Get the offset within that line */
X
X /*
X * Loop here, stepping the cursor until we match or until we reach the top
X * of the buffer
X */
X
X while (backchar(TRUE, 1)) /* As long as there're chars */
X if (match_pat (patrn)) /* See if we match */
X return (TRUE); /* Yep. Stop'er right here */
X curwp->w_dotp = initline; /* Top of buffer, just reset */
X curwp->w_doto = initoff; /* to original "point" */
X curwp->w_flag |= WFMOVE; /* In case backchar moved us */
X return (FALSE); /* And return failure */
X}
X
X/*
X * The following is a worker subroutine used by the reverse search. It
X * compares the pattern string with the characters at "." for equality. If
X * any characters mismatch, it will return FALSE.
X *
X * This isn't used for forward searches, because forward searches leave "."
X * at the end of the search string (instead of in front), so all that needs to
X * be done is match the last char input.
X */
X
Xint match_pat (patrn) /* See if the pattern string matches string at "." */
Xchar *patrn; /* String to match to buffer */
X{
X register int i; /* Generic loop index/offset */
X register int buffchar; /* character at current position */
X register LINE *curline; /* current line during scan */
X register int curoff; /* position within current line */
X
X /* setup the local scan pointer to current "." */
X
X curline = curwp->w_dotp; /* Get the current line structure */
X curoff = curwp->w_doto; /* Get the offset within that line */
X
X /* top of per character compare loop: */
X
X for (i = 0; i < strlen(patrn); i++) /* Loop for all characters in patrn */
X {
X if (curoff == llength(curline)) /* If at end of line */
X {
X curline = lforw(curline); /* Skip to the next line */
X curoff = 0; /* Start at the beginning of the line */
X if (curline == curbp->b_linep)
X return (FALSE); /* Abort if at end of buffer */
X buffchar = '\n'; /* And say the next char is NL */
X } else
X buffchar = lgetc(curline, curoff++); /* Get the next char */
X if (!eq(buffchar, patrn[i])) /* Is it what we're looking for? */
X return (FALSE); /* Nope, just punt it then */
X }
X return (TRUE); /* Everything matched? Let's celebrate*/
X}
X
X/* Routine to prompt for I-Search string. */
X
Xint promptpattern(prompt)
Xchar *prompt;
X{
X register int s;
X char tpat[NPAT+20];
X
X strcpy(tpat, prompt); /* copy prompt to output string */
X strcat(tpat, " ["); /* build new prompt string */
X expandp(pat, &tpat[strlen(tpat)], NPAT/2); /* add old pattern */
X strcat(tpat, "]<ESC>: ");
X
X /* check to see if we are executing a command line */
X if (!clexec) {
X mlwrite(tpat);
X }
X return(strlen(tpat));
X}
X
X/* routine to echo i-search characters */
X
Xint echochar(c,col)
Xint c; /* character to be echoed */
Xint col; /* column to be echoed in */
X{
X movecursor(term.t_nrow,col); /* Position the cursor */
X if ((c < ' ') || (c == 0x7F)) /* Control character? */
X {
X switch (c) /* Yes, dispatch special cases*/
X {
X case '\n': /* Newline */
X (*term.t_putchar)('<');
X (*term.t_putchar)('N');
X (*term.t_putchar)('L');
X (*term.t_putchar)('>');
X col += 3;
X break;
X
X case '\t': /* Tab */
X (*term.t_putchar)('<');
X (*term.t_putchar)('T');
X (*term.t_putchar)('A');
X (*term.t_putchar)('B');
X (*term.t_putchar)('>');
X col += 4;
X break;
X
X case 0x7F: /* Rubout: */
X (*term.t_putchar)('^'); /* Output a funny looking */
X (*term.t_putchar)('?'); /* indication of Rubout */
X col++; /* Count the extra char */
X break;
X
X default: /* Vanilla control char */
X (*term.t_putchar)('^'); /* Yes, output prefix */
X (*term.t_putchar)(c+0x40); /* Make it "^X" */
X col++; /* Count this char */
X }
X } else
X (*term.t_putchar)(c); /* Otherwise, output raw char */
X (*term.t_flush)(); /* Flush the output */
X return(++col); /* return the new column no */
X}
X
X/*
X * Routine to get the next character from the input stream. If we're reading
X * from the real terminal, force a screen update before we get the char.
X * Otherwise, we must be re-executing the command string, so just return the
X * next character.
X */
X
Xint get_char ()
X{
X int c; /* A place to get a character */
X
X update(); /* Pretty up the screen */
X c = (*term.t_getchar)(); /* Get the next character */
X return (c); /* Return the character */
X}
X
X/*
X * Hacky routine to re-eat a character. This will save the character to be
X * re-eaten by redirecting the input call to a routine here. Hack, etc.
X */
X
X/* Come here on the next term.t_getchar call: */
X
Xint un_eat()
X{
X char c;
X term.t_getchar = saved_get_char; /* restore the routine address */
X c = eaten_char; /* Get the re-eaten char */
X eaten_char = -1; /* Clear the old char */
X return (c); /* and return the last char */
X}
X
Xint re_eat(c)
Xchar c;
X{
X if (eaten_char != -1) /* If we've already been here */
X return (NULL); /* Don't do it again */
X eaten_char = c; /* Else, save the char for later */
X saved_get_char = term.t_getchar; /* Save the char get routine */
X term.t_getchar = un_eat; /* Replace it with ours */
X}
FRIDAY_NIGHT
echo isearch.shar.1 completed!
# That's all folks!
-------------------------------------------------------------------
Richard G. Knowles tektronix!pogo!richk
Tektronix, Inc
Del. Sta. 63-356
P.O. Box 1000
Wilsonville, Or 97070