home *** CD-ROM | disk | FTP | other *** search
/ rtsi.com / 2014.01.www.rtsi.com.tar / www.rtsi.com / OS9 / OSK / CMDS / pvic_10a.lzh / SRCE / search.c < prev    next >
Text File  |  1998-04-23  |  20KB  |  1,016 lines

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