home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / editor / stevie / search.c < prev    next >
C/C++ Source or Header  |  1994-01-31  |  19KB  |  979 lines

  1. /* $Header: /nw/tony/src/stevie/src/RCS/search.c,v 1.16 89/08/06 09:50:51 tony Exp $
  2.  *
  3.  * This file contains various searching-related routines. These fall into
  4.  * three groups: string searches (for /, ?, n, and N), character searches
  5.  * within a single line (for f, F, t, T, etc), and "other" kinds of searches
  6.  * like the '%' command, and 'word' searches.
  7.  */
  8.  
  9. #include "stevie.h"
  10. #include "regexp.h"    /* Henry Spencer's (modified) reg. exp. routines */
  11.  
  12. /*
  13.  * String searches
  14.  *
  15.  * The actual searches are done using Henry Spencer's regular expression
  16.  * library.
  17.  */
  18.  
  19. #define    BEGWORD    "([^a-zA-Z0-9_]|^)"    /* replaces "\<" in search strings */
  20. #define    ENDWORD    "([^a-zA-Z0-9_]|$)"    /* likewise replaces "\>" */
  21.  
  22. #define    BEGCHAR(c)    (islower(c) || isupper(c) || isdigit(c) || ((c) == '_'))
  23.  
  24. bool_t    begword;    /* does the search include a 'begin word' match */
  25.  
  26. /*
  27.  * mapstring(s) - map special backslash sequences
  28.  */
  29. static char *
  30. mapstring(s)
  31. register char    *s;
  32. {
  33.     static    char    ns[80];
  34.     register char    *p;
  35.  
  36.     begword = FALSE;
  37.  
  38.     for (p = ns; *s ;s++) {
  39.         if (*s != '\\') {    /* not an escape */
  40.             *p++ = *s;
  41.             continue;
  42.         }
  43.         switch (*++s) {
  44.         case '/':
  45.             *p++ = '/';
  46.             break;
  47.  
  48.         case '<':
  49.             strcpy(p, BEGWORD);
  50.             p += strlen(BEGWORD);
  51.             begword = TRUE;
  52.             break;
  53.  
  54.         case '>':
  55.             strcpy(p, ENDWORD);
  56.             p += strlen(ENDWORD);
  57.             break;
  58.  
  59.         default:
  60.             *p++ = '\\';
  61.             *p++ = *s;
  62.             break;
  63.         }
  64.     }
  65.     *p++ = NUL;
  66.  
  67.     return ns;
  68. }
  69.  
  70. static char *laststr = NULL;
  71. static int lastsdir;
  72.  
  73. static LPTR *
  74. ssearch(dir,str)
  75. int    dir;    /* FORWARD or BACKWARD */
  76. char    *str;
  77. {
  78.     LPTR    *bcksearch(), *fwdsearch();
  79.     LPTR    *pos;
  80.     char    *old_ls = laststr;
  81.  
  82.     reg_ic = P(P_IC);    /* tell the regexp routines how to search */
  83.  
  84.     laststr = strsave(str);
  85.     lastsdir = dir;
  86.  
  87.     if (old_ls != NULL)
  88.         free(old_ls);
  89.  
  90.     if (dir == BACKWARD) {
  91.         smsg("?%s", laststr);
  92.         pos = bcksearch(mapstring(laststr));
  93.     } else {
  94.         smsg("/%s", laststr);
  95.         pos = fwdsearch(mapstring(laststr));
  96.     }
  97.  
  98.     /*
  99.      * This is kind of a kludge, but its needed to make
  100.      * 'beginning of word' searches land on the right place.
  101.      */
  102.     if (pos != NULL && begword) {
  103.         if (pos->index != 0 || !BEGCHAR(pos->linep->s[0]))
  104.             pos->index += 1;
  105.     }
  106.     return pos;
  107. }
  108.  
  109. bool_t
  110. dosearch(dir,str)
  111. int    dir;
  112. char    *str;
  113. {
  114.     LPTR    *p;
  115.  
  116.     if (str == NULL)
  117.         str = laststr;
  118.  
  119.     got_int = FALSE;
  120.  
  121.     if ((p = ssearch(dir,str)) == NULL) {
  122.         if (got_int)
  123.             msg("Interrupt");
  124.         else
  125.             msg("Pattern not found");
  126.  
  127.         got_int = FALSE;
  128.         return FALSE;
  129.     } else {
  130.         LPTR savep;
  131.  
  132.         cursupdate();
  133.         /*
  134.          * if we're backing up, we make sure the line we're on
  135.          * is on the screen.
  136.          */
  137.         setpcmark();
  138.         *Curschar = savep = *p;
  139.         set_want_col = TRUE;
  140.         cursupdate();
  141.  
  142.         return TRUE;
  143.     }
  144. }
  145.  
  146. #define    OTHERDIR(x)    (((x) == FORWARD) ? BACKWARD : FORWARD)
  147.  
  148. bool_t
  149. repsearch(flag)
  150. int    flag;
  151. {
  152.     int    dir = lastsdir;
  153.     bool_t    found;
  154.  
  155.     if ( laststr == NULL ) {
  156.         beep();
  157.         return FALSE;
  158.     }
  159.  
  160.     found = dosearch(flag ? OTHERDIR(lastsdir) : lastsdir, laststr);
  161.  
  162.     /*
  163.      * We have to save and restore 'lastsdir' because it gets munged
  164.      * by ssearch() and winds up saving the wrong direction from here
  165.      * if 'flag' is true.
  166.      */
  167.     lastsdir = dir;
  168.  
  169.     return found;
  170. }
  171.  
  172. /*
  173.  * regerror - called by regexp routines when errors are detected.
  174.  */
  175. void
  176. regerror(s)
  177. char    *s;
  178. {
  179.     emsg(s);
  180. }
  181.  
  182. static LPTR *
  183. fwdsearch(str)
  184. register char    *str;
  185. {
  186.     static LPTR    infile;
  187.     register LPTR    *p;
  188.     regexp    *prog;
  189.  
  190.     register char    *s;
  191.     register int    i;
  192.  
  193.     if ((prog = regcomp(str)) == NULL) {
  194.         emsg("Invalid search string");
  195.         return NULL;
  196.     }
  197.  
  198.     p = Curschar;
  199.     i = Curschar->index + 1;
  200.     do {
  201.         s = p->linep->s + i;
  202.  
  203.         if (regexec(prog, s, i == 0)) {        /* got a match */
  204.             infile.linep = p->linep;
  205.             infile.index = (int) (prog->startp[0] - p->linep->s);
  206.             free((char *)prog);
  207.             return (&infile);
  208.         }
  209.         i = 0;
  210.  
  211.         if (got_int)
  212.             goto fwdfail;
  213.  
  214.     } while ((p = nextline(p)) != NULL);
  215.  
  216.     /*
  217.      * If wrapscan isn't set, then don't scan from the beginning
  218.      * of the file. Just return failure here.
  219.      */
  220.     if (!P(P_WS))
  221.         goto fwdfail;
  222.  
  223.     /* search from the beginning of the file to Curschar */
  224.     for (p = Filemem; p != NULL ;p = nextline(p)) {
  225.         s = p->linep->s;
  226.  
  227.         if (regexec(prog, s, TRUE)) {        /* got a match */
  228.             infile.linep = p->linep;
  229.             infile.index = (int) (prog->startp[0] - s);
  230.             free((char *)prog);
  231.             return (&infile);
  232.         }
  233.  
  234.         if (p->linep == Curschar->linep)
  235.             break;
  236.  
  237.         if (got_int)
  238.             goto fwdfail;
  239.     }
  240.  
  241. fwdfail:
  242.     free((char *)prog);
  243.     return NULL;
  244. }
  245.  
  246. static LPTR *
  247. bcksearch(str)
  248. char    *str;
  249. {
  250.     static LPTR    infile;
  251.     register LPTR    *p = &infile;
  252.     register char    *s;
  253.     register int    i;
  254.     register char    *match;
  255.     regexp    *prog;
  256.  
  257.     /* make sure str isn't empty */
  258.     if (str == NULL || *str == NUL)
  259.         return NULL;
  260.  
  261.     if ((prog = regcomp(str)) == NULL) {
  262.         emsg("Invalid search string");
  263.         return NULL;
  264.     }
  265.  
  266.     *p = *Curschar;
  267.     if (dec(p) == -1) {    /* already at start of file? */
  268.         *p = *Fileend;
  269.         p->index = strlen(p->linep->s) - 1;
  270.     }
  271.  
  272.     if (begword)        /* so we don't get stuck on one match */
  273.         dec(p);
  274.  
  275.     i = p->index;
  276.  
  277.     do {
  278.         s = p->linep->s;
  279.  
  280.         if (regexec(prog, s, TRUE)) {    /* match somewhere on line */
  281.  
  282.             /*
  283.              * Now, if there are multiple matches on this line,
  284.              * we have to get the last one. Or the last one
  285.              * before the cursor, if we're on that line.
  286.              */
  287.             match = prog->startp[0];
  288.  
  289.             while (regexec(prog, prog->endp[0], FALSE)) {
  290.                 if ((i >= 0) && ((prog->startp[0] - s) > i))
  291.                     break;
  292.                 match = prog->startp[0];
  293.             }
  294.  
  295.             if ((i >= 0) && ((match - s) > i)) {
  296.                 i = -1;
  297.                 continue;
  298.             }
  299.  
  300.             infile.linep = p->linep;
  301.             infile.index = (int) (match - s);
  302.             free((char *)prog);
  303.             return (&infile);
  304.         }
  305.         i = -1;
  306.  
  307.         if (got_int)
  308.             goto bckfail;
  309.  
  310.     } while ((p = prevline(p)) != NULL);
  311.  
  312.     /*
  313.      * If wrapscan isn't set, bag the search now
  314.      */
  315.     if (!P(P_WS))
  316.         goto bckfail;
  317.  
  318.     /* search backward from the end of the file */
  319.     p = prevline(Fileend);
  320.     do {
  321.         s = p->linep->s;
  322.  
  323.         if (regexec(prog, s, TRUE)) {    /* match somewhere on line */
  324.  
  325.             /*
  326.              * Now, if there are multiple matches on this line,
  327.              * we have to get the last one.
  328.              */
  329.             match = prog->startp[0];
  330.  
  331.             while (regexec(prog, prog->endp[0], FALSE))
  332.                 match = prog->startp[0];
  333.  
  334.             infile.linep = p->linep;
  335.             infile.index = (int) (match - s);
  336.             free((char *)prog);
  337.             return (&infile);
  338.         }
  339.  
  340.         if (p->linep == Curschar->linep)
  341.             break;
  342.  
  343.         if (got_int)
  344.             goto bckfail;
  345.  
  346.     } while ((p = prevline(p)) != NULL);
  347.  
  348. bckfail:
  349.     free((char *)prog);
  350.     return NULL;
  351. }
  352.  
  353. /*
  354.  * dosub(lp, up, cmd)
  355.  *
  356.  * Perform a substitution from line 'lp' to line 'up' using the
  357.  * command pointed to by 'cmd' which should be of the form:
  358.  *
  359.  * /pattern/substitution/g
  360.  *
  361.  * The trailing 'g' is optional and, if present, indicates that multiple
  362.  * substitutions should be performed on each line, if applicable.
  363.  * The usual escapes are supported as described in the regexp docs.
  364.  */
  365. void
  366. dosub(lp, up, cmd)
  367. LPTR    *lp, *up;
  368. char    *cmd;
  369. {
  370.     LINE    *cp;
  371.     char    *pat, *sub;
  372.     regexp    *prog;
  373.     int    nsubs;
  374.     bool_t    do_all;        /* do multiple substitutions per line */
  375.  
  376.     /*
  377.      * If no range was given, do the current line. If only one line
  378.      * was given, just do that one.
  379.      */
  380.     if (lp->linep == NULL)
  381.         *up = *lp = *Curschar;
  382.     else {
  383.         if (up->linep == NULL)
  384.             *up = *lp;
  385.     }
  386.  
  387.     pat = ++cmd;        /* skip the initial '/' */
  388.  
  389.     while (*cmd) {
  390.         if (*cmd == '\\')    /* next char is quoted */
  391.             cmd += 2;
  392.         else if (*cmd == '/') {    /* delimiter */
  393.             *cmd++ = NUL;
  394.             break;
  395.         } else
  396.             cmd++;        /* regular character */
  397.     }
  398.  
  399.     if (*pat == NUL) {
  400.         emsg("NULL pattern specified");
  401.         return;
  402.     }
  403.  
  404.     sub = cmd;
  405.  
  406.     do_all = FALSE;
  407.  
  408.     while (*cmd) {
  409.         if (*cmd == '\\')    /* next char is quoted */
  410.             cmd += 2;
  411.         else if (*cmd == '/') {    /* delimiter */
  412.             do_all = (cmd[1] == 'g');
  413.             *cmd++ = NUL;
  414.             break;
  415.         } else
  416.             cmd++;        /* regular character */
  417.     }
  418.  
  419.     reg_ic = P(P_IC);    /* set "ignore case" flag appropriately */
  420.  
  421.     if ((prog = regcomp(pat)) == NULL) {
  422.         emsg("Invalid search string");
  423.         return;
  424.     }
  425.  
  426.     nsubs = 0;
  427.  
  428.     for (cp = lp->linep; cp != NULL ;cp = cp->next) {
  429.         if (regexec(prog, cp->s, TRUE)) { /* a match on this line */
  430.             char    *ns, *sns, *p;
  431.  
  432.             /*
  433.              * Get some space for a temporary buffer
  434.              * to do the substitution into.
  435.              */
  436.             sns = ns = alloc(2048);
  437.             if (!sns)  return;
  438.             *sns = NUL;
  439.  
  440.             p = cp->s;
  441.  
  442.             do {
  443.                 for (ns = sns; *ns ;ns++)
  444.                     ;
  445.                 /*
  446.                  * copy up to the part that matched
  447.                  */
  448.                 while (p < prog->startp[0])
  449.                     *ns++ = *p++;
  450.  
  451.                 regsub(prog, sub, ns);
  452.  
  453.                 /*
  454.                  * continue searching after the match
  455.                  */
  456.                 p = prog->endp[0];
  457.  
  458.             } while (regexec(prog, p, FALSE) && do_all);
  459.  
  460.             for (ns = sns; *ns ;ns++)
  461.                 ;
  462.  
  463.             /*
  464.              * copy the rest of the line, that didn't match
  465.              */
  466.             while (*p)
  467.                 *ns++ = *p++;
  468.  
  469.             *ns = NUL;
  470.  
  471.             free(cp->s);        /* free the original line */
  472.             cp->s = strsave(sns);    /* and save the modified str */
  473.             cp->size = strlen(cp->s) + 1;
  474.             free(sns);        /* free the temp buffer */
  475.             nsubs++;
  476.             CHANGED;
  477.         }
  478.         if (cp == up->linep)
  479.             break;
  480.     }
  481.  
  482.     if (nsubs) {
  483.         updatescreen();
  484.         if (nsubs >= P(P_RP))
  485.             smsg("%d substitution%c", nsubs, (nsubs>1) ? 's' : ' ');
  486.     } else
  487.         msg("No match");
  488.  
  489.     free((char *)prog);
  490. }
  491.  
  492. /*
  493.  * doglob(cmd)
  494.  *
  495.  * Execute a global command of the form:
  496.  *
  497.  * g/pattern/X
  498.  *
  499.  * where 'x' is a command character, currently one of the following:
  500.  *
  501.  * d    Delete all matching lines
  502.  * p    Print all matching lines
  503.  *
  504.  * The command character (as well as the trailing slash) is optional, and
  505.  * is assumed to be 'p' if missing.
  506.  */
  507. void
  508. doglob(lp, up, cmd)
  509. LPTR    *lp, *up;
  510. char    *cmd;
  511. {
  512.     LINE    *cp;
  513.     char    *pat;
  514.     regexp    *prog;
  515.     int    ndone;
  516.     char    cmdchar = NUL;    /* what to do with matching lines */
  517.  
  518.     /*
  519.      * If no range was given, do every line. If only one line
  520.      * was given, just do that one.
  521.      */
  522.     if (lp->linep == NULL) {
  523.         *lp = *Filemem;
  524.         *up = *Fileend;
  525.     } else {
  526.         if (up->linep == NULL)
  527.             *up = *lp;
  528.     }
  529.  
  530.     pat = ++cmd;        /* skip the initial '/' */
  531.  
  532.     while (*cmd) {
  533.         if (*cmd == '\\')    /* next char is quoted */
  534.             cmd += 2;
  535.         else if (*cmd == '/') {    /* delimiter */
  536.             cmdchar = cmd[1];
  537.             *cmd++ = NUL;
  538.             break;
  539.         } else
  540.             cmd++;        /* regular character */
  541.     }
  542.     if (cmdchar == NUL)
  543.         cmdchar = 'p';
  544.  
  545.     reg_ic = P(P_IC);    /* set "ignore case" flag appropriately */
  546.  
  547.     if (cmdchar != 'd' && cmdchar != 'p') {
  548.         emsg("Invalid command character");
  549.         return;
  550.     }
  551.  
  552.     if ((prog = regcomp(pat)) == NULL) {
  553.         emsg("Invalid search string");
  554.         return;
  555.     }
  556.  
  557.     msg("");
  558.     ndone = 0;
  559.     got_int = FALSE;
  560.  
  561.     for (cp = lp->linep; cp != NULL && !got_int ;cp = cp->next) {
  562.         if (regexec(prog, cp->s, TRUE)) { /* a match on this line */
  563.             switch (cmdchar) {
  564.  
  565.             case 'd':        /* delete the line */
  566.                 if (Curschar->linep != cp) {
  567.                     LPTR    savep;
  568.  
  569.                     savep = *Curschar;
  570.                     Curschar->linep = cp;
  571.                     Curschar->index = 0;
  572.                     delline(1, FALSE);
  573.                     *Curschar = savep;
  574.                 } else
  575.                     delline(1, FALSE);
  576.                 break;
  577.  
  578.             case 'p':        /* print the line */
  579.                 prt_line(cp->s);
  580.                 outstr("\r\n");
  581.                 break;
  582.             }
  583.             ndone++;
  584.         }
  585.         if (cp == up->linep)
  586.             break;
  587.     }
  588.  
  589.     if (ndone) {
  590.         switch (cmdchar) {
  591.  
  592.         case 'd':
  593.             updatescreen();
  594.             if (ndone >= P(P_RP) || got_int)
  595.                 smsg("%s%d fewer line%c",
  596.                     got_int ? "Interrupt: " : "",
  597.                     ndone,
  598.                     (ndone > 1) ? 's' : ' ');
  599.             break;
  600.  
  601.         case 'p':
  602.             wait_return();
  603.             break;
  604.         }
  605.     } else {
  606.         if (got_int)
  607.             msg("Interrupt");
  608.         else
  609.             msg("No match");
  610.     }
  611.  
  612.     got_int = FALSE;
  613.     free((char *)prog);
  614. }
  615.  
  616. /*
  617.  * Character Searches
  618.  */
  619.  
  620. static char lastc = NUL;    /* last character searched for */
  621. static int  lastcdir;        /* last direction of character search */
  622. static int  lastctype;        /* last type of search ("find" or "to") */
  623.  
  624. /*
  625.  * searchc(c, dir, type)
  626.  *
  627.  * Search for character 'c', in direction 'dir'. If type is 0, move to
  628.  * the position of the character, otherwise move to just before the char.
  629.  */
  630. bool_t
  631. searchc(c, dir, type)
  632. char    c;
  633. int    dir;
  634. int    type;
  635. {
  636.     LPTR    save;
  637.  
  638.     save = *Curschar;    /* save position in case we fail */
  639.     lastc = c;
  640.     lastcdir = dir;
  641.     lastctype = type;
  642.  
  643.     /*
  644.      * On 'to' searches, skip one to start with so we can repeat
  645.      * searches in the same direction and have it work right.
  646.      */
  647.     if (type)
  648.         (dir == FORWARD) ? oneright() : oneleft();
  649.  
  650.     while ( (dir == FORWARD) ? oneright() : oneleft() ) {
  651.         if (gchar(Curschar) == c) {
  652.             if (type)
  653.                 (dir == FORWARD) ? oneleft() : oneright();
  654.             return TRUE;
  655.         }
  656.     }
  657.     *Curschar = save;
  658.     return FALSE;
  659. }
  660.  
  661. bool_t
  662. crepsearch(flag)
  663. int    flag;
  664. {
  665.     int    dir = lastcdir;
  666.     int    rval;
  667.  
  668.     if (lastc == NUL)
  669.         return FALSE;
  670.  
  671.     rval = searchc(lastc, flag ? OTHERDIR(lastcdir) : lastcdir, lastctype);
  672.  
  673.     lastcdir = dir;        /* restore dir., since it may have changed */
  674.  
  675.     return rval;
  676. }
  677.  
  678. /*
  679.  * "Other" Searches
  680.  */
  681.  
  682. /*
  683.  * showmatch - move the cursor to the matching paren or brace
  684.  */
  685. LPTR *
  686. showmatch()
  687. {
  688.     static    LPTR    pos;
  689.     int    (*move)(), inc(), dec();
  690.     char    initc = gchar(Curschar);    /* initial char */
  691.     char    findc;                /* terminating char */
  692.     char    c;
  693.     int    count = 0;
  694.  
  695.     pos = *Curschar;        /* set starting point */
  696.  
  697.     switch (initc) {
  698.  
  699.     case '(':
  700.         findc = ')';
  701.         move = inc;
  702.         break;
  703.     case ')':
  704.         findc = '(';
  705.         move = dec;
  706.         break;
  707.     case '{':
  708.         findc = '}';
  709.         move = inc;
  710.         break;
  711.     case '}':
  712.         findc = '{';
  713.         move = dec;
  714.         break;
  715.     case '[':
  716.         findc = ']';
  717.         move = inc;
  718.         break;
  719.     case ']':
  720.         findc = '[';
  721.         move = dec;
  722.         break;
  723.     default:
  724.         return (LPTR *) NULL;
  725.     }
  726.  
  727.     while ((*move)(&pos) != -1) {        /* until end of file */
  728.         c = gchar(&pos);
  729.         if (c == initc)
  730.             count++;
  731.         else if (c == findc) {
  732.             if (count == 0)
  733.                 return &pos;
  734.             count--;
  735.         }
  736.     }
  737.     return (LPTR *) NULL;            /* never found it */
  738. }
  739.  
  740.  
  741. /*
  742.  * The following routines do the word searches performed by the
  743.  * 'w', 'W', 'b', 'B', 'e', and 'E' commands.
  744.  */
  745.  
  746. /*
  747.  * To perform these searches, characters are placed into one of three
  748.  * classes, and transitions between classes determine word boundaries.
  749.  *
  750.  * The classes are:
  751.  *
  752.  * 0 - white space
  753.  * 1 - letters, digits, and underscore
  754.  * 2 - everything else
  755.  */
  756.  
  757. static    int    stype;        /* type of the word motion being performed */
  758.  
  759. #define    C0(c)    (((c) == ' ') || ((c) == '\t') || ((c) == NUL))
  760. #define    C1(c)    (isalpha(c) || isdigit(c) || ((c) == '_'))
  761.  
  762. /*
  763.  * cls(c) - returns the class of character 'c'
  764.  *
  765.  * The 'type' of the current search modifies the classes of characters
  766.  * if a 'W', 'B', or 'E' motion is being done. In this case, chars. from
  767.  * class 2 are reported as class 1 since only white space boundaries are
  768.  * of interest.
  769.  */
  770. static    int
  771. cls(c)
  772. char    c;
  773. {
  774.     if (C0(c))
  775.         return 0;
  776.  
  777.     if (C1(c))
  778.         return 1;
  779.  
  780.     /*
  781.      * If stype is non-zero, report these as class 1.
  782.      */
  783.     return (stype == 0) ? 2 : 1;
  784. }
  785.  
  786.  
  787. /*
  788.  * fwd_word(pos, type) - move forward one word
  789.  *
  790.  * Returns the resulting position, or NULL if EOF was reached.
  791.  */
  792. LPTR *
  793. fwd_word(p, type)
  794. LPTR    *p;
  795. int    type;
  796. {
  797.     static    LPTR    pos;
  798.     int    sclass = cls(gchar(p));        /* starting class */
  799.  
  800.     pos = *p;
  801.  
  802.     stype = type;
  803.  
  804.     /*
  805.      * We always move at least one character.
  806.      */
  807.     if (inc(&pos) == -1)
  808.         return NULL;
  809.  
  810.     if (sclass != 0) {
  811.         while (cls(gchar(&pos)) == sclass) {
  812.             if (inc(&pos) == -1)
  813.                 return NULL;
  814.         }
  815.         /*
  816.          * If we went from 1 -> 2 or 2 -> 1, return here.
  817.          */
  818.         if (cls(gchar(&pos)) != 0)
  819.             return &pos;
  820.     }
  821.  
  822.     /* We're in white space; go to next non-white */
  823.  
  824.     while (cls(gchar(&pos)) == 0) {
  825.         /*
  826.          * We'll stop if we land on a blank line
  827.          */
  828.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  829.             break;
  830.  
  831.         if (inc(&pos) == -1)
  832.             return NULL;
  833.     }
  834.  
  835.     return &pos;
  836. }
  837.  
  838. /*
  839.  * bck_word(pos, type) - move backward one word
  840.  *
  841.  * Returns the resulting position, or NULL if EOF was reached.
  842.  */
  843. LPTR *
  844. bck_word(p, type)
  845. LPTR    *p;
  846. int    type;
  847. {
  848.     static    LPTR    pos;
  849.     int    sclass = cls(gchar(p));        /* starting class */
  850.  
  851.     pos = *p;
  852.  
  853.     stype = type;
  854.  
  855.     if (dec(&pos) == -1)
  856.         return NULL;
  857.  
  858.     /*
  859.      * If we're in the middle of a word, we just have to
  860.      * back up to the start of it.
  861.      */
  862.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  863.         /*
  864.          * Move backward to start of the current word
  865.          */
  866.         while (cls(gchar(&pos)) == sclass) {
  867.             if (dec(&pos) == -1)
  868.                 return NULL;
  869.         }
  870.         inc(&pos);            /* overshot - forward one */
  871.         return &pos;
  872.     }
  873.  
  874.     /*
  875.      * We were at the start of a word. Go back to the start
  876.      * of the prior word.
  877.      */
  878.  
  879.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  880.         /*
  881.          * We'll stop if we land on a blank line
  882.          */
  883.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  884.             return &pos;
  885.  
  886.         if (dec(&pos) == -1)
  887.             return NULL;
  888.     }
  889.  
  890.     sclass = cls(gchar(&pos));
  891.  
  892.     /*
  893.      * Move backward to start of this word.
  894.      */
  895.     while (cls(gchar(&pos)) == sclass) {
  896.         if (dec(&pos) == -1)
  897.             return NULL;
  898.     }
  899.     inc(&pos);            /* overshot - forward one */
  900.  
  901.     return &pos;
  902. }
  903.  
  904. /*
  905.  * end_word(pos, type, in_change) - move to the end of the word
  906.  *
  907.  * There is an apparent bug in the 'e' motion of the real vi. At least
  908.  * on the System V Release 3 version for the 80386. Unlike 'b' and 'w',
  909.  * the 'e' motion crosses blank lines. When the real vi crosses a blank
  910.  * line in an 'e' motion, the cursor is placed on the FIRST character
  911.  * of the next non-blank line. The 'E' command, however, works correctly.
  912.  * Since this appears to be a bug, I have not duplicated it here.
  913.  *
  914.  * There's a strange special case here that the 'in_change' parameter
  915.  * helps us deal with. Vi effectively turns 'cw' into 'ce'. If we're on
  916.  * a word with only one character, we need to stick at the current
  917.  * position so we don't change two words.
  918.  *
  919.  * Returns the resulting position, or NULL if EOF was reached.
  920.  */
  921. LPTR *
  922. end_word(p, type, in_change)
  923. LPTR    *p;
  924. int    type;
  925. bool_t    in_change;
  926. {
  927.     static    LPTR    pos;
  928.     int    sclass = cls(gchar(p));        /* starting class */
  929.  
  930.     pos = *p;
  931.  
  932.     stype = type;
  933.  
  934.     if (inc(&pos) == -1)
  935.         return NULL;
  936.  
  937.     /*
  938.      * If we're in the middle of a word, we just have to
  939.      * move to the end of it.
  940.      */
  941.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  942.         /*
  943.          * Move forward to end of the current word
  944.          */
  945.         while (cls(gchar(&pos)) == sclass) {
  946.             if (inc(&pos) == -1)
  947.                 return NULL;
  948.         }
  949.         dec(&pos);            /* overshot - forward one */
  950.         return &pos;
  951.     }
  952.  
  953.     /*
  954.      * We were at the end of a word. Go to the end of the next
  955.      * word, unless we're doing a change. In that case we stick
  956.      * at the end of the current word.
  957.      */
  958.     if (in_change)
  959.         return p;
  960.  
  961.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  962.         if (inc(&pos) == -1)
  963.             return NULL;
  964.     }
  965.  
  966.     sclass = cls(gchar(&pos));
  967.  
  968.     /*
  969.      * Move forward to end of this word.
  970.      */
  971.     while (cls(gchar(&pos)) == sclass) {
  972.         if (inc(&pos) == -1)
  973.             return NULL;
  974.     }
  975.     dec(&pos);            /* overshot - forward one */
  976.  
  977.     return &pos;
  978. }
  979.