home *** CD-ROM | disk | FTP | other *** search
/ BCI NET 2 / BCI NET 2.iso / archives / applications / wp / xvi.lha / Xvi_V1.0_Src / cmdline.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-04  |  26.7 KB  |  1,260 lines

  1. /* Copyright (c) 1990,1991,1992 Chris and John Downey */
  2. #ifndef lint
  3. static char *sccsid = "@(#)cmdline.c    2.2 (Chris & John Downey) 8/6/92";
  4. #endif
  5.  
  6. /***
  7.  
  8. * program name:
  9.     xvi
  10. * function:
  11.     PD version of UNIX "vi" editor, with extensions.
  12. * module name:
  13.     cmdline.c
  14. * module function:
  15.     Command-line handling (i.e. :/? commands) - most
  16.     of the actual command functions are in ex_cmds.c.
  17. * history:
  18.     STEVIE - ST Editor for VI Enthusiasts, Version 3.10
  19.     Originally by Tim Thompson (twitch!tjt)
  20.     Extensive modifications by Tony Andrews    (onecom!wldrdg!tony)
  21.     Heavily modified by Chris & John Downey
  22.     Minor, Amiga specific modifications made by Dan Schmelzer.
  23.  
  24. ***/
  25.  
  26. #include "xvi.h"
  27.  
  28. #ifdef    MEGAMAX
  29. overlay "cmdline"
  30. #endif
  31.  
  32. /*
  33.  * The next two variables contain the bounds of any range
  34.  * given in a command. If no range was given, both will be NULL.
  35.  * If only a single line was given, u_line will be NULL.
  36.  * The a_line variable is used for those commands which take
  37.  * a third line specifier after the command, e.g. "move", "copy".
  38.  */
  39. static    Line    *l_line, *u_line;
  40. static    Line    *a_line;
  41.  
  42. /*
  43.  * Definitions for all ex commands.
  44.  */
  45.  
  46. #define    EX_ENOTFOUND    -1        /* command not found */
  47. #define    EX_EAMBIGUOUS    -2        /* could be more than one */
  48. #define    EX_ECANTFORCE    -3        /* ! given where not appropriate */
  49. #define    EX_EBADARGS    -4        /* inappropriate args given */
  50.  
  51. #define    EX_NOCMD    1
  52. #define    EX_SHCMD    2
  53. #define    EX_UNUSED    3    /* unused */
  54. #define    EX_AMPERSAND    4
  55. #define    EX_EXBUFFER    5
  56. #define    EX_LSHIFT    6
  57. #define    EX_EQUALS    7
  58. #define    EX_RSHIFT    8
  59. #define    EX_COMMENT    9
  60. #define    EX_ABBREVIATE    10
  61. #define    EX_APPEND    11
  62. #define    EX_ARGS        12
  63. #define    EX_BUFFER    13
  64. #define    EX_CHDIR    14
  65. #define    EX_CHANGE    15
  66. #define    EX_CLOSE    16
  67. #define    EX_COMPARE    17
  68. #define    EX_COPY        18
  69. #define    EX_DELETE    19
  70. #define    EX_ECHO        20
  71. #define    EX_EDIT        21
  72. #define    EX_EX        22
  73. #define    EX_FILE        23
  74. #define    EX_GLOBAL    24
  75. #define    EX_HELP        25
  76. #define    EX_INSERT    26
  77. #define    EX_JOIN        27
  78. #define    EX_K        28
  79. #define    EX_LIST        29
  80. #define    EX_MAP        30
  81. #define    EX_MARK        31
  82. #define    EX_MOVE        32
  83. #define    EX_NEXT        33
  84. #define    EX_NUMBER    34
  85. #define    EX_OPEN        35
  86. #define    EX_PRESERVE    36
  87. #define    EX_PRINT    37
  88. #define    EX_PUT        38
  89. #define    EX_QUIT        39
  90. #define    EX_READ        40
  91. #define    EX_RECOVER    41
  92. #define    EX_REWIND    42
  93. #define    EX_SET        43
  94. #define    EX_SHELL    44
  95. #define    EX_SOURCE    45
  96. #define    EX_SPLIT    46
  97. #define    EX_SUSPEND    47
  98. #define    EX_SUBSTITUTE    48
  99. #define    EX_TAG        49
  100. #define    EX_UNABBREV    50
  101. #define    EX_UNDO        51
  102. #define    EX_UNMAP    52
  103. #define    EX_V        53
  104. #define    EX_VERSION    54
  105. #define    EX_VISUAL    55
  106. #define    EX_WN        56
  107. #define    EX_WQ        57
  108. #define    EX_WRITE    58
  109. #define    EX_XIT        59
  110. #define    EX_YANK        60
  111. #define    EX_Z        61
  112. #define    EX_GOTO        62
  113. #define    EX_TILDE    63
  114.  
  115. /*
  116.  * Table of all ex commands, and whether they take an '!'.
  117.  *
  118.  * Note that this table is in strict order, sorted on
  119.  * the ASCII value of the first character of the command.
  120.  *
  121.  * The priority field is necessary to resolve clashes in
  122.  * the first one or two characters; so each group of commands
  123.  * beginning with the same letter should have at least one
  124.  * priority 1, so that there is a sensible default.
  125.  *
  126.  * Commands with argument type ec_rest need no delimiters;
  127.  * they need only be matched. This is really only used for
  128.  * single-character commands like !, " and &.
  129.  */
  130. static    struct    ecmd    {
  131.     char    *ec_name;
  132.     short    ec_command;
  133.     short    ec_priority;
  134.     unsigned    ec_flags;
  135.     /*
  136.      * Flags: EXCLAM means can use !, FILEXP means do filename
  137.      * expansion, INTEXP means do % and # expansion. EXPALL means
  138.      * do INTEXP and FILEXP (they are done in that order).
  139.      *
  140.      * EC_RANGE0 means that the range specifier (if any)
  141.      * may include line 0.
  142.      */
  143. #   define    EC_EXCLAM    0x1
  144. #   define    EC_FILEXP    0x2
  145. #   define    EC_INTEXP    0x4
  146. #   define    EC_EXPALL    EC_FILEXP|EC_INTEXP
  147. #   define    EC_RANGE0    0x8
  148.  
  149.     enum {
  150.     ec_none,        /* no arguments after command */
  151.     ec_strings,        /* whitespace-separated strings */
  152.     ec_1string,        /* like ec_strings but only one */
  153.     ec_line,        /* line number or target argument */
  154.     ec_rest,        /* rest of line passed entirely */
  155.     ec_nonalnum,        /* non-alphanumeric delimiter */
  156.     ec_1lower        /* single lower-case letter */
  157.     }    ec_arg_type;
  158. } cmdtable[] = {
  159. /*  name        command         priority    exclam    */
  160.  
  161.     /*
  162.      * The zero-length string is used for the :linenumber command.
  163.      */
  164.     "",            EX_NOCMD,        1,    EC_RANGE0,        ec_none,
  165.     "!",        EX_SHCMD,        0,    EC_INTEXP,        ec_rest,
  166.  
  167.     "#",        EX_NUMBER,        0,    0,            ec_none,
  168.     "&",        EX_AMPERSAND,   0,    0,            ec_rest,
  169.     "*",        EX_EXBUFFER,    0,    0,            ec_rest,
  170.     "<",        EX_LSHIFT,        0,    0,            ec_none,
  171.     "=",        EX_EQUALS,        0,    0,            ec_none,
  172.     ">",        EX_RSHIFT,        0,    0,            ec_none,
  173.     "@",        EX_EXBUFFER,    0,    0,            ec_rest,
  174.     "\"",        EX_COMMENT,        0,    0,            ec_rest,
  175.  
  176.     "abbreviate",   EX_ABBREVIATE,  0,    0,            ec_strings,
  177.     "append",        EX_APPEND,        1,    0,            ec_none,
  178.     "args",        EX_ARGS,        0,    0,            ec_none,
  179.  
  180.     "buffer",        EX_BUFFER,        0,    EC_EXPALL,        ec_1string,
  181.  
  182.     "cd",        EX_CHDIR,        1,    EC_EXPALL,        ec_1string,
  183.     "change",        EX_CHANGE,        2,    0,            ec_none,
  184.     "chdir",        EX_CHDIR,        1,    EC_EXPALL,        ec_1string,
  185.     "close",        EX_CLOSE,        1,    EC_EXCLAM,        ec_none,
  186.     "compare",        EX_COMPARE,        0,    0,            ec_none,
  187.     "copy",        EX_COPY,        1,    0,            ec_line,
  188.  
  189.     "delete",        EX_DELETE,        0,    0,            ec_none,
  190.  
  191.     "echo",        EX_ECHO,        0,    EC_INTEXP,        ec_strings,
  192.     "edit",        EX_EDIT,        1,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  193.     "ex",        EX_EX,        0,    EC_EXPALL,        ec_1string,
  194.  
  195.     "file",        EX_FILE,        0,    EC_EXPALL,        ec_1string,
  196.  
  197.     "global",        EX_GLOBAL,        0,    EC_EXCLAM,        ec_nonalnum,
  198.  
  199.     "help",        EX_HELP,        0,    0,            ec_none,
  200.  
  201.     "insert",        EX_INSERT,        0,    0,            ec_none,
  202.  
  203.     "join",        EX_JOIN,        0,    0,            ec_none,
  204.  
  205.     "k",        EX_K,        0,    0,            ec_1lower,
  206.  
  207.     "list",        EX_LIST,        0,    0,            ec_none,
  208.  
  209.     "map",        EX_MAP,        0,    EC_EXCLAM,        ec_strings,
  210.     "mark",        EX_MARK,        0,    0,            ec_1lower,
  211.     "move",        EX_MOVE,        1,    0,            ec_line,
  212.  
  213.     "next",        EX_NEXT,        1,    EC_EXCLAM|EC_EXPALL,    ec_strings,
  214.     "number",        EX_NUMBER,        0,    0,            ec_none,
  215.  
  216.     "open",        EX_OPEN,        0,    0,            ec_none,
  217.  
  218.     "preserve",        EX_PRESERVE,    0,    0,            ec_none,
  219.     "print",        EX_PRINT,        1,    0,            ec_none,
  220.     "put",        EX_PUT,        0,    EC_RANGE0,        ec_none,
  221.  
  222.     "quit",        EX_QUIT,        0,    EC_EXCLAM,        ec_none,
  223.  
  224.     "read",        EX_READ,        1,    EC_EXPALL|EC_RANGE0,    ec_1string,
  225.     "recover",        EX_RECOVER,        0,    0,            ec_none,
  226.     "rewind",        EX_REWIND,        0,    EC_EXCLAM,        ec_none,
  227.  
  228.     "set",        EX_SET,        0,    0,            ec_strings,
  229.     "shell",        EX_SHELL,        0,    0,            ec_none,
  230.     "source",        EX_SOURCE,        0,    EC_EXPALL,        ec_1string,
  231.     "split",        EX_SPLIT,        0,    0,            ec_none,
  232.     "stop",        EX_SUSPEND,        0,    0,            ec_none,
  233.     "substitute",   EX_SUBSTITUTE,  1,    0,            ec_nonalnum,
  234.     "suspend",        EX_SUSPEND,        0,    0,            ec_none,
  235.  
  236.     "t",        EX_COPY,        1,    0,            ec_line,
  237.     "tag",        EX_TAG,        0,    EC_EXCLAM,        ec_1string,
  238.  
  239.     "unabbreviate", EX_UNABBREV,    0,    0,            ec_strings,
  240.     "undo",        EX_UNDO,        1,    0,            ec_none,
  241.     "unmap",        EX_UNMAP,        0,    EC_EXCLAM,        ec_strings,
  242.  
  243.     "v",        EX_V,        1,    0,            ec_nonalnum,
  244.     "version",        EX_VERSION,        0,    0,            ec_none,
  245.     "visual",        EX_VISUAL,        0,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  246.  
  247.     "wn",        EX_WN,        0,    EC_EXCLAM,        ec_none,
  248.     "wq",        EX_WQ,        0,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  249.     "write",        EX_WRITE,        1,    EC_EXCLAM|EC_EXPALL,    ec_1string,
  250.  
  251.     "xit",        EX_XIT,        0,    0,            ec_none,
  252.  
  253.     "yank",        EX_YANK,        0,    0,            ec_none,
  254.  
  255.     "z",        EX_Z,        0,    0,            ec_none,
  256.  
  257.     "|",        EX_GOTO,        0,    0,            ec_none,
  258.     "~",        EX_TILDE,        0,    0,            ec_rest,
  259.  
  260.     NULL,        0,            0,    0,            ec_none,
  261. };
  262.  
  263. /*
  264.  * Internal routine declarations.
  265.  */
  266. static    int    decode_command P((char **, bool_t *, struct ecmd **));
  267. static    bool_t    get_line P((char **, Line **));
  268. static    bool_t    get_range P((char **));
  269. static    void    badcmd P((bool_t, char *));
  270. static    char    *show_line P((void));
  271. static    char    *expand_percents P((char *));
  272.  
  273. /*
  274.  * These are used for display mode.
  275.  */
  276. static    Line    *curline;
  277. static    Line    *lastline;
  278. static    bool_t    do_line_numbers;
  279.  
  280. /*
  281.  * Macro to skip over whitespace during command line interpretation.
  282.  */
  283. #define skipblanks(p)    { while (*(p) != '\0' && is_space(*(p))) (p)++; }
  284.  
  285. /*
  286.  * do_colon() - process a ':' command.
  287.  *
  288.  * The cmdline argument points to a complete command line to be processed
  289.  * (this does not include the ':' itself).
  290.  */
  291. void
  292. do_colon(cmdline, interactive)
  293. char    *cmdline;            /* optional command string */
  294. bool_t    interactive;            /* true if reading from tty */
  295. {
  296.     char    *arg;            /* ptr to string arg(s) */
  297.     int        argc = 0;        /* arg count for ec_strings */
  298.     char    **argv = NULL;        /* arg vector for ec_strings */
  299.     bool_t    exclam;            /* true if ! was given */
  300.     int        command;        /* which command it is */
  301.     struct ecmd    *ecp;            /* ptr to command entry */
  302.     unsigned    savecho;        /* previous value of echo */
  303.  
  304.     /*
  305.      * Clear the range variables.
  306.      */
  307.     l_line = NULL;
  308.     u_line = NULL;
  309.  
  310.     /*
  311.      * Parse a range, if present (and update the cmdline pointer).
  312.      */
  313.     if (!get_range(&cmdline)) {
  314.     return;
  315.     }
  316.  
  317.     /*
  318.      * Decode the command.
  319.      */
  320.     skipblanks(cmdline);
  321.     command = decode_command(&cmdline, &exclam, &ecp);
  322.  
  323.     if (command > 0) {
  324.     /*
  325.      * Check that the range specified,
  326.      * if any, is legal for the command.
  327.      */
  328.     if (!(ecp->ec_flags & EC_RANGE0)) {
  329.         if (l_line == curbuf->b_line0 || u_line == curbuf->b_line0) {
  330.         show_error(curwin,
  331.             "Specification of line 0 not allowed");
  332.         return;
  333.         }
  334.     }
  335.  
  336.     switch (ecp->ec_arg_type) {
  337.     case ec_none:
  338.         if (*cmdline != '\0' &&
  339.             (*cmdline != '!' || !(ecp->ec_flags & EC_EXCLAM))) {
  340.         command = EX_EBADARGS;
  341.         }
  342.         break;
  343.  
  344.     case ec_line:
  345.         a_line = NULL;
  346.         skipblanks(cmdline);
  347.         if (!get_line(&cmdline, &a_line) || a_line == NULL) {
  348.         command = EX_EBADARGS;
  349.         }
  350.         break;
  351.  
  352.     case ec_1lower:
  353.         /*
  354.          * One lower-case letter.
  355.          */
  356.         skipblanks(cmdline);
  357.         if (!is_lower(cmdline[0]) || cmdline[1] != '\0') {
  358.         command = EX_EBADARGS;
  359.         } else {
  360.         arg = cmdline;
  361.         }
  362.         break;
  363.  
  364.     case ec_nonalnum:
  365.     case ec_rest:
  366.     case ec_strings:
  367.     case ec_1string:
  368.         arg = cmdline;
  369.         if (ecp->ec_arg_type == ec_strings ||
  370.                     ecp->ec_arg_type == ec_1string) {
  371.         if (*arg == '\0') {
  372.             /*
  373.              * No args.
  374.              */
  375.             arg = NULL;
  376.         } else {
  377.             /*
  378.              * Null-terminate the command and skip
  379.              * whitespace to arg or end of line.
  380.              */
  381.             *arg++ = '\0';
  382.             skipblanks(arg);
  383.  
  384.             /*
  385.              * There was trailing whitespace,
  386.              * but no args.
  387.              */
  388.             if (*arg == '\0') {
  389.             arg = NULL;
  390.             }
  391.         }
  392.         } else if (ecp->ec_arg_type == ec_nonalnum && exclam) {
  393.         /*
  394.          * We don't normally touch the arguments for
  395.          * this type, but we have to null-terminate
  396.          * the '!' at least.
  397.          */
  398.         *arg++ = '\0';
  399.         }
  400.  
  401.         if (arg != NULL) {
  402.         /*
  403.          * Perform expansions on the argument string.
  404.          */
  405.         if (ecp->ec_flags & EC_INTEXP) {
  406.             arg = expand_percents(arg);
  407.         }
  408.         if (ecp->ec_flags & EC_FILEXP) {
  409.             arg = fexpand(arg);
  410.         }
  411.  
  412.         if (ecp->ec_arg_type == ec_strings) {
  413.             makeargv(arg, &argc, &argv, " \t");
  414.         }
  415.         }
  416.     }
  417.     }
  418.  
  419.     savecho = echo;
  420.  
  421.     /*
  422.      * Now do the command.
  423.      */
  424.     switch (command) {
  425.     case EX_SHCMD:
  426.     /*
  427.      * If a line range was specified, this must be a pipe command.
  428.      * Otherwise, it's just a simple shell command.
  429.      */
  430.     if (l_line != NULL) {
  431.         specify_pipe_range(curwin, l_line, u_line);
  432.         do_pipe(curwin, arg);
  433.     } else {
  434.         do_shcmd(curwin, arg);
  435.     }
  436.     break;
  437.  
  438.     case EX_ARGS:
  439.     do_args(curwin);
  440.     break;
  441.  
  442.     case EX_BUFFER:
  443.     if (arg != NULL)
  444.         echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
  445.     (void) do_buffer(curwin, arg);
  446.     move_window_to_cursor(curwin);
  447.     update_window(curwin);
  448.     break;
  449.  
  450.     case EX_CHDIR:
  451.     {
  452.     char    *error;
  453.  
  454.     if ((error = do_chdir(arg)) != NULL) {
  455.         badcmd(interactive, error);
  456.     } else if (interactive) {
  457.         char    *dirp;
  458.  
  459.         if ((dirp = alloc(MAXPATHLEN + 2)) != NULL &&
  460.         getcwd(dirp, MAXPATHLEN + 2) != NULL) {
  461.         show_message(curwin, "%s", dirp);
  462.         }
  463.         if (dirp) {
  464.         free(dirp);
  465.         }
  466.     }
  467.     break;
  468.     }
  469.  
  470.     case EX_CLOSE:
  471.     do_close_window(curwin, exclam);
  472.     break;
  473.  
  474.     case EX_COMMENT:        /* This one is easy ... */
  475.     break;
  476.  
  477.     case EX_COMPARE:
  478.     do_compare();
  479.     break;
  480.  
  481.     case EX_COPY:
  482.     do_cdmy('c', l_line, u_line, a_line);
  483.     break;
  484.  
  485.     case EX_DELETE:
  486.     do_cdmy('d', l_line, u_line, (Line *) NULL);
  487.     break;
  488.  
  489.     case EX_ECHO:            /* echo arguments on command line */
  490.     {
  491.     int    i;
  492.  
  493.     flexclear(&curwin->w_statusline);
  494.     for (i = 0; i < argc; i++) {
  495.         if (!lformat(&curwin->w_statusline, "%s ", argv[i])
  496.             || flexlen(&curwin->w_statusline) >= curwin->w_ncols) {
  497.         break;
  498.         }
  499.     }
  500.     update_sline(curwin);
  501.     break;
  502.     }
  503.  
  504.     case EX_EDIT:
  505.     case EX_VISUAL:            /* treat :vi as :edit */
  506.     echo &= ~(e_SCROLL | e_REPORT | e_SHOWINFO);
  507.     (void) do_edit(curwin, exclam, arg);
  508.     move_window_to_cursor(curwin);
  509.     update_buffer(curbuf);
  510. #if 0
  511.     show_file_info(curwin);
  512. #endif
  513.     break;
  514.  
  515.     case EX_FILE:
  516.     if (arg != NULL) {
  517.         if (curbuf->b_filename != NULL)
  518.         free(curbuf->b_filename);
  519.         curbuf->b_filename = strsave(arg);
  520.         if (curbuf->b_tempfname != NULL) {
  521.         free(curbuf->b_tempfname);
  522.         curbuf->b_tempfname = NULL;
  523.         }
  524.     }
  525.     show_file_info(curwin);
  526.     break;
  527.  
  528.     case EX_GLOBAL:
  529.     do_global(curwin, l_line, u_line, arg, !exclam);
  530.     break;
  531.  
  532.     case EX_HELP:
  533.     do_help(curwin);
  534.     break;
  535.  
  536.     case EX_MAP:
  537.     xvi_map(argc, argv, exclam, interactive);
  538.     break;
  539.  
  540.     case EX_UNMAP:
  541.     xvi_unmap(argc, argv, exclam, interactive);
  542.     break;
  543.  
  544.     case EX_MARK:
  545.     case EX_K:
  546.     {
  547.     Posn    pos;
  548.  
  549.     pos.p_index = 0;
  550.     if (l_line == NULL) {
  551.         pos.p_line = curwin->w_cursor->p_line;
  552.     } else {
  553.         pos.p_line = l_line;
  554.     }
  555.     (void) setmark(arg[0], curbuf, &pos);
  556.     break;
  557.     }
  558.  
  559.     case EX_MOVE:
  560.     do_cdmy('m', l_line, u_line, a_line);
  561.     break;
  562.  
  563.     case EX_NEXT:
  564.     /*
  565.      * do_next() handles turning off the appropriate bits
  566.      * in echo, & also calls move_window_to_cursor() &
  567.      * update_buffer() as required, so we don't have to
  568.      * do any of that here.
  569.      */
  570.     do_next(curwin, argc, argv, exclam);
  571.     break;
  572.  
  573.     case EX_PRESERVE:
  574.     if (do_preserve())
  575.         show_file_info(curwin);
  576.     break;
  577.  
  578.     case EX_LIST:
  579.     case EX_PRINT:
  580.     case EX_NUMBER:
  581.     if (l_line == NULL) {
  582.         curline = curwin->w_cursor->p_line;
  583.         lastline = curline->l_next;
  584.     } else if (u_line ==  NULL) {
  585.         curline = l_line;
  586.         lastline = l_line->l_next;
  587.     } else {
  588.         curline = l_line;
  589.         lastline = u_line->l_next;
  590.     }
  591.     do_line_numbers = (Pb(P_number) || command == EX_NUMBER);
  592.     disp_init(curwin, show_line, (int) curwin->w_ncols,
  593.                 command == EX_LIST || Pb(P_list));
  594.     break;
  595.  
  596.     case EX_PUT:
  597.     {
  598.     Posn    where;
  599.  
  600.     if (l_line != NULL) {
  601.         where.p_index = 0;
  602.         where.p_line = l_line;
  603.     } else {
  604.         where.p_index = curwin->w_cursor->p_index;
  605.         where.p_line = curwin->w_cursor->p_line;
  606.     }
  607.     do_put(curwin, &where, FORWARD, '@');
  608.     break;
  609.     }
  610.  
  611.     case EX_QUIT:
  612.     do_quit(curwin, exclam);
  613.     break;
  614.  
  615.     case EX_REWIND:
  616.     do_rewind(curwin, exclam);
  617.     break;
  618.  
  619.     case EX_READ:
  620.     if (arg == NULL) {
  621.         badcmd(interactive, "Unrecognized command");
  622.         break;
  623.     }
  624.     do_read(curwin, arg, (l_line != NULL) ? l_line :
  625.                         curwin->w_cursor->p_line);
  626.     break;
  627.  
  628.     case EX_SET:
  629.     do_set(curwin, argc, argv, interactive);
  630.     break;
  631.  
  632.     case EX_SHELL:
  633.     do_shell(curwin);
  634.     break;
  635.  
  636.     case EX_SOURCE:
  637.     if (arg == NULL) {
  638.         badcmd(interactive, "Missing filename");
  639.     } else if (do_source(interactive, arg) && interactive) {
  640.         show_file_info(curwin);
  641.     }
  642.     break;
  643.  
  644.     case EX_SPLIT:
  645.     /*
  646.      * "split".
  647.      */
  648.     do_split_window(curwin);
  649.     break;
  650.  
  651.     case EX_SUBSTITUTE:
  652.     case EX_AMPERSAND:
  653.     case EX_TILDE:
  654.     {
  655.     long        nsubs;
  656.     register long    (*func) P((Xviwin *, Line *, Line *, char *));
  657.  
  658.     switch (command) {
  659.     case EX_SUBSTITUTE:
  660.         func = do_substitute;
  661.         break;
  662.     case EX_AMPERSAND:
  663.         func = do_ampersand;
  664.         break;
  665.     case EX_TILDE:
  666.         func = do_tilde;
  667.     }
  668.  
  669.     nsubs = (*func)(curwin, l_line, u_line, arg);
  670.     update_buffer(curbuf);
  671.     cursupdate(curwin);
  672.     begin_line(curwin, TRUE);
  673.     if (nsubs >= Pn(P_report)) {
  674.         show_message(curwin, "%ld substitution%c",
  675.             nsubs,
  676.             (nsubs > 1) ? 's' : ' ');
  677.     }
  678.     break;
  679.     }
  680.  
  681.     case EX_SUSPEND:
  682.     do_suspend(curwin);
  683.     break;
  684.  
  685.     case EX_TAG:
  686.     (void) do_tag(curwin, arg, exclam, TRUE, TRUE);
  687.     break;
  688.  
  689.     case EX_V:
  690.     do_global(curwin, l_line, u_line, arg, FALSE);
  691.     break;
  692.  
  693.     case EX_VERSION:
  694.     show_message(curwin, Version);
  695. #ifdef AMIGA
  696.         amiga_version();
  697. #endif
  698.     break;
  699.  
  700.     case EX_WN:
  701.     /*
  702.      * This is not a standard "vi" command, but it
  703.      * is provided in PC vi, and it's quite useful.
  704.      */
  705.     if (do_write(curwin, (char *) NULL, (Line *) NULL, (Line *) NULL,
  706.                                 exclam)) {
  707.         /*
  708.          * See comment for EX_NEXT (above).
  709.          */
  710.         do_next(curwin, 0, argv, exclam);
  711. #if 0
  712.         move_window_to_cursor(curwin);
  713.         update_buffer(curbuf);
  714. #endif
  715.     }
  716.     break;
  717.  
  718.     case EX_WQ:
  719.     do_wq(curwin, arg, exclam);
  720.     break;
  721.  
  722.     case EX_WRITE:
  723.     (void) do_write(curwin, arg, l_line, u_line, exclam);
  724.     break;
  725.  
  726.     case EX_XIT:
  727.     do_xit(curwin);
  728.     break;
  729.  
  730.     case EX_YANK:
  731.     do_cdmy('y', l_line, u_line, (Line *) NULL);
  732.     break;
  733.  
  734.     case EX_EXBUFFER:
  735.     yp_stuff_input(curwin, arg[0], FALSE);
  736.     break;
  737.  
  738.     case EX_EQUALS:
  739.     do_equals(curwin, l_line);
  740.     break;
  741.  
  742.     case EX_LSHIFT:
  743.     case EX_RSHIFT:
  744.     if (l_line == NULL) {
  745.         l_line = curwin->w_cursor->p_line;
  746.     }
  747.     if (u_line == NULL) {
  748.         u_line = l_line;
  749.     }
  750.     tabinout((command == EX_LSHIFT) ? '<' : '>', l_line, u_line);
  751.     begin_line(curwin, TRUE);
  752.     update_buffer(curbuf);
  753.     break;
  754.  
  755.     case EX_NOCMD:
  756.     /*
  757.      * If we got a line, but no command, then go to the line.
  758.      */
  759.     if (l_line != NULL) {
  760.         if (l_line == curbuf->b_line0) {
  761.         l_line = l_line->l_next;
  762.         }
  763.         move_cursor(curwin, l_line, 0);
  764.         begin_line(curwin, TRUE);
  765.     }
  766.     break;
  767.  
  768.     case EX_ENOTFOUND:
  769.     badcmd(interactive, "Unrecognized command");
  770.     break;
  771.  
  772.     case EX_EAMBIGUOUS:
  773.     badcmd(interactive, "Ambiguous command");
  774.     break;
  775.  
  776.     case EX_ECANTFORCE:
  777.     badcmd(interactive, "Inappropriate use of !");
  778.     break;
  779.  
  780.     case EX_EBADARGS:
  781.     badcmd(interactive, "Inappropriate arguments given");
  782.     break;
  783.  
  784.     default:
  785.     /*
  786.      * Decoded successfully, but unknown to us. Whoops!
  787.      */
  788.     badcmd(interactive, "Internal error - unimplemented command.");
  789.     break;
  790.  
  791.     case EX_ABBREVIATE:
  792.     case EX_APPEND:
  793.     case EX_CHANGE:
  794.     case EX_EX:
  795.     case EX_GOTO:
  796.     case EX_INSERT:
  797.     case EX_JOIN:
  798.     case EX_OPEN:
  799.     case EX_RECOVER:
  800.     case EX_UNABBREV:
  801.     case EX_UNDO:
  802.     case EX_Z:
  803.     badcmd(interactive, "Unimplemented command.");
  804.     break;
  805.     }
  806.  
  807.     echo = savecho;
  808.  
  809.     if (argc > 0 && argv != NULL) {
  810.     free((char *) argv);
  811.     }
  812. }
  813.  
  814. /*
  815.  * Find the correct command for the given string, and return it.
  816.  *
  817.  * Updates string pointer to point to 1st char after command.
  818.  *
  819.  * Updates boolean pointed to by forcep according
  820.  * to whether an '!' was given after the command;
  821.  * if an '!' is given for a command which can't take it,
  822.  * this is an error, and EX_ECANTFORCE is returned.
  823.  * For unknown commands, EX_ENOTFOUND is returned.
  824.  * For ambiguous commands, EX_EAMBIGUOUS is returned.
  825.  *
  826.  * Also updates *cmdp to point at entry in command table.
  827.  */
  828. static int
  829. decode_command(str, forcep, cmdp)
  830. char        **str;
  831. bool_t        *forcep;
  832. struct    ecmd    **cmdp;
  833. {
  834.     struct ecmd    *cmdptr;
  835.     struct ecmd    *last_match = NULL;
  836.     bool_t    last_exclam = FALSE;
  837.     int        nmatches = 0;
  838.     char    *last_delimiter = *str;
  839.  
  840.     for (cmdptr = cmdtable; cmdptr->ec_name != NULL; cmdptr++) {
  841.     char    *name = cmdptr->ec_name;
  842.     char    *strp;
  843.     bool_t    matched;
  844.  
  845.     strp = *str;
  846.  
  847.     while (*strp == *name && *strp != '\0') {
  848.         strp++;
  849.         name++;
  850.     }
  851.  
  852.     matched = (
  853.         /*
  854.          * we've got a full match, or ...
  855.          */
  856.         *strp == '\0'
  857.         ||
  858.         /*
  859.          * ... a whitespace delimiter, or ...
  860.          */
  861.         is_space(*strp)
  862.         ||
  863.         (
  864.         /*
  865.          * ... at least one character has been
  866.          * matched, and ...
  867.          */
  868.         name > cmdptr->ec_name
  869.         &&
  870.         (
  871.             (
  872.             /*
  873.              * ... this command can accept a
  874.              * '!' qualifier, and we've found
  875.              * one ...
  876.              */
  877.             (cmdptr->ec_flags & EC_EXCLAM)
  878.             &&
  879.             *strp == '!'
  880.             )
  881.             ||
  882.             (
  883.             /*
  884.              * ... or it can take a
  885.              * non-alphanumeric delimiter
  886.              * (like '/') ...
  887.              */
  888.             cmdptr->ec_arg_type == ec_nonalnum
  889.             &&
  890.             !is_alnum(*strp)
  891.             )
  892.             ||
  893.             (
  894.             /*
  895.              * ... or it doesn't need any
  896.              * delimiter ...
  897.              */
  898.             cmdptr->ec_arg_type == ec_rest
  899.             )
  900.             ||
  901.             (
  902.             /*
  903.              * ... or we've got a full match,
  904.              * & the command expects a single
  905.              * lower-case letter as an
  906.              * argument, and we've got one ...
  907.              */
  908.             cmdptr->ec_arg_type == ec_1lower
  909.             &&
  910.             *name == '\0'
  911.             &&
  912.             is_lower(*strp)
  913.             )
  914.             ||
  915.             (
  916.             /*
  917.              * ... or we've got a partial match,
  918.              * and the command expects a line
  919.              * specifier as an argument, and the
  920.              * next character is not alphabetic.
  921.              */
  922.             cmdptr->ec_arg_type == ec_line
  923.             &&
  924.             !is_alpha(*strp)
  925.             )
  926.         )
  927.         )
  928.     );
  929.  
  930.     if (!matched)
  931.         continue;
  932.  
  933.     if (last_match == NULL ||
  934.         (last_match != NULL &&
  935.          last_match->ec_priority < cmdptr->ec_priority)) {
  936.         /*
  937.          * No previous match, or this one is better.
  938.          */
  939.         last_match = cmdptr;
  940.         last_exclam = (*strp == '!');
  941.         last_delimiter = strp;
  942.         nmatches = 1;
  943.  
  944.         /*
  945.          * If this is a complete match, we don't
  946.          * need to search the rest of the table.
  947.          */
  948.         if (*name == '\0')
  949.         break;
  950.  
  951.     } else if (last_match != NULL &&
  952.            last_match->ec_priority == cmdptr->ec_priority) {
  953.         /*
  954.          * Another match with the same priority - remember it.
  955.          */
  956.         nmatches++;
  957.     }
  958.     }
  959.  
  960.     /*
  961.      * For us to have found a good match, there must have been
  962.      * exactly one match at a certain priority, and if an '!'
  963.      * was used, it must be allowed by that match.
  964.      */
  965.     if (last_match == NULL) {
  966.     return(EX_ENOTFOUND);
  967.     } else if (nmatches != 1) {
  968.     return(EX_EAMBIGUOUS);
  969.     } else if (last_exclam && ! (last_match->ec_flags & EC_EXCLAM)) {
  970.     return(EX_ECANTFORCE);
  971.     } else {
  972.     *forcep = last_exclam;
  973.     *str = last_delimiter;
  974.     *cmdp = last_match;
  975.     return(last_match->ec_command);
  976.     }
  977. }
  978.  
  979. /*
  980.  * get_range - parse a range specifier
  981.  *
  982.  * Ranges are of the form
  983.  *
  984.  * ADDRESS1,ADDRESS2
  985.  * ADDRESS        (equivalent to "ADDRESS,ADDRESS")
  986.  * ADDRESS,        (equivalent to "ADDRESS,.")
  987.  * ,ADDRESS        (equivalent to ".,ADDRESS")
  988.  * ,            (equivalent to ".")
  989.  * %            (equivalent to "1,$")
  990.  *
  991.  * where ADDRESS is
  992.  *
  993.  * /regular expression/ [INCREMENT]
  994.  * ?regular expression? [INCREMENT]
  995.  * $   [INCREMENT]
  996.  * 'x  [INCREMENT]    (where x denotes a currently defined mark)
  997.  * .   [INCREMENT]
  998.  * INCREMENT        (equivalent to . INCREMENT)
  999.  * number [INCREMENT]
  1000.  *
  1001.  * and INCREMENT is
  1002.  *
  1003.  * + number
  1004.  * - number
  1005.  * +            (equivalent to +1)
  1006.  * -            (equivalent to -1)
  1007.  * ++            (equivalent to +2)
  1008.  * --            (equivalent to -2)
  1009.  *
  1010.  * etc.
  1011.  *
  1012.  * The pointer *cpp is updated to point to the first character following
  1013.  * the range spec. If an initial address is found, but no second, the
  1014.  * upper bound is equal to the lower, except if it is followed by ','.
  1015.  *
  1016.  * Return FALSE if an error occurred, otherwise TRUE.
  1017.  */
  1018. static bool_t
  1019. get_range(cpp)
  1020. register char    **cpp;
  1021. {
  1022.     register char    *cp;
  1023.     char        sepchar;
  1024.  
  1025. #define skipp    { cp = *cpp; skipblanks(cp); *cpp = cp; }
  1026.  
  1027.     skipp;
  1028.  
  1029.     if (*cp == '%') {
  1030.     /*
  1031.      * "%" is the same as "1,$".
  1032.      */
  1033.     l_line = curbuf->b_file;
  1034.     u_line = curbuf->b_lastline->l_prev;
  1035.     ++*cpp;
  1036.     return TRUE;
  1037.     }
  1038.  
  1039.     if (!get_line(cpp, &l_line)) {
  1040.     return FALSE;
  1041.     }
  1042.  
  1043.     skipp;
  1044.  
  1045.     sepchar = *cp;
  1046.     if (sepchar != ',' && sepchar != ';') {
  1047.     u_line = l_line;
  1048.     return TRUE;
  1049.     }
  1050.  
  1051.     ++*cpp;
  1052.  
  1053.     if (l_line == NULL) {
  1054.     /*
  1055.      * So far, we've got ",".
  1056.      *
  1057.      * ",address" is equivalent to ".,address".
  1058.      */
  1059.     l_line = curwin->w_cursor->p_line;
  1060.     }
  1061.  
  1062.     if (sepchar == ';') {
  1063.     move_cursor(curwin, (l_line != curbuf->b_line0) ?
  1064.             l_line : l_line->l_next, 0);
  1065.     }
  1066.  
  1067.     skipp;
  1068.     if (!get_line(cpp, &u_line)) {
  1069.     return FALSE;
  1070.     }
  1071.     if (u_line == NULL) {
  1072.     /*
  1073.      * "address," is equivalent to "address,.".
  1074.      */
  1075.     u_line = curwin->w_cursor->p_line;
  1076.     }
  1077.  
  1078.     /*
  1079.      * Check for reverse ordering.
  1080.      */
  1081.     if (earlier(u_line, l_line)) {
  1082.     Line    *tmp;
  1083.  
  1084.     tmp = l_line;
  1085.     l_line = u_line;
  1086.     u_line = tmp;
  1087.     }
  1088.  
  1089.     skipp;
  1090.     return TRUE;
  1091. }
  1092.  
  1093. static bool_t
  1094. get_line(cpp, lpp)
  1095.     char        **cpp;
  1096.     Line        **lpp;
  1097. {
  1098.     Line        *pos;
  1099.     char        *cp;
  1100.     char        c;
  1101.     long        lnum;
  1102.  
  1103.     cp = *cpp;
  1104.  
  1105.     /*
  1106.      * Determine the basic form, if present.
  1107.      */
  1108.     switch (c = *cp++) {
  1109.  
  1110.     case '/':
  1111.     case '?':
  1112.     pos = linesearch(curwin, (c == '/') ? FORWARD : BACKWARD,
  1113.                     &cp);
  1114.     if (pos == NULL) {
  1115.         return FALSE;
  1116.     }
  1117.     break;
  1118.  
  1119.     case '$':
  1120.     pos = curbuf->b_lastline->l_prev;
  1121.     break;
  1122.  
  1123.     /*
  1124.      * "+n" is equivalent to ".+n".
  1125.      */
  1126.     case '+':
  1127.     case '-':
  1128.     cp--;
  1129.      /* fall through ... */
  1130.     case '.':
  1131.     pos = curwin->w_cursor->p_line;
  1132.     break;
  1133.  
  1134.     case '\'': {
  1135.     Posn        *lp;
  1136.  
  1137.     lp = getmark(*cp++, curbuf);
  1138.     if (lp == NULL) {
  1139.         show_error(curwin, "Unknown mark");
  1140.         return FALSE;
  1141.     }
  1142.     pos = lp->p_line;
  1143.     break;
  1144.     }
  1145.     case '0': case '1': case '2': case '3': case '4':
  1146.     case '5': case '6': case '7': case '8': case '9':
  1147.     for (lnum = c - '0'; is_digit(*cp); cp++)
  1148.         lnum = lnum * 10 + *cp - '0';
  1149.  
  1150.     if (lnum == 0) {
  1151.         pos = curbuf->b_line0;
  1152.     } else {
  1153.         pos = gotoline(curbuf, lnum);
  1154.     }
  1155.     break;
  1156.     
  1157.     default:
  1158.     return TRUE;
  1159.     }
  1160.  
  1161.     skipblanks(cp);
  1162.  
  1163.     if (*cp == '-' || *cp == '+') {
  1164.     char    dirchar = *cp++;
  1165.  
  1166.     skipblanks(cp);
  1167.     if (is_digit(*cp)) {
  1168.         for (lnum = 0; is_digit(*cp); cp++) {
  1169.         lnum = lnum * 10 + *cp - '0';
  1170.         }
  1171.     } else {
  1172.         for (lnum = 1; *cp == dirchar; cp++) {
  1173.         lnum++;
  1174.         }
  1175.     }
  1176.  
  1177.     if (dirchar == '-')
  1178.         lnum = -lnum;
  1179.  
  1180.     pos = gotoline(curbuf, lineno(curbuf, pos) + lnum);
  1181.     }
  1182.  
  1183.     *cpp = cp;
  1184.     *lpp = pos;
  1185.     return TRUE;
  1186. }
  1187.  
  1188. /*
  1189.  * This routine is called for ambiguous, unknown,
  1190.  * badly defined or unimplemented commands.
  1191.  */
  1192. static void
  1193. badcmd(interactive, str)
  1194. bool_t    interactive;
  1195. char    *str;
  1196. {
  1197.     if (interactive) {
  1198.     show_error(curwin, str);
  1199.     }
  1200. }
  1201.  
  1202. static char *
  1203. show_line()
  1204. {
  1205.     Line    *lp;
  1206.  
  1207.     if (curline == lastline) {
  1208.     return(NULL);
  1209.     }
  1210.  
  1211.     lp = curline;
  1212.     curline = curline->l_next;
  1213.  
  1214.     if (do_line_numbers) {
  1215.     static Flexbuf    nu_line;
  1216.  
  1217.     flexclear(&nu_line);
  1218.     (void) lformat(&nu_line, NUM_FMT, lineno(curbuf, lp));
  1219.     (void) lformat(&nu_line, "%s", lp->l_text);
  1220.     return flexgetstr(&nu_line);
  1221.     } else {
  1222.     return(lp->l_text);
  1223.     }
  1224. }
  1225.  
  1226. static char *
  1227. expand_percents(str)
  1228. char    *str;
  1229. {
  1230.     static Flexbuf    newstr;
  1231.     register char    *from;
  1232.     register bool_t    escaped;
  1233.  
  1234.     if (strpbrk(str, "%#") == (char *) NULL) {
  1235.     return str;
  1236.     }
  1237.     flexclear(&newstr);
  1238.     escaped = FALSE;
  1239.     for (from = str; *from != '\0'; from++) {
  1240.     if (!escaped) {
  1241.         if (*from == '%' && curbuf->b_filename != NULL) {
  1242.         (void) lformat(&newstr, "%s", curbuf->b_filename);
  1243.         } else if (*from == '#' && altfilename != NULL) {
  1244.         (void) lformat(&newstr, "%s", altfilename);
  1245.         } else if (*from == '\\') {
  1246.         escaped = TRUE;
  1247.         } else {
  1248.         (void) flexaddch(&newstr, *from);
  1249.         }
  1250.     } else {
  1251.         if (*from != '%' && *from != '#') {
  1252.         (void) flexaddch(&newstr, '\\');
  1253.         }
  1254.         (void) flexaddch(&newstr, *from);
  1255.         escaped = FALSE;
  1256.     }
  1257.     }
  1258.     return flexgetstr(&newstr);
  1259. }
  1260.