home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / vile-src.zip / vile-8.1 / history.c < prev    next >
C/C++ Source or Header  |  1998-07-01  |  14KB  |  591 lines

  1. /*
  2.  *    history.c
  3.  *
  4.  *    Manage command-history buffer
  5.  *
  6.  * Notes:
  7.  *    This module manages an invisible, non-volatile buffer "[History]".
  8.  *    Each keyboard command to vile is logged in this buffer.  The leading
  9.  *    ':' is not included, but all other characters are preserved so that the
  10.  *    command can be replayed.
  11.  *
  12.  *    Each interactive call on 'kbd_reply()' logs the resulting string, and
  13.  *    the "glue" character (nominally, the end-of-line character), so that
  14.  *    successive calls can be spliced together. On completion of the command,
  15.  *    the composed command (in 'MyText') is flushed into the history-buffer.
  16.  *
  17.  *    The procedure 'edithistory()' is invoked from 'kbd_reply()' to provide
  18.  *    access to the history.  In particular, it presents a scrollable list of
  19.  *    strings based on a match of the command to that point.  For example, if
  20.  *    the commands
  21.  *
  22.  *        :set ts=8
  23.  *        :set ab
  24.  *        :set ai
  25.  *
  26.  *    were entered, then in response to ":set", the user would see the
  27.  *    strings "ai", "ab" and "ts".
  28.  *
  29.  *    Scrolling is accomplished by either arrow keys, or by an escaped set of
  30.  *    commands (a la 'ksh').
  31.  *
  32.  *    Note that this implementation is a compromise.  Ideally, the command
  33.  *    interpreter for ':' would be able to scroll through the entire list of
  34.  *    commands from any point, moving forward and backward through its
  35.  *    internal state of range, command, arguments.  As implemented, it is not
  36.  *    so general.  The user can backspace from the command state into the
  37.  *    range state, but not from the arguments.  Also, the history scrolling
  38.  *    is not really useful in the range state, so it is disabled there.  If
  39.  *    the command interpreter were able to easily go back and forth in its
  40.  *    state, then it would be simple to implement an 'expert' mode, in which
  41.  *    prompting would be suppressed.
  42.  *
  43.  * To do:
  44.  *    Add logic to quote arguments that should be strings, to make them
  45.  *    easier to parse back for scrolling, etc.
  46.  *
  47.  *    Integrate this with the "!!" response to the ^X-! and !-commands.
  48.  *
  49.  *    Modify the matching logic so that file commands (i.e., ":e", ":w",
  50.  *    etc.) are equivalent when matching for the argument.  Currently, the
  51.  *    history scrolling will show only arguments for identical command names.
  52.  *
  53.  *    Modify the matching logic so that search commands (i.e., "/" and "?")
  54.  *    are equivalent when matching for the argument.  Note also that these do
  55.  *    not (yet) correspond to :-commands.  Before implementing, probably will
  56.  *    have to make TESTC a settable mode.
  57.  *
  58.  *    Make the display updating work for more than simply erasing/printing
  59.  *    the entire response.  This is adequate for scrolling, but won't support
  60.  *    inline editing.
  61.  *
  62.  *    Implement other ksh-style inline command editing.
  63.  *
  64.  *    Allow left/right scrolling of input lines (when they get too long).
  65.  *
  66.  * $Header: /usr/build/vile/vile/RCS/history.c,v 1.49 1998/07/01 10:58:14 tom Exp $
  67.  *
  68.  */
  69.  
  70. #include "estruct.h"
  71. #include "edef.h"
  72.  
  73. #if    OPT_HISTORY
  74.  
  75. #define    tb_args(p)    tb_values(p), (int)tb_length(p)
  76. #define    lp_args(p)    p->l_text, llength(p)
  77.  
  78. typedef    struct    {
  79.     TBUFF **buffer;
  80.     unsigned * position;
  81.     int    (*endfunc) (EOL_ARGS);
  82.     int    eolchar;
  83.     UINT    options;
  84.     } HST;
  85.  
  86. /*--------------------------------------------------------------------------*/
  87. static    void    stopMyBuff (void);
  88.  
  89. static    const char *MyBuff = HISTORY_BufName;
  90. static    TBUFF    *MyText;    /* current command to display */
  91. static    int    MyGlue,        /* most recent eolchar */
  92.         MyLevel;    /* logging iff level is 1 */
  93.  
  94. /*--------------------------------------------------------------------------*/
  95.  
  96. static BUFFER *
  97. makeMyBuff(void)
  98. {
  99.     register BUFFER *bp;
  100.  
  101.     if (!global_g_val(GMDHISTORY)) {
  102.         bp = 0;
  103.     } else if ((bp = bfind(MyBuff, BFINVS)) != 0) {
  104.         b_set_invisible(bp);
  105.         b_clr_scratch(bp); /* make it nonvolatile */
  106.         set_rdonly(bp, non_filename(), MDVIEW);
  107.     } else {
  108.         stopMyBuff();
  109.     }
  110.     return bp;
  111. }
  112.  
  113. static void
  114. stopMyBuff(void)
  115. {
  116.     register BUFFER *bp;
  117.  
  118.     if ((bp = find_b_name(MyBuff)) != 0)
  119.         (void)zotbuf(bp);
  120.  
  121.     tb_free(&MyText);
  122. }
  123.  
  124. /*
  125.  * Returns 0 or 1 according to whether we will add the glue-character in the
  126.  * next call on 'hst_append()'.
  127.  */
  128. static int
  129. willGlue(void)
  130. {
  131.     if ((tb_length(MyText) != 0) && (isPrint(MyGlue) || MyGlue == '\r')) {
  132.         register int c = tb_values(MyText)[0];
  133.         if ((c != SHPIPE_LEFT[0]) || isRepeatable(c))
  134.             return 1;
  135.     }
  136.     return 0;
  137. }
  138.  
  139. /*
  140.  * Returns true iff we display the complete, rather than the immediate portion
  141.  * of the history line.  We do this for !-commands so that the user can see the
  142.  * entire command when scrolling.
  143.  *
  144.  * The shift-commands also are a (similar) special case.
  145.  */
  146. static int
  147. willExtend(const char * src, int srclen)
  148. {
  149.     if ((tb_length(MyText) == 0)
  150.      && (srclen > 0)) {
  151.         return (src[0] == SHPIPE_LEFT[0]) || isRepeatable(src[0]);
  152.     }
  153.     return FALSE;
  154. }
  155.  
  156. /*
  157.  * Returns a positive number if the length of the given LINE is at least as
  158.  * long as the given string in 'src' and if it matches to the length in
  159.  * 'srclen'.
  160.  */
  161. static int
  162. sameLine(LINE * lp, char * src, int srclen)
  163. {
  164.     if (srclen <= 0)
  165.         return 0;
  166.     else {
  167.         register int    dstlen = llength(lp);
  168.  
  169.         if (dstlen >= srclen) {
  170.             if (!memcmp(lp->l_text, src, (SIZE_T)srclen)) {
  171.                 if (isRepeatable(*src)
  172.                  && isRepeatable(lp->l_text[0])
  173.                  && dstlen != srclen)
  174.                     return -1;
  175.                 return (dstlen - srclen);
  176.             }
  177.         }
  178.     }
  179.     return -1;
  180. }
  181.  
  182. /*
  183.  * Returns the length of the argument from the given line
  184.  */
  185. static int
  186. parseArg(HST * parm, LINE * lp)
  187. {
  188.     int    len = llength(lp);
  189.  
  190.     if (len > 0) {
  191.         if (willExtend(lp_args(lp))) {
  192.             return len;
  193.         } else {
  194.             register char    *s = lp->l_text;
  195.             register int    n;
  196.  
  197.             for (n = willGlue()+tb_length(MyText); n < len; n++)
  198.                 if ((*parm->endfunc)(s, n, s[n], parm->eolchar))
  199.                     break;
  200.             return n;
  201.         }
  202.     }
  203.     return 0;
  204. }
  205.  
  206. /******************************************************************************/
  207. void
  208. hst_init(int c)
  209. {
  210.     if (++MyLevel == 1) {
  211.         (void)tb_init(&MyText, abortc);
  212.         MyGlue = EOS;
  213.         if (c != EOS)
  214.             (void)tb_append(&MyText, c);
  215.     }
  216. }
  217.  
  218. void
  219. hst_glue(int c)
  220. {
  221.     /* ensure we don't repeat '/' delimiter */
  222.     if (tb_length(MyText) == 0
  223.      || tb_values(MyText)[0] != c)
  224.         MyGlue = c;
  225. }
  226.  
  227. void
  228. hst_append(TBUFF * cmd, int glue)
  229. {
  230.     static    int    skip = 1;        /* e.g., after "!" */
  231.  
  232.     if (clexec || !disinp)            /* non-interactive? */
  233.         return;
  234.  
  235.     if (willExtend(tb_values(cmd), tb_length(cmd))
  236.      && tb_length(cmd) > (SIZE_T)skip) {
  237.         kbd_pushback(cmd, skip);
  238.     }
  239.  
  240.     if (willGlue())
  241.         (void)tb_append(&MyText, MyGlue);
  242.     (void)tb_bappend(&MyText, tb_values(cmd), tb_length(cmd));
  243.     MyGlue = glue;
  244. }
  245.  
  246. void
  247. hst_append_s(char *cmd, int glue)
  248. {
  249.     TBUFF *p = tb_string(cmd);
  250.     hst_append(p, glue);
  251.     tb_free(&p);
  252. }
  253.  
  254. void
  255. hst_remove(const char * cmd)
  256. {
  257.     if (MyLevel == 1) {
  258.         TBUFF    *temp    = 0;
  259.         unsigned len    = tb_length(tb_scopy(&temp, cmd)) - 1;
  260.  
  261.         while (*cmd++)
  262.             tb_unput(MyText);
  263.         kbd_kill_response(temp, &len, killc);
  264.         tb_free(&temp);
  265.     }
  266. }
  267.  
  268. void
  269. hst_flush(void)
  270. {
  271.     register BUFFER *bp = 0;
  272.     register WINDOW *wp;
  273.     register LINE    *lp;
  274.  
  275.     if (MyLevel <= 0)
  276.         return;
  277.     if (MyLevel-- != 1)
  278.         return;
  279.  
  280.     if ((tb_length(MyText) != 0)
  281.      && ((bp = makeMyBuff()) != 0)) {
  282.  
  283.         /* suppress if this is the same as previous line */
  284.         if (((lp = lback(buf_head(bp))) != 0)
  285.          && (lp != buf_head(bp))
  286.          && (sameLine(lp, tb_args(MyText)) == 0)) {
  287.             (void)tb_init(&MyText, abortc);
  288.             return;
  289.          }
  290.  
  291.         if (!addline(bp, tb_args(MyText))) {
  292.             stopMyBuff();
  293.             return;
  294.         }
  295.  
  296.         /* patch: reuse logic from slowreadf()? */
  297.         for_each_visible_window(wp) {
  298.             if (wp->w_bufp == bp) {
  299.                 wp->w_flag |= WFFORCE;
  300.                 if (wp == curwp)
  301.                     continue;
  302.                 /* force dot to the beginning of last-line */
  303.                 wp->w_force = -1;
  304.                 if (wp->w_dot.l != lback(buf_head(bp))) {
  305.                     wp->w_dot.l = lback(buf_head(bp));
  306.                     wp->w_dot.o = 0;
  307.                     wp->w_flag |= WFMOVE;
  308.                 }
  309.             }
  310.         }
  311.         updatelistbuffers();    /* force it to show current sizes */
  312.         (void)tb_init(&MyText, abortc);
  313.      }
  314. }
  315.  
  316. /*ARGSUSED*/
  317. int
  318. showhistory(int f GCC_UNUSED, int n GCC_UNUSED)
  319. {
  320.     register BUFFER *bp = makeMyBuff();
  321.  
  322.     if (!global_g_val(GMDHISTORY)) {
  323.         mlforce("history mode is not set");
  324.         return FALSE;
  325.     } else if (bp == 0 || popupbuff(bp) == FALSE) {
  326.         return no_memory("show-history");
  327.     }
  328.     return TRUE;
  329. }
  330.  
  331. /*
  332.  * Find the last line in the history buffer that matches the portion of
  333.  * the command that has been input to this point.  The substrings to the
  334.  * right (up to eolchar) will form the set of history strings that the
  335.  * user may scroll through.
  336.  */
  337. static LINE *
  338. hst_find (
  339. HST *    parm,
  340. BUFFER *bp,
  341. LINE *    lp,
  342. int    direction)
  343. {
  344.     LINE    *base    = buf_head(bp),
  345.         *lp0    = lp;
  346.  
  347.     if ((lp0 == 0)
  348.      || ((lp == base) && (direction > 0))) {
  349.         return 0;
  350.     }
  351.  
  352.     for_ever {
  353.         if (direction > 0) {
  354.             if (lp == lback(base))    /* cannot wrap-around */
  355.                 return 0;
  356.             lp = lforw(lp);
  357.         } else
  358.             lp = lback(lp);
  359.         if (lp == base)
  360.             return 0;        /* empty or no matches */
  361.  
  362.         if (!lisreal(lp)
  363.          || ((ALLOC_T)llength(lp) <= tb_length(MyText)+willGlue())
  364.          || (sameLine(lp, tb_args(MyText)) < 0))
  365.             continue;        /* prefix mismatches */
  366.  
  367.         if (willGlue()) {        /* avoid conflicts setall/set */
  368.             register int len = tb_length(MyText);
  369.             if (len > 0
  370.              && (len > 1 || !isPunct(tb_values(MyText)[0]))
  371.              && llength(lp) > len
  372.              && lp->l_text[len] != MyGlue)
  373.                 continue;
  374.         }
  375.  
  376.         /* avoid picking up lines with range-spec, since this is too
  377.          * cumbersome to splice in 'namedcmd()'.
  378.          */
  379.         if (islinespecchar(lp->l_text[0]))
  380.             continue;
  381.  
  382.         /* '/' and '?' are not (yet) :-commands.  Don't display them
  383.          * in the command-name scrolling.
  384.          */
  385.         if (tb_length(MyText) == 0) {
  386.             if (lp->l_text[0] == '/'
  387.              || lp->l_text[0] == '?')
  388.                 continue;
  389.         }
  390.  
  391.         /* compare the argument that will be shown for the original
  392.          * and current lines.
  393.          */
  394.         if (lisreal(lp0)) {
  395.             int    n0 = parseArg(parm, lp0),
  396.                 n1 = parseArg(parm, lp);
  397.             if (n0 != 0
  398.              && n1 != 0
  399.              && n0 == n1
  400.              && sameLine(lp, lp0->l_text, n0) >= 0)
  401.                 continue;
  402.         }
  403.  
  404.         return lp;
  405.     }
  406. }
  407.  
  408. /*
  409.  * Update the display of the currently-scrollable buffer on the prompt-line.
  410.  */
  411. static void
  412. hst_display(
  413. HST *    parm,
  414. char *    src,
  415. int    srclen)
  416. {
  417.     /* kill the whole buffer */
  418.     *(parm->position) = tb_length(*(parm->buffer));
  419.     wminip->w_dot.o = llength(wminip->w_dot.l);
  420.     kbd_kill_response(*(parm->buffer), parm->position, killc);
  421.  
  422.     if (src != 0) {
  423.         int    keylen    = tb_length(MyText) + willGlue();
  424.         int    uselen    = srclen - keylen;
  425.         register char    *s = src + keylen;
  426.         register int    n  = 0;
  427.  
  428.         if (willExtend(src,srclen)) {
  429.             n = uselen;
  430.         } else {
  431.             while (uselen-- > 0) {
  432.                 if ((*parm->endfunc)(s, n, s[n], parm->eolchar))
  433.                     break;
  434.                 n++;
  435.             }
  436.         }
  437.         *parm->position = kbd_show_response(parm->buffer, s, n, parm->eolchar, parm->options);
  438.     }
  439. }
  440.  
  441. /*
  442.  * Update the display using a LINE as source
  443.  */
  444. static void
  445. display_LINE(
  446. HST *    parm,
  447. LINE *    lp)
  448. {
  449.     hst_display(parm, lp_args(lp));
  450. }
  451.  
  452. /*
  453.  * Update the display using a TBUFF as source
  454.  */
  455. static void
  456. display_TBUFF(HST * parm, TBUFF * tp)
  457. {
  458.     hst_display(parm, tb_args(tp));
  459. }
  460.  
  461. /*
  462.  * Perform common scrolling functions for arrow-keys and ESC-mode.
  463.  */
  464. static    TBUFF *    original;    /* save 'buffer' on first-scroll */
  465. static    int    any_edit,    /* true iff any edit happened */
  466.         direction,    /* current scrolling +/- */
  467.         distance;    /* distance from original entry */
  468.  
  469. static LINE *
  470. hst_scroll(LINE * lp1, HST * parm)
  471. {
  472.     BUFFER    *bp = makeMyBuff();
  473.     LINE    *lp0 = buf_head(bp),
  474.         *lp2 = hst_find(parm, bp, lp1, direction);
  475.  
  476.     if (lp1 != lp2) {
  477.         if (lp2 == 0) {
  478.             if (direction+distance == 0) {
  479.                 lp1 = lp0;
  480.                 distance = 0;
  481.                 display_TBUFF(parm, original);
  482.             } else {
  483.                 if (lp1 == lp0)    /* nothing to scroll for */
  484.                     distance = 0;
  485.                 kbd_alarm();
  486.             }
  487.             return lp1;
  488.         } else {
  489.             distance += direction;
  490.             display_LINE(parm, lp2);
  491.             any_edit++;
  492.             return lp2;
  493.         }
  494.     }
  495.     return 0;
  496. }
  497.  
  498. #undef isgraph
  499. #define    isgraph(c)    (!isspecial(c) && !isSpace(c) && isPrint(c))
  500.  
  501. /*
  502.  * Invoked on an escape-character, this processes history-editing until another
  503.  * escape-character is entered.
  504.  */
  505. int
  506. edithistory (
  507. TBUFF **buffer,
  508. unsigned * position,
  509. int *    given,
  510. UINT    options,
  511. int    (*endfunc) (EOL_ARGS),
  512. int    eolchar)
  513. {
  514.     HST    param;
  515.     BUFFER    *bp;
  516.     LINE    *lp1, *lp2;
  517.     int    escaped    = FALSE;
  518.  
  519.     register int    c = *given;
  520.  
  521.     if (!isspecial(c)) {
  522.         if (is_edit_char(c)
  523.          || ABORTED(c)
  524.          || (c == quotec)
  525.          || isSpace(c)
  526.          || !isCntrl(c))
  527.             return FALSE;
  528.     }
  529.  
  530.     if ((bp = makeMyBuff()) == 0)        /* something is very wrong */
  531.         return FALSE;
  532.  
  533.     if ((lp1 = buf_head(bp)) == 0)
  534.         return FALSE;
  535.  
  536.     /* slightly better than global data... */
  537.     param.buffer   = buffer;
  538.     param.position = position;
  539.     param.endfunc  = endfunc;
  540.     param.eolchar  = eolchar == '\n' ? '\r' : eolchar;
  541.     param.options  = options;
  542.  
  543.     any_edit = 0;
  544.     distance = 0;
  545.  
  546.     /* save the original buffer, since we expect to scroll it */
  547.     if (tb_copy(&original, MyText)) {
  548.         /* make 'original' look just like a complete command... */
  549.         if (willGlue())
  550.             (void)tb_append(&original, MyGlue);
  551.         (void)tb_sappend(&original, tb_values(*buffer));
  552.     }
  553.  
  554.     /* process char-commands */
  555.     for_ever {
  556.         register const CMDFUNC *p;
  557.  
  558.         /* If the character is bound to up/down scrolling, scroll the
  559.          * history.
  560.          */
  561.         direction = 0;    /* ...unless we find scrolling-command */
  562.         if ((p = kcod2fnc(c)) != 0) {
  563.             if (CMD_U_FUNC(p) == backline)
  564.                 direction = -1;
  565.             else if (CMD_U_FUNC(p) == forwline)
  566.                 direction = 1;
  567.         }
  568.         if (ABORTED(c)) {
  569.             *given = c;
  570.             return FALSE;
  571.  
  572.         } else if ((direction != 0) && (escaped || !isgraph(c))) {
  573.  
  574.             if ((lp2 = hst_scroll(lp1, ¶m)) != 0)
  575.                 lp1 = lp2;
  576.             else    /* cannot scroll */
  577.                 kbd_alarm();
  578.         } else if (!escaped) {
  579.             *given = c;
  580.             if (any_edit)
  581.                 unkeystroke(c);
  582.             return any_edit;
  583.  
  584.         } else
  585.             kbd_alarm();
  586.  
  587.         c = keystroke();
  588.     }
  589. }
  590. #endif    /* OPT_HISTORY */
  591.