Logo Search packages:      
Sourcecode: vile version File versions

history.c

/*
 *    history.c
 *
 *    Manage command-history buffer
 *
 * Notes:
 *    This module manages an invisible, non-volatile buffer "[History]".
 *    Each keyboard command to vile is logged in this buffer.  The leading
 *    ':' is not included, but all other characters are preserved so that the
 *    command can be replayed.
 *
 *    Each interactive call on 'kbd_reply()' logs the resulting string, and
 *    the "glue" character (nominally, the end-of-line character), so that
 *    successive calls can be spliced together. On completion of the command,
 *    the composed command (in 'MyText') is flushed into the history-buffer.
 *
 *    The procedure 'edithistory()' is invoked from 'kbd_reply()' to provide
 *    access to the history.  In particular, it presents a scrollable list of
 *    strings based on a match of the command to that point.  For example, if
 *    the commands
 *
 *          :set ts=8
 *          :set ab
 *          :set ai
 *
 *    were entered, then in response to ":set", the user would see the
 *    strings "ai", "ab" and "ts".
 *
 *    Scrolling is accomplished by either arrow keys, or by an escaped set of
 *    commands (a la 'ksh').
 *
 *    Note that this implementation is a compromise.  Ideally, the command
 *    interpreter for ':' would be able to scroll through the entire list of
 *    commands from any point, moving forward and backward through its
 *    internal state of range, command, arguments.  As implemented, it is not
 *    so general.  The user can backspace from the command state into the
 *    range state, but not from the arguments.  Also, the history scrolling
 *    is not really useful in the range state, so it is disabled there.  If
 *    the command interpreter were able to easily go back and forth in its
 *    state, then it would be simple to implement an 'expert' mode, in which
 *    prompting would be suppressed.
 *
 * To do:
 *    Add logic to quote arguments that should be strings, to make them
 *    easier to parse back for scrolling, etc.
 *
 *    Integrate this with the "!!" response to the ^X-! and !-commands.
 *
 *    Modify the matching logic so that file commands (i.e., ":e", ":w",
 *    etc.) are equivalent when matching for the argument.  Currently, the
 *    history scrolling will show only arguments for identical command names.
 *
 *    Modify the matching logic so that search commands (i.e., "/" and "?")
 *    are equivalent when matching for the argument.  Note also that these do
 *    not (yet) correspond to :-commands.  Before implementing, probably will
 *    have to make TESTC a settable mode.
 *
 * $Header: /usr/build/vile/vile/RCS/history.c,v 1.76 2003/07/27 16:55:39 tom Exp $
 *
 */

#include "estruct.h"
#include "edef.h"

#if   OPT_HISTORY

#define HST_QUOTES 1

#define     tb_args(p)  tb_values(p), (int)tb_length(p)
#define     lp_args(p)  p->l_text, llength(p)

typedef struct {
    TBUFF **buffer;
    unsigned *position;
    int (*endfunc) (EOL_ARGS);
    int eolchar;
    UINT options;
} HST;

/*--------------------------------------------------------------------------*/

static TBUFF *MyText;         /* current command to display */
static int MyGlue;            /* most recent eolchar */
static int MyLevel;           /* logging iff level is 1 */

/*--------------------------------------------------------------------------*/

static void
stopMyBuff(void)
{
    BUFFER *bp;

    if ((bp = find_b_name(HISTORY_BufName)) != 0)
      (void) zotbuf(bp);

    tb_free(&MyText);
}

static BUFFER *
makeMyBuff(void)
{
    BUFFER *bp;

    if (!global_g_val(GMDHISTORY)) {
      bp = 0;
    } else if ((bp = make_ro_bp(HISTORY_BufName, BFINVS)) == 0) {
      stopMyBuff();
    } else {
      set_vilemode(bp);
    }
    return bp;
}

/*
 * Returns 0 or 1 according to whether we will add the glue-character in the
 * next call on 'hst_append()'.
 */
static int
willGlue(void)
{
    if ((tb_length(MyText) != 0) && (isPrint(MyGlue) || MyGlue == '\r')) {
      int c = tb_values(MyText)[0];
      if ((c != SHPIPE_LEFT[0]) || isRepeatable(c))
          return 1;
    }
    return 0;
}

