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