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

  1. /* cmd2.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 some of the commands - mostly ones that change text */
  12.  
  13. #ifdef AIX
  14. # define _XOPEN_SOURCE
  15. # include <sys/mode.h>
  16. # include <sys/stat.h>
  17. # undef _XOPEN_SOURCE
  18. #endif
  19. #include "config.h"
  20. #include "ctype.h"
  21. #include "vi.h"
  22. #include "regexp.h"
  23. #if TOS
  24. # include <stat.h>
  25. #else
  26. # if OSK
  27. #  include "osk.h"
  28. # else
  29. #  if AMIGA
  30. #   include "amistat.h"
  31. #  else
  32. #   include <sys/stat.h>
  33. #  endif
  34. # endif
  35. #endif
  36.  
  37.  
  38. /*ARGSUSED*/
  39. void cmd_substitute(frommark, tomark, cmd, bang, extra)
  40.     MARK    frommark;
  41.     MARK    tomark;
  42.     CMD    cmd;
  43.     int    bang;
  44.     char    *extra;    /* rest of the command line */
  45. {
  46.     char    *line;    /* a line from the file */
  47.     regexp    *re;    /* the compiled search expression */
  48.     char    *subst;    /* the substitution string */
  49.     char    *opt;    /* substitution options */
  50.     long    l;    /* a line number */
  51.     char    *s, *d;    /* used during subtitutions */
  52.     char    *conf;    /* used during confirmation */
  53.     long    chline;    /* # of lines changed */
  54.     long    chsub;    /* # of substitutions made */
  55.     static    optp;    /* boolean option: print when done? */
  56.     static    optg;    /* boolean option: substitute globally in line? */
  57.     static    optc;    /* boolean option: confirm before subst? */
  58. #ifndef CRUNCH
  59.     long    oldnlines;
  60. #endif
  61.  
  62.  
  63.     /* for now, assume this will fail */
  64.     rptlines = -1L;
  65.  
  66.     /* ":s" is equivalent to ":&". */
  67.     if (*extra == '\0')
  68.         cmd = CMD_SUBAGAIN;
  69.  
  70.     if (cmd == CMD_SUBAGAIN)
  71.     {
  72. #ifndef NO_MAGIC
  73.         if (*o_magic)
  74.             subst = "~";
  75.         else
  76. #endif
  77.         subst = "\\~";
  78.         re = regcomp("");
  79.  
  80.         /* if visual "&", then turn off the "p" and "c" options */
  81.         if (bang)
  82.         {
  83.             optp = optc = FALSE;
  84.         }
  85.     }
  86.     else /* CMD_SUBSTITUTE */
  87.     {
  88.         /* make sure we got a search pattern */
  89.         if (!*extra)
  90.         {
  91.             msg("Usage: s/regular expression/new text/");
  92.             return;
  93.         }
  94.  
  95.         /* parse & compile the search pattern */
  96.         subst = parseptrn(extra);
  97.         re = regcomp(extra + 1);
  98.     }
  99.  
  100.     /* abort if RE error -- error message already given by regcomp() */
  101.     if (!re)
  102.     {
  103.         return;
  104.     }
  105.  
  106.     if (cmd == CMD_SUBSTITUTE)
  107.     {
  108.         /* parse the substitution string & find the option string */
  109.         for (opt = subst; *opt && *opt != *extra; opt++)
  110.         {
  111.             if (*opt == '\\' && opt[1])
  112.             {
  113.                 opt++;
  114.             }
  115.         }
  116.         if (*opt)
  117.         {
  118.             *opt++ = '\0';
  119.         }
  120.  
  121.         /* analyse the option string */
  122.         if (!*o_edcompatible)
  123.         {
  124.             optp = optg = optc = FALSE;
  125.         }
  126.         while (*opt)
  127.         {
  128.             switch (*opt++)
  129.             {
  130.               case 'p':    optp = !optp;    break;
  131.               case 'g':    optg = !optg;    break;
  132.               case 'c':    optc = !optc;    break;
  133.               case ' ':
  134.               case '\t':            break;
  135.               default:
  136.                 msg("Subst options are p, c, and g -- not %c", opt[-1]);
  137.                 return;
  138.             }
  139.         }
  140.     }
  141.  
  142.     /* if "c" or "p" flag was given, and we're in visual mode, then NEWLINE */
  143.     if ((optc || optp) && mode == MODE_VI)
  144.     {
  145.         addch('\n');
  146.         exrefresh();
  147.     }
  148.  
  149.     ChangeText
  150.     {
  151.         /* reset the change counters */
  152.         chline = chsub = 0L;
  153.  
  154.         /* for each selected line */
  155.         for (l = markline(frommark); l <= markline(tomark); l++)
  156.         {
  157.             /* fetch the line */
  158.             line = fetchline(l);
  159.  
  160.             /* if it contains the search pattern... */
  161.             if (regexec(re, line, TRUE))
  162.             {
  163.                 /* increment the line change counter */
  164.                 chline++;
  165.  
  166.                 /* initialize the pointers */
  167.                 s = line;
  168.                 d = tmpblk.c;
  169.  
  170.                 /* do once or globally ... */
  171.                 do
  172.                 {
  173. #ifndef CRUNCH
  174.                     /* confirm, if necessary */
  175.                     if (optc)
  176.                     {
  177.                         for (conf = line; conf < re->startp[0]; conf++)
  178.                             addch(*conf);
  179.                         standout();
  180.                         for ( ; conf < re->endp[0]; conf++)
  181.                             addch(*conf);
  182.                         standend();
  183.                         for (; *conf; conf++)
  184.                             addch(*conf);
  185.                         addch('\n');
  186.                         exrefresh();
  187.                         if (getkey(0) != 'y')
  188.                         {
  189.                             /* copy accross the original chars */
  190.                             while (s < re->endp[0])
  191.                                 *d++ = *s++;
  192.  
  193.                             /* skip to next match on this line, if any */
  194.                             goto Continue;
  195.                         }
  196.                     }
  197. #endif /* not CRUNCH */
  198.  
  199.                     /* increment the substitution change counter */
  200.                     chsub++;
  201.  
  202.                     /* copy stuff from before the match */
  203.                     while (s < re->startp[0])
  204.                     {
  205.                         *d++ = *s++;
  206.                     }
  207.  
  208.                     /* substitute for the matched part */
  209.                     regsub(re, subst, d);
  210.                     s = re->endp[0];
  211.                     d += strlen(d);
  212.  
  213. Continue:
  214.                     /* if this regexp could conceivably match
  215.                      * a zero-length string, then require at
  216.                      * least 1 unmatched character between
  217.                      * matches.
  218.                      */
  219.                     if (re->minlen == 0)
  220.                     {
  221.                         if (!*s)
  222.                             break;
  223.                         *d++ = *s++;
  224.                     }
  225.  
  226.                 } while (optg && regexec(re, s, FALSE));
  227.  
  228.                 /* copy stuff from after the match */
  229.                 while (*d++ = *s++)    /* yes, ASSIGNMENT! */
  230.                 {
  231.                 }
  232.  
  233. #ifndef CRUNCH
  234.                 /* NOTE: since the substitution text is allowed to have ^Ms which are
  235.                  * translated into newlines, it is possible that the number of lines
  236.                  * in the file will increase after each line has been substituted.
  237.                  * we need to adjust for this.
  238.                  */
  239.                 oldnlines = nlines;
  240. #endif
  241.  
  242.                 /* replace the old version of the line with the new */
  243.                 d[-1] = '\n';
  244.                 d[0] = '\0';
  245.                 change(MARK_AT_LINE(l), MARK_AT_LINE(l + 1), tmpblk.c);
  246.  
  247. #ifndef CRUNCH
  248.                 l += nlines - oldnlines;
  249.                 tomark += MARK_AT_LINE(nlines - oldnlines);
  250. #endif
  251.  
  252.                 /* if supposed to print it, do so */
  253.                 if (optp)
  254.                 {
  255.                     addstr(tmpblk.c);
  256.                     exrefresh();
  257.                 }
  258.  
  259.                 /* move the cursor to that line */
  260.                 cursor = MARK_AT_LINE(l);
  261.             }
  262.         }
  263.     }
  264.  
  265.     /* free the regexp */
  266.     _free_(re);
  267.  
  268.     /* if done from within a ":g" command, then finish silently */
  269.     if (doingglobal)
  270.     {
  271.         rptlines = chline;
  272.         rptlabel = "changed";
  273.         return;
  274.     }
  275.  
  276.     /* Reporting */
  277.     if (chsub == 0)
  278.     {
  279.         msg("Substitution failed");
  280.     }
  281.     else if (chline >= *o_report)
  282.     {
  283.         msg("%ld substitutions on %ld lines", chsub, chline);
  284.     }
  285.     rptlines = 0L;
  286. }
  287.  
  288.  
  289.  
  290.  
  291. /*ARGSUSED*/
  292. void cmd_delete(frommark, tomark, cmd, bang, extra)
  293.     MARK    frommark;
  294.     MARK    tomark;
  295.     CMD    cmd;
  296.     int    bang;
  297.     char    *extra;
  298. {
  299.     MARK    curs2;    /* an altered form of the cursor */
  300.  
  301.     /* choose your cut buffer */
  302.     if (*extra == '"')
  303.     {
  304.         extra++;
  305.     }
  306.     if (*extra)
  307.     {
  308.         cutname(*extra);
  309.     }
  310.  
  311.     /* make sure we're talking about whole lines here */
  312.     frommark = frommark & ~(BLKSIZE - 1);
  313.     tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
  314.  
  315.     /* yank the lines */
  316.     cut(frommark, tomark);
  317.  
  318.     /* if CMD_DELETE then delete the lines */
  319.     if (cmd != CMD_YANK)
  320.     {
  321.         curs2 = cursor;
  322.         ChangeText
  323.         {
  324.             /* delete the lines */
  325.             delete(frommark, tomark);
  326.         }
  327.         if (curs2 > tomark)
  328.         {
  329.             cursor = curs2 - tomark + frommark;
  330.         }
  331.         else if (curs2 > frommark)
  332.         {
  333.             cursor = frommark;
  334.         }
  335.     }
  336. }
  337.  
  338.  
  339. /*ARGSUSED*/
  340. void cmd_append(frommark, tomark, cmd, bang, extra)
  341.     MARK    frommark;
  342.     MARK    tomark;
  343.     CMD    cmd;
  344.     int    bang;
  345.     char    *extra;
  346. {
  347.     long    l;    /* line counter */
  348.  
  349. #ifndef CRUNCH
  350.     /* if '!' then toggle auto-indent */
  351.     if (bang)
  352.     {
  353.         *o_autoindent = !*o_autoindent;
  354.     }
  355. #endif
  356.  
  357.     ChangeText
  358.     {
  359.         /* if we're doing a change, delete the old version */
  360.         if (cmd == CMD_CHANGE)
  361.         {
  362.             /* delete 'em */
  363.             cmd_delete(frommark, tomark, cmd, bang, extra);
  364.         }
  365.  
  366.         /* new lines start at the frommark line, or after it */
  367.         l = markline(frommark);
  368.         if (cmd == CMD_APPEND)
  369.         {
  370.              l++;
  371.         }
  372.  
  373.         /* get lines until no more lines, or "." line, and insert them */
  374.         while (vgets('\0', tmpblk.c, BLKSIZE) >= 0)
  375.         {
  376.             addch('\n');
  377.             if (!strcmp(tmpblk.c, "."))
  378.             {
  379.                 break;
  380.             }
  381.  
  382.             strcat(tmpblk.c, "\n");
  383.             add(MARK_AT_LINE(l), tmpblk.c);
  384.             l++;
  385.         }
  386.     }
  387.  
  388.     /* on the odd chance that we're calling this from vi mode ... */
  389.     redraw(MARK_UNSET, FALSE);
  390. }
  391.  
  392.  
  393. /*ARGSUSED*/
  394. void cmd_put(frommark, tomark, cmd, bang, extra)
  395.     MARK    frommark;
  396.     MARK    tomark;
  397.     CMD    cmd;
  398.     int    bang;
  399.     char    *extra;
  400. {
  401.     /* choose your cut buffer */
  402.     if (*extra == '"')
  403.     {
  404.         extra++;
  405.     }
  406.     if (*extra)
  407.     {
  408.         cutname(*extra);
  409.     }
  410.  
  411.     /* paste it */
  412.     ChangeText
  413.     {
  414.         cursor = paste(frommark, TRUE, FALSE);
  415.     }
  416. }
  417.  
  418.  
  419. /*ARGSUSED*/
  420. void cmd_join(frommark, tomark, cmd, bang, extra)
  421.     MARK    frommark;
  422.     MARK    tomark;
  423.     CMD    cmd;
  424.     int    bang;
  425.     char    *extra;
  426. {
  427.     long    l;
  428.     char    *scan;
  429.     int    len;    /* length of the new line */
  430.  
  431.     /* if only one line is specified, assume the following one joins too */
  432.     if (markline(frommark) == nlines)
  433.     {
  434.         msg("Nothing to join with this line");
  435.         return;
  436.     }
  437.     if (markline(frommark) == markline(tomark))
  438.     {
  439.         tomark += BLKSIZE;
  440.     }
  441.  
  442.     /* get the first line */
  443.     l = markline(frommark);
  444.     strcpy(tmpblk.c, fetchline(l));
  445.     len = strlen(tmpblk.c);
  446.  
  447.     /* build the longer line */
  448.     while (++l <= markline(tomark))
  449.     {
  450.         /* get the next line */
  451.         scan = fetchline(l);
  452.  
  453.         /* remove any leading whitespace */
  454.         while (*scan == '\t' || *scan == ' ')
  455.         {
  456.             scan++;
  457.         }
  458.  
  459.         /* see if the line will fit */
  460.         if (strlen(scan) + len + 3 > (unsigned)BLKSIZE)
  461.         {
  462.             msg("Can't join -- the resulting line would be too long");
  463.             return;
  464.         }
  465.  
  466.         /* catenate it, with a space (or two) in between */
  467.         if (!bang)
  468.         {
  469.             if (len >= 1)
  470.             {
  471.                 if (tmpblk.c[len - 1] == '.'
  472.                  || tmpblk.c[len - 1] == '?'
  473.                  || tmpblk.c[len - 1] == '!')
  474.                 {
  475.                      tmpblk.c[len++] = ' ';
  476.                      tmpblk.c[len++] = ' ';
  477.                 }
  478.                 else if (tmpblk.c[len - 1] != ' ')
  479.                 {
  480.                      tmpblk.c[len++] = ' ';
  481.                 }
  482.             }
  483.         }
  484.         strcpy(tmpblk.c + len, scan);
  485.         len += strlen(scan);
  486.     }
  487.     tmpblk.c[len++] = '\n';
  488.     tmpblk.c[len] = '\0';
  489.  
  490.     /* make the change */
  491.     ChangeText
  492.     {
  493.         frommark &= ~(BLKSIZE - 1);
  494.         tomark &= ~(BLKSIZE - 1);
  495.         tomark += BLKSIZE;
  496.         change(frommark, tomark, tmpblk.c);
  497.     }
  498.  
  499.     /* Reporting... */
  500.     rptlines = markline(tomark) - markline(frommark) - 1L;
  501.     rptlabel = "joined";
  502. }
  503.  
  504.  
  505.  
  506. /*ARGSUSED*/
  507. void cmd_shift(frommark, tomark, cmd, bang, extra)
  508.     MARK    frommark;
  509.     MARK    tomark;
  510.     CMD    cmd;
  511.     int    bang;
  512.     char    *extra;
  513. {
  514.     long    l;    /* line number counter */
  515.     int    oldidx;    /* number of chars previously used for indent */
  516.     int    newidx;    /* number of chars in the new indent string */
  517.     int    oldcol;    /* previous indent amount */
  518.     int    newcol;    /* new indent amount */
  519.     int    shift;    /* number of columns to shift by */
  520.     char    *text;    /* pointer to the old line's text */
  521.  
  522. #ifdef CRUNCH
  523.     shift = (*o_shiftwidth & 0xff);
  524. #else /* not CRUNCH */
  525.     /* figure out the shift amount */
  526.     for (shift = 1; *extra == '<' || *extra == '>'; extra++, shift++)
  527.     {
  528.     }
  529.     shift *= (*o_shiftwidth & 0xff);
  530. #endif /* not CRUNCH */
  531.  
  532.     ChangeText
  533.     {
  534.         /* for each line to shift... */
  535.         for (l = markline(frommark); l <= markline(tomark); l++)
  536.         {
  537.             /* get the line - ignore empty lines unless ! mode */
  538.             text = fetchline(l);
  539.             if (!*text && !bang)
  540.                 continue;
  541.  
  542.             /* calc oldidx and oldcol */
  543.             for (oldidx = 0, oldcol = 0;
  544.                  text[oldidx] == ' ' || text[oldidx] == '\t';
  545.                  oldidx++)
  546.             {
  547.                 if (text[oldidx] == ' ')
  548.                 {
  549.                     oldcol += 1;
  550.                 }
  551.                 else
  552.                 {
  553.                     oldcol += *o_tabstop - (oldcol % *o_tabstop);
  554.                 }
  555.             }
  556.  
  557.             /* calc newcol */
  558.             if (cmd == CMD_SHIFTR)
  559.             {
  560.                 newcol = oldcol + shift;
  561.             }
  562.             else
  563.             {
  564.                 newcol = oldcol - shift;
  565.                 if (newcol < 0)
  566.                     newcol = 0;
  567.             }
  568.  
  569.             /* if no change, then skip to next line */
  570.             if (oldcol == newcol)
  571.                 continue;
  572.  
  573.             /* build a new indent string */
  574.             newidx = 0;
  575.             if (*o_autotab)
  576.             {
  577.                 while (newcol >= *o_tabstop)
  578.                 {
  579.                     tmpblk.c[newidx++] = '\t';
  580.                     newcol -= *o_tabstop;
  581.                 }
  582.             }
  583.             while (newcol > 0)
  584.             {
  585.                 tmpblk.c[newidx++] = ' ';
  586.                 newcol--;
  587.             }
  588.             tmpblk.c[newidx] = '\0';
  589.  
  590.             /* change the old indent string into the new */
  591.             change(MARK_AT_LINE(l), MARK_AT_LINE(l) + oldidx, tmpblk.c);
  592.         }
  593.     }
  594.  
  595.     /* Reporting... */
  596.     rptlines = markline(tomark) - markline(frommark) + 1L;
  597.     if (cmd == CMD_SHIFTR)
  598.     {
  599.         rptlabel = ">ed";
  600.     }
  601.     else
  602.     {
  603.         rptlabel = "<ed";
  604.     }
  605. }
  606.  
  607.  
  608. /*ARGSUSED*/
  609. void cmd_read(frommark, tomark, cmd, bang, extra)
  610.     MARK    frommark;
  611.     MARK    tomark;
  612.     CMD    cmd;
  613.     int    bang;
  614.     char    *extra;
  615. {
  616.     int    fd, rc;    /* used while reading from the file */
  617.     char    *scan;    /* used for finding NUL characters */
  618.     int    hadnul;    /* boolean: any NULs found? */
  619.     int    addnl;    /* boolean: forced to add newlines? */
  620.     int    len;    /* number of chars in current line */
  621.     long    lines;    /* number of lines in current block */
  622.     struct stat statb;
  623.  
  624.     /* special case: if ":r !cmd" then let the filter() function do it */
  625.     if (extra[0] == '!')
  626.     {
  627.         filter(frommark, MARK_UNSET, extra + 1, TRUE);
  628.         return;
  629.     }
  630.  
  631.     /* open the file */
  632.     fd = open(extra, O_RDONLY);
  633.     if (fd < 0)
  634.     {
  635.         msg("Can't open \"%s\"", extra);
  636.         return;
  637.     }
  638.  
  639. #ifndef CRUNCH
  640.     if (stat(extra, &statb) < 0)
  641.     {
  642.         msg("Can't stat \"%s\"", extra);
  643.     }
  644. # if TOS
  645.     if (statb.st_mode & S_IJDIR)
  646. # else
  647. #  if OSK
  648.     if (statb.st_mode & S_IFDIR)
  649. #  else
  650.     if ((statb.st_mode & S_IFMT) != S_IFREG)
  651. #  endif
  652. # endif
  653.     {
  654.         msg("\"%s\" is not a regular file", extra);
  655.         return;
  656.     }
  657. #endif /* not CRUNCH */
  658.  
  659.     /* get blocks from the file, and add them */
  660.     ChangeText
  661.     {
  662.         /* insertion starts at the line following frommark */
  663.         tomark = frommark = (frommark | (BLKSIZE - 1L)) + 1L;
  664.         len = 0;
  665.         hadnul = addnl = FALSE;
  666.  
  667.         /* add an extra newline, so partial lines at the end of
  668.          * the file don't trip us up
  669.          */
  670.         add(tomark, "\n");
  671.  
  672.         /* for each chunk of text... */
  673.         while ((rc = tread(fd, tmpblk.c, BLKSIZE - 1)) > 0)
  674.         {
  675.             /* count newlines, convert NULs, etc. ... */
  676.             for (lines = 0, scan = tmpblk.c; rc > 0; rc--, scan++)
  677.             {
  678.                 /* break up long lines */
  679.                 if (*scan != '\n' && len + 2 > BLKSIZE)
  680.                 {
  681.                     *scan = '\n';
  682.                     addnl = TRUE;
  683.                 }
  684.  
  685.                 /* protect against NUL chars in file */
  686.                 if (!*scan)
  687.                 {
  688.                     *scan = 0x80;
  689.                     hadnul = TRUE;
  690.                 }
  691.  
  692.                 /* starting a new line? */
  693.                 if (*scan == '\n')
  694.                 {
  695.                     /* reset length at newline */
  696.                     len = 0;
  697.                     lines++;
  698.                 }
  699.                 else
  700.                 {
  701.                     len++;
  702.                 }
  703.             }
  704.  
  705.             /* add the text */
  706.             *scan = '\0';
  707.             add(tomark, tmpblk.c);
  708.             tomark += MARK_AT_LINE(lines) + len - markidx(tomark);
  709.         }
  710.  
  711.         /* if partial last line, then retain that first newline */
  712.         if (len > 0)
  713.         {
  714.             msg("Last line had no newline");
  715.             tomark += BLKSIZE; /* <- for the rptlines calc */
  716.         }
  717.         else /* delete that first newline */
  718.         {
  719.             delete(tomark, (tomark | (BLKSIZE - 1L)) + 1L);
  720.         }
  721.     }
  722.  
  723.     /* close the file */
  724.     close(fd);
  725.  
  726.     /* Reporting... */
  727.     rptlines = markline(tomark) - markline(frommark);
  728.     rptlabel = "read";
  729.     if (mode == MODE_EX)
  730.     {
  731.         cursor = (tomark & ~BLKSIZE) - BLKSIZE;
  732.     }
  733.     else
  734.     {
  735.         cursor = frommark;
  736.     }
  737.  
  738.     if (addnl)
  739.         msg("Newlines were added to break up long lines");
  740.     if (hadnul)
  741.         msg("NULs were converted to 0x80");
  742. }
  743.  
  744.  
  745.  
  746. /*ARGSUSED*/
  747. void cmd_undo(frommark, tomark, cmd, bang, extra)
  748.     MARK    frommark;
  749.     MARK    tomark;
  750.     CMD    cmd;
  751.     int    bang;
  752.     char    *extra;
  753. {
  754.     undo();
  755. }
  756.  
  757.  
  758. /* print the selected lines */
  759. /*ARGSUSED*/
  760. void cmd_print(frommark, tomark, cmd, bang, extra)
  761.     MARK    frommark;
  762.     MARK    tomark;
  763.     CMD    cmd;
  764.     int    bang;
  765.     char    *extra;
  766. {
  767.     REG char    *scan;
  768.     REG long    l;
  769.     REG int        col;
  770.  
  771.     for (l = markline(frommark); l <= markline(tomark); l++)
  772.     {
  773.         /* display a line number, if CMD_NUMBER */
  774.         if (cmd == CMD_NUMBER)
  775.         {
  776.             sprintf(tmpblk.c, "%6ld  ", l);
  777.             qaddstr(tmpblk.c);
  778.             col = 8;
  779.         }
  780.         else
  781.         {
  782.             col = 0;
  783.         }
  784.  
  785.         /* get the next line & display it */
  786.         for (scan = fetchline(l); *scan; scan++)
  787.         {
  788.             /* expand tabs to the proper width */
  789.             if (*scan == '\t' && cmd != CMD_LIST)
  790.             {
  791.                 do
  792.                 {
  793.                     qaddch(' ');
  794.                     col++;
  795.                 } while (col % *o_tabstop != 0);
  796.             }
  797.             else if (*scan >= 1 && *scan < ' ' || *scan == '\177')
  798.             {
  799.                 qaddch('^');
  800.                 qaddch(*scan ^ 0x40);
  801.                 col += 2;
  802.             }
  803.             else if ((*scan & 0x80) && cmd == CMD_LIST)
  804.             {
  805.                 sprintf(tmpblk.c, "\\%03o", UCHAR(*scan));
  806.                 qaddstr(tmpblk.c);
  807.                 col += 4;
  808.             }
  809.             else
  810.             {
  811.                 qaddch(*scan);
  812.                 col++;
  813.             }
  814.  
  815.             /* wrap at the edge of the screen */
  816.             if (!has_AM && col >= COLS)
  817.             {
  818.                 addch('\n');
  819.                 col -= COLS;
  820.             }
  821.         }
  822.         if (cmd == CMD_LIST)
  823.         {
  824.             qaddch('$');
  825.         }
  826.         addch('\n');
  827.         exrefresh();
  828.     }
  829.  
  830.     /* leave the cursor on the last line printed */
  831.     cursor = tomark;
  832. }
  833.  
  834.  
  835. /* move or copy selected lines */
  836. /*ARGSUSED*/
  837. void cmd_move(frommark, tomark, cmd, bang, extra)
  838.     MARK    frommark;
  839.     MARK    tomark;
  840.     CMD    cmd;
  841.     int    bang;
  842.     char    *extra;
  843. {
  844.     MARK    destmark;
  845.  
  846.     /* parse the destination linespec.  No defaults.  Line 0 is okay */
  847.     destmark = cursor;
  848.     if (!strcmp(extra, "0"))
  849.     {
  850.         destmark = 0L;
  851.     }
  852.     else if (linespec(extra, &destmark) == extra || !destmark)
  853.     {
  854.         msg("invalid destination address");
  855.         return;
  856.     }
  857.  
  858.     /* flesh the marks out to encompass whole lines */
  859.     frommark &= ~(BLKSIZE - 1);
  860.     tomark = (tomark & ~(BLKSIZE - 1)) + BLKSIZE;
  861.     destmark = (destmark & ~(BLKSIZE - 1)) + BLKSIZE;
  862.  
  863.     /* make sure the destination is valid */
  864.     if (cmd == CMD_MOVE && destmark >= frommark && destmark < tomark)
  865.     {
  866.         msg("invalid destination address");
  867.     }
  868.  
  869.     /* Do it */
  870.     ChangeText
  871.     {
  872.         /* save the text to a cut buffer */
  873.         cutname('\0');
  874.         cut(frommark, tomark);
  875.  
  876.         /* if we're not copying, delete the old text & adjust destmark */
  877.         if (cmd != CMD_COPY)
  878.         {
  879.             delete(frommark, tomark);
  880.             if (destmark >= frommark)
  881.             {
  882.                 destmark -= (tomark - frommark);
  883.             }
  884.         }
  885.  
  886.         /* add the new text */
  887.         paste(destmark, FALSE, FALSE);
  888.     }
  889.  
  890.     /* move the cursor to the last line of the moved text */
  891.     cursor = destmark + (tomark - frommark) - BLKSIZE;
  892.     if (cursor < MARK_FIRST || cursor >= MARK_LAST + BLKSIZE)
  893.     {
  894.         cursor = MARK_LAST;
  895.     }
  896.  
  897.     /* Reporting... */
  898.     rptlabel = ( (cmd == CMD_COPY) ? "copied" : "moved" );
  899. }
  900.  
  901.  
  902.  
  903. /* execute EX commands from a file */
  904. /*ARGSUSED*/
  905. void cmd_source(frommark, tomark, cmd, bang, extra)
  906.     MARK    frommark;
  907.     MARK    tomark;
  908.     CMD    cmd;
  909.     int    bang;
  910.     char    *extra;
  911. {
  912.     /* must have a filename */
  913.     if (!*extra)
  914.     {
  915.         msg("\"source\" requires a filename");
  916.         return;
  917.     }
  918.  
  919.     doexrc(extra);
  920. }
  921.  
  922.  
  923. #ifndef NO_AT
  924. /*ARGSUSED*/
  925. void cmd_at(frommark, tomark, cmd, bang, extra)
  926.     MARK    frommark;
  927.     MARK    tomark;
  928.     CMD    cmd;
  929.     int    bang;
  930.     char    *extra;
  931. {
  932.     static    nest = FALSE;
  933.     int    result;
  934.     char    buf[MAXRCLEN];
  935.     char    *scan, *end;
  936.  
  937.     /* don't allow nested macros */
  938.     if (nest)
  939.     {
  940.         msg("@ macros can't be nested");
  941.         return;
  942.     }
  943.     nest = TRUE;
  944.  
  945.     /* require a buffer name */
  946.     if (*extra == '"')
  947.         extra++;
  948.     if (!*extra || !isascii(*extra) ||!islower(*extra))
  949.     {
  950.         msg("@ requires a cut buffer name (a-z)");
  951.     }
  952.  
  953.     /* get the contents of the buffer */
  954.     result = cb2str(*extra, buf, (unsigned)(sizeof buf));
  955.     if (result <= 0)
  956.     {
  957.         msg("buffer \"%c is empty", *extra);
  958.     }
  959.     else if (result >= sizeof buf)
  960.     {
  961.         msg("buffer \"%c is too large to execute", *extra);
  962.     }
  963.     else
  964.     {
  965.         /* execute the contents of the buffer as ex commands */
  966.         for (scan = buf; scan; scan = end)
  967.         {
  968.  
  969.             /* locate the end of the command line */
  970.             for (end = buf; *end && *end != '\n'; end++)
  971.             {
  972.             }
  973.  
  974.             /* convert the '\n' to '\0' at the end */
  975.             if (*end)
  976.             {
  977.                 *end++ = '\0';
  978.             }
  979.             else
  980.             {
  981.                 end = (char *)0;
  982.             }
  983.  
  984.             /* Execute the command line, unless it is blank. */
  985.             if (*buf)
  986.             {
  987.                 doexcmd(buf, '\\');
  988.             }
  989.         }
  990.     }
  991.  
  992.     nest = FALSE;
  993. }
  994. #endif
  995.