/*
 * Returns true iff we display the complete, rather than the immediate portion
 * of the history line.  We do this for !-commands so that the user can see the
 * entire command when scrolling.
 *
 * The shift-commands also are a (similar) special case.
 */
static int
willExtend(const char *src, int srclen)
{
    if ((tb_length(MyText) == 0)
      && (srclen > 0)) {
      return (src[0] == SHPIPE_LEFT[0]) || isRepeatable(src[0]);
    }
    return FALSE;
}

/*
 * Returns a positive number if the length of the given LINE is at least as
 * long as the given string in 'src' and if it matches to the length in
 * 'srclen'.
 */
static int
sameLine(LINE *lp, char *src, int srclen)
{
    if (srclen <= 0)
      return 0;
    else {
      int dstlen = llength(lp);

      if (dstlen >= srclen) {
          if (!memcmp(lp->l_text, src, (size_t) srclen)) {
            if (isRepeatable(*src)
                && isRepeatable(lp->l_text[0])
                && dstlen != srclen)
                return -1;
            return (dstlen - srclen);
          }
      }
    }
    return -1;
}

/*
 * Given a starting offset and index limit for src, find the index of the
 * character that ends the parameter.
 */
static int
endOfParm(HST * parm, char *src, int offset, int limit)
{
    int n;

    if (limit > 0) {
      if (willExtend(src, limit)) {
          n = limit;
      } else {
#if HST_QUOTES
          int quoted = (src[offset] == DQUOTE);
          int escaped = FALSE;
#endif
          for (n = offset; n < limit; n++) {
#if HST_QUOTES
            if (quoted) {
                if (escaped) {
                  escaped = FALSE;
                } else if (src[n] == BACKSLASH) {
                  escaped = TRUE;
                } else if (n != offset && (src[n] == src[offset])) {
                  n++;
                  break;
                }
            } else
#endif
            if ((*parm->endfunc) (src, n, src[n], parm->eolchar))
                break;
          }
      }
    } else {
      n = 0;
    }
    return n;
}

/*
 * Returns the index of the character that ends the current argument on the
 * given line.
 */
static int
parseArg(HST * parm, LINE *lp)
{
    return endOfParm(parm,
                 lp->l_text,
                 willGlue() + tb_length(MyText),
                 llength(lp));
}

/*
 * Returns true if the buffer is not the first token on the current history
 * line, and if it contains blanks or quotes which would confuse us when
 * parsing.
 */
static int
needQuotes(TBUFF * src)
{
#if HST_QUOTES
    if (tb_length(MyText)
      || must_quote_token(tb_values(src), tb_length(src)))
      return TRUE;
#endif
    return FALSE;
}

#if HST_QUOTES
/*
 * Reverses append_quoted_token() by stripping the quotes and returning an
 * allocated buffer with the text.  The associated length is returned via the
 * 'actual' parameter.
 */
static char *
stripQuotes(char *src, int len, int eolchar, int *actual)
{
    char *dst = 0;

    if (len > 0) {
      TRACE(("stripQuotes(%.*s)\n", len, src));
      if ((dst = (char *) malloc((size_t) len + 1)) != 0) {
          int j, k;
          int quoted = FALSE;
          int escaped = FALSE;

          for (j = k = 0; j < len; j++) {
            if (escaped) {
                escaped = FALSE;
                switch (src[j]) {
                case DQUOTE:
                case BACKSLASH:
                default:
                  dst[k++] = src[j];
                  break;
                case 'b':
                  dst[k++] = '\b';
                  break;
                case 't':
                  dst[k++] = '\t';
                  break;
                case 'r':
                  dst[k++] = '\r';
                  break;
                case 'n':
                  dst[k++] = '\n';
                  break;
                }
            } else if (src[j] == BACKSLASH) {
                escaped = TRUE;
            } else if (src[j] == DQUOTE && src[0] == DQUOTE) {
                quoted = !quoted;
            } else if (!quoted && src[j] == eolchar) {
                break;
            } else {
                dst[k++] = src[j];
            }
          }
          dst[k] = EOS;
          *actual = k;
      }
    }
    return dst;
}
#endif

