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

  1. /* $Header: /nw2/tony/src/stevie/src/RCS/cmdline.c,v 1.22 89/08/31 10:00:23 tony Exp $
  2.  *
  3.  * Routines to parse and execute "command line" commands, such as searches
  4.  * or colon commands.
  5.  */
  6.  
  7. #include "stevie.h"
  8.  
  9. static    char    *altfile = NULL;    /* alternate file */
  10. static    int    altline;        /* line # in alternate file */
  11.  
  12. static    char    *nowrtmsg = "No write since last change (use ! to override)";
  13. static    char    *nooutfile = "No output file";
  14. static    char    *morefiles = "more files to edit";
  15.  
  16. extern    char    **files;        /* used for "n" and "rew" */
  17. extern    int    numfiles, curfile;
  18.  
  19. #define    CMDSZ    100        /* size of the command buffer */
  20.  
  21. static    bool_t    doecmd();
  22. static    void    badcmd(), get_range();
  23. static    LPTR    *get_line();
  24.  
  25. /*
  26.  * getcmdln() - read a command line from the terminal
  27.  *
  28.  * Reads a command line started by typing '/', '?', '!', or ':'. Returns a
  29.  * pointer to the string that was read. For searches, an optional trailing
  30.  * '/' or '?' is removed.
  31.  */
  32. char *
  33. getcmdln(firstc)
  34. char    firstc;
  35. {
  36.     static    char    buff[CMDSZ];
  37.     register char    *p = buff;
  38.     register int    c;
  39.     register char    *q;
  40.  
  41.     gotocmd(TRUE, firstc);
  42.  
  43.     /* collect the command string, handling '\b' and @ */
  44.     do {
  45.         switch (c = vgetc()) {
  46.  
  47.         default:        /* a normal character */
  48.             outchar(c);
  49.             *p++ = c;
  50.             break;
  51.  
  52.         case BS:
  53.             if (p > buff) {
  54.                 /*
  55.                  * this is gross, but it relies
  56.                  * only on 'gotocmd'
  57.                  */
  58.                 p--;
  59.                 gotocmd(TRUE, firstc);
  60.                 for (q = buff; q < p ;q++)
  61.                     outchar(*q);
  62.             } else {
  63.                 msg("");
  64.                 return NULL;        /* back to cmd mode */
  65.             }
  66.             break;
  67.  
  68.         case '@':            /* line kill */
  69.             p = buff;
  70.             gotocmd(TRUE, firstc);
  71.             break;
  72.  
  73.         case NL:            /* done reading the line */
  74.         case CR:
  75.             break;
  76.         }
  77.     } while (c != NL && c != CR);
  78.  
  79.     *p = '\0';
  80.  
  81.     if (firstc == '/' || firstc == '?') {    /* did we do a search? */
  82.         /*
  83.          * Look for a terminating '/' or '?'. This will be the first
  84.          * one that isn't quoted. Truncate the search string there.
  85.          */
  86.         for (p = buff; *p ;) {
  87.             if (*p == firstc) {    /* we're done */
  88.                 *p = '\0';
  89.                 break;
  90.             } else if (*p == '\\')    /* next char quoted */
  91.                 p += 2;
  92.             else
  93.                 p++;        /* normal char */
  94.         }
  95.     }
  96.     if (buff[0] == '\0')
  97.         return NULL;
  98.  
  99.     return buff;
  100. }
  101.  
  102. /*
  103.  * docmdln() - handle a colon command
  104.  *
  105.  * Handles a colon command received interactively by getcmdln() or from
  106.  * the environment variable "EXINIT" (or eventually .virc).
  107.  */
  108. void
  109. docmdln(cmdline)
  110. char    *cmdline;
  111. {
  112.     char    buff[CMDSZ];
  113.     char    cmdbuf[CMDSZ];
  114.     char    argbuf[CMDSZ];
  115.     char    *cmd, *arg;
  116.     register char    *p;
  117.     /*
  118.      * The next two variables contain the bounds of any range given in a
  119.      * command. If no range was given, both contain null line pointers.
  120.      * If only a single line was given, u_pos will contain a null line
  121.      * pointer.
  122.      */
  123.     LPTR    l_pos, u_pos;
  124.  
  125.  
  126.     /*
  127.      * Clear the range variables.
  128.      */
  129.     l_pos.linep = (struct line *) NULL;
  130.     u_pos.linep = (struct line *) NULL;
  131.  
  132.     if (cmdline == NULL)
  133.         return;
  134.  
  135.     if (strlen(cmdline) > CMDSZ-2) {
  136.         msg("Error: command line too long");
  137.         return;
  138.     }
  139.     strcpy(buff, cmdline);
  140.  
  141.     /* skip any initial white space */
  142.     for (cmd = buff; *cmd != NUL && isspace(*cmd) ;cmd++)
  143.         ;
  144.  
  145.     if (*cmd == '%') {        /* change '%' to "1,$" */
  146.         strcpy(cmdbuf, "1,$");    /* kind of gross... */
  147.         strcat(cmdbuf, cmd+1);
  148.         strcpy(cmd, cmdbuf);
  149.     }
  150.  
  151.     while ((p=strchr(cmd, '%')) != NULL && *(p-1) != '\\') {
  152.                     /* change '%' to Filename */
  153.         if (Filename == NULL) {
  154.             emsg("No filename");
  155.             return;
  156.         }
  157.         *p= NUL;
  158.         strcpy (cmdbuf, cmd);
  159.         strcat (cmdbuf, Filename);
  160.         strcat (cmdbuf, p+1);
  161.         strcpy(cmd, cmdbuf);
  162.         msg(cmd);            /*repeat */
  163.     }
  164.  
  165.     while ((p=strchr(cmd, '#')) != NULL && *(p-1) != '\\') {
  166.                     /* change '#' to Altname */
  167.         if (altfile == NULL) {
  168.             emsg("No alternate file");
  169.             return;
  170.         }
  171.         *p= NUL;
  172.         strcpy (cmdbuf, cmd);
  173.         strcat (cmdbuf, altfile);
  174.         strcat (cmdbuf, p+1);
  175.         strcpy(cmd, cmdbuf);
  176.         msg(cmd);            /*repeat */
  177.     }
  178.  
  179.     /*
  180.      * Parse a range, if present (and update the cmd pointer).
  181.      */
  182.     get_range(&cmd, &l_pos, &u_pos);
  183.  
  184.     if (l_pos.linep != NULL) {
  185.         if (LINEOF(&l_pos) > LINEOF(&u_pos)) {
  186.             emsg("Invalid range");
  187.             return;
  188.         }
  189.     }
  190.  
  191.     strcpy(cmdbuf, cmd);    /* save the unmodified command */
  192.  
  193.     /* isolate the command and find any argument */
  194.     for ( p=cmd; *p != NUL && ! isspace(*p); p++ )
  195.         ;
  196.     if ( *p == NUL )
  197.         arg = NULL;
  198.     else {
  199.         *p = NUL;
  200.         for (p++; *p != NUL && isspace(*p) ;p++)
  201.             ;
  202.         if (*p == NUL)
  203.             arg = NULL;
  204.         else {
  205.             strcpy(argbuf, p);
  206.             arg = argbuf;
  207.         }
  208.     }
  209.     if (strcmp(cmd,"q!") == 0)
  210.         getout();
  211.     if (strcmp(cmd,"q") == 0) {
  212.         if (Changed)
  213.             emsg(nowrtmsg);
  214.         else {
  215.             if ((curfile + 1) < numfiles)
  216.                 emsg(morefiles);
  217.             else
  218.                 getout();
  219.         }
  220.         return;
  221.     }
  222.     if (strcmp(cmd,"w") == 0) {
  223.         if (arg == NULL) {
  224.             if (Filename != NULL) {
  225.                 writeit(Filename, &l_pos, &u_pos);
  226.             } else
  227.                 emsg(nooutfile);
  228.         }
  229.         else
  230.             writeit(arg, &l_pos, &u_pos);
  231.         return;
  232.     }
  233.     if (strcmp(cmd,"wq") == 0) {
  234.         if (Filename != NULL) {
  235.             if (writeit(Filename, (LPTR *)NULL, (LPTR *)NULL))
  236.                 getout();
  237.         } else
  238.             emsg(nooutfile);
  239.         return;
  240.     }
  241.     if (strcmp(cmd, "x") == 0) {
  242.         doxit();
  243.         return;
  244.     }
  245.  
  246.     if (strcmp(cmd,"f") == 0 && arg == NULL) {
  247.         fileinfo();
  248.         return;
  249.     }
  250.     if (*cmd == 'n') {
  251.         if ((curfile + 1) < numfiles) {
  252.             /*
  253.              * stuff ":e[!] FILE\n"
  254.              */
  255.             stuffin(":e");
  256.             if (cmd[1] == '!')
  257.                 stuffin("!");
  258.             stuffin(" ");
  259.             stuffin(files[++curfile]);
  260.             stuffin("\n");
  261.         } else
  262.             emsg("No more files!");
  263.         return;
  264.     }
  265.     if (*cmd == 'N') {
  266.         if (curfile > 0) {
  267.             /*
  268.              * stuff ":e[!] FILE\n"
  269.              */
  270.             stuffin(":e");
  271.             if (cmd[1] == '!')
  272.                 stuffin("!");
  273.             stuffin(" ");
  274.             stuffin(files[--curfile]);
  275.             stuffin("\n");
  276.         } else
  277.             emsg("No more files!");
  278.         return;
  279.     }
  280.     if (strncmp(cmd, "rew", 3) == 0) {
  281.         if (numfiles <= 1)        /* nothing to rewind */
  282.             return;
  283.         curfile = 0;
  284.         /*
  285.          * stuff ":e[!] FILE\n"
  286.          */
  287.         stuffin(":e");
  288.         if (cmd[3] == '!')
  289.             stuffin("!");
  290.         stuffin(" ");
  291.         stuffin(files[0]);
  292.         stuffin("\n");
  293.         return;
  294.     }
  295.     if (strcmp(cmd,"e") == 0 || strcmp(cmd,"e!") == 0) {
  296.         (void) doecmd(arg, cmd[1] == '!');
  297.         return;
  298.     }
  299.     /*
  300.      * The command ":e#" gets expanded to something like ":efile", so
  301.      * detect that case here.
  302.      */
  303.     if (*cmd == 'e' && arg == NULL) {
  304.         if (cmd[1] == '!')
  305.             (void) doecmd(&cmd[2], TRUE);
  306.         else
  307.             (void) doecmd(&cmd[1], FALSE);
  308.         return;
  309.     }
  310.     if (strcmp(cmd,"f") == 0) {
  311.         Filename = strsave(arg);
  312.         filemess("");
  313.         return;
  314.     }
  315.     if (strcmp(cmd,"r") == 0) {
  316.         if (arg == NULL) {
  317.             badcmd();
  318.             return;
  319.         }
  320.         if (readfile(arg, Curschar, 1)) {
  321.             emsg("Can't open file");
  322.             return;
  323.         }
  324.         updatescreen();
  325.         CHANGED;
  326.         return;
  327.     }
  328.     if (strcmp(cmd,"=") == 0) {
  329.         smsg("%d", cntllines(Filemem, &l_pos));
  330.         return;
  331.     }
  332.     if (strncmp(cmd,"ta", 2) == 0) {
  333.         dotag(arg, cmd[2] == '!');
  334.         return;
  335.     }
  336.     if (strncmp(cmd,"set", 2) == 0) {
  337.         doset(arg);
  338.         return;
  339.     }
  340.     if (strcmp(cmd,"help") == 0) {
  341.         if (help()) {
  342.             screenclear();
  343.             updatescreen();
  344.         }
  345.         return;
  346.     }
  347.     if (strncmp(cmd, "ve", 2) == 0) {
  348.         extern    char    *Version;
  349.  
  350.         msg(Version);
  351.         return;
  352.     }
  353.     if (strcmp(cmd, "sh") == 0) {
  354.         doshell(NULL);
  355.         return;
  356.     }
  357.     if (*cmd == '!') {
  358.         doshell(cmdbuf+1);
  359.         return;
  360.     }
  361.     if (strncmp(cmd, "s/", 2) == 0) {
  362.         dosub(&l_pos, &u_pos, cmdbuf+1);
  363.         return;
  364.     }
  365.     if (strncmp(cmd, "g/", 2) == 0) {
  366.         doglob(&l_pos, &u_pos, cmdbuf+1);
  367.         return;
  368.     }
  369.     /*
  370.      * If we got a line, but no command, then go to the line.
  371.      */
  372.     if (*cmd == NUL && l_pos.linep != NULL) {
  373.         *Curschar = l_pos;
  374.         return;
  375.     }
  376.  
  377.     badcmd();
  378. }
  379.  
  380.  
  381. doxit()
  382. {
  383.     if (Changed) {
  384.         if (Filename != NULL) {
  385.             if (!writeit(Filename, (LPTR *)NULL, (LPTR *)NULL))
  386.                 return;
  387.         } else {
  388.             emsg(nooutfile);
  389.             return;
  390.         }
  391.     }
  392.     if ((curfile + 1) < numfiles)
  393.         emsg(morefiles);
  394.     else
  395.         getout();
  396. }
  397.  
  398. /*
  399.  * get_range - parse a range specifier
  400.  *
  401.  * Ranges are of the form:
  402.  *
  403.  * addr[,addr]
  404.  *
  405.  * where 'addr' is:
  406.  *
  407.  * $  [+- NUM]
  408.  * 'x [+- NUM]    (where x denotes a currently defined mark)
  409.  * .  [+- NUM]
  410.  * NUM
  411.  *
  412.  * The pointer *cp is updated to point to the first character following
  413.  * the range spec. If an initial address is found, but no second, the
  414.  * upper bound is equal to the lower.
  415.  */
  416. static void
  417. get_range(cp, lower, upper)
  418. register char    **cp;
  419. LPTR    *lower, *upper;
  420. {
  421.     register LPTR    *l;
  422.     register char    *p;
  423.  
  424.     if ((l = get_line(cp)) == NULL)
  425.         return;
  426.  
  427.     *lower = *l;
  428.  
  429.     for (p = *cp; *p != NUL && isspace(*p) ;p++)
  430.         ;
  431.  
  432.     *cp = p;
  433.  
  434.     if (*p != ',') {        /* is there another line spec ? */
  435.         *upper = *lower;
  436.         return;
  437.     }
  438.  
  439.     *cp = ++p;
  440.  
  441.     if ((l = get_line(cp)) == NULL) {
  442.         *upper = *lower;
  443.         return;
  444.     }
  445.  
  446.     *upper = *l;
  447. }
  448.  
  449. static LPTR *
  450. get_line(cp)
  451. char    **cp;
  452. {
  453.     static    LPTR    pos;
  454.     LPTR    *lp;
  455.     register char    *p, c;
  456.     register int    lnum;
  457.  
  458.     pos.index = 0;        /* shouldn't matter... check back later */
  459.  
  460.     p = *cp;
  461.     /*
  462.      * Determine the basic form, if present.
  463.      */
  464.     switch (c = *p++) {
  465.  
  466.     case '$':
  467.         pos.linep = Fileend->linep->prev;
  468.         break;
  469.  
  470.     case '.':
  471.         pos.linep = Curschar->linep;
  472.         break;
  473.  
  474.     case '\'':
  475.         if ((lp = getmark(*p++)) == NULL) {
  476.             emsg("Unknown mark");
  477.             return (LPTR *) NULL;
  478.         }
  479.         pos = *lp;
  480.         break;
  481.  
  482.     case '0': case '1': case '2': case '3': case '4':
  483.     case '5': case '6': case '7': case '8': case '9':
  484.         for (lnum = c - '0'; isdigit(*p) ;p++)
  485.             lnum = (lnum * 10) + (*p - '0');
  486.  
  487.         pos = *gotoline(lnum);
  488.         break;
  489.  
  490.     default:
  491.         return (LPTR *) NULL;
  492.     }
  493.  
  494.     while (*p != NUL && isspace(*p))
  495.         p++;
  496.  
  497.     if (*p == '-' || *p == '+') {
  498.         bool_t    neg = (*p++ == '-');
  499.  
  500.         for (lnum = 0; isdigit(*p) ;p++)
  501.             lnum = (lnum * 10) + (*p - '0');
  502.  
  503.         if (neg)
  504.             lnum = -lnum;
  505.  
  506.         pos = *gotoline( cntllines(Filemem, &pos) + lnum );
  507.     }
  508.  
  509.     *cp = p;
  510.     return &pos;
  511. }
  512.  
  513. static void
  514. badcmd()
  515. {
  516.     emsg("Unrecognized command");
  517. }
  518.  
  519. #define    LSIZE    256    /* max. size of a line in the tags file */
  520.  
  521. /*
  522.  * dotag(tag, force) - goto tag
  523.  */
  524. void
  525. dotag(tag, force)
  526. char    *tag;
  527. bool_t    force;
  528. {
  529.     FILE    *tp, *fopen();
  530.     char    lbuf[LSIZE];        /* line buffer */
  531.     char    pbuf[LSIZE];        /* search pattern buffer */
  532.     register char    *fname, *str;
  533.     register char    *p;
  534.  
  535.     if ((tp = fopen("tags", "r")) == NULL) {
  536.         emsg("Can't open tags file");
  537.         return;
  538.     }
  539.  
  540.     while (fgets(lbuf, LSIZE, tp) != NULL) {
  541.     
  542.         if ((fname = strchr(lbuf, TAB)) == NULL) {
  543.             emsg("Format error in tags file");
  544.             return;
  545.         }
  546.         *fname++ = '\0';
  547.         if ((str = strchr(fname, TAB)) == NULL) {
  548.             emsg("Format error in tags file");
  549.             return;
  550.         }
  551.         *str++ = '\0';
  552.  
  553.         if (strcmp(lbuf, tag) == 0) {
  554.  
  555.             /*
  556.              * Scan through the search string. If we see a magic
  557.              * char, we have to quote it. This lets us use "real"
  558.              * implementations of ctags.
  559.              */
  560.             p = pbuf;
  561.             *p++ = *str++;        /* copy the '/' or '?' */
  562.             *p++ = *str++;        /* copy the '^' */
  563.  
  564.             for (; *str != NUL ;str++) {
  565.                 if (*str == '\\') {
  566.                     *p++ = *str++;
  567.                     *p++ = *str;
  568.                 } else if (strchr("/?", *str) != NULL) {
  569.                     if (str[1] != '\n') {
  570.                         *p++ = '\\';
  571.                         *p++ = *str;
  572.                     } else
  573.                         *p++ = *str;
  574.                 } else if (strchr("^()*.", *str) != NULL) {
  575.                     *p++ = '\\';
  576.                     *p++ = *str;
  577.                 } else
  578.                     *p++ = *str;
  579.             }
  580.             *p = NUL;
  581.  
  582.             /*
  583.              * This looks out of order, but by calling stuffin()
  584.              * before doecmd() we keep an extra screen update
  585.              * from occuring. This stuffins() have no effect
  586.              * until we get back to the main loop, anyway.
  587.              */
  588.             stuffin(pbuf);        /* str has \n at end */
  589.             stuffin("\007");    /* CTRL('g') */
  590.  
  591.             if (doecmd(fname, force)) {
  592.                 fclose(tp);
  593.                 return;
  594.             } else
  595.                 stuffin(NULL);    /* clear the input */
  596.         }
  597.     }
  598.     emsg("tag not found");
  599.     fclose(tp);
  600. }
  601.  
  602. static    bool_t
  603. doecmd(arg, force)
  604. char    *arg;
  605. bool_t    force;
  606. {
  607.     int    line = 1;        /* line # to go to in new file */
  608.  
  609.     if (!force && Changed) {
  610.         emsg(nowrtmsg);
  611.         if (altfile)
  612.             free(altfile);
  613.         altfile = strsave(arg);
  614.         return FALSE;
  615.     }
  616.     if (arg != NULL) {
  617.         /*
  618.          * First detect a ":e" on the current file. This is mainly
  619.          * for ":ta" commands where the destination is within the
  620.          * current file.
  621.          */
  622.         if (Filename != NULL && strcmp(arg, Filename) == 0) {
  623.             if (!Changed || (Changed && !force))
  624.                 return TRUE;
  625.         }
  626.         if (altfile) {
  627.             if (strcmp (arg, altfile) == 0)
  628.                 line = altline;
  629.             free(altfile);
  630.         }
  631.         altfile = Filename;
  632.         altline = cntllines(Filemem, Curschar);
  633.         Filename = strsave(arg);
  634.     }
  635.     if (Filename == NULL) {
  636.         emsg("No filename");
  637.         return FALSE;
  638.     }
  639.  
  640.     /* clear mem and read file */
  641.     freeall();
  642.     filealloc();
  643.     UNCHANGED;
  644.  
  645.     if (readfile(Filename, Filemem, 0))
  646.         filemess("[New File]");
  647.  
  648.     *Topchar = *Curschar;
  649.     if (line != 1) {
  650.         stuffnum(line);
  651.         stuffin("G");
  652.     }
  653.     do_mlines();
  654.     setpcmark();
  655.     updatescreen();
  656.     return TRUE;
  657. }
  658.  
  659. void
  660. gotocmd(clr, firstc)
  661. bool_t  clr;
  662. char    firstc;
  663. {
  664.     windgoto(Rows-1,0);
  665.     if (clr)
  666.         outcstr(T_EL);        /* clear the bottom line */
  667.     if (firstc)
  668.         outchar(firstc);
  669. }
  670.  
  671. /*
  672.  * msg(s) - displays the string 's' on the status line
  673.  */
  674. void
  675. msg(s)
  676. char    *s;
  677. {
  678.     gotocmd(TRUE, 0);
  679.     outstr(s);
  680.     flushbuf();
  681. }
  682.  
  683. /*VARARGS1*/
  684. void
  685. smsg(s, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)
  686. char    *s;
  687. int    a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16;
  688. {
  689.     char    sbuf[80];
  690.  
  691.     sprintf(sbuf, s,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16);
  692.     msg(sbuf);
  693. }
  694.  
  695. /*
  696.  * emsg() - display an error message
  697.  *
  698.  * Rings the bell, if appropriate, and calls message() to do the real work
  699.  */
  700. void
  701. emsg(s)
  702. char    *s;
  703. {
  704.     if (P(P_EB))
  705.         beep();
  706.     msg(s);
  707. }
  708.  
  709. void
  710. wait_return()
  711. {
  712.     register char    c;
  713.  
  714.     if (got_int)
  715.         outstr("Interrupt: ");
  716.  
  717.     outstr("Press RETURN to continue");
  718.     do {
  719.         c = vgetc();
  720.     } while (c != CR && c != NL && c != ' ' && c != ':');
  721.  
  722.     if (c == ':') {
  723.         outchar(NL);
  724.         docmdln(getcmdln(c));
  725.     } else
  726.         screenclear();
  727.  
  728.     updatescreen();
  729. }
  730.