home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 313_01 / search.c < prev    next >
C/C++ Source or Header  |  1990-04-22  |  20KB  |  1,003 lines

  1. /* $Header: /nw2/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.             *sns = NUL;
  438.  
  439.             p = cp->s;
  440.  
  441.             do {
  442.                 for (ns = sns; *ns ;ns++)
  443.                     ;
  444.                 /*
  445.                  * copy up to the part that matched
  446.                  */
  447.                 while (p < prog->startp[0])
  448.                     *ns++ = *p++;
  449.  
  450.                 regsub(prog, sub, ns);
  451.  
  452.                 /*
  453.                  * continue searching after the match
  454.                  */
  455.                 p = prog->endp[0];
  456.  
  457.             } while (regexec(prog, p, FALSE) && do_all);
  458.  
  459.             for (ns = sns; *ns ;ns++)
  460.                 ;
  461.  
  462.             /*
  463.              * copy the rest of the line, that didn't match
  464.              */
  465.             while (*p)
  466.                 *ns++ = *p++;
  467.  
  468.             *ns = NUL;
  469.  
  470.             free(cp->s);        /* free the original line */
  471.             cp->s = strsave(sns);    /* and save the modified str */
  472.             cp->size = strlen(cp->s) + 1;
  473.             free(sns);        /* free the temp buffer */
  474.             nsubs++;
  475.             CHANGED;
  476.         }
  477.         if (cp == up->linep)
  478.             break;
  479.     }
  480.  
  481.     if (nsubs) {
  482.         updatescreen();
  483.         if (nsubs >= P(P_RP))
  484.             smsg("%d substitution%c", nsubs, (nsubs>1) ? 's' : ' ');
  485.     } else
  486.         msg("No match");
  487.  
  488.     free((char *)prog);
  489. }
  490.  
  491. /*
  492.  * doglob(cmd)
  493.  *
  494.  * Execute a global command of the form:
  495.  *
  496.  * g/pattern/X
  497.  *
  498.  * where 'x' is a command character, currently one of the following:
  499.  *
  500.  * d    Delete all matching lines
  501.  * p    Print all matching lines
  502.  *
  503.  * The command character (as well as the trailing slash) is optional, and
  504.  * is assumed to be 'p' if missing.
  505.  */
  506. void
  507. doglob(lp, up, cmd)
  508. LPTR    *lp, *up;
  509. char    *cmd;
  510. {
  511.     LINE    *cp;
  512.     char    *pat;
  513.     regexp    *prog;
  514.     int    ndone;
  515.     char    cmdchar = NUL;    /* what to do with matching lines */
  516.  
  517.     /*
  518.      * If no range was given, do every line. If only one line
  519.      * was given, just do that one.
  520.      */
  521.     if (lp->linep == NULL) {
  522.         *lp = *Filemem;
  523.         *up = *Fileend;
  524.     } else {
  525.         if (up->linep == NULL)
  526.             *up = *lp;
  527.     }
  528.  
  529.     pat = ++cmd;        /* skip the initial '/' */
  530.  
  531.     while (*cmd) {
  532.         if (*cmd == '\\')    /* next char is quoted */
  533.             cmd += 2;
  534.         else if (*cmd == '/') {    /* delimiter */
  535.             cmdchar = cmd[1];
  536.             *cmd++ = NUL;
  537.             break;
  538.         } else
  539.             cmd++;        /* regular character */
  540.     }
  541.     if (cmdchar == NUL)
  542.         cmdchar = 'p';
  543.  
  544.     reg_ic = P(P_IC);    /* set "ignore case" flag appropriately */
  545.  
  546.     if (cmdchar != 'd' && cmdchar != 'p') {
  547.         emsg("Invalid command character");
  548.         return;
  549.     }
  550.  
  551.     if ((prog = regcomp(pat)) == NULL) {
  552.         emsg("Invalid search string");
  553.         return;
  554.     }
  555.  
  556.     msg("");
  557.     ndone = 0;
  558.     got_int = FALSE;
  559.  
  560.     for (cp = lp->linep; cp != NULL && !got_int ;cp = cp->next) {
  561.         if (regexec(prog, cp->s, TRUE)) { /* a match on this line */
  562.             switch (cmdchar) {
  563.  
  564.             case 'd':        /* delete the line */
  565.                 if (Curschar->linep != cp) {
  566.                     LPTR    savep;
  567.  
  568.                     savep = *Curschar;
  569.                     Curschar->linep = cp;
  570.                     Curschar->index = 0;
  571.                     delline(1, FALSE);
  572.                     *Curschar = savep;
  573.                 } else
  574.                     delline(1, FALSE);
  575.                 break;
  576.  
  577.             case 'p':        /* print the line */
  578.                 prt_line(cp->s);
  579.                 outstr("\r\n");
  580.                 break;
  581.             }
  582.             ndone++;
  583.         }
  584.         if (cp == up->linep)
  585.             break;
  586.     }
  587.  
  588.     if (ndone) {
  589.         switch (cmdchar) {
  590.  
  591.         case 'd':
  592.             updatescreen();
  593.             if (ndone >= P(P_RP) || got_int)
  594.                 smsg("%s%d fewer line%c",
  595.                     got_int ? "Interrupt: " : "",
  596.                     ndone,
  597.                     (ndone > 1) ? 's' : ' ');
  598.             break;
  599.  
  600.         case 'p':
  601.             wait_return();
  602.             break;
  603.         }
  604.     } else {
  605.         if (got_int)
  606.             msg("Interrupt");
  607.         else
  608.             msg("No match");
  609.     }
  610.  
  611.     got_int = FALSE;
  612.     free((char *)prog);
  613. }
  614.  
  615. /*
  616.  * Character Searches
  617.  */
  618.  
  619. static char lastc = NUL;    /* last character searched for */
  620. static int  lastcdir;        /* last direction of character search */
  621. static int  lastctype;        /* last type of search ("find" or "to") */
  622.  
  623. /*
  624.  * searchc(c, dir, type)
  625.  *
  626.  * Search for character 'c', in direction 'dir'. If type is 0, move to
  627.  * the position of the character, otherwise move to just before the char.
  628.  */
  629. bool_t
  630. searchc(c, dir, type)
  631. char    c;
  632. int    dir;
  633. int    type;
  634. {
  635.     LPTR    save;
  636.  
  637.     save = *Curschar;    /* save position in case we fail */
  638.     lastc = c;
  639.     lastcdir = dir;
  640.     lastctype = type;
  641.  
  642.     /*
  643.      * On 'to' searches, skip one to start with so we can repeat
  644.      * searches in the same direction and have it work right.
  645.      */
  646.     if (type)
  647.         (dir == FORWARD) ? oneright() : oneleft();
  648.  
  649.     while ( (dir == FORWARD) ? oneright() : oneleft() ) {
  650.         if (gchar(Curschar) == c) {
  651.             if (type)
  652.                 (dir == FORWARD) ? oneleft() : oneright();
  653.             return TRUE;
  654.         }
  655.     }
  656.     *Curschar = save;
  657.     return FALSE;
  658. }
  659.  
  660. bool_t
  661. crepsearch(flag)
  662. int    flag;
  663. {
  664.     int    dir = lastcdir;
  665.     int    rval;
  666.  
  667.     if (lastc == NUL)
  668.         return FALSE;
  669.  
  670.     rval = searchc(lastc, flag ? OTHERDIR(lastcdir) : lastcdir, lastctype);
  671.  
  672.     lastcdir = dir;        /* restore dir., since it may have changed */
  673.  
  674.     return rval;
  675. }
  676.  
  677. /*
  678.  * "Other" Searches
  679.  */
  680.  
  681. /*
  682.  * showmatch - move the cursor to the matching paren or brace
  683.  */
  684. LPTR *
  685. showmatch()
  686. {
  687.     static    LPTR    pos;
  688.     int    (*move)(), inc(), dec();
  689.     char    initc = gchar(Curschar);    /* initial char */
  690.     char    findc;                /* terminating char */
  691.     char    c;
  692.     int    count = 0;
  693.  
  694.     pos = *Curschar;        /* set starting point */
  695.  
  696.     switch (initc) {
  697.  
  698.     case '(':
  699.         findc = ')';
  700.         move = inc;
  701.         break;
  702.     case ')':
  703.         findc = '(';
  704.         move = dec;
  705.         break;
  706.     case '{':
  707.         findc = '}';
  708.         move = inc;
  709.         break;
  710.     case '}':
  711.         findc = '{';
  712.         move = dec;
  713.         break;
  714.     case '[':
  715.         findc = ']';
  716.         move = inc;
  717.         break;
  718.     case ']':
  719.         findc = '[';
  720.         move = dec;
  721.         break;
  722.     default:
  723.         return (LPTR *) NULL;
  724.     }
  725.  
  726.     while ((*move)(&pos) != -1) {        /* until end of file */
  727.         c = gchar(&pos);
  728.         if (c == initc)
  729.             count++;
  730.         else if (c == findc) {
  731.             if (count == 0)
  732.                 return &pos;
  733.             count--;
  734.         }
  735.     }
  736.     return (LPTR *) NULL;            /* never found it */
  737. }
  738.  
  739. /*
  740.  * findfunc(dir) - Find the next function in direction 'dir'
  741.  *
  742.  * Return TRUE if a function was found.
  743.  */
  744. bool_t
  745. findfunc(dir)
  746. int    dir;
  747. {
  748.     LPTR    *curr;
  749.  
  750.     curr = Curschar;
  751.  
  752.     do {
  753.         curr = (dir == FORWARD) ? nextline(curr) : prevline(curr);
  754.  
  755.         if (curr != NULL && curr->linep->s[0] == '{') {
  756.             setpcmark();
  757.             *Curschar = *curr;
  758.             return TRUE;
  759.         }
  760.     } while (curr != NULL);
  761.  
  762.     return FALSE;
  763. }
  764.  
  765. /*
  766.  * The following routines do the word searches performed by the
  767.  * 'w', 'W', 'b', 'B', 'e', and 'E' commands.
  768.  */
  769.  
  770. /*
  771.  * To perform these searches, characters are placed into one of three
  772.  * classes, and transitions between classes determine word boundaries.
  773.  *
  774.  * The classes are:
  775.  *
  776.  * 0 - white space
  777.  * 1 - letters, digits, and underscore
  778.  * 2 - everything else
  779.  */
  780.  
  781. static    int    stype;        /* type of the word motion being performed */
  782.  
  783. #define    C0(c)    (((c) == ' ') || ((c) == '\t') || ((c) == NUL))
  784. #define    C1(c)    (isalpha(c) || isdigit(c) || ((c) == '_'))
  785.  
  786. /*
  787.  * cls(c) - returns the class of character 'c'
  788.  *
  789.  * The 'type' of the current search modifies the classes of characters
  790.  * if a 'W', 'B', or 'E' motion is being done. In this case, chars. from
  791.  * class 2 are reported as class 1 since only white space boundaries are
  792.  * of interest.
  793.  */
  794. static    int
  795. cls(c)
  796. char    c;
  797. {
  798.     if (C0(c))
  799.         return 0;
  800.  
  801.     if (C1(c))
  802.         return 1;
  803.  
  804.     /*
  805.      * If stype is non-zero, report these as class 1.
  806.      */
  807.     return (stype == 0) ? 2 : 1;
  808. }
  809.  
  810.  
  811. /*
  812.  * fwd_word(pos, type) - move forward one word
  813.  *
  814.  * Returns the resulting position, or NULL if EOF was reached.
  815.  */
  816. LPTR *
  817. fwd_word(p, type)
  818. LPTR    *p;
  819. int    type;
  820. {
  821.     static    LPTR    pos;
  822.     int    sclass = cls(gchar(p));        /* starting class */
  823.  
  824.     pos = *p;
  825.  
  826.     stype = type;
  827.  
  828.     /*
  829.      * We always move at least one character.
  830.      */
  831.     if (inc(&pos) == -1)
  832.         return NULL;
  833.  
  834.     if (sclass != 0) {
  835.         while (cls(gchar(&pos)) == sclass) {
  836.             if (inc(&pos) == -1)
  837.                 return NULL;
  838.         }
  839.         /*
  840.          * If we went from 1 -> 2 or 2 -> 1, return here.
  841.          */
  842.         if (cls(gchar(&pos)) != 0)
  843.             return &pos;
  844.     }
  845.  
  846.     /* We're in white space; go to next non-white */
  847.  
  848.     while (cls(gchar(&pos)) == 0) {
  849.         /*
  850.          * We'll stop if we land on a blank line
  851.          */
  852.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  853.             break;
  854.  
  855.         if (inc(&pos) == -1)
  856.             return NULL;
  857.     }
  858.  
  859.     return &pos;
  860. }
  861.  
  862. /*
  863.  * bck_word(pos, type) - move backward one word
  864.  *
  865.  * Returns the resulting position, or NULL if EOF was reached.
  866.  */
  867. LPTR *
  868. bck_word(p, type)
  869. LPTR    *p;
  870. int    type;
  871. {
  872.     static    LPTR    pos;
  873.     int    sclass = cls(gchar(p));        /* starting class */
  874.  
  875.     pos = *p;
  876.  
  877.     stype = type;
  878.  
  879.     if (dec(&pos) == -1)
  880.         return NULL;
  881.  
  882.     /*
  883.      * If we're in the middle of a word, we just have to
  884.      * back up to the start of it.
  885.      */
  886.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  887.         /*
  888.          * Move backward to start of the current word
  889.          */
  890.         while (cls(gchar(&pos)) == sclass) {
  891.             if (dec(&pos) == -1)
  892.                 return NULL;
  893.         }
  894.         inc(&pos);            /* overshot - forward one */
  895.         return &pos;
  896.     }
  897.  
  898.     /*
  899.      * We were at the start of a word. Go back to the start
  900.      * of the prior word.
  901.      */
  902.  
  903.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  904.         /*
  905.          * We'll stop if we land on a blank line
  906.          */
  907.         if (pos.index == 0 && pos.linep->s[0] == NUL)
  908.             return &pos;
  909.  
  910.         if (dec(&pos) == -1)
  911.             return NULL;
  912.     }
  913.  
  914.     sclass = cls(gchar(&pos));
  915.  
  916.     /*
  917.      * Move backward to start of this word.
  918.      */
  919.     while (cls(gchar(&pos)) == sclass) {
  920.         if (dec(&pos) == -1)
  921.             return NULL;
  922.     }
  923.     inc(&pos);            /* overshot - forward one */
  924.  
  925.     return &pos;
  926. }
  927.  
  928. /*
  929.  * end_word(pos, type, in_change) - move to the end of the word
  930.  *
  931.  * There is an apparent bug in the 'e' motion of the real vi. At least
  932.  * on the System V Release 3 version for the 80386. Unlike 'b' and 'w',
  933.  * the 'e' motion crosses blank lines. When the real vi crosses a blank
  934.  * line in an 'e' motion, the cursor is placed on the FIRST character
  935.  * of the next non-blank line. The 'E' command, however, works correctly.
  936.  * Since this appears to be a bug, I have not duplicated it here.
  937.  *
  938.  * There's a strange special case here that the 'in_change' parameter
  939.  * helps us deal with. Vi effectively turns 'cw' into 'ce'. If we're on
  940.  * a word with only one character, we need to stick at the current
  941.  * position so we don't change two words.
  942.  *
  943.  * Returns the resulting position, or NULL if EOF was reached.
  944.  */
  945. LPTR *
  946. end_word(p, type, in_change)
  947. LPTR    *p;
  948. int    type;
  949. bool_t    in_change;
  950. {
  951.     static    LPTR    pos;
  952.     int    sclass = cls(gchar(p));        /* starting class */
  953.  
  954.     pos = *p;
  955.  
  956.     stype = type;
  957.  
  958.     if (inc(&pos) == -1)
  959.         return NULL;
  960.  
  961.     /*
  962.      * If we're in the middle of a word, we just have to
  963.      * move to the end of it.
  964.      */
  965.     if (cls(gchar(&pos)) == sclass && sclass != 0) {
  966.         /*
  967.          * Move forward to end of the current word
  968.          */
  969.         while (cls(gchar(&pos)) == sclass) {
  970.             if (inc(&pos) == -1)
  971.                 return NULL;
  972.         }
  973.         dec(&pos);            /* overshot - forward one */
  974.         return &pos;
  975.     }
  976.  
  977.     /*
  978.      * We were at the end of a word. Go to the end of the next
  979.      * word, unless we're doing a change. In that case we stick
  980.      * at the end of the current word.
  981.      */
  982.     if (in_change)
  983.         return p;
  984.  
  985.     while (cls(gchar(&pos)) == 0) {        /* skip any white space */
  986.         if (inc(&pos) == -1)
  987.             return NULL;
  988.     }
  989.  
  990.     sclass = cls(gchar(&pos));
  991.  
  992.     /*
  993.      * Move forward to end of this word.
  994.      */
  995.     while (cls(gchar(&pos)) == sclass) {
  996.         if (inc(&pos) == -1)
  997.             return NULL;
  998.     }
  999.     dec(&pos);            /* overshot - forward one */
  1000.  
  1001.     return &pos;
  1002. }
  1003.