static void
glueBufferToResult(TBUFF ** dst, TBUFF * src)
{
    int shell_cmd = ((tb_length(*dst) != 0 && isShellOrPipe(tb_values(*dst)))
                 || (tb_length(*dst) != 0 && isShellOrPipe(tb_values(src))));

    if (willGlue())
      (void) tb_append(dst, MyGlue);

    if (!shell_cmd
      && needQuotes(src)) {
      append_quoted_token(dst, tb_values(src), tb_length(src));
    } else {
      (void) tb_bappend(dst, tb_args(src));
    }
}

/******************************************************************************/

void
hst_reset(void)
{
    MyLevel = 0;
    (void) tb_init(&MyText, esc_c);
}

void
hst_init(int c)
{
    if (++MyLevel == 1) {
      (void) tb_init(&MyText, esc_c);
      MyGlue = EOS;
      if (c != EOS)
          (void) tb_append(&MyText, c);
    }
}

void
hst_glue(int c)
{
    /* ensure we don't repeat '/' delimiter */
    if (tb_length(MyText) == 0
      || tb_values(MyText)[0] != c)
      MyGlue = c;
}

void
hst_append(TBUFF * cmd, int glue)
{
    static int skip = 1;      /* e.g., after "!" */

    if (clexec || !vl_echo || qpasswd) {
      /* noninteractive, $disinp=FALSE, or querying for password */

      return;
    }
    if (isreturn(glue))
      glue = ' ';

    TRACE(("hst_append(cmd=%d:%d:%s) glue='%c'\n",
         willExtend(tb_args(cmd)),
         (int) tb_length(cmd),
         tb_visible(cmd),
         glue));
    TRACE(("...MyText        :%d:%s\n", tb_length(MyText), tb_visible(MyText)));
    if (willExtend(tb_args(cmd))
      && tb_length(cmd) > (size_t) skip) {
      kbd_pushback(cmd, skip);
    }

    glueBufferToResult(&MyText, cmd);
    TRACE(("...MyText        :%d:%s\n", tb_length(MyText), tb_visible(MyText)));
    MyGlue = glue;
}

void
hst_append_s(char *cmd, int glue)
{
    TBUFF *p = tb_string(cmd);
    hst_append(p, glue);
    tb_free(&p);
}

void
hst_remove(const char *cmd)
{
    if (MyLevel == 1 && *cmd != EOS) {
      TBUFF *temp = 0;
      unsigned len = tb_length(tb_scopy(&temp, cmd)) - 1;

      TRACE(("hst_remove(%s)\n", cmd));
      while (*cmd++)
          tb_unput(MyText);
      kbd_kill_response(temp, &len, killc);
      tb_free(&temp);
    }
}

void
hst_flush(void)
{
    BUFFER *bp;
    WINDOW *wp;
    LINE *lp;

    if (MyLevel <= 0)
      return;
    if (MyLevel-- != 1)
      return;

    if ((tb_length(MyText) != 0)
      && ((bp = makeMyBuff()) != 0)) {

      /* suppress if this is the same as previous line */
      if (((lp = lback(buf_head(bp))) != 0)
          && (lp != buf_head(bp))
          && (sameLine(lp, tb_args(MyText)) == 0)) {
          (void) tb_init(&MyText, esc_c);
          return;
      }

      if (!addline(bp, tb_args(MyText))) {
          stopMyBuff();
          return;
      }

      /* patch: reuse logic from slowreadf()? */
      for_each_visible_window(wp) {
          if (wp->w_bufp == bp) {
            wp->w_flag |= WFFORCE;
            if (wp == curwp)
                continue;
            /* force dot to the beginning of last-line */
            wp->w_force = -1;
            if (wp->w_dot.l != lback(buf_head(bp))) {
                wp->w_dot.l = lback(buf_head(bp));
                wp->w_dot.o = 0;
                wp->w_flag |= WFMOVE;
            }
          }
      }
      updatelistbuffers();    /* force it to show current sizes */
      (void) tb_init(&MyText, esc_c);
    }
}

