#! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'stevie/search.c' <<'END_OF_FILE' X/* $Header: /nw/tony/src/stevie/src/RCS/search.c,v 1.16 89/08/06 09:50:51 tony Exp $ X * X * This file contains various searching-related routines. These fall into X * three groups: string searches (for /, ?, n, and N), character searches X * within a single line (for f, F, t, T, etc), and "other" kinds of searches X * like the '%' command, and 'word' searches. X */ X X#include "stevie.h" X#include "regexp.h" /* Henry Spencer's (modified) reg. exp. routines */ X X/* X * String searches X * X * The actual searches are done using Henry Spencer's regular expression X * library. X */ X X#define BEGWORD "([^a-zA-Z0-9_]|^)" /* replaces "\<" in search strings */ X#define ENDWORD "([^a-zA-Z0-9_]|$)" /* likewise replaces "\>" */ X X#define BEGCHAR(c) (islower(c) || isupper(c) || isdigit(c) || ((c) == '_')) X Xbool_t begword; /* does the search include a 'begin word' match */ X X/* X * mapstring(s) - map special backslash sequences X */ Xstatic char * Xmapstring(s) Xregister char *s; X{ X static char ns[80]; X register char *p; X X begword = FALSE; X X for (p = ns; *s ;s++) { X if (*s != '\\') { /* not an escape */ X *p++ = *s; X continue; X } X switch (*++s) { X case '/': X *p++ = '/'; X break; X X case '<': X strcpy(p, BEGWORD); X p += strlen(BEGWORD); X begword = TRUE; X break; X X case '>': X strcpy(p, ENDWORD); X p += strlen(ENDWORD); X break; X X default: X *p++ = '\\'; X *p++ = *s; X break; X } X } X *p++ = NUL; X X return ns; X} X Xstatic char *laststr = NULL; Xstatic int lastsdir; X Xstatic LPTR * Xssearch(dir,str) Xint dir; /* FORWARD or BACKWARD */ Xchar *str; X{ X LPTR *bcksearch(), *fwdsearch(); X LPTR *pos; X char *old_ls = laststr; X X reg_ic = P(P_IC); /* tell the regexp routines how to search */ X X laststr = strsave(str); X lastsdir = dir; X X if (old_ls != NULL) X free(old_ls); X X if (dir == BACKWARD) { X smsg("?%s", laststr); X pos = bcksearch(mapstring(laststr)); X } else { X smsg("/%s", laststr); X pos = fwdsearch(mapstring(laststr)); X } X X /* X * This is kind of a kludge, but its needed to make X * 'beginning of word' searches land on the right place. X */ X if (pos != NULL && begword) { X if (pos->index != 0 || !BEGCHAR(pos->linep->s[0])) X pos->index += 1; X } X return pos; X} X Xbool_t Xdosearch(dir,str) Xint dir; Xchar *str; X{ X LPTR *p; X X if (str == NULL) X str = laststr; X X got_int = FALSE; X X if ((p = ssearch(dir,str)) == NULL) { X if (got_int) X msg("Interrupt"); X else X msg("Pattern not found"); X X got_int = FALSE; X return FALSE; X } else { X LPTR savep; X X cursupdate(); X /* X * if we're backing up, we make sure the line we're on X * is on the screen. X */ X setpcmark(); X *Curschar = savep = *p; X set_want_col = TRUE; X cursupdate(); X X return TRUE; X } X} X X#define OTHERDIR(x) (((x) == FORWARD) ? BACKWARD : FORWARD) X Xbool_t Xrepsearch(flag) Xint flag; X{ X int dir = lastsdir; X bool_t found; X X if ( laststr == NULL ) { X beep(); X return FALSE; X } X X found = dosearch(flag ? OTHERDIR(lastsdir) : lastsdir, laststr); X X /* X * We have to save and restore 'lastsdir' because it gets munged X * by ssearch() and winds up saving the wrong direction from here X * if 'flag' is true. X */ X lastsdir = dir; X X return found; X} X X/* X * regerror - called by regexp routines when errors are detected. X */ Xvoid Xregerror(s) Xchar *s; X{ X emsg(s); X} X Xstatic LPTR * Xfwdsearch(str) Xregister char *str; X{ X static LPTR infile; X register LPTR *p; X regexp *prog; X X register char *s; X register int i; X X if ((prog = regcomp(str)) == NULL) { X emsg("Invalid search string"); X return NULL; X } X X p = Curschar; X i = Curschar->index + 1; X do { X s = p->linep->s + i; X X if (regexec(prog, s, i == 0)) { /* got a match */ X infile.linep = p->linep; X infile.index = (int) (prog->startp[0] - p->linep->s); X free((char *)prog); X return (&infile); X } X i = 0; X X if (got_int) X goto fwdfail; X X } while ((p = nextline(p)) != NULL); X X /* X * If wrapscan isn't set, then don't scan from the beginning X * of the file. Just return failure here. X */ X if (!P(P_WS)) X goto fwdfail; X X /* search from the beginning of the file to Curschar */ X for (p = Filemem; p != NULL ;p = nextline(p)) { X s = p->linep->s; X X if (regexec(prog, s, TRUE)) { /* got a match */ X infile.linep = p->linep; X infile.index = (int) (prog->startp[0] - s); X free((char *)prog); X return (&infile); X } X X if (p->linep == Curschar->linep) X break; X X if (got_int) X goto fwdfail; X } X Xfwdfail: X free((char *)prog); X return NULL; X} X Xstatic LPTR * Xbcksearch(str) Xchar *str; X{ X static LPTR infile; X register LPTR *p = &infile; X register char *s; X register int i; X register char *match; X regexp *prog; X X /* make sure str isn't empty */ X if (str == NULL || *str == NUL) X return NULL; X X if ((prog = regcomp(str)) == NULL) { X emsg("Invalid search string"); X return NULL; X } X X *p = *Curschar; X if (dec(p) == -1) { /* already at start of file? */ X *p = *Fileend; X p->index = strlen(p->linep->s) - 1; X } X X if (begword) /* so we don't get stuck on one match */ X dec(p); X X i = p->index; X X do { X s = p->linep->s; X X if (regexec(prog, s, TRUE)) { /* match somewhere on line */ X X /* X * Now, if there are multiple matches on this line, X * we have to get the last one. Or the last one X * before the cursor, if we're on that line. X */ X match = prog->startp[0]; X X while (regexec(prog, prog->endp[0], FALSE)) { X if ((i >= 0) && ((prog->startp[0] - s) > i)) X break; X match = prog->startp[0]; X } X X if ((i >= 0) && ((match - s) > i)) { X i = -1; X continue; X } X X infile.linep = p->linep; X infile.index = (int) (match - s); X free((char *)prog); X return (&infile); X } X i = -1; X X if (got_int) X goto bckfail; X X } while ((p = prevline(p)) != NULL); X X /* X * If wrapscan isn't set, bag the search now X */ X if (!P(P_WS)) X goto bckfail; X X /* search backward from the end of the file */ X p = prevline(Fileend); X do { X s = p->linep->s; X X if (regexec(prog, s, TRUE)) { /* match somewhere on line */ X X /* X * Now, if there are multiple matches on this line, X * we have to get the last one. X */ X match = prog->startp[0]; X X while (regexec(prog, prog->endp[0], FALSE)) X match = prog->startp[0]; X X infile.linep = p->linep; X infile.index = (int) (match - s); X free((char *)prog); X return (&infile); X } X X if (p->linep == Curschar->linep) X break; X X if (got_int) X goto bckfail; X X } while ((p = prevline(p)) != NULL); X Xbckfail: X free((char *)prog); X return NULL; X} X X/* X * dosub(lp, up, cmd) X * X * Perform a substitution from line 'lp' to line 'up' using the X * command pointed to by 'cmd' which should be of the form: X * X * /pattern/substitution/g X * X * The trailing 'g' is optional and, if present, indicates that multiple X * substitutions should be performed on each line, if applicable. X * The usual escapes are supported as described in the regexp docs. X */ Xvoid Xdosub(lp, up, cmd) XLPTR *lp, *up; Xchar *cmd; X{ X LINE *cp; X char *pat, *sub; X regexp *prog; X int nsubs; X bool_t do_all; /* do multiple substitutions per line */ X X /* X * If no range was given, do the current line. If only one line X * was given, just do that one. X */ X if (lp->linep == NULL) X *up = *lp = *Curschar; X else { X if (up->linep == NULL) X *up = *lp; X } X X pat = ++cmd; /* skip the initial '/' */ X X while (*cmd) { X if (*cmd == '\\') /* next char is quoted */ X cmd += 2; X else if (*cmd == '/') { /* delimiter */ X *cmd++ = NUL; X break; X } else X cmd++; /* regular character */ X } X X if (*pat == NUL) { X emsg("NULL pattern specified"); X return; X } X X sub = cmd; X X do_all = FALSE; X X while (*cmd) { X if (*cmd == '\\') /* next char is quoted */ X cmd += 2; X else if (*cmd == '/') { /* delimiter */ X do_all = (cmd[1] == 'g'); X *cmd++ = NUL; X break; X } else X cmd++; /* regular character */ X } X X reg_ic = P(P_IC); /* set "ignore case" flag appropriately */ X X if ((prog = regcomp(pat)) == NULL) { X emsg("Invalid search string"); X return; X } X X nsubs = 0; X X for (cp = lp->linep; cp != NULL ;cp = cp->next) { X if (regexec(prog, cp->s, TRUE)) { /* a match on this line */ X char *ns, *sns, *p; X X /* X * Get some space for a temporary buffer X * to do the substitution into. X */ X sns = ns = alloc(2048); X if (!sns) return; X *sns = NUL; X X p = cp->s; X X do { X for (ns = sns; *ns ;ns++) X ; X /* X * copy up to the part that matched X */ X while (p < prog->startp[0]) X *ns++ = *p++; X X regsub(prog, sub, ns); X X /* X * continue searching after the match X */ X p = prog->endp[0]; X X } while (regexec(prog, p, FALSE) && do_all); X X for (ns = sns; *ns ;ns++) X ; X X /* X * copy the rest of the line, that didn't match X */ X while (*p) X *ns++ = *p++; X X *ns = NUL; X X free(cp->s); /* free the original line */ X cp->s = strsave(sns); /* and save the modified str */ X cp->size = strlen(cp->s) + 1; X free(sns); /* free the temp buffer */ X nsubs++; X CHANGED; X } X if (cp == up->linep) X break; X } X X if (nsubs) { X updatescreen(); X if (nsubs >= P(P_RP)) X smsg("%d substitution%c", nsubs, (nsubs>1) ? 's' : ' '); X } else X msg("No match"); X X free((char *)prog); X} X X/* X * doglob(cmd) X * X * Execute a global command of the form: X * X * g/pattern/X X * X * where 'x' is a command character, currently one of the following: X * X * d Delete all matching lines X * p Print all matching lines X * X * The command character (as well as the trailing slash) is optional, and X * is assumed to be 'p' if missing. X */ Xvoid Xdoglob(lp, up, cmd) XLPTR *lp, *up; Xchar *cmd; X{ X LINE *cp; X char *pat; X regexp *prog; X int ndone; X char cmdchar = NUL; /* what to do with matching lines */ X X /* X * If no range was given, do every line. If only one line X * was given, just do that one. X */ X if (lp->linep == NULL) { X *lp = *Filemem; X *up = *Fileend; X } else { X if (up->linep == NULL) X *up = *lp; X } X X pat = ++cmd; /* skip the initial '/' */ X X while (*cmd) { X if (*cmd == '\\') /* next char is quoted */ X cmd += 2; X else if (*cmd == '/') { /* delimiter */ X cmdchar = cmd[1]; X *cmd++ = NUL; X break; X } else X cmd++; /* regular character */ X } X if (cmdchar == NUL) X cmdchar = 'p'; X X reg_ic = P(P_IC); /* set "ignore case" flag appropriately */ X X if (cmdchar != 'd' && cmdchar != 'p') { X emsg("Invalid command character"); X return; X } X X if ((prog = regcomp(pat)) == NULL) { X emsg("Invalid search string"); X return; X } X X msg(""); X ndone = 0; X got_int = FALSE; X X for (cp = lp->linep; cp != NULL && !got_int ;cp = cp->next) { X if (regexec(prog, cp->s, TRUE)) { /* a match on this line */ X switch (cmdchar) { X X case 'd': /* delete the line */ X if (Curschar->linep != cp) { X LPTR savep; X X savep = *Curschar; X Curschar->linep = cp; X Curschar->index = 0; X delline(1, FALSE); X *Curschar = savep; X } else X delline(1, FALSE); X break; X X case 'p': /* print the line */ X prt_line(cp->s); X outstr("\r\n"); X break; X } X ndone++; X } X if (cp == up->linep) X break; X } X X if (ndone) { X switch (cmdchar) { X X case 'd': X updatescreen(); X if (ndone >= P(P_RP) || got_int) X smsg("%s%d fewer line%c", X got_int ? "Interrupt: " : "", X ndone, X (ndone > 1) ? 's' : ' '); X break; X X case 'p': X wait_return(); X break; X } X } else { X if (got_int) X msg("Interrupt"); X else X msg("No match"); X } X X got_int = FALSE; X free((char *)prog); X} X X/* X * Character Searches X */ X Xstatic char lastc = NUL; /* last character searched for */ Xstatic int lastcdir; /* last direction of character search */ Xstatic int lastctype; /* last type of search ("find" or "to") */ X X/* X * searchc(c, dir, type) X * X * Search for character 'c', in direction 'dir'. If type is 0, move to X * the position of the character, otherwise move to just before the char. X */ Xbool_t Xsearchc(c, dir, type) Xchar c; Xint dir; Xint type; X{ X LPTR save; X X save = *Curschar; /* save position in case we fail */ X lastc = c; X lastcdir = dir; X lastctype = type; X X /* X * On 'to' searches, skip one to start with so we can repeat X * searches in the same direction and have it work right. X */ X if (type) X (dir == FORWARD) ? oneright() : oneleft(); X X while ( (dir == FORWARD) ? oneright() : oneleft() ) { X if (gchar(Curschar) == c) { X if (type) X (dir == FORWARD) ? oneleft() : oneright(); X return TRUE; X } X } X *Curschar = save; X return FALSE; X} X Xbool_t Xcrepsearch(flag) Xint flag; X{ X int dir = lastcdir; X int rval; X X if (lastc == NUL) X return FALSE; X X rval = searchc(lastc, flag ? OTHERDIR(lastcdir) : lastcdir, lastctype); X X lastcdir = dir; /* restore dir., since it may have changed */ X X return rval; X} X X/* X * "Other" Searches X */ X X/* X * showmatch - move the cursor to the matching paren or brace X */ XLPTR * Xshowmatch() X{ X static LPTR pos; X int (*move)(), inc(), dec(); X char initc = gchar(Curschar); /* initial char */ X char findc; /* terminating char */ X char c; X int count = 0; X X pos = *Curschar; /* set starting point */ X X switch (initc) { X X case '(': X findc = ')'; X move = inc; X break; X case ')': X findc = '('; X move = dec; X break; X case '{': X findc = '}'; X move = inc; X break; X case '}': X findc = '{'; X move = dec; X break; X case '[': X findc = ']'; X move = inc; X break; X case ']': X findc = '['; X move = dec; X break; X default: X return (LPTR *) NULL; X } X X while ((*move)(&pos) != -1) { /* until end of file */ X c = gchar(&pos); X if (c == initc) X count++; X else if (c == findc) { X if (count == 0) X return &pos; X count--; X } X } X return (LPTR *) NULL; /* never found it */ X} X X X/* X * The following routines do the word searches performed by the X * 'w', 'W', 'b', 'B', 'e', and 'E' commands. X */ X X/* X * To perform these searches, characters are placed into one of three X * classes, and transitions between classes determine word boundaries. X * X * The classes are: X * X * 0 - white space X * 1 - letters, digits, and underscore X * 2 - everything else X */ X Xstatic int stype; /* type of the word motion being performed */ X X#define C0(c) (((c) == ' ') || ((c) == '\t') || ((c) == NUL)) X#define C1(c) (isalpha(c) || isdigit(c) || ((c) == '_')) X X/* X * cls(c) - returns the class of character 'c' X * X * The 'type' of the current search modifies the classes of characters X * if a 'W', 'B', or 'E' motion is being done. In this case, chars. from X * class 2 are reported as class 1 since only white space boundaries are X * of interest. X */ Xstatic int Xcls(c) Xchar c; X{ X if (C0(c)) X return 0; X X if (C1(c)) X return 1; X X /* X * If stype is non-zero, report these as class 1. X */ X return (stype == 0) ? 2 : 1; X} X X X/* X * fwd_word(pos, type) - move forward one word X * X * Returns the resulting position, or NULL if EOF was reached. X */ XLPTR * Xfwd_word(p, type) XLPTR *p; Xint type; X{ X static LPTR pos; X int sclass = cls(gchar(p)); /* starting class */ X X pos = *p; X X stype = type; X X /* X * We always move at least one character. X */ X if (inc(&pos) == -1) X return NULL; X X if (sclass != 0) { X while (cls(gchar(&pos)) == sclass) { X if (inc(&pos) == -1) X return NULL; X } X /* X * If we went from 1 -> 2 or 2 -> 1, return here. X */ X if (cls(gchar(&pos)) != 0) X return &pos; X } X X /* We're in white space; go to next non-white */ X X while (cls(gchar(&pos)) == 0) { X /* X * We'll stop if we land on a blank line X */ X if (pos.index == 0 && pos.linep->s[0] == NUL) X break; X X if (inc(&pos) == -1) X return NULL; X } X X return &pos; X} X X/* X * bck_word(pos, type) - move backward one word X * X * Returns the resulting position, or NULL if EOF was reached. X */ XLPTR * Xbck_word(p, type) XLPTR *p; Xint type; X{ X static LPTR pos; X int sclass = cls(gchar(p)); /* starting class */ X X pos = *p; X X stype = type; X X if (dec(&pos) == -1) X return NULL; X X /* X * If we're in the middle of a word, we just have to X * back up to the start of it. X */ X if (cls(gchar(&pos)) == sclass && sclass != 0) { X /* X * Move backward to start of the current word X */ X while (cls(gchar(&pos)) == sclass) { X if (dec(&pos) == -1) X return NULL; X } X inc(&pos); /* overshot - forward one */ X return &pos; X } X X /* X * We were at the start of a word. Go back to the start X * of the prior word. X */ X X while (cls(gchar(&pos)) == 0) { /* skip any white space */ X /* X * We'll stop if we land on a blank line X */ X if (pos.index == 0 && pos.linep->s[0] == NUL) X return &pos; X X if (dec(&pos) == -1) X return NULL; X } X X sclass = cls(gchar(&pos)); X X /* X * Move backward to start of this word. X */ X while (cls(gchar(&pos)) == sclass) { X if (dec(&pos) == -1) X return NULL; X } X inc(&pos); /* overshot - forward one */ X X return &pos; X} X X/* X * end_word(pos, type, in_change) - move to the end of the word X * X * There is an apparent bug in the 'e' motion of the real vi. At least X * on the System V Release 3 version for the 80386. Unlike 'b' and 'w', X * the 'e' motion crosses blank lines. When the real vi crosses a blank X * line in an 'e' motion, the cursor is placed on the FIRST character X * of the next non-blank line. The 'E' command, however, works correctly. X * Since this appears to be a bug, I have not duplicated it here. X * X * There's a strange special case here that the 'in_change' parameter X * helps us deal with. Vi effectively turns 'cw' into 'ce'. If we're on X * a word with only one character, we need to stick at the current X * position so we don't change two words. X * X * Returns the resulting position, or NULL if EOF was reached. X */ XLPTR * Xend_word(p, type, in_change) XLPTR *p; Xint type; Xbool_t in_change; X{ X static LPTR pos; X int sclass = cls(gchar(p)); /* starting class */ X X pos = *p; X X stype = type; X X if (inc(&pos) == -1) X return NULL; X X /* X * If we're in the middle of a word, we just have to X * move to the end of it. X */ X if (cls(gchar(&pos)) == sclass && sclass != 0) { X /* X * Move forward to end of the current word X */ X while (cls(gchar(&pos)) == sclass) { X if (inc(&pos) == -1) X return NULL; X } X dec(&pos); /* overshot - forward one */ X return &pos; X } X X /* X * We were at the end of a word. Go to the end of the next X * word, unless we're doing a change. In that case we stick X * at the end of the current word. X */ X if (in_change) X return p; X X while (cls(gchar(&pos)) == 0) { /* skip any white space */ X if (inc(&pos) == -1) X return NULL; X } X X sclass = cls(gchar(&pos)); X X /* X * Move forward to end of this word. X */ X while (cls(gchar(&pos)) == sclass) { X if (inc(&pos) == -1) X return NULL; X } X dec(&pos); /* overshot - forward one */ X X return &pos; X} END_OF_FILE if test 18329 -ne `wc -c <'stevie/search.c'`; then echo shar: \"'stevie/search.c'\" unpacked with wrong size! fi # end of 'stevie/search.c' fi echo shar: End of archive 10 \(of 13\). cp /dev/null ark10isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 13 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0