home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / elvis184.zip / src / ex.c < prev    next >
C/C++ Source or Header  |  1995-05-26  |  22KB  |  922 lines

  1. /* ex.c */
  2.  
  3. /* Author:
  4.  *    Steve Kirkendall
  5.  *    1500 SW Park #326
  6.  *    Portland OR, 97201
  7.  *    kirkenda@cs.pdx.edu
  8.  */
  9.  
  10.  
  11. /* This file contains the code for reading ex commands. */
  12.  
  13. #include "config.h"
  14. #include "ctype.h"
  15. #include "vi.h"
  16.  
  17. /* the usual "min" macro.  Note that the minumum argument is evaluated twice. */
  18. #define MIN(a, b)    ((a)<=(b) ? (a) : (b))
  19.  
  20. /* This data type is used to describe the possible argument combinations */
  21. typedef unsigned short ARGT;
  22. #define FROM    1        /* allow a linespec */
  23. #define    TO    2        /* allow a second linespec */
  24. #define BANG    4        /* allow a ! after the command name */
  25. #define EXTRA    8        /* allow extra args after command name */
  26. #define XFILE    16        /* expand wildcards in extra part */
  27. #define NOSPC    32        /* no spaces allowed in the extra part */
  28. #define    DFLALL    64        /* default file range is 1,$ */
  29. #define DFLNONE    128        /* no default file range */
  30. #define NODFL    256        /* do not default to the current file name */
  31. #define EXRCOK    512        /* can be in a .exrc file */
  32. #define NL    1024        /* if mode!=MODE_EX, then write a newline first */
  33. #define PLUS    2048        /* allow a line number, as in ":e +32 foo" */
  34. #define ZERO    4096        /* allow 0 to be given as a line number */
  35. #define NOBAR    8192        /* treat following '|' chars as normal */
  36. #define UNSAFE    16384        /* don't allow in modelines or ./.exrc */
  37. #define APRINT    32768        /* maybe autoprint after these commands */
  38. #define FILES    (XFILE + EXTRA)    /* multiple extra files allowed */
  39. #define WORD1    (EXTRA + NOSPC)    /* one extra word allowed */
  40. #define FILE1    (FILES + NOSPC)    /* 1 file allowed, defaults to current file */
  41. #define NAMEDF    (FILE1 + NODFL)    /* 1 file allowed, defaults to "" */
  42. #define NAMEDFS    (FILES + NODFL)    /* multiple files allowed, default is "" */
  43. #define RANGE    (FROM + TO)    /* range of linespecs allowed */
  44. #define NONE    0        /* no args allowed at all */
  45.  
  46. /* This array maps ex command names to command codes. The order in which
  47.  * command names are listed below is significant -- ambiguous abbreviations
  48.  * are always resolved to be the first possible match.  (e.g. "r" is taken
  49.  * to mean "read", not "rewind", because "read" comes before "rewind")
  50.  */
  51. static struct
  52. {
  53.     char    *name;    /* name of the command */
  54.     CMD    code;    /* enum code of the command */
  55.     void    (*fn) P_((MARK, MARK, CMD, int, char *));
  56.             /* function which executes the command */
  57.     ARGT    argt;    /* command line arguments permitted/needed/used */
  58. }
  59.     cmdnames[] =
  60. {   /*    cmd name    cmd code    function    arguments */
  61.     {"print",    CMD_PRINT,    cmd_print,    RANGE+NL    },
  62.  
  63.     {"append",    CMD_APPEND,    cmd_append,    FROM+ZERO+BANG    },
  64. #ifdef DEBUG
  65.     {"bug",        CMD_DEBUG,    cmd_debug,    RANGE+BANG+EXTRA+NL},
  66. #endif
  67.     {"change",    CMD_CHANGE,    cmd_append,    RANGE+BANG    },
  68.     {"delete",    CMD_DELETE,    cmd_delete,    RANGE+WORD1+APRINT},
  69.     {"edit",    CMD_EDIT,    cmd_edit,    BANG+FILE1+PLUS    },
  70.     {"file",    CMD_FILE,    cmd_file,    NAMEDF+UNSAFE    },
  71.     {"global",    CMD_GLOBAL,    cmd_global,    RANGE+BANG+EXTRA+DFLALL+NOBAR},
  72.     {"insert",    CMD_INSERT,    cmd_append,    FROM+BANG    },
  73.     {"join",    CMD_INSERT,    cmd_join,    RANGE+BANG+APRINT},
  74.     {"k",        CMD_MARK,    cmd_mark,    FROM+WORD1    },
  75.     {"list",    CMD_LIST,    cmd_print,    RANGE+NL    },
  76.     {"move",    CMD_MOVE,    cmd_move,    RANGE+EXTRA+APRINT},
  77.     {"next",    CMD_NEXT,    cmd_next,    BANG+NAMEDFS+UNSAFE},
  78.     {"Next",    CMD_PREVIOUS,    cmd_next,    BANG+UNSAFE    },
  79.     {"quit",    CMD_QUIT,    cmd_xit,    BANG        },
  80.     {"read",    CMD_READ,    cmd_read,    FROM+ZERO+NAMEDF},
  81.     {"substitute",    CMD_SUBSTITUTE,    cmd_substitute,    RANGE+EXTRA+NOBAR+APRINT},
  82.     {"to",        CMD_COPY,    cmd_move,    RANGE+EXTRA+APRINT},
  83.     {"undo",    CMD_UNDO,    cmd_undo,    NONE+APRINT    },
  84.     {"vglobal",    CMD_VGLOBAL,    cmd_global,    RANGE+EXTRA+DFLALL+NOBAR},
  85.     {"write",    CMD_WRITE,    cmd_write,    RANGE+BANG+FILE1+DFLALL+UNSAFE},
  86.     {"xit",        CMD_XIT,    cmd_xit,    BANG+NL        },
  87.     {"yank",    CMD_YANK,    cmd_delete,    RANGE+WORD1    },
  88.  
  89.     {"!",        CMD_BANG,    cmd_shell,    EXRCOK+RANGE+NAMEDFS+DFLNONE+NL+NOBAR+UNSAFE},
  90.     {"\"",        CMD_COMMENT,    cmd_comment,    EXRCOK+BANG+EXTRA+NOBAR},
  91.     {"#",        CMD_NUMBER,    cmd_print,    RANGE+NL    },
  92.     {"<",        CMD_SHIFTL,    cmd_shift,    RANGE+EXTRA+APRINT},
  93.     {">",        CMD_SHIFTR,    cmd_shift,    RANGE+EXTRA+APRINT},
  94.     {"=",        CMD_EQUAL,    cmd_file,    RANGE        },
  95.     {"&",        CMD_SUBAGAIN,    cmd_substitute,    RANGE        },
  96.     {"~",        CMD_SUBAGAIN,    cmd_substitute,    RANGE        },
  97. #ifndef NO_AT
  98.     {"@",        CMD_AT,        cmd_at,        EXTRA        },
  99. #endif
  100.  
  101. #ifndef NO_ABBR
  102.     {"abbreviate",    CMD_ABBR,    cmd_map,    EXRCOK+BANG+EXTRA+UNSAFE},
  103. #endif
  104. #ifndef NO_IF
  105.     {"and",        CMD_AND,    cmd_if,        EXRCOK+EXTRA    },
  106. #endif
  107.     {"args",    CMD_ARGS,    cmd_args,    EXRCOK+NAMEDFS+UNSAFE},
  108. #ifndef NO_ERRLIST
  109.     {"cc",        CMD_CC,        cmd_make,    BANG+FILES+UNSAFE},
  110. #endif
  111.     {"cd",        CMD_CD,        cmd_cd,        EXRCOK+BANG+NAMEDF+UNSAFE},
  112.     {"copy",    CMD_COPY,    cmd_move,    RANGE+EXTRA+APRINT},
  113. #ifndef NO_DIGRAPH
  114.     {"digraph",    CMD_DIGRAPH,    cmd_digraph,    EXRCOK+BANG+EXTRA},
  115. #endif
  116. #ifndef NO_IF
  117.     {"else",    CMD_ELSE,    cmd_then,    EXRCOK+EXTRA+NOBAR },
  118. #endif
  119. #ifndef NO_ERRLIST
  120.     {"errlist",    CMD_ERRLIST,    cmd_errlist,    BANG+NAMEDF    },
  121. #endif
  122.     {"ex",        CMD_EDIT,    cmd_edit,    BANG+FILE1+UNSAFE},
  123. #ifndef NO_IF
  124.     {"if",        CMD_IF,        cmd_if,        EXRCOK+EXTRA    },
  125. #endif
  126.     {"mark",    CMD_MARK,    cmd_mark,    FROM+WORD1    },
  127. #ifndef NO_MKEXRC
  128.     {"mkexrc",    CMD_MKEXRC,    cmd_mkexrc,    NAMEDF+UNSAFE    },
  129. #endif
  130.     {"number",    CMD_NUMBER,    cmd_print,    RANGE+NL    },
  131. #ifndef NO_IF
  132.     {"or",        CMD_OR,        cmd_if,        EXRCOK+EXTRA    },
  133. #endif
  134. #ifndef NO_TAGSTACK
  135.     {"pop",        CMD_POP,    cmd_pop,    BANG+WORD1+UNSAFE},
  136. #endif
  137.     {"put",        CMD_PUT,    cmd_put,    FROM+ZERO+WORD1    },
  138.     {"set",        CMD_SET,    cmd_set,    EXRCOK+EXTRA    },
  139.     {"shell",    CMD_SHELL,    cmd_shell,    NL+UNSAFE    },
  140.     {"source",    CMD_SOURCE,    cmd_source,    EXRCOK+NAMEDF    },
  141. #ifdef SIGTSTP
  142.     {"stop",    CMD_STOP,    cmd_suspend,    NONE+UNSAFE    },
  143. #endif
  144.     {"tag",        CMD_TAG,    cmd_tag,    BANG+WORD1+UNSAFE},
  145. #ifndef NO_IF
  146.     {"then",    CMD_THEN,    cmd_then,    EXRCOK+EXTRA+NOBAR },
  147. #endif
  148.     {"version",    CMD_VERSION,    cmd_version,    EXRCOK+NONE    },
  149.     {"visual",    CMD_VISUAL,    cmd_edit,    BANG+NAMEDF+UNSAFE},
  150.     {"wq",        CMD_WQUIT,    cmd_xit,    NL+BANG        },
  151.  
  152. #ifdef DEBUG
  153.     {"debug",    CMD_DEBUG,    cmd_debug,    RANGE+BANG+EXTRA+NL},
  154.     {"validate",    CMD_VALIDATE,    cmd_validate,    BANG+NL        },
  155. #endif
  156.     {"chdir",    CMD_CD,        cmd_cd,        EXRCOK+BANG+NAMEDF+UNSAFE},
  157. #ifndef NO_COLOR
  158.     {"color",    CMD_COLOR,    cmd_color,    EXRCOK+EXTRA},
  159. #endif
  160. #ifndef NO_ERRLIST
  161.     {"make",    CMD_MAKE,    cmd_make,    BANG+NAMEDFS+UNSAFE},
  162. #endif
  163.     {"map",        CMD_MAP,    cmd_map,    EXRCOK+BANG+EXTRA+UNSAFE},
  164.     {"previous",    CMD_PREVIOUS,    cmd_next,    BANG+UNSAFE    },
  165.     {"rewind",    CMD_REWIND,    cmd_next,    BANG+UNSAFE    },
  166. #ifdef SIGTSTP
  167.     {"suspend",    CMD_SUSPEND,    cmd_suspend,    NONE+UNSAFE    },
  168. #endif
  169.     {"unmap",    CMD_UNMAP,    cmd_map,    EXRCOK+BANG+EXTRA+UNSAFE},
  170. #ifndef NO_ABBR
  171.     {"unabbreviate",CMD_UNABBR,    cmd_map,    EXRCOK+EXTRA+UNSAFE},
  172. #endif
  173.  
  174.     {(char *)0}
  175. };
  176.  
  177.  
  178. /* This function parses a search pattern - given a pointer to a / or ?,
  179.  * it replaces the ending / or ? with a \0, and returns a pointer to the
  180.  * stuff that came after the pattern.
  181.  */
  182. char    *parseptrn(ptrn)
  183.     REG char    *ptrn;
  184. {
  185.     REG char     *scan;
  186.  
  187.     for (scan = ptrn + 1;
  188.          *scan && *scan != *ptrn;
  189.          scan++)
  190.     {
  191.         /* allow backslashed versions of / and ? in the pattern */
  192.         if (*scan == '\\' && scan[1] != '\0')
  193.         {
  194.             scan++;
  195.             if (*scan == '[' && scan[1] != '\0')    /*-g.t.*/
  196.             {                    /*-g.t.*/
  197.                 scan++;                /*-g.t.*/
  198.             }                    /*-g.t.*/
  199.         }
  200.         /* allow / and ? between [ and ] */        /*-g.t.*/
  201.         if (*scan == '[' && scan[1] != '\0')        /*-g.t.*/
  202.         {                        /*-g.t.*/
  203.             scan++;                    /*-g.t.*/
  204.             while (*scan != ']' && scan[1] != '\0')    /*-g.t.*/
  205.             {                    /*-g.t.*/
  206.                 scan++;                /*-g.t.*/
  207.             }                    /*-g.t.*/
  208.         }
  209.     }
  210.     if (*scan)
  211.     {
  212.         *scan++ = '\0';
  213.     }
  214.  
  215.     return scan;
  216. }
  217.  
  218.  
  219. /* This function parses a line specifier for ex commands */
  220. char *linespec(s, markptr)
  221.     REG char    *s;        /* start of the line specifier */
  222.     MARK        *markptr;    /* where to store the mark's value */
  223. {
  224.     long        num;
  225.     REG char    *t;
  226.  
  227.     /* parse each ;-delimited clause of this linespec */
  228.     do
  229.     {
  230.         /* skip an initial ';', if any */
  231.         if (*s == ';')
  232.         {
  233.             s++;
  234.         }
  235.  
  236.         /* skip leading spaces */
  237.         while (isspace(*s))
  238.         {
  239.             s++;
  240.         }
  241.  
  242.         /* dot means current position */
  243.         if (*s == '.')
  244.         {
  245.             s++;
  246.             *markptr = cursor;
  247.         }
  248.         /* '$' means the last line */
  249.         else if (*s == '$')
  250.         {
  251.             s++;
  252.             *markptr = MARK_LAST;
  253.         }
  254.         /* digit means an absolute line number */
  255.         else if (isdigit(*s))
  256.         {
  257.             for (num = 0; isdigit(*s); s++)
  258.             {
  259.                 num = num * 10 + *s - '0';
  260.             }
  261.             *markptr = MARK_AT_LINE(num);
  262.         }
  263.         /* appostrophe means go to a set mark */
  264.         else if (*s == '\'')
  265.         {
  266.             s++;
  267.             *markptr = m_tomark(cursor, 1L, (int)*s);
  268.             s++;
  269.         }
  270.         /* slash means do a search */
  271.         else if (*s == '/' || *s == '?')
  272.         {
  273.             /* put a '\0' at the end of the search pattern */
  274.             t = parseptrn(s);
  275.  
  276.             /* search for the pattern */
  277.             *markptr &= ~(BLKSIZE - 1);
  278.             if (*s == '/')
  279.             {
  280.                 pfetch(markline(*markptr));
  281.                 if (plen > 0)
  282.                     *markptr += plen - 1;
  283.                 *markptr = m_fsrch(*markptr, s);
  284.             }
  285.             else
  286.             {
  287.                 *markptr = m_bsrch(*markptr, s);
  288.             }
  289.  
  290.             /* adjust command string pointer */
  291.             s = t;
  292.         }
  293.  
  294.         /* if linespec was faulty, quit now */
  295.         if (!*markptr)
  296.         {
  297.             beep();
  298.             return s;
  299.         }
  300.  
  301.         /* maybe add an offset */
  302.         t = s;
  303.         if (*t == '-' || *t == '+')
  304.         {
  305.             s++;
  306.             for (num = 0; isdigit(*s); s++)
  307.             {
  308.                 num = num * 10 + *s - '0';
  309.             }
  310.             if (num == 0)
  311.             {
  312.                 num = 1;
  313.             }
  314.             *markptr = m_updnto(*markptr, num, *t);
  315.         }
  316.     } while (*s == ';' || *s == '+' || *s == '-');
  317.  
  318.     /* protect against invalid line numbers */
  319.     num = markline(*markptr);
  320.     if (num < 1L || num > nlines)
  321.     {
  322.         msg("Invalid line number -- must be from 1 to %ld", nlines);
  323.         *markptr = MARK_UNSET;
  324.     }
  325.  
  326.     return s;
  327. }
  328.  
  329.  
  330.  
  331. /* This function reads an ex command and executes it. */
  332. void ex()
  333. {
  334.     char        cmdbuf[150];
  335.     REG int        cmdlen;
  336.     static long    oldline;
  337.     int        autoprint;
  338.  
  339.     significant = FALSE;
  340.     oldline = markline(cursor);
  341.  
  342.     while (mode == MODE_EX)
  343.     {
  344.         /* read a line */
  345. #ifdef CRUNCH
  346.         cmdlen = vgets(':', cmdbuf, sizeof(cmdbuf));
  347. #else
  348.         cmdlen = vgets(*o_prompt ? ':' : '\0', cmdbuf, sizeof(cmdbuf));
  349. #endif
  350.         if (cmdlen < 0)
  351.         {
  352.             return;
  353.         }
  354.  
  355.         /* if empty line, assume ".+1" */
  356.         if (cmdlen == 0)
  357.         {
  358.             strcpy(cmdbuf, ".+1");
  359.             qaddch('\r');
  360.             clrtoeol();
  361.         }
  362.         else
  363.         {
  364.             addch('\n');
  365.         }
  366.         refresh();
  367.  
  368.         /* parse & execute the command */
  369.         autoprint = doexcmd(cmdbuf, '\\');
  370.  
  371.         /* handle autoprint */
  372.         if (significant || markline(cursor) != oldline)
  373.         {
  374.             significant = FALSE;
  375.             oldline = markline(cursor);
  376.             if (*o_autoprint && mode == MODE_EX && autoprint)
  377.             {
  378.                 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
  379.             }
  380.         }
  381.     }
  382. }
  383.  
  384.  
  385.  
  386. /* This function executes a single command line.  The '\n' at the end of the
  387.  * line should be replaced by '\0'.  The line may contain multiple commands,
  388.  * separated by '|' characters.  To pass a '|' character as part of an
  389.  * argument to a command, precede the '|' with a quote character.
  390.  *
  391.  * Returns TRUE if the command allows autoprint, FALSE if it doesn't.
  392.  *
  393.  * NOTE: The cmdbuf string may be altered.
  394.  */
  395. int doexcmd(cmdbuf, qchar)
  396.     char        *cmdbuf;    /* string containing an ex command */
  397.     int        qchar;        /* quote character for '|' */
  398. {
  399.     REG char    *scan;        /* used to scan thru cmdbuf */
  400.     MARK        frommark;    /* first linespec */
  401.     MARK        tomark;        /* second linespec */
  402.     REG int        cmdlen;        /* length of the command name given */
  403.     CMD        cmd;        /* what command is this? */
  404.     ARGT        argt;        /* argument types for this command */
  405.     short        forceit;    /* bang version of a command? */
  406.     REG int        cmdidx;        /* index of command */
  407.     REG char    *build;        /* used while copying filenames */
  408.     char        *tmp;
  409.     int        iswild;        /* boolean: filenames use wildcards? */
  410.     int        isdfl;        /* using default line ranges? */
  411.     int        didsub;        /* did we substitute file names for % or # */
  412.     char        *nextcmd;    /* next command in this same string */
  413. #ifndef NO_VISIBLE
  414.     long        chgd;        /* used to detect change to value of "changes" */
  415. #endif
  416.     int        autoprint = FALSE;/* Boolean: allow autoprint? */
  417.  
  418.     /* ex commands can't be undone via the shift-U command */
  419.     U_line = 0L;
  420.  
  421.     /* permit extra colons at the start of the line */
  422.     for (; *cmdbuf == ':'; cmdbuf++)
  423.     {
  424.     }
  425.  
  426.     /* execute all '|'-delimited commands in cmdbuf, one at a time */
  427.     for (nextcmd = (char *)0; cmdbuf; cmdbuf = nextcmd, nextcmd = (char *)0)
  428.     {
  429.         /* parse the line specifier */
  430.         scan = cmdbuf;
  431.         if (nlines < 1)
  432.         {
  433.             /* no file, so don't allow addresses */
  434.         }
  435.         else if (*scan == '%')
  436.         {
  437.             /* '%' means all lines */
  438.             frommark = MARK_FIRST;
  439.             tomark = MARK_LAST;
  440.             scan++;
  441.         }
  442.         else if (*scan == '0')
  443.         {
  444.             scan++;
  445.             frommark = tomark = (*scan ? MARK_UNSET : MARK_FIRST);
  446.         }
  447.         else
  448.         {
  449.             frommark = cursor;
  450.             scan = linespec(scan, &frommark);
  451.             tomark = frommark;
  452.             if (frommark && *scan == ',')
  453.             {
  454.                 scan++;
  455.                 tomark = cursor;
  456.                 scan = linespec(scan, &tomark);
  457.             }
  458.             if (!tomark)
  459.             {
  460.                 /* faulty line spec -- fault already described */
  461.                 return;
  462.             }
  463.             if (frommark > tomark)
  464.             {
  465.                 msg("first address exceeds the second");
  466.                 return;
  467.             }
  468.         }
  469.         isdfl = (scan == cmdbuf);
  470.  
  471.         /* skip whitespace */
  472.         while (isspace(*scan))
  473.         {
  474.             scan++;
  475.         }
  476.  
  477.         /* Figure out how long the command name is.  If no command, then the
  478.          * length is 0, which will match the "print" command.
  479.          */ 
  480.         if (!*scan)
  481.         {
  482.             /* if both endpoints are at the same line, then just
  483.              * move to the start of that line without printing.
  484.              */
  485.             if (frommark == tomark)
  486.             {
  487.                 if (tomark != MARK_UNSET)
  488.                     cursor = tomark;
  489.                 return;
  490.             }
  491.             cmdlen = 0;
  492.         }
  493.         else if (!isalpha(*scan))
  494.         {
  495.             cmdlen = 1;
  496.         }
  497.         else
  498.         {
  499.             for (cmdlen = 1;
  500.                  isalpha(scan[cmdlen]);
  501.                  cmdlen++)
  502.             {
  503.             }
  504.         }
  505.  
  506.         /* Lookup the command code.
  507.          *
  508.          * If the given command name is shorter than the cannonical
  509.          * form of the name, then only compare the given portion in
  510.          * order to allow abbreviations to be recognized.
  511.          *
  512.          * However, if the given command is longer than the
  513.          * cannonical form, then only compare the canonical
  514.          * portion so that arguments can be appended to complete
  515.          * command names without requiring intervening whitespace;
  516.          * this is mostly so that users can type ":ka" when they
  517.          * mean ":k a".
  518.          */
  519.         for (cmdidx = 0;
  520.              cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, MIN(cmdlen, strlen(cmdnames[cmdidx].name)));
  521.              cmdidx++)
  522.         {
  523.         }
  524.         argt = cmdnames[cmdidx].argt;
  525.         cmd = cmdnames[cmdidx].code;
  526.         if (cmd == CMD_NULL)
  527.         {
  528.             msg("Unknown command \"%.*s\"", cmdlen, scan);
  529.             return;
  530.         }
  531.  
  532. #ifndef NO_SAFER
  533.         if ((argt & UNSAFE) && *o_safer)
  534.         {
  535.             msg("command \"%.*s\" is unsafe", cmdlen, scan);
  536.             return;
  537.         }
  538. #endif
  539.  
  540.         /* if the command doesn't have NOBAR set, then replace | with \0 */
  541.         if (!(argt & NOBAR))
  542.         {
  543.             /* find the next unquoted '|'.  For any quoted '|',
  544.              * delete the quote character but leave the '|'.
  545.              */
  546.             for (build = nextcmd = scan; *nextcmd && *nextcmd != '|'; nextcmd++)
  547.             {
  548.                 if (nextcmd[0] == qchar && nextcmd[1] == '|')
  549.                 {
  550.                     nextcmd++;
  551.                 }
  552.                 *build++ = *nextcmd;
  553.             }
  554.  
  555.             /* was a '|' found? */
  556.             if (*nextcmd)
  557.             {
  558.                 /* Yes!  Leave nextcmd pointing to char after '|' */
  559.                 nextcmd++;
  560.             }
  561.             else
  562.             {
  563.                 /* No!  Set nextcmd to NULL so we stop after this */
  564.                 nextcmd = (char *)0;
  565.             }
  566.  
  567.             /* mark the end of this particular command */
  568.             *build = '\0';
  569.         }
  570.  
  571.         /* if the command name ended with a bang, set the forceit flag */
  572.         scan += MIN(cmdlen, strlen(cmdnames[cmdidx].name));
  573.         if ((argt & BANG) && *scan == '!')
  574.         {
  575.             scan++;
  576.             forceit = 1;
  577.         }
  578.         else
  579.         {
  580.             forceit = 0;
  581.         }
  582.  
  583.         /* skip any more whitespace, to leave scan pointing to arguments */
  584.         while (isspace(*scan))
  585.         {
  586.             scan++;
  587.         }
  588.  
  589.         /* For "read" and "write" commands, if a !program is given
  590.          * instead of a filename, then don't complain about whitespace
  591.          * in the command, and don't treat '|' as command separator.
  592.          */
  593.         if ((cmd == CMD_READ || cmd == CMD_WRITE) && *scan == '!')
  594.         {
  595.             argt &= ~NOSPC;
  596.             argt |= NOBAR;
  597.         }
  598.  
  599.         /* a couple of special cases for filenames */
  600.         if (argt & XFILE)
  601.         {
  602.             /* if names were given, process them */
  603.             if (*scan)
  604.             {
  605.                 for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
  606.                 {
  607.                     switch (*scan)
  608.                     {
  609.                       case '\\':
  610.                         if (scan[1] == '\\' || scan[1] == '%' || scan[1] == '#')
  611.                         {
  612.                             *build++ = *++scan;
  613.                         }
  614.                         else if (scan[1] == '@')
  615.                         {
  616.                             tmp = get_cursor_word(cursor);
  617.                             if (!tmp)
  618.                             {
  619.                                 tmp = "@";
  620.                             }
  621.                             strcpy(build, tmp);
  622.                             build += strlen(build);
  623.                             scan++;
  624.                         }
  625.                         else
  626.                         {
  627.                             *build++ = '\\';
  628.                         }
  629.                         break;
  630.  
  631.                       case '%':
  632.                         if (!*origname)
  633.                         {
  634.                             msg("No filename to substitute for %%");
  635.                             return;
  636.                         }
  637.                         strcpy(build, origname);
  638.                         while (*build)
  639.                         {
  640.                             build++;
  641.                         }
  642.                         didsub = TRUE;
  643.                         break;
  644.  
  645.                       case '#':
  646.                         if (!*prevorig)
  647.                         {
  648.                             msg("No filename to substitute for #");
  649.                             return;
  650.                         }
  651.                         strcpy(build, prevorig);
  652.                         while (*build)
  653.                         {
  654.                             build++;
  655.                         }
  656.                         didsub = TRUE;
  657.                         break;
  658.  
  659.                       case '*':
  660.                       case '?':
  661. #if !(MSDOS || TOS)
  662.                       case '[':
  663.                       case '`':
  664.                       case '{': /* } */
  665.                       case '$':
  666.                       case '~':
  667. #endif
  668.                         *build++ = *scan;
  669.                         iswild = TRUE;
  670.                         break;
  671.  
  672.                       default:
  673.                         *build++ = *scan;
  674.                     }
  675.                 }
  676.                 *build = '\0';
  677.  
  678.                 if (cmd == CMD_BANG
  679.                  || cmd == CMD_READ && tmpblk.c[0] == '!'
  680.                  || cmd == CMD_WRITE && tmpblk.c[0] == '!')
  681.                 {
  682. #ifndef NO_SAFER
  683.                     if (*o_safer)
  684.                     {
  685.                         msg("unsafe to run external program");
  686.                         return;
  687.                     }
  688. #endif
  689.                     if (didsub)
  690.                     {
  691.                         if (mode != MODE_EX)
  692.                         {
  693.                             addch('\n');
  694.                         }
  695.                         addstr(tmpblk.c);
  696.                         addch('\n');
  697.                         exrefresh();
  698.                     }
  699.                 }
  700. #ifndef NO_SAFER
  701.                 else if (iswild && *o_safer)
  702.                 {
  703.                     msg("wildcard expansion is unsafe");
  704.                     return;
  705.                 }
  706. #endif
  707.                 else
  708.                 {
  709.                     if (iswild && tmpblk.c[0] != '>')
  710.                     {
  711.                         scan = wildcard(tmpblk.c);
  712.                     }
  713.                 }
  714.             }
  715.             else /* no names given, maybe assume origname */
  716.             {
  717.                 if (!(argt & NODFL))
  718.                 {
  719.                     strcpy(tmpblk.c, origname);
  720.                 }
  721.                 else
  722.                 {
  723.                     *tmpblk.c = '\0';
  724.                 }
  725.             }
  726.  
  727.             scan = tmpblk.c;
  728.         }
  729.  
  730.         /* bad arguments? */
  731.         if (!(argt & EXRCOK) && nlines < 1L)
  732.         {
  733.             msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
  734.             return;
  735.         }
  736.         if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
  737.         {
  738.             msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
  739.             return;
  740.         }
  741.         if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
  742.         {
  743.             msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
  744.             return;
  745.         }
  746.         if (!(argt & TO) && tomark != frommark && nlines >= 1L)
  747.         {
  748.             msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
  749.             return;
  750.         }
  751.         if (!(argt & EXTRA) && *scan)
  752.         {
  753.             msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
  754.             return;
  755.         }
  756.         if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
  757.         {
  758.             build = scan;
  759. #ifndef CRUNCH
  760.             if ((argt & PLUS) && *build == '+')
  761.             {
  762.                 while (*build && !isspace(*build))
  763.                 {
  764.                     build++;
  765.                 }
  766.                 while (*build && isspace(*build))
  767.                 {
  768.                     build++;
  769.                 }
  770.             }
  771. #endif /* not CRUNCH */
  772.             for (; *build; build++)
  773.             {
  774.                 if (isspace(*build))
  775.                 {
  776.                     msg("Too many %s to \"%s\" command.",
  777.                         (argt & XFILE) ? "filenames" : "arguments",
  778.                         cmdnames[cmdidx].name);
  779.                     return;
  780.                 }
  781.             }
  782.         }
  783.  
  784.         /* some commands have special default ranges */
  785.         if (isdfl)
  786.         {
  787. #ifndef NO_VISIBLE
  788.             if (V_from && (argt & RANGE))
  789.             {
  790.                 if (cursor < V_from)
  791.                 {
  792.                     frommark = cursor;
  793.                     tomark = V_from;
  794.                 }
  795.                 else
  796.                 {
  797.                     frommark = V_from;
  798.                     tomark = cursor;
  799.                 }
  800.             }
  801.             else
  802. #endif
  803.             if (argt & DFLALL)
  804.             {
  805.                 frommark = MARK_FIRST;
  806.                 tomark = MARK_LAST;
  807.             }
  808.             else if (argt & DFLNONE)
  809.             {
  810.                 frommark = tomark = 0L;
  811.             }
  812.         }
  813.  
  814.         /* write a newline if called from visual mode */
  815.         if ((argt & NL) && mode != MODE_EX && !exwrote)
  816.         {
  817.             addch('\n');
  818.             exrefresh();
  819.         }
  820.  
  821.         /* act on the command */
  822. #ifndef NO_VISIBLE
  823.         chgd = changes;
  824. #endif
  825.         (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
  826. #ifndef NO_VISIBLE
  827.         /* Commands that change the file could interact with visible
  828.          * line marking in funny ways.  If lines are visibly marked, 
  829.          * and the file has changed, then we'd better not execute any
  830.          * remaining commands, for safety's sake.
  831.          */
  832.         if (V_from && chgd != changes && nextcmd)
  833.         {
  834.             msg("Skipping \"%s\" after visible mark lost", nextcmd);
  835.             break;
  836.         }
  837. #endif
  838.         /* if the command allows autoprint, remember that! */
  839.         if (argt & APRINT)
  840.         {
  841.             autoprint = TRUE;
  842.         }
  843.     }
  844. #ifndef NO_VISIBLE
  845.     V_from = MARK_UNSET;
  846. #endif
  847.     return autoprint;
  848. }
  849.  
  850.  
  851. /* This function executes EX commands from a file.  It returns 1 normally, or
  852.  * 0 if the file could not be opened for reading.
  853.  */
  854. int doexrc(filename)
  855.     char    *filename;    /* name of a ".exrc" file */
  856. {
  857.     int    fd;        /* file descriptor */
  858.     int    len;        /* bytes in the buffer */
  859.     char    *scan;        /* current position in the buffer */
  860.     char    *build;        /* used during buffer shifting */
  861.  
  862.     /* !!! kludge: we use U_text as the buffer.  This has the side-effect
  863.      * of interfering with the shift-U visual command.  Disable shift-U.
  864.      */
  865.     U_line = 0L;
  866.  
  867.     /* open the file */
  868.     fd = open(filename, O_RDONLY);
  869.     if (fd < 0)
  870.     {
  871.         return 0;
  872.     }
  873.  
  874.     /* read the first block from the file */
  875.     len = tread(fd, U_text, sizeof U_text);
  876.  
  877.     /* for each command line in the buffer... */
  878.     while (len > 0)
  879.     {
  880.         /* locate the end of the command. */
  881.         for (scan = U_text; scan < &U_text[len] && *scan != '\n'; scan++)
  882.         {
  883.         }
  884.         if (scan >= &U_text[len])
  885.         {
  886.             /* No newline found!  Stop reading the .exrc */
  887.             break;
  888.         }
  889.  
  890.         /* convert the '\n' to '\0' at the end */
  891.         *scan++ = '\0';
  892.  
  893.         /* Skip leading whitespace.  This is done mostly so that lines
  894.          * which contain ONLY whitespace can be recognized as blank.
  895.          */
  896.         for (build = U_text; *build && isspace(*build); build++)
  897.         {
  898.         }
  899.  
  900.         /* Execute the command line, unless it is blank.  Blank lines
  901.          * in a .exrc file are ignored.
  902.          */
  903.         if (*build)
  904.         {
  905.             doexcmd(build, ctrl('V'));
  906.         }
  907.  
  908.         /* try to shift more of the file into the buffer */
  909.         for (build = U_text; scan < &U_text[len]; )
  910.         {
  911.             *build++ = *scan++;
  912.         }
  913.         len = (int)(build - U_text);
  914.         len += tread(fd, build, sizeof U_text - len);
  915.     }
  916.  
  917.     /* close the file */
  918.     close(fd);
  919.  
  920.     return 1;
  921. }
  922.