/*ARGSUSED*/
int
showhistory(int f GCC_UNUSED, int n GCC_UNUSED)
{
    BUFFER *bp = makeMyBuff();

    if (!global_g_val(GMDHISTORY)) {
      mlforce("history mode is not set");
      return FALSE;
    } else if (bp == 0 || popupbuff(bp) == FALSE) {
      return no_memory("show-history");
    }
    return TRUE;
}

/*
 * Find the last line in the history buffer that matches the portion of
 * the command that has been input to this point.  The substrings to the
 * right (up to eolchar) will form the set of history strings that the
 * user may scroll through.
 */
static LINE *
hst_find(HST * parm, BUFFER *bp, LINE *lp, int direction)
{
    LINE *base = buf_head(bp);
    LINE *lp0 = lp;

    if ((lp0 == 0)
      || ((lp == base) && (direction > 0))) {
      return 0;
    }

    for_ever {
      if (direction > 0) {
          if (lp == lback(base))    /* cannot wrap-around */
            return 0;
          lp = lforw(lp);
      } else
          lp = lback(lp);
      if (lp == base)
          return 0;           /* empty or no matches */

      if (!lisreal(lp)
          || ((size_t) llength(lp) <= tb_length(MyText) + willGlue())
          || (sameLine(lp, tb_args(MyText)) < 0))
          continue;           /* prefix mismatches */

      if (willGlue()) { /* avoid conflicts setall/set */
          int len = tb_length(MyText);
          if (len > 0
            && (len > 1 || !isPunct(tb_values(MyText)[0]))
            && llength(lp) > len
            && lp->l_text[len] != MyGlue)
            continue;
      }

      /* avoid picking up lines with range-spec, since this is too
       * cumbersome to splice in 'namedcmd()'.
       */
      if (islinespecchar(lp->l_text[0]))
          continue;

      /* '/' and '?' are not (yet) :-commands.  Don't display them
       * in the command-name scrolling.
       */
      if (tb_length(MyText) == 0) {
          if (lp->l_text[0] == '/'
            || lp->l_text[0] == '?')
            continue;
      }

      /* compare the argument that will be shown for the original
       * and current lines.
       */
      if (lisreal(lp0)) {
          int n0 = parseArg(parm, lp0);
          int n1 = parseArg(parm, lp);
          if (n0 != 0
            && n1 != 0
            && n0 == n1
            && sameLine(lp, lp0->l_text, n0) >= 0)
            continue;
      }

      return lp;
    }
}

/*
 * Update the display of the currently-scrollable buffer on the prompt-line.
 */
static void
hst_display(HST * parm, char *src, int srclen)
{
    TRACE(("hst_display(%.*s) eolchar='%c'\n",
         srclen, src,
         isreturn(parm->eolchar) ? ' ' : parm->eolchar));

    /* kill the whole buffer */
    *(parm->position) = tb_length(*(parm->buffer));
    wminip->w_dot.o = llength(wminip->w_dot.l);
    kbd_kill_response(*(parm->buffer), parm->position, killc);

    if (src != 0 && srclen != 0) {
      char *stripped;
      int offset = tb_length(MyText) + willGlue();
      int n = endOfParm(parm, src, offset, srclen) - offset;

      src += offset;
      stripped = src;
#if HST_QUOTES
      TRACE(("...offset=%d, n=%d, MyText=%s\n", offset, n, tb_visible(MyText)));
      if (tb_length(MyText) != 0 && isShellOrPipe(tb_values(MyText)))
          TRACE(("...MyText is a shell command\n"));
      else if (tb_length(MyText) == 0 && offset == 0 && isShellOrPipe(src))
          TRACE(("...src is a shell command\n"));
      else if (*src == DQUOTE || isSpace(parm->eolchar))
          stripped = stripQuotes(src, n,
                           isSpace(parm->eolchar) ? ' ' : parm->eolchar,
                           &n);
#endif
      TRACE(("...hst_display offset=%d, string='%.*s'\n", offset, n, stripped));
      *parm->position = kbd_show_response(parm->buffer,
                                  stripped,
                                  n,
                                  parm->eolchar, parm->options);
#if HST_QUOTES
      if (stripped != 0 && stripped != src)
          free(stripped);
#endif
    }
}

/*
 * Update the display using a LINE as source
 */
static void
display_LINE(HST * parm, LINE *lp)
{
    hst_display(parm, lp_args(lp));
}

/*
 * Update the display using a TBUFF as source
 */
static void
display_TBUFF(HST * parm, TBUFF * tp)
{
    hst_display(parm, tb_args(tp));
}

/*
 * Perform common scrolling functions for arrow-keys and ESC-mode.
 */
static TBUFF *h_original;     /* save 'buffer' on first-scroll */
static int h_was_edited;      /* true iff any edit happened */
static int h_direction;       /* current scrolling +/- */
static int h_distance;        /* distance from original entry */

static LINE *
hst_scroll(LINE *lp1, HST * parm)
{
    BUFFER *bp = makeMyBuff();
    LINE *lp0 = buf_head(bp);
    LINE *lp2 = hst_find(parm, bp, lp1, h_direction);

    if (lp1 != lp2) {
      if (lp2 == 0) {
          if (h_direction + h_distance == 0) {
            lp1 = lp0;
            h_distance = 0;
            display_TBUFF(parm, h_original);
          } else {
            if (lp1 == lp0)   /* nothing to scroll for */
                h_distance = 0;
            kbd_alarm();
          }
          return lp1;
      } else {
          h_distance += h_direction;
          display_LINE(parm, lp2);
          h_was_edited++;
          return lp2;
      }
    }
    return 0;
}

/*
 * Invoked on an escape-character, this processes history-editing until another
 * escape-character is entered.
 */
int
edithistory(
             TBUFF ** buffer,
             unsigned *position,
             int *given,
             UINT options,
             int (*endfunc) (EOL_ARGS),
             int eolchar)
{
    HST param;
    BUFFER *bp;
    LINE *lp1, *lp2;
    int escaped = FALSE;
    int c = *given;

    if (!isSpecial(c)) {
      if (is_edit_char(c)
          || ABORTED(c)
          || (c == quotec)
          || isSpace(c)
          || !isCntrl(c))
          return FALSE;
    }

    if ((bp = makeMyBuff()) == 0)   /* something is very wrong */
      return FALSE;

    if ((lp1 = buf_head(bp)) == 0)
      return FALSE;

    /* slightly better than global data... */
    param.buffer = buffer;
    param.position = position;
    param.endfunc = endfunc;
    param.eolchar = (eolchar == '\n') ? '\r' : eolchar;
    param.options = options;

    h_was_edited = 0;
    h_distance = 0;

    /* save the original buffer, since we expect to scroll it */
    if (tb_copy(&h_original, MyText)) {
      /* make 'original' look just like a complete command... */
      glueBufferToResult(&h_original, *buffer);
    }

    /* process char-commands */
    for_ever {
      const CMDFUNC *p;

      /* If the character is bound to up/down scrolling, scroll the
       * history.
       */
      h_direction = 0;  /* ...unless we find scrolling-command */
      if ((p = DefaultKeyBinding(c)) != 0) {
          if (CMD_U_FUNC(p) == backline)
            h_direction = -1;
          else if (CMD_U_FUNC(p) == forwline)
            h_direction = 1;
      }
      if (ABORTED(c)) {
          *given = c;
          return FALSE;

      } else if ((h_direction != 0) && (escaped || !isGraph(c))) {

          if ((lp2 = hst_scroll(lp1, &param)) != 0)
            lp1 = lp2;
          else          /* cannot scroll */
            kbd_alarm();
      } else if (!escaped) {
          *given = c;
          if (h_was_edited)
            unkeystroke(c);
          return h_was_edited;

      } else
          kbd_alarm();

      c = keystroke();
    }
}
#endif /* OPT_HISTORY */

Generated by  Doxygen 1.6.0   Back to index